summaryrefslogtreecommitdiff
path: root/src/camel
diff options
context:
space:
mode:
authorMilan Crha <mcrha@redhat.com>2016-10-11 11:47:14 +0200
committerMilan Crha <mcrha@redhat.com>2016-10-11 11:47:14 +0200
commitd7931c6dd9db1e090f4bb466983c3dced19e2201 (patch)
tree31e31eef195355e800c63be6b4dcfefe6e37bb84 /src/camel
parent4febe3ae82e850ca9f17229dd2dbd9cdd8708a8f (diff)
downloadevolution-data-server-d7931c6dd9db1e090f4bb466983c3dced19e2201.tar.gz
Reorganize directory structure
Let's have it as it's common to be, which means top level src/ for sources, single data/ for data, and so on.
Diffstat (limited to 'src/camel')
-rw-r--r--src/camel/CMakeLists.txt463
-rw-r--r--src/camel/CODING.STYLE19
-rw-r--r--src/camel/README99
-rw-r--r--src/camel/README.HACKING14
-rw-r--r--src/camel/README.mt171
-rw-r--r--src/camel/camel-address.c253
-rw-r--r--src/camel/camel-address.h96
-rw-r--r--src/camel/camel-async-closure.c201
-rw-r--r--src/camel/camel-async-closure.h38
-rw-r--r--src/camel/camel-block-file.c1250
-rw-r--r--src/camel/camel-block-file.h196
-rw-r--r--src/camel/camel-certdb.c823
-rw-r--r--src/camel/camel-certdb.h139
-rw-r--r--src/camel/camel-charset-map-private.h11142
-rw-r--r--src/camel/camel-charset-map.c451
-rw-r--r--src/camel/camel-charset-map.h50
-rw-r--r--src/camel/camel-cipher-context.c1631
-rw-r--r--src/camel/camel-cipher-context.h353
-rw-r--r--src/camel/camel-data-cache.c590
-rw-r--r--src/camel/camel-data-cache.h95
-rw-r--r--src/camel/camel-data-wrapper.c1461
-rw-r--r--src/camel/camel-data-wrapper.h234
-rw-r--r--src/camel/camel-db.c2778
-rw-r--r--src/camel/camel-db.h346
-rw-r--r--src/camel/camel-debug.c1181
-rw-r--r--src/camel/camel-debug.h153
-rw-r--r--src/camel/camel-enums.h477
-rw-r--r--src/camel/camel-file-utils.c645
-rw-r--r--src/camel/camel-file-utils.h78
-rw-r--r--src/camel/camel-filter-driver.c1886
-rw-r--r--src/camel/camel-filter-driver.h134
-rw-r--r--src/camel/camel-filter-input-stream.c261
-rw-r--r--src/camel/camel-filter-input-stream.h74
-rw-r--r--src/camel/camel-filter-output-stream.c281
-rw-r--r--src/camel/camel-filter-output-stream.h74
-rw-r--r--src/camel/camel-filter-search.c1166
-rw-r--r--src/camel/camel-filter-search.h55
-rw-r--r--src/camel/camel-folder-search.c2243
-rw-r--r--src/camel/camel-folder-search.h305
-rw-r--r--src/camel/camel-folder-summary.c5316
-rw-r--r--src/camel/camel-folder-summary.h632
-rw-r--r--src/camel/camel-folder-thread.c954
-rw-r--r--src/camel/camel-folder-thread.h71
-rw-r--r--src/camel/camel-folder.c4625
-rw-r--r--src/camel/camel-folder.h579
-rw-r--r--src/camel/camel-gpg-context.c2738
-rw-r--r--src/camel/camel-gpg-context.h80
-rw-r--r--src/camel/camel-gpg-photo-saver.c117
-rw-r--r--src/camel/camel-html-parser.c788
-rw-r--r--src/camel/camel-html-parser.h99
-rw-r--r--src/camel/camel-iconv.c560
-rw-r--r--src/camel/camel-iconv.h49
-rw-r--r--src/camel/camel-index-control.c223
-rw-r--r--src/camel/camel-index.c455
-rw-r--r--src/camel/camel-index.h217
-rw-r--r--src/camel/camel-internet-address.c607
-rw-r--r--src/camel/camel-internet-address.h94
-rw-r--r--src/camel/camel-junk-filter.c177
-rw-r--r--src/camel/camel-junk-filter.h100
-rw-r--r--src/camel/camel-local-settings.c220
-rw-r--r--src/camel/camel-local-settings.h78
-rw-r--r--src/camel/camel-lock-client.c352
-rw-r--r--src/camel/camel-lock-client.h38
-rw-r--r--src/camel/camel-lock-helper.c398
-rw-r--r--src/camel/camel-lock-helper.h66
-rw-r--r--src/camel/camel-lock.c449
-rw-r--r--src/camel/camel-lock.h60
-rw-r--r--src/camel/camel-medium.c371
-rw-r--r--src/camel/camel-medium.h109
-rw-r--r--src/camel/camel-memchunk.c457
-rw-r--r--src/camel/camel-memchunk.h47
-rw-r--r--src/camel/camel-mempool.c220
-rw-r--r--src/camel/camel-mempool.h73
-rw-r--r--src/camel/camel-mime-filter-basic.c352
-rw-r--r--src/camel/camel-mime-filter-basic.h70
-rw-r--r--src/camel/camel-mime-filter-bestenc.c335
-rw-r--r--src/camel/camel-mime-filter-bestenc.h99
-rw-r--r--src/camel/camel-mime-filter-canon.c215
-rw-r--r--src/camel/camel-mime-filter-canon.h76
-rw-r--r--src/camel/camel-mime-filter-charset.c299
-rw-r--r--src/camel/camel-mime-filter-charset.h71
-rw-r--r--src/camel/camel-mime-filter-crlf.c201
-rw-r--r--src/camel/camel-mime-filter-crlf.h72
-rw-r--r--src/camel/camel-mime-filter-enriched.c612
-rw-r--r--src/camel/camel-mime-filter-enriched.h73
-rw-r--r--src/camel/camel-mime-filter-from.c209
-rw-r--r--src/camel/camel-mime-filter-from.h69
-rw-r--r--src/camel/camel-mime-filter-gzip.c482
-rw-r--r--src/camel/camel-mime-filter-gzip.h71
-rw-r--r--src/camel/camel-mime-filter-html.c210
-rw-r--r--src/camel/camel-mime-filter-html.h69
-rw-r--r--src/camel/camel-mime-filter-index.c206
-rw-r--r--src/camel/camel-mime-filter-index.h77
-rw-r--r--src/camel/camel-mime-filter-linewrap.c202
-rw-r--r--src/camel/camel-mime-filter-linewrap.h77
-rw-r--r--src/camel/camel-mime-filter-pgp.c214
-rw-r--r--src/camel/camel-mime-filter-pgp.h69
-rw-r--r--src/camel/camel-mime-filter-progress.c163
-rw-r--r--src/camel/camel-mime-filter-progress.h77
-rw-r--r--src/camel/camel-mime-filter-tohtml.c630
-rw-r--r--src/camel/camel-mime-filter-tohtml.h77
-rw-r--r--src/camel/camel-mime-filter-windows.c209
-rw-r--r--src/camel/camel-mime-filter-windows.h74
-rw-r--r--src/camel/camel-mime-filter-yenc.c574
-rw-r--r--src/camel/camel-mime-filter-yenc.h114
-rw-r--r--src/camel/camel-mime-filter.c351
-rw-r--r--src/camel/camel-mime-filter.h124
-rw-r--r--src/camel/camel-mime-message.c1434
-rw-r--r--src/camel/camel-mime-message.h154
-rw-r--r--src/camel/camel-mime-parser.c2015
-rw-r--r--src/camel/camel-mime-parser.h157
-rw-r--r--src/camel/camel-mime-part-utils.c240
-rw-r--r--src/camel/camel-mime-part-utils.h43
-rw-r--r--src/camel/camel-mime-part.c1715
-rw-r--r--src/camel/camel-mime-part.h143
-rw-r--r--src/camel/camel-mime-utils.c5243
-rw-r--r--src/camel/camel-mime-utils.h259
-rw-r--r--src/camel/camel-movemail.c576
-rw-r--r--src/camel/camel-movemail.h36
-rw-r--r--src/camel/camel-msgport.c473
-rw-r--r--src/camel/camel-msgport.h64
-rw-r--r--src/camel/camel-multipart-encrypted.c55
-rw-r--r--src/camel/camel-multipart-encrypted.h76
-rw-r--r--src/camel/camel-multipart-signed.c877
-rw-r--r--src/camel/camel-multipart-signed.h89
-rw-r--r--src/camel/camel-multipart.c642
-rw-r--r--src/camel/camel-multipart.h101
-rw-r--r--src/camel/camel-net-utils.c848
-rw-r--r--src/camel/camel-net-utils.h105
-rw-r--r--src/camel/camel-network-service.c1150
-rw-r--r--src/camel/camel-network-service.h118
-rw-r--r--src/camel/camel-network-settings.c489
-rw-r--r--src/camel/camel-network-settings.h100
-rw-r--r--src/camel/camel-nntp-address.c187
-rw-r--r--src/camel/camel-nntp-address.h74
-rw-r--r--src/camel/camel-null-output-stream.c111
-rw-r--r--src/camel/camel-null-output-stream.h71
-rw-r--r--src/camel/camel-object-bag.c612
-rw-r--r--src/camel/camel-object-bag.h60
-rw-r--r--src/camel/camel-object.c543
-rw-r--r--src/camel/camel-object.h116
-rw-r--r--src/camel/camel-offline-folder.c660
-rw-r--r--src/camel/camel-offline-folder.h99
-rw-r--r--src/camel/camel-offline-settings.c222
-rw-r--r--src/camel/camel-offline-settings.h84
-rw-r--r--src/camel/camel-offline-store.c379
-rw-r--r--src/camel/camel-offline-store.h79
-rw-r--r--src/camel/camel-operation.c439
-rw-r--r--src/camel/camel-operation.h84
-rw-r--r--src/camel/camel-partition-table.c1038
-rw-r--r--src/camel/camel-partition-table.h211
-rw-r--r--src/camel/camel-provider.c465
-rw-r--r--src/camel/camel-provider.h217
-rw-r--r--src/camel/camel-sasl-anonymous.c155
-rw-r--r--src/camel/camel-sasl-anonymous.h72
-rw-r--r--src/camel/camel-sasl-cram-md5.c163
-rw-r--r--src/camel/camel-sasl-cram-md5.h67
-rw-r--r--src/camel/camel-sasl-digest-md5.c983
-rw-r--r--src/camel/camel-sasl-digest-md5.h68
-rw-r--r--src/camel/camel-sasl-gssapi.c610
-rw-r--r--src/camel/camel-sasl-gssapi.h73
-rw-r--r--src/camel/camel-sasl-login.c131
-rw-r--r--src/camel/camel-sasl-login.h67
-rw-r--r--src/camel/camel-sasl-ntlm.c1012
-rw-r--r--src/camel/camel-sasl-ntlm.h66
-rw-r--r--src/camel/camel-sasl-plain.c108
-rw-r--r--src/camel/camel-sasl-plain.h67
-rw-r--r--src/camel/camel-sasl-popb4smtp.c176
-rw-r--r--src/camel/camel-sasl-popb4smtp.h67
-rw-r--r--src/camel/camel-sasl.c951
-rw-r--r--src/camel/camel-sasl.h138
-rw-r--r--src/camel/camel-search-private.c874
-rw-r--r--src/camel/camel-search-private.h114
-rw-r--r--src/camel/camel-search-sql-sexp.c915
-rw-r--r--src/camel/camel-search-sql-sexp.h36
-rw-r--r--src/camel/camel-service.c2380
-rw-r--r--src/camel/camel-service.h221
-rw-r--r--src/camel/camel-session.c1910
-rw-r--r--src/camel/camel-session.h260
-rw-r--r--src/camel/camel-settings.c224
-rw-r--r--src/camel/camel-settings.h87
-rw-r--r--src/camel/camel-sexp.c1856
-rw-r--r--src/camel/camel-sexp.h254
-rw-r--r--src/camel/camel-smime-context.c1437
-rw-r--r--src/camel/camel-smime-context.h89
-rw-r--r--src/camel/camel-store-settings.c144
-rw-r--r--src/camel/camel-store-settings.h78
-rw-r--r--src/camel/camel-store-summary.c1047
-rw-r--r--src/camel/camel-store-summary.h185
-rw-r--r--src/camel/camel-store.c3141
-rw-r--r--src/camel/camel-store.h410
-rw-r--r--src/camel/camel-stream-buffer.c526
-rw-r--r--src/camel/camel-stream-buffer.h96
-rw-r--r--src/camel/camel-stream-filter.c526
-rw-r--r--src/camel/camel-stream-filter.h74
-rw-r--r--src/camel/camel-stream-fs.c335
-rw-r--r--src/camel/camel-stream-fs.h79
-rw-r--r--src/camel/camel-stream-mem.c410
-rw-r--r--src/camel/camel-stream-mem.h82
-rw-r--r--src/camel/camel-stream-null.c138
-rw-r--r--src/camel/camel-stream-null.h69
-rw-r--r--src/camel/camel-stream-process.c285
-rw-r--r--src/camel/camel-stream-process.h73
-rw-r--r--src/camel/camel-stream.c704
-rw-r--r--src/camel/camel-stream.h125
-rw-r--r--src/camel/camel-string-utils.c376
-rw-r--r--src/camel/camel-string-utils.h47
-rw-r--r--src/camel/camel-subscribable.c627
-rw-r--r--src/camel/camel-subscribable.h134
-rw-r--r--src/camel/camel-text-index.c2004
-rw-r--r--src/camel/camel-text-index.h187
-rw-r--r--src/camel/camel-transport.c270
-rw-r--r--src/camel/camel-transport.h101
-rw-r--r--src/camel/camel-trie.c393
-rw-r--r--src/camel/camel-trie.h44
-rw-r--r--src/camel/camel-uid-cache.c335
-rw-r--r--src/camel/camel-uid-cache.h55
-rw-r--r--src/camel/camel-url-scanner.c552
-rw-r--r--src/camel/camel-url-scanner.h67
-rw-r--r--src/camel/camel-url.c814
-rw-r--r--src/camel/camel-url.h102
-rw-r--r--src/camel/camel-utf8.c422
-rw-r--r--src/camel/camel-utf8.h51
-rw-r--r--src/camel/camel-vee-data-cache.c830
-rw-r--r--src/camel/camel-vee-data-cache.h212
-rw-r--r--src/camel/camel-vee-folder.c1800
-rw-r--r--src/camel/camel-vee-folder.h137
-rw-r--r--src/camel/camel-vee-store.c1051
-rw-r--r--src/camel/camel-vee-store.h90
-rw-r--r--src/camel/camel-vee-summary.c576
-rw-r--r--src/camel/camel-vee-summary.h93
-rw-r--r--src/camel/camel-vtrash-folder.c266
-rw-r--r--src/camel/camel-vtrash-folder.h80
-rw-r--r--src/camel/camel-win32.c133
-rw-r--r--src/camel/camel-win32.h54
-rw-r--r--src/camel/camel.c386
-rw-r--r--src/camel/camel.h171
-rw-r--r--src/camel/camel.pc.in15
-rw-r--r--src/camel/devel-docs/camel-index.txt407
-rw-r--r--src/camel/devel-docs/camel_data_wrapper.diabin0 -> 3062 bytes
-rw-r--r--src/camel/devel-docs/camel_parser_states.diabin0 -> 2505 bytes
-rw-r--r--src/camel/devel-docs/camel_stream.diabin0 -> 2669 bytes
-rwxr-xr-xsrc/camel/gentables.pl105
-rw-r--r--src/camel/providers/CMakeLists.txt9
-rw-r--r--src/camel/providers/imapx/CMakeLists.txt93
-rw-r--r--src/camel/providers/imapx/camel-imapx-command.c465
-rw-r--r--src/camel/providers/imapx/camel-imapx-command.h102
-rw-r--r--src/camel/providers/imapx/camel-imapx-conn-manager.c2797
-rw-r--r--src/camel/providers/imapx/camel-imapx-conn-manager.h190
-rw-r--r--src/camel/providers/imapx/camel-imapx-folder.c1482
-rw-r--r--src/camel/providers/imapx/camel-imapx-folder.h108
-rw-r--r--src/camel/providers/imapx/camel-imapx-input-stream.c1053
-rw-r--r--src/camel/providers/imapx/camel-imapx-input-stream.h156
-rw-r--r--src/camel/providers/imapx/camel-imapx-job.c544
-rw-r--r--src/camel/providers/imapx/camel-imapx-job.h124
-rw-r--r--src/camel/providers/imapx/camel-imapx-list-response.c841
-rw-r--r--src/camel/providers/imapx/camel-imapx-list-response.h125
-rw-r--r--src/camel/providers/imapx/camel-imapx-logger.c240
-rw-r--r--src/camel/providers/imapx/camel-imapx-logger.h72
-rw-r--r--src/camel/providers/imapx/camel-imapx-mailbox.c1262
-rw-r--r--src/camel/providers/imapx/camel-imapx-mailbox.h189
-rw-r--r--src/camel/providers/imapx/camel-imapx-namespace-response.c593
-rw-r--r--src/camel/providers/imapx/camel-imapx-namespace-response.h97
-rw-r--r--src/camel/providers/imapx/camel-imapx-namespace.c195
-rw-r--r--src/camel/providers/imapx/camel-imapx-namespace.h104
-rw-r--r--src/camel/providers/imapx/camel-imapx-provider.c180
-rw-r--r--src/camel/providers/imapx/camel-imapx-search.c720
-rw-r--r--src/camel/providers/imapx/camel-imapx-search.h82
-rw-r--r--src/camel/providers/imapx/camel-imapx-server.c6614
-rw-r--r--src/camel/providers/imapx/camel-imapx-server.h304
-rw-r--r--src/camel/providers/imapx/camel-imapx-settings.c1865
-rw-r--r--src/camel/providers/imapx/camel-imapx-settings.h174
-rw-r--r--src/camel/providers/imapx/camel-imapx-status-response.c443
-rw-r--r--src/camel/providers/imapx/camel-imapx-status-response.h99
-rw-r--r--src/camel/providers/imapx/camel-imapx-store-summary.c368
-rw-r--r--src/camel/providers/imapx/camel-imapx-store-summary.h78
-rw-r--r--src/camel/providers/imapx/camel-imapx-store.c3701
-rw-r--r--src/camel/providers/imapx/camel-imapx-store.h129
-rw-r--r--src/camel/providers/imapx/camel-imapx-summary.c417
-rw-r--r--src/camel/providers/imapx/camel-imapx-summary.h91
-rw-r--r--src/camel/providers/imapx/camel-imapx-tokens.txt63
-rw-r--r--src/camel/providers/imapx/camel-imapx-utils.c3301
-rw-r--r--src/camel/providers/imapx/camel-imapx-utils.h401
-rw-r--r--src/camel/providers/imapx/libcamelimapx.urls1
-rw-r--r--src/camel/providers/local/CMakeLists.txt84
-rw-r--r--src/camel/providers/local/camel-local-folder.c723
-rw-r--r--src/camel/providers/local/camel-local-folder.h123
-rw-r--r--src/camel/providers/local/camel-local-private.c52
-rw-r--r--src/camel/providers/local/camel-local-private.h50
-rw-r--r--src/camel/providers/local/camel-local-provider.c258
-rw-r--r--src/camel/providers/local/camel-local-store.c815
-rw-r--r--src/camel/providers/local/camel-local-store.h86
-rw-r--r--src/camel/providers/local/camel-local-summary.c770
-rw-r--r--src/camel/providers/local/camel-local-summary.h110
-rw-r--r--src/camel/providers/local/camel-maildir-folder.c513
-rw-r--r--src/camel/providers/local/camel-maildir-folder.h66
-rw-r--r--src/camel/providers/local/camel-maildir-store.c1375
-rw-r--r--src/camel/providers/local/camel-maildir-store.h63
-rw-r--r--src/camel/providers/local/camel-maildir-summary.c878
-rw-r--r--src/camel/providers/local/camel-maildir-summary.h85
-rw-r--r--src/camel/providers/local/camel-mbox-folder.c524
-rw-r--r--src/camel/providers/local/camel-mbox-folder.h69
-rw-r--r--src/camel/providers/local/camel-mbox-store.c1017
-rw-r--r--src/camel/providers/local/camel-mbox-store.h62
-rw-r--r--src/camel/providers/local/camel-mbox-summary.c1407
-rw-r--r--src/camel/providers/local/camel-mbox-summary.h104
-rw-r--r--src/camel/providers/local/camel-mh-folder.c252
-rw-r--r--src/camel/providers/local/camel-mh-folder.h65
-rw-r--r--src/camel/providers/local/camel-mh-settings.c146
-rw-r--r--src/camel/providers/local/camel-mh-settings.h66
-rw-r--r--src/camel/providers/local/camel-mh-store.c760
-rw-r--r--src/camel/providers/local/camel-mh-store.h63
-rw-r--r--src/camel/providers/local/camel-mh-summary.c373
-rw-r--r--src/camel/providers/local/camel-mh-summary.h66
-rw-r--r--src/camel/providers/local/camel-spool-folder.c176
-rw-r--r--src/camel/providers/local/camel-spool-folder.h71
-rw-r--r--src/camel/providers/local/camel-spool-settings.c144
-rw-r--r--src/camel/providers/local/camel-spool-settings.h66
-rw-r--r--src/camel/providers/local/camel-spool-store.c711
-rw-r--r--src/camel/providers/local/camel-spool-store.h64
-rw-r--r--src/camel/providers/local/camel-spool-summary.c370
-rw-r--r--src/camel/providers/local/camel-spool-summary.h83
-rw-r--r--src/camel/providers/local/libcamellocal.urls4
-rw-r--r--src/camel/providers/nntp/CMakeLists.txt57
-rw-r--r--src/camel/providers/nntp/camel-nntp-folder.c857
-rw-r--r--src/camel/providers/nntp/camel-nntp-folder.h75
-rw-r--r--src/camel/providers/nntp/camel-nntp-private.h45
-rw-r--r--src/camel/providers/nntp/camel-nntp-provider.c165
-rw-r--r--src/camel/providers/nntp/camel-nntp-resp-codes.h51
-rw-r--r--src/camel/providers/nntp/camel-nntp-settings.c391
-rw-r--r--src/camel/providers/nntp/camel-nntp-settings.h77
-rw-r--r--src/camel/providers/nntp/camel-nntp-store-summary.c389
-rw-r--r--src/camel/providers/nntp/camel-nntp-store-summary.h118
-rw-r--r--src/camel/providers/nntp/camel-nntp-store.c2356
-rw-r--r--src/camel/providers/nntp/camel-nntp-store.h138
-rw-r--r--src/camel/providers/nntp/camel-nntp-stream.c482
-rw-r--r--src/camel/providers/nntp/camel-nntp-stream.h93
-rw-r--r--src/camel/providers/nntp/camel-nntp-summary.c584
-rw-r--r--src/camel/providers/nntp/camel-nntp-summary.h77
-rw-r--r--src/camel/providers/nntp/libcamelnntp.urls2
-rw-r--r--src/camel/providers/pop3/CMakeLists.txt53
-rw-r--r--src/camel/providers/pop3/camel-pop3-engine.c556
-rw-r--r--src/camel/providers/pop3/camel-pop3-engine.h176
-rw-r--r--src/camel/providers/pop3/camel-pop3-folder.c1170
-rw-r--r--src/camel/providers/pop3/camel-pop3-folder.h85
-rw-r--r--src/camel/providers/pop3/camel-pop3-provider.c172
-rw-r--r--src/camel/providers/pop3/camel-pop3-settings.c543
-rw-r--r--src/camel/providers/pop3/camel-pop3-settings.h86
-rw-r--r--src/camel/providers/pop3/camel-pop3-store.c1249
-rw-r--r--src/camel/providers/pop3/camel-pop3-store.h83
-rw-r--r--src/camel/providers/pop3/camel-pop3-stream.c458
-rw-r--r--src/camel/providers/pop3/camel-pop3-stream.h90
-rw-r--r--src/camel/providers/pop3/libcamelpop3.urls1
-rw-r--r--src/camel/providers/sendmail/CMakeLists.txt49
-rw-r--r--src/camel/providers/sendmail/camel-sendmail-provider.c61
-rw-r--r--src/camel/providers/sendmail/camel-sendmail-settings.c515
-rw-r--r--src/camel/providers/sendmail/camel-sendmail-settings.h91
-rw-r--r--src/camel/providers/sendmail/camel-sendmail-transport.c381
-rw-r--r--src/camel/providers/sendmail/camel-sendmail-transport.h62
-rw-r--r--src/camel/providers/sendmail/libcamelsendmail.urls1
-rw-r--r--src/camel/providers/smtp/CMakeLists.txt47
-rw-r--r--src/camel/providers/smtp/camel-smtp-provider.c124
-rw-r--r--src/camel/providers/smtp/camel-smtp-settings.c171
-rw-r--r--src/camel/providers/smtp/camel-smtp-settings.h61
-rw-r--r--src/camel/providers/smtp/camel-smtp-transport.c1891
-rw-r--r--src/camel/providers/smtp/camel-smtp-transport.h74
-rw-r--r--src/camel/providers/smtp/libcamelsmtp.urls1
-rw-r--r--src/camel/tests/CMakeLists.txt53
-rw-r--r--src/camel/tests/README44
-rw-r--r--src/camel/tests/data/camel-test.gpg.pub24
-rw-r--r--src/camel/tests/data/camel-test.gpg.sec33
-rwxr-xr-xsrc/camel/tests/data/gendoc.pl65
-rwxr-xr-xsrc/camel/tests/data/genline.pl72
-rwxr-xr-xsrc/camel/tests/data/getaddr.pl32
-rw-r--r--src/camel/tests/folder/CMakeLists.txt15
-rw-r--r--src/camel/tests/folder/README14
-rw-r--r--src/camel/tests/folder/test1.c60
-rw-r--r--src/camel/tests/folder/test10.c123
-rw-r--r--src/camel/tests/folder/test11.c229
-rw-r--r--src/camel/tests/folder/test2.c62
-rw-r--r--src/camel/tests/folder/test3.c373
-rw-r--r--src/camel/tests/folder/test4.c61
-rw-r--r--src/camel/tests/folder/test5.c61
-rw-r--r--src/camel/tests/folder/test6.c59
-rw-r--r--src/camel/tests/folder/test7.c59
-rw-r--r--src/camel/tests/folder/test8.c235
-rw-r--r--src/camel/tests/folder/test9.c253
-rw-r--r--src/camel/tests/lib/CMakeLists.txt72
-rw-r--r--src/camel/tests/lib/address-data.h137
-rw-r--r--src/camel/tests/lib/addresses.c68
-rw-r--r--src/camel/tests/lib/addresses.h18
-rw-r--r--src/camel/tests/lib/camel-test-provider.c34
-rw-r--r--src/camel/tests/lib/camel-test-provider.h22
-rw-r--r--src/camel/tests/lib/camel-test.c356
-rw-r--r--src/camel/tests/lib/camel-test.h81
-rw-r--r--src/camel/tests/lib/folders.c627
-rw-r--r--src/camel/tests/lib/folders.h28
-rw-r--r--src/camel/tests/lib/messages.c332
-rw-r--r--src/camel/tests/lib/messages.h31
-rw-r--r--src/camel/tests/lib/session.c37
-rw-r--r--src/camel/tests/lib/session.h52
-rw-r--r--src/camel/tests/message/CMakeLists.txt7
-rw-r--r--src/camel/tests/message/README8
-rw-r--r--src/camel/tests/message/test1.c212
-rw-r--r--src/camel/tests/message/test2.c385
-rw-r--r--src/camel/tests/message/test4.c122
-rw-r--r--src/camel/tests/mime-filter/CMakeLists.txt8
-rw-r--r--src/camel/tests/mime-filter/charset-gb2312.0.in448
-rw-r--r--src/camel/tests/mime-filter/charset-gb2312.0.out448
-rw-r--r--src/camel/tests/mime-filter/charset-iso-2022-jp.0.in5
-rw-r--r--src/camel/tests/mime-filter/charset-iso-2022-jp.0.out5
-rw-r--r--src/camel/tests/mime-filter/crlf-1.in19
-rw-r--r--src/camel/tests/mime-filter/crlf-1.out19
-rw-r--r--src/camel/tests/mime-filter/data/html.0.in10
-rw-r--r--src/camel/tests/mime-filter/data/html.0.out10
-rw-r--r--src/camel/tests/mime-filter/data/html.1.in10
-rw-r--r--src/camel/tests/mime-filter/data/html.1.out10
-rw-r--r--src/camel/tests/mime-filter/test-charset.c208
-rw-r--r--src/camel/tests/mime-filter/test-crlf.c197
-rw-r--r--src/camel/tests/mime-filter/test-tohtml.c199
-rw-r--r--src/camel/tests/mime-filter/test1.c139
-rw-r--r--src/camel/tests/misc/CMakeLists.txt11
-rw-r--r--src/camel/tests/misc/README6
-rw-r--r--src/camel/tests/misc/rfc2047.c102
-rw-r--r--src/camel/tests/misc/split.c125
-rw-r--r--src/camel/tests/misc/test1.c69
-rw-r--r--src/camel/tests/misc/test2.c133
-rw-r--r--src/camel/tests/misc/url-scan.c125
-rw-r--r--src/camel/tests/misc/url.c137
-rw-r--r--src/camel/tests/misc/utf7.c118
-rw-r--r--src/camel/tests/smime/CMakeLists.txt7
-rw-r--r--src/camel/tests/smime/README2
-rw-r--r--src/camel/tests/smime/pgp-mime.c200
-rw-r--r--src/camel/tests/smime/pgp.c231
-rw-r--r--src/camel/tests/smime/pkcs7.c190
435 files changed, 184787 insertions, 0 deletions
diff --git a/src/camel/CMakeLists.txt b/src/camel/CMakeLists.txt
new file mode 100644
index 000000000..6e7ea43c3
--- /dev/null
+++ b/src/camel/CMakeLists.txt
@@ -0,0 +1,463 @@
+glib_mkenums(camel-enumtypes camel-enums.h CAMEL_ENUMTYPES_H)
+
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/camel-mime-tables.c
+ COMMAND ${PERL} ${CMAKE_CURRENT_SOURCE_DIR}/gentables.pl >${CMAKE_CURRENT_BINARY_DIR}/camel-mime-tables.c
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/gentables.pl
+)
+
+set(SOURCES
+ camel.c
+ camel-address.c
+ camel-async-closure.c
+ camel-block-file.c
+ camel-certdb.c
+ camel-charset-map.c
+ camel-cipher-context.c
+ camel-data-cache.c
+ camel-data-wrapper.c
+ camel-db.c
+ camel-debug.c
+ camel-file-utils.c
+ camel-filter-driver.c
+ camel-filter-input-stream.c
+ camel-filter-output-stream.c
+ camel-filter-search.c
+ camel-folder-search.c
+ camel-folder-summary.c
+ camel-folder-thread.c
+ camel-folder.c
+ camel-gpg-context.c
+ camel-html-parser.c
+ camel-iconv.c
+ camel-index.c
+ camel-internet-address.c
+ camel-junk-filter.c
+ camel-local-settings.c
+ camel-lock.c
+ camel-medium.c
+ camel-memchunk.c
+ camel-mempool.c
+ camel-mime-filter-basic.c
+ camel-mime-filter-bestenc.c
+ camel-mime-filter-canon.c
+ camel-mime-filter-charset.c
+ camel-mime-filter-crlf.c
+ camel-mime-filter-enriched.c
+ camel-mime-filter-from.c
+ camel-mime-filter-gzip.c
+ camel-mime-filter-html.c
+ camel-mime-filter-index.c
+ camel-mime-filter-linewrap.c
+ camel-mime-filter-pgp.c
+ camel-mime-filter-progress.c
+ camel-mime-filter-tohtml.c
+ camel-mime-filter-windows.c
+ camel-mime-filter-yenc.c
+ camel-mime-filter.c
+ camel-mime-message.c
+ camel-mime-parser.c
+ camel-mime-part-utils.c
+ camel-mime-part.c
+ camel-mime-utils.c
+ camel-msgport.c
+ camel-multipart-encrypted.c
+ camel-multipart-signed.c
+ camel-multipart.c
+ camel-net-utils.c
+ camel-network-service.c
+ camel-network-settings.c
+ camel-nntp-address.c
+ camel-null-output-stream.c
+ camel-object-bag.c
+ camel-object.c
+ camel-offline-folder.c
+ camel-offline-settings.c
+ camel-offline-store.c
+ camel-operation.c
+ camel-partition-table.c
+ camel-provider.c
+ camel-sasl-anonymous.c
+ camel-sasl-cram-md5.c
+ camel-sasl-digest-md5.c
+ camel-sasl-gssapi.c
+ camel-sasl-login.c
+ camel-sasl-ntlm.c
+ camel-sasl-plain.c
+ camel-sasl-popb4smtp.c
+ camel-sasl.c
+ camel-search-private.c
+ camel-search-sql-sexp.c
+ camel-service.c
+ camel-session.c
+ camel-settings.c
+ camel-sexp.c
+ camel-smime-context.c
+ camel-store-settings.c
+ camel-store-summary.c
+ camel-store.c
+ camel-stream-buffer.c
+ camel-stream-filter.c
+ camel-stream-fs.c
+ camel-stream-mem.c
+ camel-stream-null.c
+ camel-stream.c
+ camel-string-utils.c
+ camel-subscribable.c
+ camel-text-index.c
+ camel-transport.c
+ camel-trie.c
+ camel-uid-cache.c
+ camel-url-scanner.c
+ camel-url.c
+ camel-utf8.c
+ camel-vee-data-cache.c
+ camel-vee-folder.c
+ camel-vee-store.c
+ camel-vee-summary.c
+ camel-vtrash-folder.c
+ ${CMAKE_CURRENT_BINARY_DIR}/camel-enumtypes.c
+ ${CMAKE_CURRENT_BINARY_DIR}/camel-mime-tables.c
+)
+
+if(WIN32)
+ list(APPEND SOURCES
+ camel-win32.c
+ )
+else(WIN32)
+ list(APPEND SOURCES
+ camel-lock-client.c
+ camel-movemail.c
+ camel-stream-process.c
+ )
+endif(WIN32)
+
+set(HEADERS
+ camel.h
+ camel-address.h
+ camel-async-closure.h
+ camel-block-file.h
+ camel-certdb.h
+ camel-charset-map.h
+ camel-cipher-context.h
+ camel-data-cache.h
+ camel-data-wrapper.h
+ camel-db.h
+ camel-debug.h
+ camel-enums.h
+ camel-file-utils.h
+ camel-filter-driver.h
+ camel-filter-input-stream.h
+ camel-filter-output-stream.h
+ camel-filter-search.h
+ camel-folder-search.h
+ camel-folder-summary.h
+ camel-folder-thread.h
+ camel-folder.h
+ camel-gpg-context.h
+ camel-html-parser.h
+ camel-iconv.h
+ camel-index.h
+ camel-internet-address.h
+ camel-junk-filter.h
+ camel-local-settings.h
+ camel-lock-client.h
+ camel-lock-helper.h
+ camel-lock.h
+ camel-medium.h
+ camel-memchunk.h
+ camel-mempool.h
+ camel-mime-filter-basic.h
+ camel-mime-filter-bestenc.h
+ camel-mime-filter-canon.h
+ camel-mime-filter-charset.h
+ camel-mime-filter-crlf.h
+ camel-mime-filter-enriched.h
+ camel-mime-filter-from.h
+ camel-mime-filter-gzip.h
+ camel-mime-filter-html.h
+ camel-mime-filter-index.h
+ camel-mime-filter-linewrap.h
+ camel-mime-filter-pgp.h
+ camel-mime-filter-progress.h
+ camel-mime-filter-tohtml.h
+ camel-mime-filter-windows.h
+ camel-mime-filter-yenc.h
+ camel-mime-filter.h
+ camel-mime-message.h
+ camel-mime-parser.h
+ camel-mime-part-utils.h
+ camel-mime-part.h
+ camel-mime-utils.h
+ camel-movemail.h
+ camel-msgport.h
+ camel-multipart-encrypted.h
+ camel-multipart-signed.h
+ camel-multipart.h
+ camel-net-utils.h
+ camel-network-service.h
+ camel-network-settings.h
+ camel-nntp-address.h
+ camel-null-output-stream.h
+ camel-object-bag.h
+ camel-object.h
+ camel-offline-folder.h
+ camel-offline-settings.h
+ camel-offline-store.h
+ camel-operation.h
+ camel-partition-table.h
+ camel-provider.h
+ camel-sasl-anonymous.h
+ camel-sasl-cram-md5.h
+ camel-sasl-digest-md5.h
+ camel-sasl-gssapi.h
+ camel-sasl-login.h
+ camel-sasl-ntlm.h
+ camel-sasl-plain.h
+ camel-sasl-popb4smtp.h
+ camel-sasl.h
+ camel-search-private.h
+ camel-search-sql-sexp.h
+ camel-service.h
+ camel-session.h
+ camel-settings.h
+ camel-sexp.h
+ camel-smime-context.h
+ camel-store-settings.h
+ camel-store-summary.h
+ camel-store.h
+ camel-stream-buffer.h
+ camel-stream-filter.h
+ camel-stream-fs.h
+ camel-stream-mem.h
+ camel-stream-null.h
+ camel-stream-process.h
+ camel-stream.h
+ camel-string-utils.h
+ camel-subscribable.h
+ camel-text-index.h
+ camel-transport.h
+ camel-trie.h
+ camel-uid-cache.h
+ camel-url-scanner.h
+ camel-url.h
+ camel-utf8.h
+ camel-vee-data-cache.h
+ camel-vee-folder.h
+ camel-vee-store.h
+ camel-vee-summary.h
+ camel-vtrash-folder.h
+ ${CMAKE_CURRENT_BINARY_DIR}/camel-enumtypes.h
+)
+
+add_library(camel SHARED
+ ${SOURCES}
+ ${HEADERS}
+)
+
+set_target_properties(camel PROPERTIES
+ VERSION "${LIBCAMEL_CURRENT}.${LIBCAMEL_REVISION}.${LIBCAMEL_AGE}"
+ SOVERSION ${LIBCAMEL_CURRENT}
+ OUTPUT_NAME camel-${API_VERSION}
+)
+
+target_compile_definitions(camel PRIVATE
+ -DG_LOG_DOMAIN=\"camel\"
+ -DCAMEL_LIBEXECDIR=\"${LIBEXEC_INSTALL_DIR}\"
+ -DCAMEL_PROVIDERDIR=\"${camel_providerdir}\"
+ -DE_DATA_SERVER_PREFIX=\"${CMAKE_INSTALL_PREFIX}\"
+ -DLOCALEDIR=\"${LOCALE_INSTALL_DIR}\"
+ -DCAMEL_COMPILATION
+)
+
+target_compile_options(camel PUBLIC
+ ${CAMEL_CFLAGS}
+ ${SOCKET_CFLAGS}
+ ${ICONV_CFLAGS}
+ ${REGEX_CFLAGS}
+ ${LIBDWFL_CFLAGS}
+)
+
+target_include_directories(camel PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CAMEL_INCLUDE_DIRS}
+ ${SOCKET_INCLUDE_DIRS}
+ ${ICONV_INCLUDE_DIRS}
+ ${REGEX_INCLUDE_DIRS}
+)
+
+target_link_libraries(camel
+ ${CAMEL_LDFLAGS}
+ ${SOCKET_LDFLAGS}
+ ${ICONV_LDFLAGS}
+ ${REGEX_LDFLAGS}
+ ${LIBDWFL_LDFLAGS}
+)
+
+install(TARGETS camel
+ DESTINATION ${LIB_INSTALL_DIR}
+)
+
+install(FILES ${HEADERS}
+ DESTINATION ${privincludedir}/camel
+)
+
+add_pkgconfig_file(camel.pc.in camel-${API_VERSION}.pc)
+
+set(gir_sources ${SOURCES} ${HEADERS})
+set(gir_identifies_prefixes Camel camel)
+set(gir_includes GObject-2.0 Gio-2.0 libxml2-2.0)
+set(gir_cflags ${CAMEL_CFLAGS} -DCAMEL_COMPILATION)
+set(gir_libdirs)
+set(gir_libs camel)
+set(gir_deps)
+
+# Remove the below set() once the typelib will be buildable; the gi-r-compiler crashes with
+# ERROR:girepository/girparser.c:343:state_switch: assertion failed: (ctx->state != newstate)
+gir_construct_names(Camel ${API_VERSION} camel_gir_name camel_gir_vars_prefix)
+set(${camel_gir_vars_prefix}_SKIP_TYPELIB ON)
+
+gir_add_introspection_simple(
+ Camel
+ camel
+ ${API_VERSION}
+ "camel/camel.h"
+ gir_identifies_prefixes
+ gir_includes
+ gir_cflags
+ gir_libdirs
+ gir_libs
+ gir_deps
+ gir_sources
+)
+
+if(NOT WIN32)
+ add_executable(camel-lock-helper
+ camel-lock.c
+ camel-lock.h
+ camel-lock-helper.c
+ camel-lock-helper.h
+ )
+
+ set_target_properties(camel-lock-helper PROPERTIES
+ OUTPUT_NAME camel-lock-helper-${API_VERSION}
+ )
+
+ target_compile_definitions(camel-lock-helper PRIVATE
+ -DG_LOG_DOMAIN=\"camel-lock-helper\"
+ -DCAMEL_LIBEXECDIR=\"${LIBEXEC_INSTALL_DIR}\"
+ -DCAMEL_PROVIDERDIR=\"${camel_providerdir}\"
+ -DE_DATA_SERVER_PREFIX=\"${CMAKE_INSTALL_PREFIX}\"
+ -DLOCALEDIR=\"${LOCALE_INSTALL_DIR}\"
+ -DCAMEL_COMPILATION
+ )
+
+ target_compile_options(camel-lock-helper PUBLIC
+ ${GNOME_PLATFORM_CFLAGS}
+ )
+
+ target_include_directories(camel-lock-helper PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${GNOME_PLATFORM_INCLUDE_DIRS}
+ )
+
+ target_link_libraries(camel-lock-helper
+ ${GNOME_PLATFORM_LDFLAGS}
+ )
+
+ install(TARGETS camel-lock-helper
+ DESTINATION ${LIBEXEC_INSTALL_DIR}
+ )
+endif(NOT WIN32)
+
+add_executable(camel-index-control
+ camel-index-control.c
+)
+
+add_dependencies(camel-index-control camel)
+
+set_target_properties(camel-index-control PROPERTIES
+ OUTPUT_NAME camel-index-control-${API_VERSION}
+)
+
+target_compile_definitions(camel-index-control PRIVATE
+ -DG_LOG_DOMAIN=\"camel-index-control-${API_VERSION}\"
+ -DCAMEL_LIBEXECDIR=\"${LIBEXEC_INSTALL_DIR}\"
+ -DCAMEL_PROVIDERDIR=\"${camel_providerdir}\"
+ -DE_DATA_SERVER_PREFIX=\"${CMAKE_INSTALL_PREFIX}\"
+ -DLOCALEDIR=\"${LOCALE_INSTALL_DIR}\"
+ -DCAMEL_COMPILATION
+)
+
+target_compile_options(camel-index-control PUBLIC
+ ${CAMEL_CFLAGS}
+ ${SOCKET_CFLAGS}
+ ${ICONV_CFLAGS}
+ ${REGEX_CFLAGS}
+ ${LIBDWFL_CFLAGS}
+)
+
+target_include_directories(camel-index-control PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CAMEL_INCLUDE_DIRS}
+ ${SOCKET_INCLUDE_DIRS}
+ ${ICONV_INCLUDE_DIRS}
+ ${REGEX_INCLUDE_DIRS}
+)
+
+target_link_libraries(camel-index-control
+ camel
+ ${CAMEL_LDFLAGS}
+ ${SOCKET_LDFLAGS}
+ ${ICONV_LDFLAGS}
+ ${REGEX_LDFLAGS}
+ ${LIBDWFL_LDFLAGS}
+)
+
+install(TARGETS camel-index-control
+ DESTINATION ${LIBEXEC_INSTALL_DIR}
+)
+
+add_executable(camel-gpg-photo-saver
+ camel-gpg-photo-saver.c
+)
+
+target_compile_definitions(camel-gpg-photo-saver PRIVATE
+ -DG_LOG_DOMAIN=\"camel-gpg-photo-saver\"
+)
+
+target_compile_options(camel-gpg-photo-saver PUBLIC
+ ${GNOME_PLATFORM_CFLAGS}
+)
+
+target_include_directories(camel-gpg-photo-saver PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${GNOME_PLATFORM_INCLUDE_DIRS}
+)
+
+target_link_libraries(camel-gpg-photo-saver
+ ${GNOME_PLATFORM_LDFLAGS}
+)
+
+install(TARGETS camel-gpg-photo-saver
+ DESTINATION ${LIBEXEC_INSTALL_DIR}
+)
+
+add_subdirectory(providers)
+add_subdirectory(tests)
diff --git a/src/camel/CODING.STYLE b/src/camel/CODING.STYLE
new file mode 100644
index 000000000..58e9c68bb
--- /dev/null
+++ b/src/camel/CODING.STYLE
@@ -0,0 +1,19 @@
+Note to hackers
+---------------
+
+When hacking on camel (and on the gnome mailer in general),
+be sure to follow the same coding style as the initial authors.
+Please read the file HACKING in gnumeric and follow the
+general guidelines explained in it.
+
+Please take a look at camel source files and try to exactly
+imitate the coding style. We are perfectly aware that this
+is not the best and unique style, but it is absolutely
+mandatory that Camel is homogeneous. If you find the current
+coding style to have some weaknesses, please contact the
+authors to discuss this matter.
+
+Thanks.
+
+ Bertrand.
+
diff --git a/src/camel/README b/src/camel/README
new file mode 100644
index 000000000..cbb8f56b4
--- /dev/null
+++ b/src/camel/README
@@ -0,0 +1,99 @@
+
+ CAMEL
+
+
+ A generic Messaging Library
+
+
+ ----
+
+
+Introduction:
+-------------
+
+Camel is a generic messaging library. It supports the standard
+messaging system for receiving and sending messages. It is the
+messaging backend for Evolution.
+
+The name "camel" stands for ... nothing. Open area of development there.
+You know, that "bazaar" thing. Maybe could we organize a big contest on
+gnome-list to find the best explanation :)
+
+Camel draws heavily from JavaMail and the IMAP4rev1 RFC. People
+wanting to hack on a provider should read the JavaMail API
+specification, but CMC and MAPI are of interest too.
+
+
+Organization:
+-------------
+
+The library is roughly a set of abstract classes, some kind of generic
+"interfaces" (IDL interfaces, not Java interfaces).
+
+Particular implementations are called providers.
+
+Here are the basic objects:
+
+* CamelService : An abstract class representing an access to a server.
+Handles the connection and authentication to any server.
+
+* CamelStore (CamelService): A hierarchy of folders on a server.
+
+* CamelFolder : An object containing messages. A folder is always
+associated with a store.
+
+* CamelMessage : An object contained in folders. Is defined by a set
+of attributes and a content. (Attributes include: the date it was
+received, the sender address, .....)
+
+* CamelTransport (CamelService): A way to send messages.
+
+....
+...
+
+
+#include Ordering:
+------------------
+
+Not all Unix system header files #include the headers that they
+themselves reference, so in order to maintain portability it is often
+important to #include headers in the proper order.
+
+This information can often be deduced from reading the man pages
+involving the functions you are using.
+
+For example, `man 2 open` informs us that the following headers are
+necessary and lists them in the following order:
+
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+
+Another common header that is often needed for low-level I/O is
+unistd.h which is required by functions such as read() and
+write(). The Linux man pages don't seem to specify what its
+dependencies are, but often it depends on sys/types.h.
+
+If you are going to be doing any socket I/O you'll be needing
+sys/socket.h which often depends on sys/types.h.
+
+A tried and true #include ordering scheme for source files doing I/O
+is this:
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+Feel free to cut out any headers your code doesn't actually need in
+the list.
diff --git a/src/camel/README.HACKING b/src/camel/README.HACKING
new file mode 100644
index 000000000..71e191757
--- /dev/null
+++ b/src/camel/README.HACKING
@@ -0,0 +1,14 @@
+You want to hack on Camel ?
+
+Thanks. Camel aims at being the best messaging
+library for Linux and your help is welcome.
+Please be sure to read the following files before
+commiting any change or sending any patch:
+
+CODING.STYLE
+README.COPYRIGHT
+
+
+Thanks.
+
+ Bertrand <Bertrand.Guiheneuf@aful.org>
diff --git a/src/camel/README.mt b/src/camel/README.mt
new file mode 100644
index 000000000..aeece1b0b
--- /dev/null
+++ b/src/camel/README.mt
@@ -0,0 +1,171 @@
+
+This version of camel is working towards being multi-thread safe
+(MT-SAFE). At least, for the important api's.
+
+This code has now been merged into the main head, but this file
+will remain here as a log of how it was done, incase any issues
+arise. The ChangeLog of course has a much more detailed list
+of changes.
+
+Intended method
+===============
+
+I intend working on it in several stages:
+
+1. Making the api multi-threadable. Basically removing some const-returns,
+and copying some data where it wasn't before. The api should
+still continue to work if not being used in a multithreaded
+application. There is not a significant amount of work here since
+this was more or less the intention all along.
+
+Some functions where references to objects are returned may have to be
+changed slightly, so that refcounts are incremented before return.
+This doesn't affect much though.
+
+camel_folder::get_message_info done
+camel_folder_summary::uid done
+camel_folder_summary::index done
+camel_folder::get_summary
+ Needs to ref each summary item it points to. done
+camel_folder::free_summary
+ Needs to unref each summary item it points to. done
+camel_folder_get_message_tag
+ needs to copy the tag return
+camel_maildir_summary filename string
+ should not be able to modify the string
+ array contents after it has been added to
+ the summary.
+camel_folder done
+ Make every camel-folder use a camel-folder-summary.
+ This just reduces some of the code duplication,
+ since everything but vee-folder does this already.
+
+2. Adding high level locks for proof of concept. The locks will
+be stored in private or global data, so the api should remain the same for
+non-threaded applications.
+
+A per-folder lock which governs access to the folder
+ summary, the folder file or
+ communications socket, etc. done
+Locking for exceptions. done
+Per store locks for internal stuff. done
+Per-service locks for various internal lists and
+ caches done
+
+3. Further fine-grained locking where it can be done/is worthwhile.
+
+A per-index lock for libibex done
+Locking for the search object half done
+Internal lock for the folder_summary itself
+ So that searching can be detatched from other
+ folder operations, etc. done
+Possibly a lock for access to parts of a mime-part
+ or message
+
+4. A method to cancel operations.
+
+Individual outstanding operations must be cancellable, and not just
+'all current operations'. This will probably not use pthread_cancel
+type of cancelling.
+
+This will however, probably use a method for starting a new thread,
+through camel, that can then be cancelled, and/or some method of
+registering that a thread can be cancelled. Blocking states within
+camel, within that thread, will then act as checkpoints for if the
+operation, and if it is cancelled, the operation will abort
+(i.e. fail, with an appropriate exception code).
+
+Operation cancelling should also function when the application is not
+multi-threaded. Not sure of the api for this yet, probably a callback
+system. Hopefully the api for both scenarios can be made the same.
+
+Other thoughts
+==============
+
+Basically much of the code in camel that does the actual work does NOT
+need to be thread safe to make it safely usable in an mt context.
+
+camel-folder, camel-summary, camel-imap-search, and the camel-service
+classes (at least) are the important ones to be made multithreaded.
+
+For other things, they are either resources that are created
+one-off (for example, camel-mime-message, and its associated
+parts, like camel-internet-address), or multithreadedness
+doesn't make a lot of sense - e.g. camel-stream, or camel-mime-parser.
+
+So basically the approach is a low-risk one. Adding the minimum
+number of locks to start with, and providing further fine-grained
+locks as required. The locks should not need to be particularly
+fine-grained in order to get reasonable results.
+
+Log of changes
+==============
+
+Changed CamelFolder:get_message_info() to return a ref'd copy, requiring
+all get_message_info()'s to have a matching free_message_info().
+
+Moved the CamelFolder frozen changelog data to a private structure.
+
+Added a mutex for CamelFolder frozen changelog stuff (it was just easy
+to do, although it isn't needed yet).
+
+Added a single mutex around all other CamelFolder functions that need
+it, this is just the first cut at mt'edness.
+
+Fixed all camel-folder implementations that call any other
+camel-folder functions to call via virtual methods, to bypass the locks.
+
+Added camel-store private data.
+
+Added a single mutex lock for camel-store's folder functions.
+
+Added camel-service private data.
+
+Added a single mutex lock for camel-service's connect stuff.
+
+Added a mutex for remote-store stream io stuff.
+
+Added a mutex for imap, so it can bracket a compound command
+exclusively. Pop doesn't need this since you can only have a single
+folder per store, and the folder interface is already forced
+single-threaded.
+
+Added mutex for camel-session, most operations.
+
+Running the tests finds at least 1 deadlock so far. Need to
+work on that.
+
+Fixed get_summary to ref/unref its items.
+
+Removed the global folder lock from the toplevel
+camel_folder_search(), each implementation must now handle locking.
+
+Fixed the local-folder implementation of searching. imap-folder
+searching should already be mt-safe through the command lock.
+
+Fixed imap summary to ref/unref too.
+
+Built some test cases, and expanded the test framework library to
+handle multiple threads. It works!
+
+Next, added a recursive mutex class, so that locking inside imap had
+any chance of working. Got imap working.
+
+Moved the camel folder summary into the base folder class, and fixed
+everything to use it that way.
+
+Made the vfolder use a real camel-folder-summary rather than a
+hashtable + array that it was using, and probably fixed some problems
+which caused evolution-mail not to always catch flag updates. Oh, and
+made it sync/expunge all its subfolders when sync/expungeing.
+
+Made the camel-folder summary completely mt-safe.
+
+Removed all of the locks on the folder functions dealing directly with
+the summary, so now for example all summary lookups will not be
+interupted by long operations.
+
+Made the nntp newsrc thing mt-safe, because of some unfortunate
+sideeffect of it being called from the summary interaction code in
+nntp-folder.
+
diff --git a/src/camel/camel-address.c b/src/camel/camel-address.c
new file mode 100644
index 000000000..9b6e6d355
--- /dev/null
+++ b/src/camel/camel-address.c
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "camel-address.h"
+
+G_DEFINE_TYPE (CamelAddress, camel_address, G_TYPE_OBJECT)
+
+static void
+address_finalize (GObject *object)
+{
+ CamelAddress *address = CAMEL_ADDRESS (object);
+
+ camel_address_remove (address, -1);
+ g_ptr_array_free (address->addresses, TRUE);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_address_parent_class)->finalize (object);
+}
+
+static void
+camel_address_class_init (CamelAddressClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = address_finalize;
+}
+
+static void
+camel_address_init (CamelAddress *address)
+{
+ address->addresses = g_ptr_array_new ();
+}
+
+/**
+ * camel_address_new:
+ *
+ * Create a new #CamelAddress object.
+ *
+ * Returns: a new #CamelAddress object
+ **/
+CamelAddress *
+camel_address_new (void)
+{
+ return g_object_new (CAMEL_TYPE_ADDRESS, NULL);
+}
+
+/**
+ * camel_address_new_clone:
+ * @addr: a #CamelAddress object
+ *
+ * Clone an existing address type.
+ *
+ * Returns: (transfer full): the cloned address
+ **/
+CamelAddress *
+camel_address_new_clone (CamelAddress *addr)
+{
+ CamelAddress *new;
+
+ new = g_object_new (G_OBJECT_TYPE (addr), NULL);
+ camel_address_cat (new, addr);
+
+ return new;
+}
+
+/**
+ * camel_address_length:
+ * @addr: a #CamelAddress object
+ *
+ * Get the number of addresses stored in the address @addr.
+ *
+ * Returns: the number of addresses contained in @addr
+ **/
+gint
+camel_address_length (CamelAddress *addr)
+{
+ return addr->addresses->len;
+}
+
+/**
+ * camel_address_decode:
+ * @addr: a #CamelAddress object
+ * @raw: raw address description
+ *
+ * Construct a new address from a raw address field.
+ *
+ * Returns: the number of addresses parsed or %-1 on fail
+ **/
+gint
+camel_address_decode (CamelAddress *addr,
+ const gchar *raw)
+{
+ CamelAddressClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_ADDRESS (addr), -1);
+
+ class = CAMEL_ADDRESS_GET_CLASS (addr);
+ g_return_val_if_fail (class->decode != NULL, -1);
+
+ return class->decode (addr, raw);
+}
+
+/**
+ * camel_address_encode:
+ * @addr: a #CamelAddress object
+ *
+ * Encode an address in a format suitable for a raw header.
+ *
+ * Returns: the encoded address
+ **/
+gchar *
+camel_address_encode (CamelAddress *addr)
+{
+ CamelAddressClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_ADDRESS (addr), NULL);
+
+ class = CAMEL_ADDRESS_GET_CLASS (addr);
+ g_return_val_if_fail (class->encode != NULL, NULL);
+
+ return class->encode (addr);
+}
+
+/**
+ * camel_address_unformat:
+ * @addr: a #CamelAddress object
+ * @raw: raw address description
+ *
+ * Attempt to convert a previously formatted and/or edited
+ * address back into internal form.
+ *
+ * Returns: the number of addresses parsed or %-1 on fail
+ **/
+gint
+camel_address_unformat (CamelAddress *addr,
+ const gchar *raw)
+{
+ CamelAddressClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_ADDRESS (addr), -1);
+
+ class = CAMEL_ADDRESS_GET_CLASS (addr);
+ g_return_val_if_fail (class->unformat != NULL, -1);
+
+ return class->unformat (addr, raw);
+}
+
+/**
+ * camel_address_format:
+ * @addr: a #CamelAddress object
+ *
+ * Format an address in a format suitable for display.
+ *
+ * Returns: a newly allocated string containing the formatted addresses
+ **/
+gchar *
+camel_address_format (CamelAddress *addr)
+{
+ CamelAddressClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_ADDRESS (addr), NULL);
+
+ class = CAMEL_ADDRESS_GET_CLASS (addr);
+ g_return_val_if_fail (class->format != NULL, NULL);
+
+ return class->format (addr);
+}
+
+/**
+ * camel_address_cat:
+ * @dest: destination #CamelAddress object
+ * @source: source #CamelAddress object
+ *
+ * Concatenate one address onto another. The addresses must
+ * be of the same type.
+ *
+ * Returns: the number of addresses concatenated
+ **/
+gint
+camel_address_cat (CamelAddress *dest,
+ CamelAddress *source)
+{
+ CamelAddressClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_ADDRESS (dest), -1);
+ g_return_val_if_fail (CAMEL_IS_ADDRESS (source), -1);
+
+ class = CAMEL_ADDRESS_GET_CLASS (dest);
+ g_return_val_if_fail (class->cat != NULL, -1);
+
+ return class->cat (dest, source);
+}
+
+/**
+ * camel_address_copy:
+ * @dest: destination #CamelAddress object
+ * @source: source #CamelAddress object
+ *
+ * Copy the contents of one address into another.
+ *
+ * Returns: the number of addresses copied
+ **/
+gint
+camel_address_copy (CamelAddress *dest,
+ CamelAddress *source)
+{
+ g_return_val_if_fail (CAMEL_IS_ADDRESS (dest), -1);
+ g_return_val_if_fail (CAMEL_IS_ADDRESS (source), -1);
+
+ camel_address_remove (dest, -1);
+ return camel_address_cat (dest, source);
+}
+
+/**
+ * camel_address_remove:
+ * @addr: a #CamelAddress object
+ * @index: The address to remove, use %-1 to remove all address.
+ *
+ * Remove an address by index, or all addresses.
+ **/
+void
+camel_address_remove (CamelAddress *addr,
+ gint index)
+{
+ CamelAddressClass *class;
+
+ g_return_if_fail (CAMEL_IS_ADDRESS (addr));
+
+ class = CAMEL_ADDRESS_GET_CLASS (addr);
+ g_return_if_fail (class->remove != NULL);
+
+ if (index == -1) {
+ for (index = addr->addresses->len; index>-1; index--)
+ class->remove (addr, index);
+ } else
+ class->remove (addr, index);
+}
diff --git a/src/camel/camel-address.h b/src/camel/camel-address.h
new file mode 100644
index 000000000..186d6cdd2
--- /dev/null
+++ b/src/camel/camel-address.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_ADDRESS_H
+#define CAMEL_ADDRESS_H
+
+#include <glib-object.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_ADDRESS \
+ (camel_address_get_type ())
+#define CAMEL_ADDRESS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_ADDRESS, CamelAddress))
+#define CAMEL_ADDRESS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_ADDRESS, CamelAddressClass))
+#define CAMEL_IS_ADDRESS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_ADDRESS))
+#define CAMEL_IS_ADDRESS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_ADDRESS))
+#define CAMEL_ADDRESS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_ADDRESS, CamelAddressClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelAddress CamelAddress;
+typedef struct _CamelAddressClass CamelAddressClass;
+typedef struct _CamelAddressPrivate CamelAddressPrivate;
+
+struct _CamelAddress {
+ GObject parent;
+
+ GPtrArray *addresses;
+
+ CamelAddressPrivate *priv;
+};
+
+struct _CamelAddressClass {
+ GObjectClass parent_class;
+
+ gint (*decode) (CamelAddress *addr,
+ const gchar *raw);
+ gchar * (*encode) (CamelAddress *addr);
+ gint (*unformat) (CamelAddress *addr,
+ const gchar *raw);
+ gchar * (*format) (CamelAddress *addr);
+ gint (*cat) (CamelAddress *dest,
+ CamelAddress *source);
+ void (*remove) (CamelAddress *addr,
+ gint index);
+};
+
+GType camel_address_get_type (void);
+CamelAddress * camel_address_new (void);
+CamelAddress * camel_address_new_clone (CamelAddress *addr);
+gint camel_address_length (CamelAddress *addr);
+gint camel_address_decode (CamelAddress *addr,
+ const gchar *raw);
+gchar * camel_address_encode (CamelAddress *addr);
+gint camel_address_unformat (CamelAddress *addr,
+ const gchar *raw);
+gchar * camel_address_format (CamelAddress *addr);
+gint camel_address_cat (CamelAddress *dest,
+ CamelAddress *source);
+gint camel_address_copy (CamelAddress *dest,
+ CamelAddress *source);
+void camel_address_remove (CamelAddress *addr,
+ gint index);
+
+G_END_DECLS
+
+#endif /* CAMEL_ADDRESS_H */
diff --git a/src/camel/camel-async-closure.c b/src/camel/camel-async-closure.c
new file mode 100644
index 000000000..f765571fa
--- /dev/null
+++ b/src/camel/camel-async-closure.c
@@ -0,0 +1,201 @@
+/*
+ * camel-async-closure.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * SECTION: camel-async-closure
+ * @short_description: Run asynchronous functions synchronously
+ * @include: camel/camel.h
+ *
+ * #CamelAsyncClosure provides a simple way to run an asynchronous function
+ * synchronously without blocking the current thread.
+ *
+ * 1) Create a #CamelAsyncClosure with camel_async_closure_new().
+ *
+ * 2) Call the asynchronous function passing camel_async_closure_callback()
+ * as the #GAsyncReadyCallback argument and the #CamelAsyncClosure as the
+ * data argument.
+ *
+ * 3) Call camel_async_closure_wait() and collect the #GAsyncResult.
+ *
+ * 4) Call the corresponding asynchronous "finish" function, passing the
+ * #GAsyncResult returned by camel_async_closure_wait().
+ *
+ * 5) If needed, repeat steps 2-4 for additional asynchronous functions
+ * using the same #CamelAsyncClosure.
+ *
+ * 6) Finally, free the #CamelAsyncClosure with camel_async_closure_free().
+ **/
+
+#include "camel-async-closure.h"
+
+/**
+ * CamelAsyncClosure:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.12
+ **/
+struct _CamelAsyncClosure {
+ GMainLoop *loop;
+ GMainContext *context;
+ GAsyncResult *result;
+ gboolean finished;
+ GMutex lock;
+};
+
+/**
+ * camel_async_closure_new:
+ *
+ * Creates a new #CamelAsyncClosure for use with asynchronous functions.
+ *
+ * Returns: a new #CamelAsyncClosure
+ *
+ * Since: 3.12
+ **/
+CamelAsyncClosure *
+camel_async_closure_new (void)
+{
+ CamelAsyncClosure *closure;
+
+ closure = g_slice_new0 (CamelAsyncClosure);
+ closure->context = g_main_context_new ();
+ closure->loop = g_main_loop_new (closure->context, FALSE);
+ closure->finished = FALSE;
+ g_mutex_init (&closure->lock);
+
+ g_main_context_push_thread_default (closure->context);
+
+ return closure;
+}
+
+static gboolean
+camel_async_closure_unlock_mutex_cb (gpointer user_data)
+{
+ CamelAsyncClosure *closure = user_data;
+
+ g_return_val_if_fail (closure != NULL, FALSE);
+
+ g_mutex_unlock (&closure->lock);
+
+ return FALSE;
+}
+
+/**
+ * camel_async_closure_wait:
+ * @closure: a #CamelAsyncClosure
+ *
+ * Call this function immediately after starting an asynchronous operation.
+ * The function waits for the asynchronous operation to complete and returns
+ * its #GAsyncResult to be passed to the operation's "finish" function.
+ *
+ * This function can be called repeatedly on the same #CamelAsyncClosure to
+ * easily string together multiple asynchronous operations.
+ *
+ * Returns: (transfer none): a #GAsyncResult which is owned by the closure
+ *
+ * Since: 3.12
+ **/
+GAsyncResult *
+camel_async_closure_wait (CamelAsyncClosure *closure)
+{
+ g_return_val_if_fail (closure != NULL, NULL);
+
+ g_mutex_lock (&closure->lock);
+ if (closure->finished) {
+ g_mutex_unlock (&closure->lock);
+ } else {
+ GSource *idle_source;
+
+ /* Unlock the closure->lock in the main loop, to ensure thread safety.
+ It should be processed before anything else, otherwise deadlock happens. */
+ idle_source = g_idle_source_new ();
+ g_source_set_callback (idle_source, camel_async_closure_unlock_mutex_cb, closure, NULL);
+ g_source_set_priority (idle_source, G_PRIORITY_HIGH * 2);
+ g_source_attach (idle_source, closure->context);
+ g_source_unref (idle_source);
+
+ g_main_loop_run (closure->loop);
+ }
+
+ return closure->result;
+}
+
+/**
+ * camel_async_closure_free:
+ * @closure: a #CamelAsyncClosure
+ *
+ * Frees the @closure and the resources it holds.
+ *
+ * Since: 3.12
+ **/
+void
+camel_async_closure_free (CamelAsyncClosure *closure)
+{
+ g_return_if_fail (closure != NULL);
+
+ g_main_context_pop_thread_default (closure->context);
+
+ g_main_loop_unref (closure->loop);
+ g_main_context_unref (closure->context);
+
+ g_mutex_lock (&closure->lock);
+ g_clear_object (&closure->result);
+ g_mutex_unlock (&closure->lock);
+ g_mutex_clear (&closure->lock);
+
+ g_slice_free (CamelAsyncClosure, closure);
+}
+
+/**
+ * camel_async_closure_callback:
+ * @source_object: a #GObject or %NULL
+ * @result: a #GAsyncResult
+ * @closure: a #CamelAsyncClosure
+ *
+ * Pass this function as the #GAsyncReadyCallback argument of an asynchronous
+ * function, and the #CamelAsyncClosure as the data argument.
+ *
+ * This causes camel_async_closure_wait() to terminate and return @result.
+ *
+ * Since: 3.12
+ **/
+void
+camel_async_closure_callback (GObject *source_object,
+ GAsyncResult *result,
+ gpointer closure)
+{
+ CamelAsyncClosure *real_closure;
+
+ g_return_if_fail (G_IS_ASYNC_RESULT (result));
+ g_return_if_fail (closure != NULL);
+
+ real_closure = closure;
+
+ g_mutex_lock (&real_closure->lock);
+
+ /* Replace any previous result. */
+ if (real_closure->result != NULL)
+ g_object_unref (real_closure->result);
+ real_closure->result = g_object_ref (result);
+ real_closure->finished = TRUE;
+
+ g_mutex_unlock (&real_closure->lock);
+
+ g_main_loop_quit (real_closure->loop);
+}
+
diff --git a/src/camel/camel-async-closure.h b/src/camel/camel-async-closure.h
new file mode 100644
index 000000000..da597cef6
--- /dev/null
+++ b/src/camel/camel-async-closure.h
@@ -0,0 +1,38 @@
+/*
+ * camel-async-closure.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_ASYNC_CLOSURE_H
+#define CAMEL_ASYNC_CLOSURE_H
+
+#include <gio/gio.h>
+
+typedef struct _CamelAsyncClosure CamelAsyncClosure;
+
+CamelAsyncClosure *
+ camel_async_closure_new (void);
+GAsyncResult * camel_async_closure_wait (CamelAsyncClosure *closure);
+void camel_async_closure_free (CamelAsyncClosure *closure);
+void camel_async_closure_callback (GObject *source_object,
+ GAsyncResult *result,
+ gpointer closure);
+
+#endif /* CAMEL_ASYNC_CLOSURE_H */
+
diff --git a/src/camel/camel-block-file.c b/src/camel/camel-block-file.c
new file mode 100644
index 000000000..92ec24b60
--- /dev/null
+++ b/src/camel/camel-block-file.c
@@ -0,0 +1,1250 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gstdio.h>
+
+#include "camel-block-file.h"
+#include "camel-file-utils.h"
+
+#define d(x) /*(printf("%s(%d):%s: ", __FILE__, __LINE__, __PRETTY_FUNCTION__),(x))*/
+
+/* Locks must be obtained in the order defined */
+
+struct _CamelBlockFilePrivate {
+ struct _CamelBlockFile *base;
+
+ GMutex root_lock; /* for modifying the root block */
+ GMutex cache_lock; /* for refcounting, flag manip, cache manip */
+ GMutex io_lock; /* for all io ops */
+
+ guint deleted : 1;
+};
+
+#define CAMEL_BLOCK_FILE_LOCK(kf, lock) (g_mutex_lock(&(kf)->priv->lock))
+#define CAMEL_BLOCK_FILE_TRYLOCK(kf, lock) (g_mutex_trylock(&(kf)->priv->lock))
+#define CAMEL_BLOCK_FILE_UNLOCK(kf, lock) (g_mutex_unlock(&(kf)->priv->lock))
+
+#define LOCK(x) g_mutex_lock(&x)
+#define UNLOCK(x) g_mutex_unlock(&x)
+
+static GMutex block_file_lock;
+
+/* lru cache of block files */
+static GQueue block_file_list = G_QUEUE_INIT;
+/* list to store block files that are actually intialised */
+static GQueue block_file_active_list = G_QUEUE_INIT;
+static gint block_file_count = 0;
+static gint block_file_threshhold = 10;
+
+static gint sync_nolock (CamelBlockFile *bs);
+static gint sync_block_nolock (CamelBlockFile *bs, CamelBlock *bl);
+
+G_DEFINE_TYPE (CamelBlockFile, camel_block_file, G_TYPE_OBJECT)
+
+static gint
+block_file_validate_root (CamelBlockFile *bs)
+{
+ CamelBlockRoot *br;
+ struct stat st;
+ gint retval;
+
+ br = bs->root;
+
+ retval = fstat (bs->fd, &st);
+
+ d (printf ("Validate root: '%s'\n", bs->path));
+ d (printf ("version: %.8s (%.8s)\n", bs->root->version, bs->version));
+ d (printf (
+ "block size: %d (%d)%s\n",
+ br->block_size, bs->block_size,
+ br->block_size != bs->block_size ? " BAD":" OK"));
+ d (printf (
+ "free: %ld (%d add size < %ld)%s\n",
+ (glong) br->free,
+ br->free / bs->block_size * bs->block_size,
+ (glong) st.st_size,
+ (br->free > st.st_size) ||
+ (br->free % bs->block_size) != 0 ? " BAD":" OK"));
+ d (printf (
+ "last: %ld (%d and size: %ld)%s\n",
+ (glong) br->last,
+ br->last / bs->block_size * bs->block_size,
+ (glong) st.st_size,
+ (br->last != st.st_size) ||
+ ((br->last % bs->block_size) != 0) ? " BAD": " OK"));
+ d (printf (
+ "flags: %s\n",
+ (br->flags & CAMEL_BLOCK_FILE_SYNC) ? "SYNC" : "unSYNC"));
+
+ if (br->last == 0
+ || memcmp (bs->root->version, bs->version, 8) != 0
+ || br->block_size != bs->block_size
+ || (br->free % bs->block_size) != 0
+ || (br->last % bs->block_size) != 0
+ || retval == -1
+ || st.st_size != br->last
+ || br->free > st.st_size
+ || (br->flags & CAMEL_BLOCK_FILE_SYNC) == 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static gint
+block_file_init_root (CamelBlockFile *bs)
+{
+ CamelBlockRoot *br = bs->root;
+
+ memset (br, 0, bs->block_size);
+ memcpy (br->version, bs->version, 8);
+ br->last = bs->block_size;
+ br->flags = CAMEL_BLOCK_FILE_SYNC;
+ br->free = 0;
+ br->block_size = bs->block_size;
+
+ return 0;
+}
+
+static void
+block_file_finalize (GObject *object)
+{
+ CamelBlockFile *bs = CAMEL_BLOCK_FILE (object);
+ CamelBlock *bl;
+
+ if (bs->root_block)
+ camel_block_file_sync (bs);
+
+ /* remove from lru list */
+ LOCK (block_file_lock);
+
+ if (bs->fd != -1)
+ block_file_count--;
+
+ /* XXX This is only supposed to be in one block file list
+ * at a time, but not sure if we can guarantee which,
+ * so try removing from both lists. */
+ g_queue_remove (&block_file_list, bs->priv);
+ g_queue_remove (&block_file_active_list, bs->priv);
+
+ UNLOCK (block_file_lock);
+
+ while ((bl = g_queue_pop_head (&bs->block_cache)) != NULL) {
+ if (bl->refcount != 0)
+ g_warning ("Block '%u' still referenced", bl->id);
+ g_free (bl);
+ }
+
+ g_hash_table_destroy (bs->blocks);
+
+ if (bs->root_block)
+ camel_block_file_unref_block (bs, bs->root_block);
+ g_free (bs->path);
+ if (bs->fd != -1)
+ close (bs->fd);
+
+ g_mutex_clear (&bs->priv->io_lock);
+ g_mutex_clear (&bs->priv->cache_lock);
+ g_mutex_clear (&bs->priv->root_lock);
+
+ g_free (bs->priv);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_block_file_parent_class)->finalize (object);
+}
+
+static void
+camel_block_file_class_init (CamelBlockFileClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = block_file_finalize;
+
+ class->validate_root = block_file_validate_root;
+ class->init_root = block_file_init_root;
+}
+
+static guint
+block_hash_func (gconstpointer v)
+{
+ return ((camel_block_t) GPOINTER_TO_UINT (v)) >> CAMEL_BLOCK_SIZE_BITS;
+}
+
+static void
+camel_block_file_init (CamelBlockFile *bs)
+{
+ bs->fd = -1;
+ bs->block_size = CAMEL_BLOCK_SIZE;
+ g_queue_init (&bs->block_cache);
+ bs->blocks = g_hash_table_new ((GHashFunc) block_hash_func, NULL);
+ /* this cache size and the text index size have been tuned for about the best
+ * with moderate memory usage. Doubling the memory usage barely affects performance. */
+ bs->block_cache_limit = 256;
+
+ bs->priv = g_malloc0 (sizeof (*bs->priv));
+ bs->priv->base = bs;
+
+ g_mutex_init (&bs->priv->root_lock);
+ g_mutex_init (&bs->priv->cache_lock);
+ g_mutex_init (&bs->priv->io_lock);
+
+ /* link into lru list */
+ LOCK (block_file_lock);
+ g_queue_push_head (&block_file_list, bs->priv);
+ UNLOCK (block_file_lock);
+}
+
+/* 'use' a block file for io */
+static gint
+block_file_use (CamelBlockFile *bs)
+{
+ CamelBlockFile *bf;
+ GList *link;
+ gint err;
+
+ /* We want to:
+ * remove file from active list
+ * lock it
+ *
+ * Then when done:
+ * unlock it
+ * add it back to end of active list
+ */
+
+ CAMEL_BLOCK_FILE_LOCK (bs, io_lock);
+
+ if (bs->fd != -1)
+ return 0;
+ else if (bs->priv->deleted) {
+ CAMEL_BLOCK_FILE_UNLOCK (bs, io_lock);
+ errno = ENOENT;
+ return -1;
+ } else {
+ d (printf ("Turning block file online: %s\n", bs->path));
+ }
+
+ if ((bs->fd = g_open (bs->path, bs->flags | O_BINARY, 0600)) == -1) {
+ err = errno;
+ CAMEL_BLOCK_FILE_UNLOCK (bs, io_lock);
+ errno = err;
+ return -1;
+ }
+
+ LOCK (block_file_lock);
+
+ link = g_queue_find (&block_file_list, bs->priv);
+ if (link != NULL) {
+ g_queue_unlink (&block_file_list, link);
+ g_queue_push_tail_link (&block_file_active_list, link);
+ }
+
+ block_file_count++;
+
+ link = g_queue_peek_head_link (&block_file_list);
+
+ while (link != NULL && block_file_count > block_file_threshhold) {
+ struct _CamelBlockFilePrivate *nw = link->data;
+
+ /* We never hit the current blockfile here, as its removed from the list first */
+ bf = nw->base;
+ if (bf->fd != -1) {
+ /* Need to trylock, as any of these lock levels might be trying
+ * to lock the block_file_lock, so we need to check and abort if so */
+ if (CAMEL_BLOCK_FILE_TRYLOCK (bf, root_lock)) {
+ if (CAMEL_BLOCK_FILE_TRYLOCK (bf, cache_lock)) {
+ if (CAMEL_BLOCK_FILE_TRYLOCK (bf, io_lock)) {
+ d (printf ("[%d] Turning block file offline: %s\n", block_file_count - 1, bf->path));
+ sync_nolock (bf);
+ close (bf->fd);
+ bf->fd = -1;
+ block_file_count--;
+ CAMEL_BLOCK_FILE_UNLOCK (bf, io_lock);
+ }
+ CAMEL_BLOCK_FILE_UNLOCK (bf, cache_lock);
+ }
+ CAMEL_BLOCK_FILE_UNLOCK (bf, root_lock);
+ }
+ }
+
+ link = g_list_next (link);
+ }
+
+ UNLOCK (block_file_lock);
+
+ return 0;
+}
+
+static void
+block_file_unuse (CamelBlockFile *bs)
+{
+ GList *link;
+
+ LOCK (block_file_lock);
+ link = g_queue_find (&block_file_active_list, bs->priv);
+ if (link != NULL) {
+ g_queue_unlink (&block_file_active_list, link);
+ g_queue_push_tail_link (&block_file_list, link);
+ }
+ UNLOCK (block_file_lock);
+
+ CAMEL_BLOCK_FILE_UNLOCK (bs, io_lock);
+}
+
+/*
+ * o = camel_cache_get (c, key);
+ * camel_cache_unref (c, key);
+ * camel_cache_add (c, key, o);
+ * camel_cache_remove (c, key);
+ */
+
+/**
+ * camel_block_file_new:
+ * @path:
+ * @:
+ * @block_size:
+ *
+ * Allocate a new block file, stored at @path. @version contains an 8 character
+ * version string which must match the head of the file, or the file will be
+ * intitialised.
+ *
+ * @block_size is currently ignored and is set to CAMEL_BLOCK_SIZE.
+ *
+ * Returns: The new block file, or NULL if it could not be created.
+ **/
+CamelBlockFile *
+camel_block_file_new (const gchar *path,
+ gint flags,
+ const gchar version[8],
+ gsize block_size)
+{
+ CamelBlockFileClass *class;
+ CamelBlockFile *bs;
+
+ bs = g_object_new (CAMEL_TYPE_BLOCK_FILE, NULL);
+ memcpy (bs->version, version, 8);
+ bs->path = g_strdup (path);
+ bs->flags = flags;
+
+ bs->root_block = camel_block_file_get_block (bs, 0);
+ if (bs->root_block == NULL) {
+ g_object_unref (bs);
+ return NULL;
+ }
+ camel_block_file_detach_block (bs, bs->root_block);
+ bs->root = (CamelBlockRoot *) &bs->root_block->data;
+
+ /* we only need these flags on first open */
+ bs->flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
+
+ class = CAMEL_BLOCK_FILE_GET_CLASS (bs);
+
+ /* Do we need to init the root block? */
+ if (class->validate_root (bs) == -1) {
+ d (printf ("Initialise root block: %.8s\n", version));
+
+ class->init_root (bs);
+ camel_block_file_touch_block (bs, bs->root_block);
+ if (block_file_use (bs) == -1) {
+ g_object_unref (bs);
+ return NULL;
+ }
+ if (sync_block_nolock (bs, bs->root_block) == -1
+ || ftruncate (bs->fd, bs->root->last) == -1) {
+ block_file_unuse (bs);
+ g_object_unref (bs);
+ return NULL;
+ }
+ block_file_unuse (bs);
+ }
+
+ return bs;
+}
+
+gint
+camel_block_file_rename (CamelBlockFile *bs,
+ const gchar *path)
+{
+ gint ret;
+ struct stat st;
+ gint err;
+
+ g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
+ g_return_val_if_fail (path != NULL, -1);
+
+ CAMEL_BLOCK_FILE_LOCK (bs, io_lock);
+
+ ret = g_rename (bs->path, path);
+ if (ret == -1) {
+ /* Maybe the rename actually worked */
+ err = errno;
+ if (g_stat (path, &st) == 0
+ && g_stat (bs->path, &st) == -1
+ && errno == ENOENT)
+ ret = 0;
+ errno = err;
+ }
+
+ if (ret != -1) {
+ g_free (bs->path);
+ bs->path = g_strdup (path);
+ }
+
+ CAMEL_BLOCK_FILE_UNLOCK (bs, io_lock);
+
+ return ret;
+}
+
+gint
+camel_block_file_delete (CamelBlockFile *bs)
+{
+ gint ret;
+
+ g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
+
+ CAMEL_BLOCK_FILE_LOCK (bs, io_lock);
+
+ if (bs->fd != -1) {
+ LOCK (block_file_lock);
+ block_file_count--;
+ UNLOCK (block_file_lock);
+ close (bs->fd);
+ bs->fd = -1;
+ }
+
+ bs->priv->deleted = TRUE;
+ ret = g_unlink (bs->path);
+
+ CAMEL_BLOCK_FILE_UNLOCK (bs, io_lock);
+
+ return ret;
+
+}
+
+/**
+ * camel_block_file_new_block:
+ * @bs:
+ *
+ * Allocate a new block, return a pointer to it. Old blocks
+ * may be flushed to disk during this call.
+ *
+ * Returns: The block, or NULL if an error occurred.
+ **/
+CamelBlock *
+camel_block_file_new_block (CamelBlockFile *bs)
+{
+ CamelBlock *bl;
+
+ g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), NULL);
+
+ CAMEL_BLOCK_FILE_LOCK (bs, root_lock);
+
+ if (bs->root->free) {
+ bl = camel_block_file_get_block (bs, bs->root->free);
+ if (bl == NULL)
+ goto fail;
+ bs->root->free = ((camel_block_t *) bl->data)[0];
+ } else {
+ bl = camel_block_file_get_block (bs, bs->root->last);
+ if (bl == NULL)
+ goto fail;
+ bs->root->last += CAMEL_BLOCK_SIZE;
+ }
+
+ bs->root_block->flags |= CAMEL_BLOCK_DIRTY;
+
+ bl->flags |= CAMEL_BLOCK_DIRTY;
+ memset (bl->data, 0, CAMEL_BLOCK_SIZE);
+fail:
+ CAMEL_BLOCK_FILE_UNLOCK (bs, root_lock);
+
+ return bl;
+}
+
+/**
+ * camel_block_file_free_block:
+ * @bs:
+ * @id:
+ *
+ *
+ **/
+gint
+camel_block_file_free_block (CamelBlockFile *bs,
+ camel_block_t id)
+{
+ CamelBlock *bl;
+
+ g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
+
+ bl = camel_block_file_get_block (bs, id);
+ if (bl == NULL)
+ return -1;
+
+ CAMEL_BLOCK_FILE_LOCK (bs, root_lock);
+
+ ((camel_block_t *) bl->data)[0] = bs->root->free;
+ bs->root->free = bl->id;
+ bs->root_block->flags |= CAMEL_BLOCK_DIRTY;
+ bl->flags |= CAMEL_BLOCK_DIRTY;
+ camel_block_file_unref_block (bs, bl);
+
+ CAMEL_BLOCK_FILE_UNLOCK (bs, root_lock);
+
+ return 0;
+}
+
+/**
+ * camel_block_file_get_block:
+ * @bs:
+ * @id:
+ *
+ * Retreive a block @id.
+ *
+ * Returns: The block, or NULL if blockid is invalid or a file error
+ * occurred.
+ **/
+CamelBlock *
+camel_block_file_get_block (CamelBlockFile *bs,
+ camel_block_t id)
+{
+ CamelBlock *bl;
+
+ g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), NULL);
+
+ /* Sanity check: Dont allow reading of root block (except before its been read)
+ * or blocks with invalid block id's */
+ if ((bs->root == NULL && id != 0)
+ || (bs->root != NULL && (id > bs->root->last || id == 0))
+ || (id % bs->block_size) != 0) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
+
+ bl = g_hash_table_lookup (bs->blocks, GUINT_TO_POINTER (id));
+
+ d (printf ("Get block %08x: %s\n", id, bl?"cached":"must read"));
+
+ if (bl == NULL) {
+ GQueue trash = G_QUEUE_INIT;
+ GList *link;
+
+ /* LOCK io_lock */
+ if (block_file_use (bs) == -1) {
+ CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
+ return NULL;
+ }
+
+ bl = g_malloc0 (sizeof (*bl));
+ bl->id = id;
+ if (lseek (bs->fd, id, SEEK_SET) == -1 ||
+ camel_read (bs->fd, (gchar *) bl->data, CAMEL_BLOCK_SIZE, NULL, NULL) == -1) {
+ block_file_unuse (bs);
+ CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
+ g_free (bl);
+ return NULL;
+ }
+
+ bs->block_cache_count++;
+ g_hash_table_insert (bs->blocks, GUINT_TO_POINTER (bl->id), bl);
+
+ /* flush old blocks */
+ link = g_queue_peek_tail_link (&bs->block_cache);
+
+ while (link != NULL && bs->block_cache_count > bs->block_cache_limit) {
+ CamelBlock *flush = link->data;
+
+ if (flush->refcount == 0) {
+ if (sync_block_nolock (bs, flush) != -1) {
+ g_hash_table_remove (bs->blocks, GUINT_TO_POINTER (flush->id));
+ g_queue_push_tail (&trash, link);
+ link->data = NULL;
+ g_free (flush);
+ bs->block_cache_count--;
+ }
+ }
+
+ link = g_list_previous (link);
+ }
+
+ /* Remove deleted blocks from the cache. */
+ while ((link = g_queue_pop_head (&trash)) != NULL)
+ g_queue_delete_link (&bs->block_cache, link);
+
+ /* UNLOCK io_lock */
+ block_file_unuse (bs);
+ } else {
+ g_queue_remove (&bs->block_cache, bl);
+ }
+
+ g_queue_push_head (&bs->block_cache, bl);
+ bl->refcount++;
+
+ CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
+
+ d (printf ("Got block %08x\n", id));
+
+ return bl;
+}
+
+/**
+ * camel_block_file_detach_block:
+ * @bs:
+ * @bl:
+ *
+ * Detatch a block from the block file's cache. The block should
+ * be unref'd or attached when finished with. The block file will
+ * perform no writes of this block or flushing of it if the cache
+ * fills.
+ **/
+void
+camel_block_file_detach_block (CamelBlockFile *bs,
+ CamelBlock *bl)
+{
+ g_return_if_fail (CAMEL_IS_BLOCK_FILE (bs));
+ g_return_if_fail (bl != NULL);
+
+ CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
+
+ g_hash_table_remove (bs->blocks, GUINT_TO_POINTER (bl->id));
+ g_queue_remove (&bs->block_cache, bl);
+ bl->flags |= CAMEL_BLOCK_DETACHED;
+
+ CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
+}
+
+/**
+ * camel_block_file_attach_block:
+ * @bs:
+ * @bl:
+ *
+ * Reattach a block that has been detached.
+ **/
+void
+camel_block_file_attach_block (CamelBlockFile *bs,
+ CamelBlock *bl)
+{
+ g_return_if_fail (CAMEL_IS_BLOCK_FILE (bs));
+ g_return_if_fail (bl != NULL);
+
+ CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
+
+ g_hash_table_insert (bs->blocks, GUINT_TO_POINTER (bl->id), bl);
+ g_queue_push_tail (&bs->block_cache, bl);
+ bl->flags &= ~CAMEL_BLOCK_DETACHED;
+
+ CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
+}
+
+/**
+ * camel_block_file_touch_block:
+ * @bs:
+ * @bl:
+ *
+ * Mark a block as dirty. The block will be written to disk if
+ * it ever expires from the cache.
+ **/
+void
+camel_block_file_touch_block (CamelBlockFile *bs,
+ CamelBlock *bl)
+{
+ g_return_if_fail (CAMEL_IS_BLOCK_FILE (bs));
+ g_return_if_fail (bl != NULL);
+
+ CAMEL_BLOCK_FILE_LOCK (bs, root_lock);
+ CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
+
+ bl->flags |= CAMEL_BLOCK_DIRTY;
+
+ if ((bs->root->flags & CAMEL_BLOCK_FILE_SYNC) && bl != bs->root_block) {
+ d (printf ("turning off sync flag\n"));
+ bs->root->flags &= ~CAMEL_BLOCK_FILE_SYNC;
+ bs->root_block->flags |= CAMEL_BLOCK_DIRTY;
+ camel_block_file_sync_block (bs, bs->root_block);
+ }
+
+ CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
+ CAMEL_BLOCK_FILE_UNLOCK (bs, root_lock);
+}
+
+/**
+ * camel_block_file_unref_block:
+ * @bs:
+ * @bl:
+ *
+ * Mark a block as unused. If a block is used it will not be
+ * written to disk, or flushed from memory.
+ *
+ * If a block is detatched and this is the last reference, the
+ * block will be freed.
+ **/
+void
+camel_block_file_unref_block (CamelBlockFile *bs,
+ CamelBlock *bl)
+{
+ g_return_if_fail (CAMEL_IS_BLOCK_FILE (bs));
+ g_return_if_fail (bl != NULL);
+
+ CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
+
+ if (bl->refcount == 1 && (bl->flags & CAMEL_BLOCK_DETACHED))
+ g_free (bl);
+ else
+ bl->refcount--;
+
+ CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
+}
+
+static gint
+sync_block_nolock (CamelBlockFile *bs,
+ CamelBlock *bl)
+{
+ d (printf ("Sync block %08x: %s\n", bl->id, (bl->flags & CAMEL_BLOCK_DIRTY)?"dirty":"clean"));
+
+ if (bl->flags & CAMEL_BLOCK_DIRTY) {
+ if (lseek (bs->fd, bl->id, SEEK_SET) == -1
+ || write (bs->fd, bl->data, CAMEL_BLOCK_SIZE) != CAMEL_BLOCK_SIZE) {
+ return -1;
+ }
+ bl->flags &= ~CAMEL_BLOCK_DIRTY;
+ }
+
+ return 0;
+}
+
+static gint
+sync_nolock (CamelBlockFile *bs)
+{
+ GList *head, *link;
+ gint work = FALSE;
+
+ head = g_queue_peek_head_link (&bs->block_cache);
+
+ for (link = head; link != NULL; link = g_list_next (link)) {
+ CamelBlock *bl = link->data;
+
+ if (bl->flags & CAMEL_BLOCK_DIRTY) {
+ work = TRUE;
+ if (sync_block_nolock (bs, bl) == -1)
+ return -1;
+ }
+ }
+
+ if (!work
+ && (bs->root_block->flags & CAMEL_BLOCK_DIRTY) == 0
+ && (bs->root->flags & CAMEL_BLOCK_FILE_SYNC) != 0)
+ return 0;
+
+ d (printf ("turning on sync flag\n"));
+
+ bs->root->flags |= CAMEL_BLOCK_FILE_SYNC;
+ bs->root_block->flags |= CAMEL_BLOCK_DIRTY;
+
+ return sync_block_nolock (bs, bs->root_block);
+}
+
+/**
+ * camel_block_file_sync_block:
+ * @bs:
+ * @bl:
+ *
+ * Flush a block to disk immediately. The block will only
+ * be flushed to disk if it is marked as dirty (touched).
+ *
+ * Returns: -1 on io error.
+ **/
+gint
+camel_block_file_sync_block (CamelBlockFile *bs,
+ CamelBlock *bl)
+{
+ gint ret;
+
+ g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
+ g_return_val_if_fail (bl != NULL, -1);
+
+ /* LOCK io_lock */
+ if (block_file_use (bs) == -1)
+ return -1;
+
+ ret = sync_block_nolock (bs, bl);
+
+ block_file_unuse (bs);
+
+ return ret;
+}
+
+/**
+ * camel_block_file_sync:
+ * @bs:
+ *
+ * Sync all dirty blocks to disk, including the root block.
+ *
+ * Returns: -1 on io error.
+ **/
+gint
+camel_block_file_sync (CamelBlockFile *bs)
+{
+ gint ret;
+
+ g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), -1);
+
+ CAMEL_BLOCK_FILE_LOCK (bs, root_lock);
+ CAMEL_BLOCK_FILE_LOCK (bs, cache_lock);
+
+ /* LOCK io_lock */
+ if (block_file_use (bs) == -1)
+ ret = -1;
+ else {
+ ret = sync_nolock (bs);
+ block_file_unuse (bs);
+ }
+
+ CAMEL_BLOCK_FILE_UNLOCK (bs, cache_lock);
+ CAMEL_BLOCK_FILE_UNLOCK (bs, root_lock);
+
+ return ret;
+}
+
+/* ********************************************************************** */
+
+struct _CamelKeyFilePrivate {
+ struct _CamelKeyFile *base;
+ GMutex lock;
+ guint deleted : 1;
+};
+
+#define CAMEL_KEY_FILE_LOCK(kf, lock) (g_mutex_lock(&(kf)->priv->lock))
+#define CAMEL_KEY_FILE_TRYLOCK(kf, lock) (g_mutex_trylock(&(kf)->priv->lock))
+#define CAMEL_KEY_FILE_UNLOCK(kf, lock) (g_mutex_unlock(&(kf)->priv->lock))
+
+static GMutex key_file_lock;
+
+/* lru cache of block files */
+static GQueue key_file_list = G_QUEUE_INIT;
+static GQueue key_file_active_list = G_QUEUE_INIT;
+static gint key_file_count = 0;
+static const gint key_file_threshhold = 10;
+
+G_DEFINE_TYPE (CamelKeyFile, camel_key_file, G_TYPE_OBJECT)
+
+static void
+key_file_finalize (GObject *object)
+{
+ CamelKeyFile *bs = CAMEL_KEY_FILE (object);
+
+ LOCK (key_file_lock);
+
+ /* XXX This is only supposed to be in one key file list
+ * at a time, but not sure if we can guarantee which,
+ * so try removing from both lists. */
+ g_queue_remove (&key_file_list, bs->priv);
+ g_queue_remove (&key_file_active_list, bs->priv);
+
+ if (bs-> fp) {
+ key_file_count--;
+ fclose (bs->fp);
+ }
+
+ UNLOCK (key_file_lock);
+
+ g_free (bs->path);
+
+ g_mutex_clear (&bs->priv->lock);
+
+ g_free (bs->priv);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_key_file_parent_class)->finalize (object);
+}
+
+static void
+camel_key_file_class_init (CamelKeyFileClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = key_file_finalize;
+}
+
+static void
+camel_key_file_init (CamelKeyFile *bs)
+{
+ bs->priv = g_malloc0 (sizeof (*bs->priv));
+ bs->priv->base = bs;
+
+ g_mutex_init (&bs->priv->lock);
+
+ LOCK (key_file_lock);
+ g_queue_push_head (&key_file_list, bs->priv);
+ UNLOCK (key_file_lock);
+}
+
+/* 'use' a key file for io */
+static gint
+key_file_use (CamelKeyFile *bs)
+{
+ CamelKeyFile *bf;
+ gint err, fd;
+ const gchar *flag;
+ GList *link;
+
+ /* We want to:
+ * remove file from active list
+ * lock it
+ *
+ * Then when done:
+ * unlock it
+ * add it back to end of active list
+ */
+
+ /* TODO: Check header on reset? */
+
+ CAMEL_KEY_FILE_LOCK (bs, lock);
+
+ if (bs->fp != NULL)
+ return 0;
+ else if (bs->priv->deleted) {
+ CAMEL_KEY_FILE_UNLOCK (bs, lock);
+ errno = ENOENT;
+ return -1;
+ } else {
+ d (printf ("Turning key file online: '%s'\n", bs->path));
+ }
+
+ if ((bs->flags & O_ACCMODE) == O_RDONLY)
+ flag = "rb";
+ else
+ flag = "a+b";
+
+ if ((fd = g_open (bs->path, bs->flags | O_BINARY, 0600)) == -1
+ || (bs->fp = fdopen (fd, flag)) == NULL) {
+ err = errno;
+ if (fd != -1)
+ close (fd);
+ CAMEL_KEY_FILE_UNLOCK (bs, lock);
+ errno = err;
+ return -1;
+ }
+
+ LOCK (key_file_lock);
+
+ link = g_queue_find (&key_file_list, bs->priv);
+ if (link != NULL) {
+ g_queue_unlink (&key_file_list, link);
+ g_queue_push_tail_link (&key_file_active_list, link);
+ }
+
+ key_file_count++;
+
+ link = g_queue_peek_head_link (&key_file_list);
+ while (link != NULL && key_file_count > key_file_threshhold) {
+ struct _CamelKeyFilePrivate *nw = link->data;
+
+ /* We never hit the current keyfile here, as its removed from the list first */
+ bf = nw->base;
+ if (bf->fp != NULL) {
+ /* Need to trylock, as any of these lock levels might be trying
+ * to lock the key_file_lock, so we need to check and abort if so */
+ if (CAMEL_BLOCK_FILE_TRYLOCK (bf, lock)) {
+ d (printf ("Turning key file offline: %s\n", bf->path));
+ fclose (bf->fp);
+ bf->fp = NULL;
+ key_file_count--;
+ CAMEL_BLOCK_FILE_UNLOCK (bf, lock);
+ }
+ }
+
+ link = g_list_next (link);
+ }
+
+ UNLOCK (key_file_lock);
+
+ return 0;
+}
+
+static void
+key_file_unuse (CamelKeyFile *bs)
+{
+ GList *link;
+
+ LOCK (key_file_lock);
+ link = g_queue_find (&key_file_active_list, bs->priv);
+ if (link != NULL) {
+ g_queue_unlink (&key_file_active_list, link);
+ g_queue_push_tail_link (&key_file_list, link);
+ }
+ UNLOCK (key_file_lock);
+
+ CAMEL_KEY_FILE_UNLOCK (bs, lock);
+}
+
+/**
+ * camel_key_file_new:
+ * @path:
+ * @flags: open flags
+ * @version: Version string (header) of file. Currently
+ * written but not checked.
+ *
+ * Create a new key file. A linked list of record blocks.
+ *
+ * Returns: A new key file, or NULL if the file could not
+ * be opened/created/initialised.
+ **/
+CamelKeyFile *
+camel_key_file_new (const gchar *path,
+ gint flags,
+ const gchar version[8])
+{
+ CamelKeyFile *kf;
+ goffset last;
+ gint err;
+
+ d (printf ("New key file '%s'\n", path));
+
+ kf = g_object_new (CAMEL_TYPE_KEY_FILE, NULL);
+ kf->path = g_strdup (path);
+ kf->fp = NULL;
+ kf->flags = flags;
+ kf->last = 8;
+
+ if (key_file_use (kf) == -1) {
+ g_object_unref (kf);
+ kf = NULL;
+ } else {
+ fseek (kf->fp, 0, SEEK_END);
+ last = ftell (kf->fp);
+ if (last == 0) {
+ fwrite (version, sizeof (gchar), 8, kf->fp);
+ last += 8;
+ }
+ kf->last = last;
+
+ err = ferror (kf->fp);
+ key_file_unuse (kf);
+
+ /* we only need these flags on first open */
+ kf->flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
+
+ if (err) {
+ g_object_unref (kf);
+ kf = NULL;
+ }
+ }
+
+ return kf;
+}
+
+gint
+camel_key_file_rename (CamelKeyFile *kf,
+ const gchar *path)
+{
+ gint ret;
+ struct stat st;
+ gint err;
+
+ g_return_val_if_fail (CAMEL_IS_KEY_FILE (kf), -1);
+ g_return_val_if_fail (path != NULL, -1);
+
+ CAMEL_KEY_FILE_LOCK (kf, lock);
+
+ ret = g_rename (kf->path, path);
+ if (ret == -1) {
+ /* Maybe the rename actually worked */
+ err = errno;
+ if (g_stat (path, &st) == 0
+ && g_stat (kf->path, &st) == -1
+ && errno == ENOENT)
+ ret = 0;
+ errno = err;
+ }
+
+ if (ret != -1) {
+ g_free (kf->path);
+ kf->path = g_strdup (path);
+ }
+
+ CAMEL_KEY_FILE_UNLOCK (kf, lock);
+
+ return ret;
+}
+
+gint
+camel_key_file_delete (CamelKeyFile *kf)
+{
+ gint ret;
+
+ g_return_val_if_fail (CAMEL_IS_KEY_FILE (kf), -1);
+
+ CAMEL_KEY_FILE_LOCK (kf, lock);
+
+ if (kf->fp) {
+ LOCK (key_file_lock);
+ key_file_count--;
+ UNLOCK (key_file_lock);
+ fclose (kf->fp);
+ kf->fp = NULL;
+ }
+
+ kf->priv->deleted = TRUE;
+ ret = g_unlink (kf->path);
+
+ CAMEL_KEY_FILE_UNLOCK (kf, lock);
+
+ return ret;
+
+}
+
+/**
+ * camel_key_file_write:
+ * @kf:
+ * @parent:
+ * @len:
+ * @records:
+ *
+ * Write a new list of records to the key file.
+ *
+ * Returns: -1 on io error. The key file will remain unchanged.
+ **/
+gint
+camel_key_file_write (CamelKeyFile *kf,
+ camel_block_t *parent,
+ gsize len,
+ camel_key_t *records)
+{
+ camel_block_t next;
+ guint32 size;
+ gint ret = -1;
+
+ g_return_val_if_fail (CAMEL_IS_KEY_FILE (kf), -1);
+ g_return_val_if_fail (parent != NULL, -1);
+ g_return_val_if_fail (records != NULL, -1);
+
+ d (printf ("write key %08x len = %d\n", *parent, len));
+
+ if (len == 0) {
+ d (printf (" new parent = %08x\n", *parent));
+ return 0;
+ }
+
+ /* LOCK */
+ if (key_file_use (kf) == -1)
+ return -1;
+
+ size = len;
+
+ /* FIXME: Use io util functions? */
+ next = kf->last;
+ if (fseek (kf->fp, kf->last, SEEK_SET) == -1)
+ return -1;
+
+ fwrite (parent, sizeof (*parent), 1, kf->fp);
+ fwrite (&size, sizeof (size), 1, kf->fp);
+ fwrite (records, sizeof (records[0]), len, kf->fp);
+
+ if (ferror (kf->fp)) {
+ clearerr (kf->fp);
+ } else {
+ kf->last = ftell (kf->fp);
+ *parent = next;
+ ret = len;
+ }
+
+ /* UNLOCK */
+ key_file_unuse (kf);
+
+ d (printf (" new parent = %08x\n", *parent));
+
+ return ret;
+}
+
+/**
+ * camel_key_file_read:
+ * @kf:
+ * @start: The record pointer. This will be set to the next record pointer on success.
+ * @len: Number of records read, if != NULL.
+ * @records: Records, allocated, must be freed with g_free, if != NULL.
+ *
+ * Read the next block of data from the key file. Returns the number of
+ * records.
+ *
+ * Returns: -1 on io error.
+ **/
+gint
+camel_key_file_read (CamelKeyFile *kf,
+ camel_block_t *start,
+ gsize *len,
+ camel_key_t **records)
+{
+ guint32 size;
+ glong pos;
+ camel_block_t next;
+ gint ret = -1;
+
+ g_return_val_if_fail (CAMEL_IS_KEY_FILE (kf), -1);
+ g_return_val_if_fail (start != NULL, -1);
+
+ pos = *start;
+ if (pos == 0)
+ return 0;
+
+ /* LOCK */
+ if (key_file_use (kf) == -1)
+ return -1;
+
+ if (fseek (kf->fp, pos, SEEK_SET) == -1
+ || fread (&next, sizeof (next), 1, kf->fp) != 1
+ || fread (&size, sizeof (size), 1, kf->fp) != 1
+ || size > 1024) {
+ clearerr (kf->fp);
+ goto fail;
+ }
+
+ if (len)
+ *len = size;
+
+ if (records) {
+ camel_key_t *keys = g_malloc (size * sizeof (camel_key_t));
+
+ if (fread (keys, sizeof (camel_key_t), size, kf->fp) != size) {
+ g_free (keys);
+ goto fail;
+ }
+ *records = keys;
+ }
+
+ *start = next;
+
+ ret = 0;
+fail:
+ /* UNLOCK */
+ key_file_unuse (kf);
+
+ return ret;
+}
diff --git a/src/camel/camel-block-file.h b/src/camel/camel-block-file.h
new file mode 100644
index 000000000..9c02075eb
--- /dev/null
+++ b/src/camel/camel-block-file.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_BLOCK_FILE_H
+#define CAMEL_BLOCK_FILE_H
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#include <glib-object.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_BLOCK_FILE \
+ (camel_block_file_get_type ())
+#define CAMEL_BLOCK_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_BLOCK_FILE, CamelBlockFile))
+#define CAMEL_BLOCK_FILE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_BLOCK_FILE, CamelBlockFileClass))
+#define CAMEL_IS_BLOCK_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_BLOCK_FILE))
+#define CAMEL_IS_BLOCK_FILE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_BLOCK_FILE))
+#define CAMEL_BLOCK_FILE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_BLOCK_FILE, CamelBlockFileClass))
+
+#define CAMEL_TYPE_KEY_FILE \
+ (camel_key_file_get_type ())
+#define CAMEL_KEY_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_KEY_FILE, CamelKeyFile))
+#define CAMEL_KEY_FILE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_KEY_FILE, CamelKeyFileClass))
+#define CAMEL_IS_KEY_FILE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_KEY_FILE))
+#define CAMEL_IS_KEY_FILE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_KEY_FILE))
+#define CAMEL_KEY_FILE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_KEY_FILE, CamelKeyFileClass))
+
+G_BEGIN_DECLS
+
+typedef guint32 camel_block_t; /* block offset, absolute, bottom BLOCK_SIZE_BITS always 0 */
+typedef guint32 camel_key_t; /* this is a bitfield of (block offset:BLOCK_SIZE_BITS) */
+
+typedef struct _CamelBlockRoot CamelBlockRoot;
+typedef struct _CamelBlock CamelBlock;
+typedef struct _CamelBlockFile CamelBlockFile;
+typedef struct _CamelBlockFileClass CamelBlockFileClass;
+typedef struct _CamelBlockFilePrivate CamelBlockFilePrivate;
+
+typedef enum {
+ CAMEL_BLOCK_FILE_SYNC = 1 << 0
+} CamelBlockFileFlags;
+
+#define CAMEL_BLOCK_SIZE (1024)
+#define CAMEL_BLOCK_SIZE_BITS (10) /* # bits to contain block_size bytes */
+
+typedef enum {
+ CAMEL_BLOCK_DIRTY = 1 << 0,
+ CAMEL_BLOCK_DETACHED = 1 << 1
+} CamelBlockFlags;
+
+struct _CamelBlockRoot {
+ gchar version[8]; /* version number */
+
+ guint32 flags; /* flags for file */
+ guint32 block_size; /* block size of this file */
+ camel_block_t free; /* free block list */
+ camel_block_t last; /* pointer to end of blocks */
+
+ /* subclasses tack on, but no more than CAMEL_BLOCK_SIZE! */
+};
+
+/* LRU cache of blocks */
+struct _CamelBlock {
+ camel_block_t id;
+ CamelBlockFlags flags;
+ guint32 refcount;
+ guint32 align00;
+
+ guchar data[CAMEL_BLOCK_SIZE];
+};
+
+struct _CamelBlockFile {
+ GObject parent;
+ CamelBlockFilePrivate *priv;
+
+ gchar version[8];
+ gchar *path;
+ CamelBlockFileFlags flags;
+
+ gint fd;
+ gsize block_size;
+
+ CamelBlockRoot *root;
+ CamelBlock *root_block;
+
+ /* make private? */
+ gint block_cache_limit;
+ gint block_cache_count;
+ GQueue block_cache;
+ GHashTable *blocks;
+};
+
+struct _CamelBlockFileClass {
+ GObjectClass parent_class;
+
+ gint (*validate_root)(CamelBlockFile *bs);
+ gint (*init_root)(CamelBlockFile *bs);
+};
+
+GType camel_block_file_get_type (void);
+CamelBlockFile *camel_block_file_new (const gchar *path,
+ gint flags,
+ const gchar version[8],
+ gsize block_size);
+gint camel_block_file_rename (CamelBlockFile *bs,
+ const gchar *path);
+gint camel_block_file_delete (CamelBlockFile *kf);
+CamelBlock * camel_block_file_new_block (CamelBlockFile *bs);
+gint camel_block_file_free_block (CamelBlockFile *bs,
+ camel_block_t id);
+CamelBlock * camel_block_file_get_block (CamelBlockFile *bs,
+ camel_block_t id);
+void camel_block_file_detach_block (CamelBlockFile *bs,
+ CamelBlock *bl);
+void camel_block_file_attach_block (CamelBlockFile *bs,
+ CamelBlock *bl);
+void camel_block_file_touch_block (CamelBlockFile *bs,
+ CamelBlock *bl);
+void camel_block_file_unref_block (CamelBlockFile *bs,
+ CamelBlock *bl);
+gint camel_block_file_sync_block (CamelBlockFile *bs,
+ CamelBlock *bl);
+gint camel_block_file_sync (CamelBlockFile *bs);
+
+/* ********************************************************************** */
+
+typedef struct _CamelKeyFile CamelKeyFile;
+typedef struct _CamelKeyFileClass CamelKeyFileClass;
+typedef struct _CamelKeyFilePrivate CamelKeyFilePrivate;
+
+struct _CamelKeyFile {
+ GObject parent;
+ CamelKeyFilePrivate *priv;
+
+ FILE *fp;
+ gchar *path;
+ gint flags;
+ goffset last;
+};
+
+struct _CamelKeyFileClass {
+ GObjectClass parent_class;
+};
+
+GType camel_key_file_get_type (void);
+
+CamelKeyFile * camel_key_file_new (const gchar *path, gint flags, const gchar version[8]);
+gint camel_key_file_rename (CamelKeyFile *kf, const gchar *path);
+gint camel_key_file_delete (CamelKeyFile *kf);
+
+gint camel_key_file_write (CamelKeyFile *kf, camel_block_t *parent, gsize len, camel_key_t *records);
+gint camel_key_file_read (CamelKeyFile *kf, camel_block_t *start, gsize *len, camel_key_t **records);
+
+G_END_DECLS
+
+#endif /* CAMEL_BLOCK_FILE_H */
diff --git a/src/camel/camel-certdb.c b/src/camel/camel-certdb.c
new file mode 100644
index 000000000..31a301bf8
--- /dev/null
+++ b/src/camel/camel-certdb.c
@@ -0,0 +1,823 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gstdio.h>
+
+#include "camel-certdb.h"
+#include "camel-file-utils.h"
+#include "camel-win32.h"
+
+#define CAMEL_CERTDB_VERSION 0x100
+
+#define CAMEL_CERTDB_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_CERTDB, CamelCertDBPrivate))
+
+struct _CamelCertDBPrivate {
+ gchar *filename;
+ guint32 version;
+ guint32 saved_certs;
+ gboolean dirty;
+
+ GPtrArray *certs;
+ GHashTable *cert_hash;
+
+ GMutex db_lock; /* for the db hashtable/array */
+ GMutex io_lock; /* load/save lock, for access to saved_count, etc */
+};
+
+G_DEFINE_TYPE (CamelCertDB, camel_certdb, G_TYPE_OBJECT)
+G_DEFINE_BOXED_TYPE (CamelCert,
+ camel_cert,
+ camel_cert_ref,
+ camel_cert_unref)
+
+typedef struct {
+ gchar *hostname;
+ gchar *fingerprint;
+} CamelCertDBKey;
+
+static CamelCertDBKey *
+certdb_key_new (const gchar *hostname,
+ const gchar *fingerprint)
+{
+ CamelCertDBKey *key;
+
+ key = g_new0 (CamelCertDBKey, 1);
+ key->hostname = g_strdup (hostname);
+ key->fingerprint = g_strdup (fingerprint);
+
+ return key;
+}
+
+static void
+certdb_key_free (gpointer ptr)
+{
+ CamelCertDBKey *key = ptr;
+
+ if (!key)
+ return;
+
+ g_free (key->hostname);
+ g_free (key->fingerprint);
+ g_free (key);
+}
+
+static guint
+certdb_key_hash (gconstpointer ptr)
+{
+ const CamelCertDBKey *key = ptr;
+
+ if (!key)
+ return 0;
+
+ /* hash by fingerprint only, but compare by both hostname and fingerprint */
+ return g_str_hash (key->fingerprint);
+}
+
+static gboolean
+certdb_key_equal (gconstpointer ptr1,
+ gconstpointer ptr2)
+{
+ const CamelCertDBKey *key1 = ptr1, *key2 = ptr2;
+ gboolean same_hostname;
+
+ if (!key1 || !key2)
+ return key1 == key2;
+
+ if (!key1->hostname || !key2->hostname)
+ same_hostname = key1->hostname == key2->hostname;
+ else
+ same_hostname = g_ascii_strcasecmp (key1->hostname, key2->hostname) == 0;
+
+ if (same_hostname) {
+ if (!key1->fingerprint || !key2->fingerprint)
+ return key1->fingerprint == key2->fingerprint;
+
+ return g_ascii_strcasecmp (key1->fingerprint, key2->fingerprint) == 0;
+ }
+
+ return same_hostname;
+}
+
+static void
+certdb_finalize (GObject *object)
+{
+ CamelCertDBPrivate *priv;
+
+ priv = CAMEL_CERTDB_GET_PRIVATE (object);
+
+ if (priv->dirty)
+ camel_certdb_save (CAMEL_CERTDB (object));
+
+ camel_certdb_clear (CAMEL_CERTDB (object));
+ g_ptr_array_free (priv->certs, TRUE);
+ g_hash_table_destroy (priv->cert_hash);
+
+ g_free (priv->filename);
+
+ g_mutex_clear (&priv->db_lock);
+ g_mutex_clear (&priv->io_lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_certdb_parent_class)->finalize (object);
+}
+
+static gint
+certdb_header_load (CamelCertDB *certdb,
+ FILE *istream)
+{
+ if (camel_file_util_decode_uint32 (
+ istream, &certdb->priv->version) == -1)
+ return -1;
+ if (camel_file_util_decode_uint32 (
+ istream, &certdb->priv->saved_certs) == -1)
+ return -1;
+
+ return 0;
+}
+
+static gint
+certdb_header_save (CamelCertDB *certdb,
+ FILE *ostream)
+{
+ if (camel_file_util_encode_uint32 (
+ ostream, certdb->priv->version) == -1)
+ return -1;
+ if (camel_file_util_encode_uint32 (
+ ostream, certdb->priv->saved_certs) == -1)
+ return -1;
+
+ return 0;
+}
+
+static CamelCert *
+certdb_cert_load (CamelCertDB *certdb,
+ FILE *istream)
+{
+ CamelCert *cert;
+
+ cert = camel_cert_new ();
+
+ if (camel_file_util_decode_string (istream, &cert->issuer) == -1)
+ goto error;
+ if (camel_file_util_decode_string (istream, &cert->subject) == -1)
+ goto error;
+ if (camel_file_util_decode_string (istream, &cert->hostname) == -1)
+ goto error;
+ if (camel_file_util_decode_string (istream, &cert->fingerprint) == -1)
+ goto error;
+ if (camel_file_util_decode_uint32 (istream, &cert->trust) == -1)
+ goto error;
+
+ /* unset temporary trusts on load */
+ if (cert->trust == CAMEL_CERT_TRUST_TEMPORARY)
+ cert->trust = CAMEL_CERT_TRUST_UNKNOWN;
+
+ return cert;
+
+error:
+ camel_cert_unref (cert);
+
+ return NULL;
+}
+
+static gint
+certdb_cert_save (CamelCertDB *certdb,
+ CamelCert *cert,
+ FILE *ostream)
+{
+ if (camel_file_util_encode_string (ostream, cert->issuer) == -1)
+ return -1;
+ if (camel_file_util_encode_string (ostream, cert->subject) == -1)
+ return -1;
+ if (camel_file_util_encode_string (ostream, cert->hostname) == -1)
+ return -1;
+ if (camel_file_util_encode_string (ostream, cert->fingerprint) == -1)
+ return -1;
+ if (camel_file_util_encode_uint32 (ostream, cert->trust) == -1)
+ return -1;
+
+ return 0;
+}
+
+static void
+camel_certdb_class_init (CamelCertDBClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelCertDBPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = certdb_finalize;
+
+ class->header_load = certdb_header_load;
+ class->header_save = certdb_header_save;
+ class->cert_load = certdb_cert_load;
+ class->cert_save = certdb_cert_save;
+}
+
+static void
+camel_certdb_init (CamelCertDB *certdb)
+{
+ certdb->priv = CAMEL_CERTDB_GET_PRIVATE (certdb);
+
+ certdb->priv->version = CAMEL_CERTDB_VERSION;
+
+ certdb->priv->certs = g_ptr_array_new ();
+ certdb->priv->cert_hash = g_hash_table_new_full (
+ (GHashFunc) certdb_key_hash,
+ (GEqualFunc) certdb_key_equal,
+ (GDestroyNotify) certdb_key_free,
+ (GDestroyNotify) NULL);
+
+ g_mutex_init (&certdb->priv->db_lock);
+ g_mutex_init (&certdb->priv->io_lock);
+}
+
+CamelCert *
+camel_cert_new (void)
+{
+ CamelCert *cert;
+
+ cert = g_slice_new0 (CamelCert);
+ cert->refcount = 1;
+
+ return cert;
+}
+
+CamelCert *
+camel_cert_ref (CamelCert *cert)
+{
+ g_return_val_if_fail (cert != NULL, NULL);
+ g_return_val_if_fail (cert->refcount > 0, NULL);
+
+ g_atomic_int_inc (&cert->refcount);
+ return cert;
+}
+
+void
+camel_cert_unref (CamelCert *cert)
+{
+ g_return_if_fail (cert != NULL);
+ g_return_if_fail (cert->refcount > 0);
+
+ if (g_atomic_int_dec_and_test (&cert->refcount)) {
+ g_free (cert->issuer);
+ g_free (cert->subject);
+ g_free (cert->hostname);
+ g_free (cert->fingerprint);
+
+ if (cert->rawcert != NULL)
+ g_bytes_unref (cert->rawcert);
+
+ g_slice_free (CamelCert, cert);
+ }
+}
+
+static const gchar *
+certdb_get_cert_dir (void)
+{
+ static gchar *cert_dir = NULL;
+
+ if (G_UNLIKELY (cert_dir == NULL)) {
+ const gchar *data_dir;
+ const gchar *home_dir;
+ gchar *old_dir;
+
+ home_dir = g_get_home_dir ();
+ data_dir = g_get_user_data_dir ();
+
+ cert_dir = g_build_filename (data_dir, "camel_certs", NULL);
+
+ /* Move the old certificate directory if present. */
+ old_dir = g_build_filename (home_dir, ".camel_certs", NULL);
+ if (g_file_test (old_dir, G_FILE_TEST_IS_DIR)) {
+ if (g_rename (old_dir, cert_dir) == -1) {
+ g_warning ("%s: Failed to rename '%s' to '%s': %s", G_STRFUNC, old_dir, cert_dir, g_strerror (errno));
+ }
+ }
+ g_free (old_dir);
+
+ g_mkdir_with_parents (cert_dir, 0700);
+ }
+
+ return cert_dir;
+}
+
+gboolean
+camel_cert_load_cert_file (CamelCert *cert,
+ GError **error)
+{
+ gchar *contents = NULL;
+ gchar *filename;
+ gsize length;
+ const gchar *cert_dir;
+
+ g_return_val_if_fail (cert != NULL, FALSE);
+
+ if (cert->rawcert) {
+ g_bytes_unref (cert->rawcert);
+ cert->rawcert = NULL;
+ }
+
+ cert_dir = certdb_get_cert_dir ();
+ filename = g_build_filename (cert_dir, cert->fingerprint, NULL);
+
+ if (g_file_get_contents (filename, &contents, &length, error))
+ cert->rawcert = g_bytes_new_take (contents, length);
+
+ g_free (filename);
+
+ return cert->rawcert != NULL;
+}
+
+gboolean
+camel_cert_save_cert_file (CamelCert *cert,
+ const GByteArray *der_data,
+ GError **error)
+{
+ GFile *file;
+ GFileOutputStream *output_stream;
+ gchar *filename;
+ const gchar *cert_dir;
+
+ g_return_val_if_fail (cert != NULL, FALSE);
+ g_return_val_if_fail (der_data != NULL, FALSE);
+
+ if (cert->rawcert) {
+ g_bytes_unref (cert->rawcert);
+ cert->rawcert = NULL;
+ }
+
+ cert_dir = certdb_get_cert_dir ();
+ filename = g_build_filename (cert_dir, cert->fingerprint, NULL);
+ file = g_file_new_for_path (filename);
+
+ output_stream = g_file_replace (
+ file, NULL, FALSE,
+ G_FILE_CREATE_REPLACE_DESTINATION,
+ NULL, error);
+
+ g_object_unref (file);
+ g_free (filename);
+
+ if (output_stream != NULL) {
+ gssize n_written;
+ GBytes *bytes;
+
+ /* XXX Treat GByteArray as though its data is owned by
+ * GTlsCertificate. That means avoiding functions
+ * like g_byte_array_free_to_bytes() that alter or
+ * reset the GByteArray. */
+ bytes = g_bytes_new (der_data->data, der_data->len);
+
+ /* XXX Not handling partial writes, but GIO does not make
+ * it easy. Need a g_output_stream_write_all_bytes().
+ * (see: https://bugzilla.gnome.org/708838) */
+ n_written = g_output_stream_write_bytes (
+ G_OUTPUT_STREAM (output_stream),
+ bytes, NULL, error);
+
+ if (n_written < 0) {
+ g_bytes_unref (bytes);
+ bytes = NULL;
+ }
+
+ cert->rawcert = bytes;
+
+ g_object_unref (output_stream);
+ }
+
+ return cert->rawcert != NULL;
+}
+
+CamelCertDB *
+camel_certdb_new (void)
+{
+ return g_object_new (CAMEL_TYPE_CERTDB, NULL);
+}
+
+static CamelCertDB *default_certdb = NULL;
+static GMutex default_certdb_lock;
+
+void
+camel_certdb_set_default (CamelCertDB *certdb)
+{
+ g_mutex_lock (&default_certdb_lock);
+
+ if (default_certdb)
+ g_object_unref (default_certdb);
+
+ if (certdb)
+ g_object_ref (certdb);
+
+ default_certdb = certdb;
+
+ g_mutex_unlock (&default_certdb_lock);
+}
+
+
+/**
+ * camel_certdb_get_default:
+ *
+ * FIXME Document me!
+ *
+ * Returns: (transfer full):
+ **/
+CamelCertDB *
+camel_certdb_get_default (void)
+{
+ CamelCertDB *certdb;
+
+ g_mutex_lock (&default_certdb_lock);
+
+ if (default_certdb)
+ g_object_ref (default_certdb);
+
+ certdb = default_certdb;
+
+ g_mutex_unlock (&default_certdb_lock);
+
+ return certdb;
+}
+
+void
+camel_certdb_set_filename (CamelCertDB *certdb,
+ const gchar *filename)
+{
+ g_return_if_fail (CAMEL_IS_CERTDB (certdb));
+ g_return_if_fail (filename != NULL);
+
+ g_mutex_lock (&certdb->priv->db_lock);
+
+ g_free (certdb->priv->filename);
+ certdb->priv->filename = g_strdup (filename);
+
+ g_mutex_unlock (&certdb->priv->db_lock);
+}
+
+gint
+camel_certdb_load (CamelCertDB *certdb)
+{
+ CamelCertDBClass *class;
+ CamelCert *cert;
+ FILE *in;
+ gint i;
+
+ g_return_val_if_fail (CAMEL_IS_CERTDB (certdb), -1);
+ g_return_val_if_fail (certdb->priv->filename != NULL, -1);
+
+ in = g_fopen (certdb->priv->filename, "rb");
+ if (in == NULL)
+ return -1;
+
+ class = CAMEL_CERTDB_GET_CLASS (certdb);
+ if (!class->header_load || !class->cert_load) {
+ fclose (in);
+ in = NULL;
+ g_warn_if_reached ();
+
+ return -1;
+ }
+
+ g_mutex_lock (&certdb->priv->io_lock);
+ if (class->header_load (certdb, in) == -1)
+ goto error;
+
+ for (i = 0; i < certdb->priv->saved_certs; i++) {
+ cert = class->cert_load (certdb, in);
+
+ if (cert == NULL)
+ goto error;
+
+ /* NOTE: If we are upgrading from an evolution-data-server version
+ * prior to the change to look up certs by hostname (bug 606181),
+ * this "put" will result in duplicate entries for the same
+ * hostname being dropped. The change will become permanent on
+ * disk the next time the certdb is dirtied for some reason and
+ * has to be saved. */
+ camel_certdb_put (certdb, cert);
+ }
+
+ g_mutex_unlock (&certdb->priv->io_lock);
+
+ if (fclose (in) != 0)
+ return -1;
+
+ certdb->priv->dirty = FALSE;
+
+ return 0;
+
+ error:
+
+ g_warning ("Cannot load certificate database: %s", g_strerror (ferror (in)));
+
+ g_mutex_unlock (&certdb->priv->io_lock);
+
+ fclose (in);
+
+ return -1;
+}
+
+gint
+camel_certdb_save (CamelCertDB *certdb)
+{
+ CamelCertDBClass *class;
+ CamelCert *cert;
+ gchar *filename;
+ gsize filename_len;
+ gint fd, i;
+ FILE *out;
+
+ g_return_val_if_fail (CAMEL_IS_CERTDB (certdb), -1);
+ g_return_val_if_fail (certdb->priv->filename != NULL, -1);
+
+ /* no change, nothing new to save, simply return success */
+ if (!certdb->priv->dirty)
+ return 0;
+
+ filename_len = strlen (certdb->priv->filename) + 4;
+ filename = alloca (filename_len);
+ g_snprintf (filename, filename_len, "%s~", certdb->priv->filename);
+
+ fd = g_open (filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0600);
+ if (fd == -1)
+ return -1;
+
+ out = fdopen (fd, "wb");
+ if (out == NULL) {
+ i = errno;
+ close (fd);
+ g_unlink (filename);
+ errno = i;
+ return -1;
+ }
+
+ class = CAMEL_CERTDB_GET_CLASS (certdb);
+ if (!class->header_save || !class->cert_save) {
+ fclose (out);
+ out = NULL;
+ g_warn_if_reached ();
+
+ return -1;
+ }
+
+ g_mutex_lock (&certdb->priv->io_lock);
+
+ certdb->priv->saved_certs = certdb->priv->certs->len;
+ if (class->header_save (certdb, out) == -1)
+ goto error;
+
+ for (i = 0; i < certdb->priv->saved_certs; i++) {
+ cert = (CamelCert *) certdb->priv->certs->pdata[i];
+
+ if (class->cert_save (certdb, cert, out) == -1)
+ goto error;
+ }
+
+ g_mutex_unlock (&certdb->priv->io_lock);
+
+ if (fflush (out) != 0 || fsync (fileno (out)) == -1) {
+ i = errno;
+ fclose (out);
+ g_unlink (filename);
+ errno = i;
+ return -1;
+ }
+
+ if (fclose (out) != 0) {
+ i = errno;
+ g_unlink (filename);
+ errno = i;
+ return -1;
+ }
+
+ if (g_rename (filename, certdb->priv->filename) == -1) {
+ i = errno;
+ g_unlink (filename);
+ errno = i;
+ return -1;
+ }
+
+ certdb->priv->dirty = FALSE;
+
+ return 0;
+
+ error:
+
+ g_warning ("Cannot save certificate database: %s", g_strerror (ferror (out)));
+
+ g_mutex_unlock (&certdb->priv->io_lock);
+
+ i = errno;
+ fclose (out);
+ g_unlink (filename);
+ errno = i;
+
+ return -1;
+}
+
+void
+camel_certdb_touch (CamelCertDB *certdb)
+{
+ g_return_if_fail (CAMEL_IS_CERTDB (certdb));
+
+ certdb->priv->dirty = TRUE;
+}
+
+/**
+ * camel_certdb_get_host:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+CamelCert *
+camel_certdb_get_host (CamelCertDB *certdb,
+ const gchar *hostname,
+ const gchar *fingerprint)
+{
+ CamelCert *cert;
+ CamelCertDBKey *key;
+
+ g_return_val_if_fail (CAMEL_IS_CERTDB (certdb), NULL);
+
+ g_mutex_lock (&certdb->priv->db_lock);
+
+ key = certdb_key_new (hostname, fingerprint);
+
+ cert = g_hash_table_lookup (certdb->priv->cert_hash, key);
+ if (cert != NULL)
+ camel_cert_ref (cert);
+
+ certdb_key_free (key);
+
+ g_mutex_unlock (&certdb->priv->db_lock);
+
+ return cert;
+}
+
+/**
+ * camel_certdb_put:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_certdb_put (CamelCertDB *certdb,
+ CamelCert *cert)
+{
+ CamelCert *old_cert;
+ CamelCertDBKey *key;
+
+ g_return_if_fail (CAMEL_IS_CERTDB (certdb));
+
+ g_mutex_lock (&certdb->priv->db_lock);
+
+ key = certdb_key_new (cert->hostname, cert->fingerprint);
+
+ /* Replace an existing entry with the same hostname. */
+ old_cert = g_hash_table_lookup (certdb->priv->cert_hash, key);
+ if (old_cert != NULL) {
+ g_hash_table_remove (certdb->priv->cert_hash, key);
+ g_ptr_array_remove (certdb->priv->certs, old_cert);
+ camel_cert_unref (old_cert);
+ }
+
+ camel_cert_ref (cert);
+ g_ptr_array_add (certdb->priv->certs, cert);
+ /* takes ownership of 'key' */
+ g_hash_table_insert (certdb->priv->cert_hash, key, cert);
+
+ certdb->priv->dirty = TRUE;
+
+ g_mutex_unlock (&certdb->priv->db_lock);
+}
+
+/**
+ * camel_certdb_remove_host:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_certdb_remove_host (CamelCertDB *certdb,
+ const gchar *hostname,
+ const gchar *fingerprint)
+{
+ CamelCert *cert;
+ CamelCertDBKey *key;
+
+ g_return_if_fail (CAMEL_IS_CERTDB (certdb));
+
+ g_mutex_lock (&certdb->priv->db_lock);
+
+ key = certdb_key_new (hostname, fingerprint);
+ cert = g_hash_table_lookup (certdb->priv->cert_hash, key);
+ if (cert != NULL) {
+ g_hash_table_remove (certdb->priv->cert_hash, key);
+ g_ptr_array_remove (certdb->priv->certs, cert);
+ camel_cert_unref (cert);
+
+ certdb->priv->dirty = TRUE;
+ }
+
+ certdb_key_free (key);
+
+ g_mutex_unlock (&certdb->priv->db_lock);
+}
+
+static gboolean
+cert_remove (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ return TRUE;
+}
+
+void
+camel_certdb_clear (CamelCertDB *certdb)
+{
+ CamelCert *cert;
+ gint i;
+
+ g_return_if_fail (CAMEL_IS_CERTDB (certdb));
+
+ g_mutex_lock (&certdb->priv->db_lock);
+
+ g_hash_table_foreach_remove (certdb->priv->cert_hash, cert_remove, NULL);
+ for (i = 0; i < certdb->priv->certs->len; i++) {
+ cert = (CamelCert *) certdb->priv->certs->pdata[i];
+ camel_cert_unref (cert);
+ }
+
+ certdb->priv->saved_certs = 0;
+ g_ptr_array_set_size (certdb->priv->certs, 0);
+ certdb->priv->dirty = TRUE;
+
+ g_mutex_unlock (&certdb->priv->db_lock);
+}
+
+/**
+ * camel_certdb_list_certs:
+ * @certdb: a #CamelCertDB
+ *
+ * Gathers a list of known certificates. Each certificate in the returned #GSList
+ * is referenced, thus unref it with camel_cert_unref() when done with it, the same
+ * as free the list itself.
+ *
+ * Returns: (transfer full) (element-type CamelCert): Newly allocated list of
+ * referenced CamelCert-s, which are stored in the @certdb.
+ *
+ * Since: 3.16
+ **/
+GSList *
+camel_certdb_list_certs (CamelCertDB *certdb)
+{
+ GSList *certs = NULL;
+ gint ii;
+
+ g_return_val_if_fail (CAMEL_IS_CERTDB (certdb), NULL);
+
+ g_mutex_lock (&certdb->priv->db_lock);
+
+ for (ii = 0; ii < certdb->priv->certs->len; ii++) {
+ CamelCert *cert = (CamelCert *) certdb->priv->certs->pdata[ii];
+
+ camel_cert_ref (cert);
+ certs = g_slist_prepend (certs, cert);
+ }
+
+ g_mutex_unlock (&certdb->priv->db_lock);
+
+ return g_slist_reverse (certs);
+}
diff --git a/src/camel/camel-certdb.h b/src/camel/camel-certdb.h
new file mode 100644
index 000000000..6f0b09233
--- /dev/null
+++ b/src/camel/camel-certdb.h
@@ -0,0 +1,139 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_CERTDB_H
+#define CAMEL_CERTDB_H
+
+#include <stdio.h>
+#include <glib-object.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_CERTDB \
+ (camel_certdb_get_type ())
+#define CAMEL_CERTDB(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_CERTDB, CamelCertDB))
+#define CAMEL_CERTDB_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_CERTDB, CamelCertDBClass))
+#define CAMEL_IS_CERTDB(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_CERTDB))
+#define CAMEL_IS_CERTDB_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_CERTDB))
+#define CAMEL_CERTDB_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_CERTDB, CamelCertDBClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelCertDB CamelCertDB;
+typedef struct _CamelCertDBClass CamelCertDBClass;
+typedef struct _CamelCertDBPrivate CamelCertDBPrivate;
+
+typedef enum {
+ CAMEL_CERT_TRUST_UNKNOWN,
+ CAMEL_CERT_TRUST_NEVER,
+ CAMEL_CERT_TRUST_MARGINAL,
+ CAMEL_CERT_TRUST_FULLY,
+ CAMEL_CERT_TRUST_ULTIMATE,
+ CAMEL_CERT_TRUST_TEMPORARY
+} CamelCertTrust;
+
+typedef struct {
+ volatile gint refcount;
+
+ gchar *issuer;
+ gchar *subject;
+ gchar *hostname;
+ gchar *fingerprint;
+
+ CamelCertTrust trust;
+ GBytes *rawcert; /* loaded on demand, with camel_cert_load_cert_file() */
+} CamelCert;
+
+struct _CamelCertDB {
+ GObject parent;
+ CamelCertDBPrivate *priv;
+};
+
+struct _CamelCertDBClass {
+ GObjectClass parent_class;
+
+ gint (*header_load) (CamelCertDB *certdb,
+ FILE *istream);
+ gint (*header_save) (CamelCertDB *certdb,
+ FILE *ostream);
+
+ CamelCert * (*cert_load) (CamelCertDB *certdb,
+ FILE *istream);
+ gint (*cert_save) (CamelCertDB *certdb,
+ CamelCert *cert,
+ FILE *ostream);
+};
+
+GType camel_cert_get_type (void) G_GNUC_CONST;
+CamelCert * camel_cert_new (void);
+CamelCert * camel_cert_ref (CamelCert *cert);
+void camel_cert_unref (CamelCert *cert);
+gboolean camel_cert_load_cert_file (CamelCert *cert,
+ GError **error);
+gboolean camel_cert_save_cert_file (CamelCert *cert,
+ const GByteArray *der_data,
+ GError **error);
+
+GType camel_certdb_get_type (void) G_GNUC_CONST;
+CamelCertDB * camel_certdb_new (void);
+void camel_certdb_set_default (CamelCertDB *certdb);
+CamelCertDB * camel_certdb_get_default (void);
+void camel_certdb_set_filename (CamelCertDB *certdb,
+ const gchar *filename);
+gint camel_certdb_load (CamelCertDB *certdb);
+gint camel_certdb_save (CamelCertDB *certdb);
+void camel_certdb_touch (CamelCertDB *certdb);
+
+/* The lookup key was changed from fingerprint to hostname to fix bug 606181. */
+
+/* Get the certificate for the given hostname, if any. */
+CamelCert * camel_certdb_get_host (CamelCertDB *certdb,
+ const gchar *hostname,
+ const gchar *fingerprint);
+
+/* Store cert for cert->hostname, replacing any existing certificate for the
+ * same hostname. */
+void camel_certdb_put (CamelCertDB *certdb,
+ CamelCert *cert);
+
+/* Remove any user-accepted certificate for the given hostname. */
+void camel_certdb_remove_host (CamelCertDB *certdb,
+ const gchar *hostname,
+ const gchar *fingerprint);
+
+void camel_certdb_clear (CamelCertDB *certdb);
+
+GSList * camel_certdb_list_certs (CamelCertDB *certdb);
+
+G_END_DECLS
+
+#endif /* CAMEL_CERTDB_H */
diff --git a/src/camel/camel-charset-map-private.h b/src/camel/camel-charset-map-private.h
new file mode 100644
index 000000000..549749771
--- /dev/null
+++ b/src/camel/camel-charset-map-private.h
@@ -0,0 +1,11142 @@
+/* This file is automatically generated: DO NOT EDIT */
+
+static guchar m000[256] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf,
+ 0xdf, 0xdf, 0xdf, 0xdf, 0x5f, 0x5f, 0x5f, 0x5f,
+ 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
+ 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
+ 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
+ 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x1f, 0x1f, 0x1f,
+ 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3,
+ 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3,
+ 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3,
+ 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3,
+ 0xff, 0x00, 0x80, 0xc0, 0xa3, 0x80, 0xc0, 0xd3,
+ 0xc3, 0xcc, 0x00, 0xc0, 0xc0, 0xf3, 0x80, 0x82,
+ 0xcf, 0xc0, 0xcc, 0xc0, 0x83, 0x80, 0x80, 0xcc,
+ 0x83, 0x80, 0x00, 0xc0, 0x80, 0xc0, 0x80, 0x00,
+ 0x00, 0x03, 0x03, 0x02, 0x03, 0x02, 0x02, 0x01,
+ 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x03, 0x02, 0x03, 0x83,
+ 0x02, 0x00, 0x03, 0x02, 0x03, 0x01, 0x00, 0x03,
+ 0x00, 0x03, 0x03, 0x02, 0x03, 0x02, 0x02, 0x01,
+ 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x03, 0x02, 0x03, 0x8f,
+ 0x02, 0x00, 0x03, 0x02, 0x03, 0x01, 0x00, 0x00,
+};
+
+static guchar m001[256] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7,
+ 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+ 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+ 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+ 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+ 0xff, 0xf5, 0xf7, 0xf7, 0xfb, 0xf5, 0xfb, 0xff,
+ 0xf1, 0xff, 0xf5, 0xff, 0xff, 0xff, 0xff, 0xf5,
+ 0xff, 0xff, 0xf7, 0xf7, 0xf1, 0xff, 0xff, 0xff,
+ 0xf1, 0xf7, 0xf5, 0xff, 0xf3, 0xf3, 0xf3, 0xf5,
+ 0xf5, 0xf5, 0xf5, 0xf5, 0xf7, 0xf7, 0xf7, 0xf5,
+ 0xf5, 0xf7, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5,
+ 0xf4, 0xf5, 0xf5, 0xf7, 0xf5, 0xf7, 0xf7, 0xf7,
+ 0xf7, 0xf5, 0xf5, 0xf5, 0xf7, 0xf4, 0xf4, 0xf7,
+ 0xf5, 0xf5, 0xf5, 0xf5, 0xf7, 0xf7, 0xf7, 0xf5,
+ 0xf5, 0xf7, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5,
+ 0xf4, 0xf5, 0xf5, 0xf7, 0xf5, 0xf7, 0xf7, 0xf7,
+ 0xf7, 0xf5, 0xf5, 0xf5, 0xf7, 0xf4, 0xf4, 0xf5,
+};
+
+static guchar m002[256] = {
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+};
+
+static guchar m010[256] = {
+ 0x02, 0x02, 0x01, 0x01, 0x03, 0x03, 0x01, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x01, 0x01,
+ 0x03, 0x03, 0x02, 0x02, 0x00, 0x00, 0x02, 0x02,
+ 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x02, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02,
+ 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x00,
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01,
+ 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x02, 0x02,
+ 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01,
+ 0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01,
+ 0x01, 0x01, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x03, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m011[256] = {
+ 0xf2, 0xf2, 0xf0, 0xf0, 0xf2, 0xf2, 0xf2, 0xf2,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf2, 0xf2, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf2, 0xf2, 0xf0, 0xf0, 0xf2, 0xf2,
+ 0xf2, 0xf2, 0xf0, 0xf0, 0xf0, 0xf0, 0xf1, 0xf1,
+ 0xf0, 0xf0, 0xf2, 0xf2, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf2, 0xf2, 0xf0, 0xf0, 0xf2, 0xf2,
+ 0xf1, 0xf1, 0xf0, 0xf0, 0xf0, 0xf0, 0xf2, 0xf2,
+ 0xf0, 0xf0, 0xf0, 0xf2, 0xf2, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf2, 0xf2, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf4, 0xf4, 0xf0, 0xf0, 0xf2, 0xf2,
+ 0xf0, 0xf0, 0xf2, 0xf2, 0xf0, 0xf0, 0xf1, 0xf1,
+ 0xf6, 0xf6, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf2, 0xf2, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf2, 0xf2, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf4, 0xf2, 0xf2, 0xf2, 0xf2, 0xf6, 0xf6, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+};
+
+#define m012 m002
+
+static guchar m020[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m021[256] = {
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+};
+
+#define m022 m002
+
+static guchar m030[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x00,
+ 0x40, 0x40, 0x40, 0x00, 0x40, 0x00, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+#define m031 m021
+
+#define m032 m002
+
+static guchar m040[256] = {
+ 0x00, 0x1c, 0x10, 0x10, 0x18, 0x10, 0x18, 0x18,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x10,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x00, 0x1c, 0x10, 0x10, 0x18, 0x10, 0x18, 0x18,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x10,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m041[256] = {
+ 0xf0, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
+ 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf0, 0xf8, 0xf8,
+ 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
+ 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
+ 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
+ 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
+ 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
+ 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
+ 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
+ 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
+ 0xf0, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
+ 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf0, 0xf8, 0xf8,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf8, 0xf8, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+};
+
+#define m042 m002
+
+static guchar m050[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+#define m051 m021
+
+#define m052 m002
+
+static guchar m060[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+#define m061 m021
+
+#define m062 m002
+
+#define m071 m021
+
+#define m072 m002
+
+#define m081 m021
+
+#define m082 m002
+
+#define m091 m021
+
+#define m092 m002
+
+#define m0a1 m021
+
+#define m0a2 m002
+
+#define m0b1 m021
+
+#define m0b2 m002
+
+#define m0c1 m021
+
+#define m0c2 m002
+
+#define m0d1 m021
+
+#define m0d2 m002
+
+#define m0e1 m021
+
+#define m0e2 m002
+
+#define m0f1 m021
+
+#define m0f2 m002
+
+#define m101 m021
+
+#define m102 m002
+
+#define m111 m021
+
+#define m112 m002
+
+#define m121 m021
+
+#define m122 m002
+
+#define m131 m021
+
+#define m132 m002
+
+#define m141 m021
+
+#define m142 m002
+
+#define m151 m021
+
+#define m152 m002
+
+#define m161 m021
+
+#define m162 m002
+
+#define m171 m021
+
+#define m172 m002
+
+#define m181 m021
+
+#define m182 m002
+
+#define m191 m021
+
+#define m192 m002
+
+static guchar m1a1[256] = {
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+ 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+ 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+ 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+};
+
+#define m1a2 m002
+
+static guchar m1b1[256] = {
+ 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+ 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+ 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+ 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+ 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+};
+
+#define m1b2 m002
+
+static guchar m1c1[256] = {
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+};
+
+#define m1c2 m002
+
+#define m1d1 m1c1
+
+static guchar m1d2[256] = {
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+};
+
+#define m1e1 m1c1
+
+static guchar m1e2[256] = {
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+};
+
+#define m1f1 m1c1
+
+#define m1f2 m1e2
+
+static guchar m200[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x80,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m201[256] = {
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xf0, 0xc0, 0xc0, 0xc8, 0xc8, 0xf0, 0xf0, 0xc0,
+ 0xf8, 0xfa, 0xc8, 0xc0, 0xfa, 0xfa, 0xca, 0xc0,
+ 0xf8, 0xf8, 0xc8, 0xc0, 0xc0, 0xf0, 0xf8, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xf8, 0xc0, 0xf0, 0xf0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc8, 0xc8, 0xf0, 0xc0, 0xc0, 0xf0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0xc0, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0xc0,
+ 0x40, 0xc0, 0xc0, 0xc0, 0xc0, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0xc0, 0x40, 0x40, 0xcc, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+};
+
+static guchar m202[256] = {
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0f, 0x0f, 0x0d,
+ 0x0f, 0x0f, 0x0d, 0x0d, 0x0f, 0x0f, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x0f, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m210[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m211[256] = {
+ 0x40, 0x40, 0x40, 0xf0, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0xc0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0xc0, 0x40, 0x40, 0xc8, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0xc0, 0xc8, 0x40, 0x40, 0x40, 0xc0, 0x40,
+ 0x40, 0x40, 0x40, 0xf0, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0xc0, 0xc0, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0xc0, 0xc0, 0xc0, 0xc0, 0x40,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0xf0, 0x40, 0xf0, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+};
+
+static guchar m212[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0e, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m220[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m221[256] = {
+ 0xf0, 0x40, 0xf0, 0xf0, 0x40, 0x40, 0x40, 0xf0,
+ 0xf0, 0x40, 0x40, 0xf0, 0x40, 0x40, 0x40, 0xc0,
+ 0x40, 0xc0, 0x70, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0xf0, 0x40, 0x40, 0xf0, 0xf0, 0x40,
+ 0xf0, 0x40, 0x40, 0x40, 0x40, 0xc0, 0x40, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0xc0, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0xf0, 0xf0, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0xc0, 0xf0, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0xf0, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0xf0, 0xf0, 0x40, 0x40, 0xc0, 0xc0, 0x70, 0x70,
+ 0x40, 0x40, 0xf0, 0xf0, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0xf0, 0xf0, 0x40, 0x40, 0xf0, 0xf0,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0xc0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0xf0, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+};
+
+static guchar m222[256] = {
+ 0x0d, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0f, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0f, 0x0f, 0x0c,
+ 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0d, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0f, 0x0e, 0x0e,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0f, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x0f, 0x0f, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0e, 0x0e,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m230[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m231[256] = {
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0xf0, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+};
+
+static guchar m232[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m241[256] = {
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+};
+
+static guchar m242[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m250[256] = {
+ 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x0c, 0x0c, 0x04, 0x0c, 0x04, 0x04, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x04, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x04, 0x0c, 0x04, 0x04, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x04, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m251[256] = {
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0xf0, 0xc0, 0xc0, 0xf0,
+ 0xf0, 0xc0, 0xc0, 0xf0, 0xf0, 0xc0, 0xc0, 0xf0,
+ 0xf0, 0xc0, 0xc0, 0xf0, 0xf0, 0xf0, 0xc0, 0xc0,
+ 0xf0, 0xc0, 0xc0, 0xf0, 0xf0, 0xf0, 0xc0, 0xc0,
+ 0xf0, 0xc0, 0xc0, 0xf0, 0xf0, 0xc0, 0xc0, 0xf0,
+ 0xf0, 0xc0, 0xc0, 0xf0, 0xf0, 0xc0, 0xc0, 0xf0,
+ 0xf0, 0xc0, 0xc0, 0xf0, 0xf0, 0xc0, 0xc0, 0xf0,
+ 0xc0, 0xc0, 0xf0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xf0, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0xc0, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0xf0, 0xf0, 0x40, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0xf0, 0xf0, 0x40, 0x40, 0xc0, 0xc0,
+ 0x40, 0x40, 0x40, 0x40, 0xf0, 0xf0, 0x40, 0x40,
+ 0xc0, 0xc0, 0x40, 0x40, 0x40, 0x40, 0xf0, 0xf0,
+ 0xc0, 0x40, 0x40, 0xf0, 0x40, 0x40, 0xf0, 0xf0,
+ 0xc0, 0xc0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x70,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+};
+
+static guchar m252[256] = {
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0f, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0f, 0x0f, 0x0c, 0x0c, 0x0d, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c,
+ 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0f,
+ 0x0d, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0f, 0x0f,
+ 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m261[256] = {
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0xf0, 0xf0, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0xc0, 0xc0,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0xc0, 0x40, 0xc0, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0xf0, 0x40, 0xf0, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0xc0, 0xc0, 0x40, 0xc0, 0xc0, 0xc0, 0x40, 0xc0,
+ 0xc0, 0xc0, 0xf0, 0x40, 0xc0, 0xf0, 0x40, 0x70,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+};
+
+static guchar m262[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0d, 0x0c, 0x0d, 0x0d, 0x0d, 0x0c, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m271[256] = {
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+};
+
+static guchar m272[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+#define m281 m271
+
+#define m282 m272
+
+#define m291 m271
+
+#define m292 m272
+
+#define m2a1 m271
+
+#define m2a2 m272
+
+#define m2b1 m271
+
+#define m2b2 m272
+
+#define m2c1 m271
+
+#define m2c2 m272
+
+#define m2d1 m271
+
+#define m2d2 m272
+
+#define m2e1 m271
+
+#define m2e2 m272
+
+#define m2f1 m271
+
+#define m2f2 m272
+
+static guchar m301[256] = {
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0x70, 0x70, 0x70,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x70, 0xf0, 0xf0, 0xf0, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x70, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x70, 0x70, 0x70, 0x70, 0x40,
+ 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x40,
+ 0x40, 0x40, 0x40, 0x70, 0x70, 0x70, 0x70, 0x40,
+};
+
+static guchar m302[256] = {
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0c, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m311[256] = {
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+};
+
+static guchar m312[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m321[256] = {
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0x40, 0x40, 0xc0, 0xc0,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m322[256] = {
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m331[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m332[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0d, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+#define m342 m272
+
+#define m352 m272
+
+static guchar m362[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+};
+
+static guchar m372[256] = {
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+};
+
+#define m382 m372
+
+#define m392 m372
+
+#define m3a2 m372
+
+#define m3b2 m372
+
+#define m3c2 m372
+
+#define m3d2 m372
+
+#define m3e2 m372
+
+#define m3f2 m372
+
+#define m402 m372
+
+#define m412 m372
+
+#define m422 m372
+
+#define m432 m372
+
+#define m442 m372
+
+#define m452 m372
+
+#define m462 m372
+
+#define m472 m372
+
+#define m482 m372
+
+#define m492 m372
+
+#define m4a2 m372
+
+#define m4b2 m372
+
+#define m4c2 m372
+
+#define m4d2 m372
+
+static guchar m4e1[256] = {
+ 0xf0, 0xf0, 0x40, 0xf0, 0x40, 0x40, 0x00, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0xf0, 0x70, 0x00,
+ 0x70, 0xf0, 0x40, 0x00, 0xf0, 0xf0, 0xf0, 0x70,
+ 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40,
+ 0x00, 0x70, 0x00, 0x40, 0x40, 0x00, 0x70, 0x00,
+ 0x40, 0x00, 0x70, 0x40, 0x00, 0xf0, 0x40, 0x40,
+ 0x40, 0x70, 0xf0, 0x00, 0x00, 0x40, 0x70, 0x00,
+ 0xf0, 0xf0, 0x00, 0xf0, 0x70, 0x00, 0x00, 0x70,
+ 0x40, 0x40, 0xf0, 0xf0, 0x40, 0xf0, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0xf0, 0x00, 0xf0, 0xf0, 0xf0,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0xf0, 0x70,
+ 0xf0, 0xf0, 0x40, 0x00, 0x40, 0xf0, 0xf0, 0xf0,
+ 0x00, 0x00, 0x70, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x70, 0x00, 0xf0, 0x40, 0x40, 0x80, 0x80,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40,
+ 0x70, 0x00, 0xf0, 0x00, 0x00, 0x70, 0xf0, 0x00,
+ 0xf0, 0x70, 0x70, 0xf0, 0xf0, 0x40, 0xf0, 0x00,
+ 0x80, 0xf0, 0xf0, 0x00, 0xf0, 0xf0, 0x40, 0x40,
+ 0xf0, 0x70, 0x00, 0xf0, 0x70, 0x40, 0xf0, 0x70,
+ 0x70, 0xf0, 0xf0, 0x00, 0xf0, 0xf0, 0xf0, 0x00,
+ 0xf0, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x40,
+ 0x70, 0x00, 0x00, 0x70, 0x00, 0x00, 0xf0, 0x00,
+ 0x00, 0x40, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0xf0, 0x70, 0x40, 0xf0, 0x00, 0x70, 0xf0,
+ 0x00, 0x00, 0xf0, 0xf0, 0x00, 0xf0, 0x70, 0x70,
+ 0x40, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x40, 0x40, 0x00, 0xf0, 0x70, 0xf0,
+ 0x40, 0x40, 0x40, 0xf0, 0xf0, 0xf0, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x40,
+ 0xf0, 0x40, 0xf0, 0x40, 0x00, 0x40, 0xf0, 0xf0,
+ 0x00, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x40, 0x40,
+};
+
+static guchar m4e2[256] = {
+ 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x08, 0x08, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0e, 0x0f, 0x0e, 0x0c,
+ 0x0e, 0x0f, 0x08, 0x0a, 0x0f, 0x0f, 0x0f, 0x08,
+ 0x0f, 0x0f, 0x0a, 0x0a, 0x0a, 0x0a, 0x0f, 0x0c,
+ 0x08, 0x08, 0x0a, 0x08, 0x0a, 0x0a, 0x0c, 0x0a,
+ 0x0a, 0x08, 0x0a, 0x0e, 0x0a, 0x0f, 0x0c, 0x08,
+ 0x0e, 0x0c, 0x0f, 0x0c, 0x0a, 0x08, 0x0a, 0x08,
+ 0x0f, 0x0f, 0x0a, 0x0f, 0x0c, 0x0a, 0x0a, 0x0a,
+ 0x08, 0x08, 0x0d, 0x0f, 0x08, 0x0f, 0x08, 0x0e,
+ 0x0e, 0x0a, 0x08, 0x0f, 0x0a, 0x0f, 0x0f, 0x0f,
+ 0x0a, 0x08, 0x0e, 0x0e, 0x0a, 0x08, 0x0f, 0x08,
+ 0x0f, 0x0f, 0x08, 0x08, 0x0e, 0x0f, 0x0f, 0x0f,
+ 0x0a, 0x0a, 0x08, 0x08, 0x08, 0x08, 0x0a, 0x08,
+ 0x08, 0x0e, 0x08, 0x09, 0x08, 0x09, 0x08, 0x08,
+ 0x0a, 0x0a, 0x08, 0x0f, 0x08, 0x08, 0x09, 0x09,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0f, 0x0c,
+ 0x08, 0x08, 0x0d, 0x0c, 0x0c, 0x08, 0x0f, 0x08,
+ 0x0f, 0x0a, 0x08, 0x0f, 0x0f, 0x0e, 0x0f, 0x0a,
+ 0x09, 0x0f, 0x0f, 0x0e, 0x0f, 0x0f, 0x08, 0x08,
+ 0x0b, 0x0c, 0x0a, 0x0f, 0x08, 0x08, 0x0d, 0x0e,
+ 0x0a, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0a,
+ 0x0f, 0x0a, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x08,
+ 0x08, 0x08, 0x0a, 0x0e, 0x08, 0x0a, 0x0d, 0x08,
+ 0x08, 0x0c, 0x0f, 0x0a, 0x08, 0x08, 0x08, 0x0a,
+ 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x0a, 0x0e, 0x0f,
+ 0x0c, 0x0e, 0x0f, 0x0f, 0x08, 0x0f, 0x0a, 0x08,
+ 0x08, 0x0a, 0x08, 0x0a, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0c, 0x08, 0x0c, 0x0f, 0x0e, 0x0f,
+ 0x08, 0x0e, 0x08, 0x0f, 0x0f, 0x0f, 0x08, 0x08,
+ 0x0e, 0x0c, 0x0a, 0x0a, 0x0a, 0x08, 0x08, 0x08,
+ 0x0f, 0x0c, 0x0f, 0x0e, 0x0c, 0x0e, 0x0f, 0x0f,
+ 0x08, 0x08, 0x08, 0x0f, 0x08, 0x0e, 0x08, 0x0e,
+};
+
+static guchar m4f1[256] = {
+ 0x40, 0xf0, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0xf0, 0xf0, 0xc0, 0x40, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x00, 0x40, 0x70, 0x00, 0x70, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xf0,
+ 0x70, 0x40, 0x00, 0x40, 0xf0, 0x40, 0xf0, 0x40,
+ 0xf0, 0x40, 0xf0, 0x40, 0xf0, 0xf0, 0x40, 0x00,
+ 0x40, 0x00, 0x40, 0xf0, 0x00, 0x00, 0xf0, 0xf0,
+ 0xc0, 0x40, 0x00, 0x40, 0x40, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x40, 0x70, 0x40, 0xf0, 0x40, 0x70,
+ 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x70, 0x40,
+ 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0x40, 0x00, 0x40, 0x00, 0x40, 0xf0,
+ 0xf0, 0x40, 0x00, 0xf0, 0x00, 0x70, 0xf0, 0x40,
+ 0x40, 0x40, 0xc0, 0x70, 0x70, 0x40, 0xc0, 0xf0,
+ 0x00, 0xc0, 0x40, 0xf0, 0xc0, 0x40, 0xf0, 0x00,
+ 0xf0, 0x40, 0xc0, 0xf0, 0x40, 0xf0, 0x40, 0xf0,
+ 0x40, 0xf0, 0x40, 0x40, 0x40, 0x00, 0xf0, 0x40,
+ 0xf0, 0x40, 0x40, 0xf0, 0x00, 0xf0, 0x40, 0x40,
+ 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0xf0, 0xf0,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0xf0, 0xf0, 0x40,
+ 0x00, 0x40, 0x00, 0x40, 0x40, 0x40, 0x40, 0xf0,
+ 0x40, 0x40, 0xf0, 0xf0, 0xf0, 0x40, 0x40, 0x00,
+ 0x40, 0xc0, 0xf0, 0x40, 0x40, 0x40, 0xf0, 0x40,
+ 0x70, 0xf0, 0x40, 0x80, 0xf0, 0x00, 0x00, 0xf0,
+ 0x70, 0x00, 0xf0, 0x70, 0x40, 0xf0, 0x00, 0xf0,
+ 0xc0, 0xf0, 0x40, 0x70, 0x70, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0,
+ 0x40, 0x80, 0x40, 0xf0, 0x00, 0xf0, 0x70, 0x00,
+ 0xf0, 0x00, 0xf0, 0x00, 0x40, 0x40, 0x70, 0x40,
+};
+
+static guchar m4f2[256] = {
+ 0x0c, 0x0f, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0f, 0x0f, 0x0d, 0x08, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0a,
+ 0x0e, 0x0e, 0x0a, 0x0a, 0x08, 0x0c, 0x0a, 0x0a,
+ 0x0a, 0x08, 0x0e, 0x08, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x08, 0x08, 0x0a, 0x0a, 0x0c, 0x0c, 0x08, 0x0f,
+ 0x0e, 0x08, 0x0a, 0x0c, 0x0f, 0x08, 0x0f, 0x08,
+ 0x0f, 0x08, 0x0f, 0x0c, 0x0f, 0x0f, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0f, 0x08, 0x08, 0x0f, 0x0d,
+ 0x0d, 0x0c, 0x08, 0x08, 0x0c, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0c, 0x0e, 0x0c, 0x0f, 0x0c, 0x0e,
+ 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0c, 0x0c, 0x0e, 0x0e, 0x0a, 0x08, 0x0e,
+ 0x08, 0x0f, 0x0c, 0x0c, 0x0e, 0x08, 0x0c, 0x0f,
+ 0x0f, 0x08, 0x08, 0x0f, 0x0e, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0e, 0x0e, 0x0c, 0x0f, 0x0f,
+ 0x0c, 0x0d, 0x0c, 0x0f, 0x0f, 0x0c, 0x0d, 0x0c,
+ 0x0f, 0x0e, 0x09, 0x0f, 0x08, 0x0f, 0x08, 0x0f,
+ 0x0c, 0x0f, 0x0c, 0x08, 0x0e, 0x0c, 0x0d, 0x0e,
+ 0x0d, 0x08, 0x0c, 0x0f, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x0a, 0x08, 0x08, 0x0a, 0x08, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x08, 0x0a, 0x08, 0x0f, 0x0f,
+ 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0f, 0x0d, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x0f,
+ 0x0c, 0x0c, 0x0d, 0x0f, 0x0f, 0x0e, 0x08, 0x0c,
+ 0x08, 0x0d, 0x0f, 0x0c, 0x08, 0x0c, 0x0f, 0x0e,
+ 0x0e, 0x0f, 0x08, 0x0d, 0x0d, 0x08, 0x0c, 0x0f,
+ 0x0e, 0x0c, 0x0f, 0x0c, 0x0e, 0x0f, 0x0e, 0x0f,
+ 0x0d, 0x0f, 0x08, 0x0a, 0x08, 0x08, 0x0a, 0x08,
+ 0x0a, 0x0a, 0x0a, 0x08, 0x0c, 0x0a, 0x0f, 0x0f,
+ 0x08, 0x0f, 0x08, 0x0f, 0x0c, 0x0d, 0x0c, 0x0c,
+ 0x0f, 0x08, 0x0f, 0x08, 0x08, 0x08, 0x0e, 0x08,
+};
+
+static guchar m501[256] = {
+ 0x40, 0x40, 0x80, 0x00, 0x40, 0x70, 0xf0, 0x40,
+ 0x00, 0xf0, 0x40, 0xf0, 0x40, 0xf0, 0x40, 0x70,
+ 0x40, 0xf0, 0xf0, 0x40, 0x70, 0x00, 0xf0, 0x40,
+ 0x40, 0xf0, 0xf0, 0x40, 0xc0, 0x40, 0xc0, 0xf0,
+ 0x00, 0xf0, 0x40, 0xf0, 0xf0, 0x70, 0xf0, 0xc0,
+ 0xf0, 0x70, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0x00,
+ 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x70, 0x00,
+ 0x00, 0x70, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0x40, 0xf0, 0x00, 0x40, 0x40, 0xf0,
+ 0xf0, 0xf0, 0x40, 0x00, 0x40, 0x00, 0x40, 0xf0,
+ 0x70, 0x40, 0x40, 0x40, 0x00, 0xf0, 0x70, 0x40,
+ 0x00, 0x40, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x40,
+ 0x40, 0x00, 0x40, 0x40, 0x00, 0xf0, 0x40, 0x40,
+ 0x00, 0x00, 0x40, 0x00, 0x70, 0x40, 0x00, 0x00,
+ 0x40, 0x40, 0x70, 0x00, 0xf0, 0xf0, 0xf0, 0x00,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
+ 0xf0, 0x40, 0x00, 0x40, 0x40, 0xf0, 0x40, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0xf0, 0x40, 0x40,
+ 0x40, 0xf0, 0x40, 0x40, 0x40, 0x00, 0x40, 0x00,
+ 0xf0, 0xf0, 0x70, 0x40, 0x40, 0x00, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0xf0, 0xf0, 0x00, 0x40,
+ 0x40, 0x00, 0xf0, 0xf0, 0x70, 0xf0, 0x00, 0xf0,
+ 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0xf0, 0x00,
+ 0x40, 0x00, 0x70, 0x40, 0x40, 0xf0, 0x00, 0x40,
+ 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x70, 0x40, 0xf0,
+ 0x40, 0xf0, 0x00, 0x40, 0x40, 0xf0, 0xf0, 0x00,
+ 0x40, 0x00, 0xf0, 0x00, 0x40, 0x40, 0xf0, 0x40,
+ 0x00, 0x00, 0x40, 0x70, 0x40, 0xf0, 0x40, 0xf0,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0xf0, 0x70, 0x40,
+ 0x00, 0x40, 0x40, 0x00, 0x00, 0x70, 0x40, 0x00,
+ 0x00, 0xf0, 0x40, 0xf0, 0x00, 0x00, 0x40, 0x80,
+};
+
+static guchar m502[256] = {
+ 0x0c, 0x08, 0x09, 0x08, 0x08, 0x0c, 0x0d, 0x0c,
+ 0x08, 0x0d, 0x08, 0x0d, 0x0e, 0x0f, 0x0c, 0x0e,
+ 0x08, 0x0d, 0x0f, 0x0c, 0x0e, 0x0c, 0x0d, 0x0c,
+ 0x0e, 0x0f, 0x0f, 0x0c, 0x0f, 0x08, 0x0d, 0x0f,
+ 0x0c, 0x0f, 0x0c, 0x0d, 0x09, 0x0e, 0x0f, 0x0d,
+ 0x0f, 0x0e, 0x0f, 0x0d, 0x0f, 0x0f, 0x0a, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0c,
+ 0x08, 0x08, 0x0a, 0x09, 0x0e, 0x08, 0x0a, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0f, 0x08, 0x0c, 0x0c, 0x0f,
+ 0x0f, 0x0d, 0x0c, 0x0c, 0x0e, 0x0c, 0x0e, 0x0f,
+ 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0f, 0x08, 0x0c,
+ 0x08, 0x08, 0x0f, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0a, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x0d, 0x0f, 0x0e,
+ 0x09, 0x08, 0x0c, 0x0a, 0x08, 0x0c, 0x0a, 0x0a,
+ 0x0f, 0x08, 0x0c, 0x0c, 0x08, 0x0f, 0x08, 0x0c,
+ 0x0a, 0x08, 0x08, 0x0c, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x08, 0x0d, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x08, 0x08, 0x0c, 0x0e, 0x08, 0x0a, 0x08, 0x0a,
+ 0x0a, 0x0a, 0x08, 0x08, 0x0f, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0f, 0x0d, 0x0c, 0x0d, 0x0c, 0x0d,
+ 0x0c, 0x08, 0x0e, 0x0e, 0x08, 0x0c, 0x0d, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0d, 0x0c, 0x08, 0x08, 0x0c, 0x0f,
+ 0x08, 0x0d, 0x08, 0x0c, 0x0c, 0x0d, 0x0f, 0x0c,
+ 0x08, 0x08, 0x0f, 0x0c, 0x08, 0x0c, 0x09, 0x08,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0e, 0x0f, 0x0e, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0e, 0x08, 0x0e, 0x0c, 0x08,
+ 0x0c, 0x0d, 0x08, 0x0f, 0x08, 0x0c, 0x0c, 0x0d,
+};
+
+static guchar m511[256] = {
+ 0xf0, 0xf0, 0x70, 0x40, 0xf0, 0x00, 0xc0, 0x40,
+ 0x40, 0xf0, 0x00, 0x40, 0x40, 0x40, 0x40, 0x00,
+ 0x40, 0x00, 0xf0, 0x00, 0x70, 0x70, 0x70, 0x40,
+ 0x70, 0x40, 0x70, 0x40, 0x40, 0x40, 0x40, 0xf0,
+ 0x00, 0xf0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0x40, 0x00, 0xf0, 0x00, 0x40, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0xf0, 0x40, 0x40, 0x40, 0x00, 0xf0,
+ 0x40, 0x40, 0xf0, 0x70, 0xf0, 0x00, 0x00, 0x70,
+ 0xf0, 0xf0, 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0x40,
+ 0x70, 0x00, 0xf0, 0x40, 0x70, 0x40, 0x00, 0x40,
+ 0x40, 0x00, 0x70, 0x00, 0xf0, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0xf0, 0x00, 0x40, 0xf0, 0x40, 0x80,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x00,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x00,
+ 0xf0, 0x00, 0x70, 0x40, 0x40, 0x70, 0xf0, 0x00,
+ 0x00, 0x70, 0xf0, 0x40, 0x70, 0xf0, 0x40, 0x70,
+ 0x70, 0x70, 0xf0, 0x70, 0x00, 0xf0, 0x70, 0xf0,
+ 0x40, 0x70, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0xf0, 0x40, 0x70, 0x40, 0x70, 0xf0, 0x70, 0x00,
+ 0x70, 0x70, 0xf0, 0x70, 0xf0, 0x40, 0x00, 0x00,
+ 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0xf0, 0xf0,
+ 0x40, 0x00, 0x40, 0x00, 0x40, 0xf0, 0x40, 0x40,
+ 0x00, 0x00, 0x40, 0x00, 0xf0, 0x70, 0xf0, 0x00,
+ 0x40, 0xf0, 0x00, 0xf0, 0xf0, 0xf0, 0x00, 0x40,
+ 0x00, 0x40, 0x40, 0x40, 0x00, 0x40, 0x70, 0x00,
+ 0x40, 0x00, 0x00, 0x70, 0xf0, 0xf0, 0xc0, 0x00,
+ 0x70, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x70, 0x70,
+ 0x00, 0x70, 0x70, 0x00, 0x00, 0x70, 0x40, 0x00,
+ 0xf0, 0xf0, 0x40, 0x40, 0x40, 0x70, 0xf0, 0x40,
+ 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0xf0, 0x70, 0x00,
+};
+
+static guchar m512[256] = {
+ 0x0d, 0x09, 0x0c, 0x0c, 0x0d, 0x0c, 0x0f, 0x0e,
+ 0x0c, 0x0d, 0x0c, 0x0e, 0x0c, 0x08, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x08, 0x0d,
+ 0x0c, 0x0f, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0d, 0x08, 0x08, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x08, 0x0d,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0d, 0x0c, 0x08, 0x0e,
+ 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0d,
+ 0x0f, 0x0f, 0x08, 0x0f, 0x0d, 0x0f, 0x09, 0x08,
+ 0x08, 0x0a, 0x0d, 0x08, 0x0e, 0x0e, 0x0a, 0x0c,
+ 0x08, 0x0c, 0x0e, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0f, 0x0c, 0x08, 0x0f, 0x08, 0x0d,
+ 0x0f, 0x0d, 0x09, 0x0f, 0x0f, 0x0f, 0x0f, 0x08,
+ 0x0a, 0x0f, 0x08, 0x0a, 0x0a, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0a, 0x08, 0x0a, 0x0f, 0x0a, 0x08, 0x08,
+ 0x0f, 0x0a, 0x0a, 0x08, 0x08, 0x0a, 0x09, 0x0c,
+ 0x0a, 0x0e, 0x0d, 0x08, 0x0a, 0x0f, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0f, 0x0c, 0x0c, 0x0f, 0x0a, 0x0f,
+ 0x0c, 0x0a, 0x08, 0x0a, 0x0a, 0x08, 0x0c, 0x08,
+ 0x0f, 0x08, 0x0e, 0x08, 0x0e, 0x0f, 0x08, 0x08,
+ 0x08, 0x08, 0x0d, 0x0a, 0x0f, 0x08, 0x08, 0x0a,
+ 0x0e, 0x0e, 0x0a, 0x0a, 0x08, 0x0a, 0x0f, 0x0f,
+ 0x08, 0x0c, 0x08, 0x0a, 0x0e, 0x0f, 0x0c, 0x08,
+ 0x0a, 0x08, 0x08, 0x08, 0x0f, 0x0c, 0x0f, 0x0a,
+ 0x0c, 0x0b, 0x0c, 0x0f, 0x0f, 0x0d, 0x0c, 0x0a,
+ 0x0c, 0x0a, 0x08, 0x08, 0x0c, 0x08, 0x08, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0a, 0x0d, 0x0f, 0x0d, 0x08,
+ 0x0e, 0x0f, 0x08, 0x08, 0x0a, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x0a, 0x08, 0x0a, 0x08, 0x0a,
+ 0x0f, 0x0d, 0x08, 0x0e, 0x08, 0x0e, 0x0f, 0x08,
+ 0x0f, 0x0f, 0x0f, 0x0a, 0x0a, 0x0f, 0x08, 0x0a,
+};
+
+static guchar m521[256] = {
+ 0xf0, 0x40, 0x40, 0xf0, 0x70, 0x40, 0xf0, 0xf0,
+ 0xf0, 0x00, 0xf0, 0x70, 0x00, 0x00, 0xf0, 0x00,
+ 0x00, 0xf0, 0x40, 0x40, 0x70, 0x40, 0x40, 0xf0,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0xf0, 0xf0, 0x00, 0x70,
+ 0x40, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x00,
+ 0xf0, 0x40, 0x40, 0x70, 0x00, 0x40, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0x70, 0x40, 0x00, 0xf0,
+ 0x00, 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40, 0xf0, 0x40,
+ 0x40, 0x00, 0x40, 0xf0, 0x40, 0x80, 0x70, 0x40,
+ 0x40, 0xc0, 0x00, 0x70, 0x70, 0x70, 0x40, 0x00,
+ 0x00, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x40, 0xf0,
+ 0x70, 0x70, 0xf0, 0x70, 0x70, 0xf0, 0x00, 0x40,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xf0,
+ 0x40, 0x00, 0x40, 0xf0, 0x00, 0x40, 0x00, 0xf0,
+ 0xf0, 0xf0, 0x40, 0x00, 0x40, 0xf0, 0x00, 0x00,
+ 0x00, 0xf0, 0xf0, 0x40, 0x70, 0x40, 0x40, 0x40,
+ 0x40, 0x00, 0x40, 0xf0, 0x40, 0x00, 0x00, 0xf0,
+ 0xf0, 0x00, 0x00, 0xf0, 0xc0, 0x40, 0x40, 0x40,
+ 0x00, 0xf0, 0xf0, 0xf0, 0x70, 0x70, 0x00, 0x40,
+ 0x40, 0x70, 0x00, 0x00, 0x70, 0x70, 0x40, 0x40,
+ 0x40, 0x70, 0x40, 0x40, 0x70, 0x40, 0xf0, 0x00,
+ 0x40, 0xf0, 0x00, 0xf0, 0x40, 0xf0, 0x40, 0xf0,
+ 0x40, 0xf0, 0x00, 0x00, 0x40, 0xf0, 0x00, 0x40,
+ 0x00, 0x40, 0xf0, 0x00, 0x40, 0xf0, 0xc0, 0x70,
+ 0xf0, 0xf0, 0x00, 0xc0, 0x40, 0xf0, 0xf0, 0xf0,
+ 0x70, 0x40, 0xf0, 0xf0, 0xf0, 0x40, 0x70, 0x70,
+ 0x40, 0x40, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0x70, 0xf0, 0x40, 0xf0, 0x40, 0x40,
+ 0xf0, 0x70, 0xf0, 0x80, 0x00, 0x00, 0xf0, 0xf0,
+};
+
+static guchar m522[256] = {
+ 0x0f, 0x0e, 0x0a, 0x0f, 0x08, 0x08, 0x0f, 0x0f,
+ 0x0f, 0x0c, 0x0f, 0x08, 0x0c, 0x0a, 0x0f, 0x08,
+ 0x0c, 0x0f, 0x0e, 0x0c, 0x08, 0x08, 0x0e, 0x0f,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x0a, 0x0c, 0x08, 0x08, 0x0f, 0x0d, 0x08, 0x08,
+ 0x0e, 0x0f, 0x0d, 0x0a, 0x08, 0x0a, 0x0f, 0x08,
+ 0x0f, 0x0c, 0x0c, 0x0e, 0x08, 0x0c, 0x0f, 0x0f,
+ 0x0f, 0x0b, 0x0f, 0x0f, 0x08, 0x0a, 0x08, 0x0a,
+ 0x0a, 0x0e, 0x0a, 0x0f, 0x0c, 0x08, 0x0c, 0x0d,
+ 0x08, 0x0c, 0x0f, 0x0d, 0x0f, 0x0f, 0x0c, 0x08,
+ 0x0a, 0x0a, 0x0c, 0x08, 0x0f, 0x0c, 0x0f, 0x08,
+ 0x08, 0x08, 0x0c, 0x0d, 0x0e, 0x0d, 0x0e, 0x0c,
+ 0x08, 0x0f, 0x0c, 0x08, 0x08, 0x0a, 0x08, 0x0a,
+ 0x08, 0x0f, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x08, 0x08, 0x0f, 0x08, 0x0c, 0x0d, 0x08, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0f, 0x08, 0x0f,
+ 0x0c, 0x0e, 0x0e, 0x0d, 0x0c, 0x08, 0x08, 0x0d,
+ 0x0f, 0x0d, 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x08,
+ 0x0a, 0x0d, 0x09, 0x0e, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0f, 0x08, 0x0a, 0x0a, 0x0f,
+ 0x0f, 0x0a, 0x0a, 0x0f, 0x09, 0x08, 0x0c, 0x08,
+ 0x0a, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0c, 0x08,
+ 0x08, 0x0a, 0x0a, 0x0a, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0f, 0x0a,
+ 0x0c, 0x0d, 0x0c, 0x0f, 0x08, 0x09, 0x08, 0x0f,
+ 0x08, 0x0f, 0x08, 0x0a, 0x08, 0x0d, 0x08, 0x08,
+ 0x0a, 0x08, 0x0f, 0x0c, 0x08, 0x0d, 0x0f, 0x0c,
+ 0x0f, 0x0d, 0x08, 0x0d, 0x08, 0x0d, 0x0d, 0x0f,
+ 0x08, 0x08, 0x0d, 0x0d, 0x0f, 0x08, 0x0c, 0x08,
+ 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x08, 0x08, 0x0c,
+ 0x0e, 0x0c, 0x08, 0x0d, 0x0c, 0x0d, 0x08, 0x0c,
+ 0x0d, 0x0a, 0x0f, 0x0d, 0x0c, 0x08, 0x0f, 0x0f,
+};
+
+static guchar m531[256] = {
+ 0x40, 0x70, 0x70, 0x40, 0x00, 0xf0, 0x70, 0x00,
+ 0xf0, 0x00, 0x40, 0x40, 0x40, 0xf0, 0x00, 0xf0,
+ 0xf0, 0x40, 0x00, 0x40, 0x00, 0xf0, 0xf0, 0xf0,
+ 0x40, 0xf0, 0x70, 0x40, 0x40, 0x70, 0x40, 0x40,
+ 0xf0, 0xf0, 0x00, 0xf0, 0x00, 0x40, 0x00, 0x40,
+ 0x40, 0x40, 0xf0, 0x40, 0x40, 0x40, 0x00, 0xf0,
+ 0x40, 0x70, 0x40, 0x70, 0x00, 0x40, 0x00, 0x00,
+ 0x70, 0xf0, 0x70, 0x70, 0x40, 0x40, 0x40, 0xf0,
+ 0xf0, 0xf0, 0x40, 0xf0, 0x80, 0x70, 0x70, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0x40, 0x40, 0xf0, 0x00, 0x00,
+ 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0xf0,
+ 0x70, 0x40, 0xf0, 0x40, 0xf0, 0x00, 0xf0, 0x00,
+ 0xf0, 0x40, 0x00, 0x40, 0x00, 0x40, 0xf0, 0x00,
+ 0x80, 0x70, 0x00, 0x00, 0x40, 0x40, 0x70, 0xf0,
+ 0xf0, 0xf0, 0x40, 0x70, 0xf0, 0xf0, 0x00, 0xf0,
+ 0x70, 0x40, 0x00, 0x70, 0x00, 0x80, 0x40, 0xf0,
+ 0x00, 0x00, 0x70, 0x40, 0xf0, 0x00, 0x00, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0xc0, 0x40, 0x00, 0x70, 0x00,
+ 0xf0, 0x40, 0xf0, 0x00, 0x00, 0x40, 0x00, 0xf0,
+ 0xf0, 0x40, 0x00, 0x00, 0x40, 0xf0, 0xf0, 0x00,
+ 0x70, 0x70, 0x40, 0x40, 0x00, 0xf0, 0x70, 0x40,
+ 0x70, 0x00, 0x40, 0x70, 0x40, 0x40, 0x70, 0x40,
+ 0x40, 0x00, 0x40, 0xf0, 0x00, 0x40, 0x00, 0x00,
+ 0x40, 0x00, 0x70, 0xf0, 0x00, 0x40, 0x00, 0x00,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0xf0, 0x70, 0x40,
+ 0x00, 0x00, 0x40, 0x40, 0xf0, 0x40, 0xf0, 0xf0,
+ 0x00, 0x70, 0x40, 0xf0, 0x00, 0x40, 0x40, 0x70,
+ 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0x40,
+ 0x70, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x40, 0x70, 0x70,
+ 0xf0, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m532[256] = {
+ 0x0a, 0x08, 0x08, 0x08, 0x08, 0x0f, 0x0e, 0x08,
+ 0x0f, 0x0c, 0x0c, 0x0c, 0x08, 0x0f, 0x0c, 0x0f,
+ 0x0f, 0x0c, 0x0c, 0x08, 0x08, 0x0f, 0x0f, 0x0f,
+ 0x08, 0x0f, 0x0e, 0x08, 0x0c, 0x0e, 0x08, 0x0c,
+ 0x0f, 0x0f, 0x0c, 0x0f, 0x08, 0x08, 0x0a, 0x08,
+ 0x08, 0x08, 0x0f, 0x08, 0x08, 0x0c, 0x0a, 0x0d,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x08, 0x0c,
+ 0x08, 0x0f, 0x0a, 0x0a, 0x0c, 0x0c, 0x0e, 0x0f,
+ 0x0d, 0x0f, 0x08, 0x0f, 0x0d, 0x0e, 0x08, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x08, 0x0c, 0x0d, 0x0a, 0x0a,
+ 0x08, 0x0f, 0x0f, 0x0f, 0x0d, 0x0a, 0x0a, 0x0f,
+ 0x08, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0a,
+ 0x0f, 0x0e, 0x0a, 0x0e, 0x0a, 0x08, 0x0f, 0x0a,
+ 0x09, 0x0a, 0x08, 0x0a, 0x0c, 0x08, 0x0e, 0x0f,
+ 0x0f, 0x0f, 0x0c, 0x0e, 0x0b, 0x0f, 0x08, 0x0f,
+ 0x0e, 0x0c, 0x0a, 0x0c, 0x0c, 0x09, 0x08, 0x0f,
+ 0x08, 0x08, 0x0e, 0x08, 0x0f, 0x0a, 0x0a, 0x08,
+ 0x08, 0x0a, 0x0c, 0x0a, 0x0a, 0x0a, 0x0c, 0x0c,
+ 0x08, 0x08, 0x0c, 0x09, 0x0c, 0x0a, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x0f, 0x08, 0x0c, 0x0e, 0x0c, 0x0f,
+ 0x09, 0x08, 0x0a, 0x0a, 0x0c, 0x0f, 0x0b, 0x0c,
+ 0x0a, 0x0a, 0x08, 0x08, 0x0c, 0x0d, 0x0a, 0x08,
+ 0x08, 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0a, 0x08,
+ 0x08, 0x0c, 0x08, 0x0f, 0x08, 0x08, 0x08, 0x0a,
+ 0x08, 0x0a, 0x0a, 0x0d, 0x08, 0x08, 0x08, 0x08,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0a, 0x0f, 0x08, 0x08,
+ 0x08, 0x0a, 0x08, 0x08, 0x0f, 0x08, 0x0f, 0x0f,
+ 0x0a, 0x0a, 0x08, 0x0f, 0x08, 0x08, 0x08, 0x0e,
+ 0x0a, 0x0d, 0x0d, 0x0f, 0x0f, 0x0f, 0x0e, 0x08,
+ 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0e, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0e, 0x0a, 0x0a,
+ 0x0f, 0x0a, 0x08, 0x0e, 0x0e, 0x0a, 0x08, 0x08,
+};
+
+static guchar m541[256] = {
+ 0x00, 0x70, 0x40, 0xf0, 0xf0, 0x00, 0x00, 0x00,
+ 0xf0, 0xf0, 0xf0, 0x70, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0xf0, 0x00, 0xf0, 0x00, 0xf0,
+ 0xf0, 0x40, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40,
+ 0x40, 0xf0, 0x40, 0xf0, 0x70, 0x70, 0x70, 0x40,
+ 0x00, 0x40, 0x00, 0x80, 0x40, 0x40, 0x70, 0x00,
+ 0xf0, 0xf0, 0x00, 0xf0, 0xf0, 0x70, 0xf0, 0x00,
+ 0x70, 0x00, 0xf0, 0x40, 0x40, 0x00, 0x70, 0x40,
+ 0xf0, 0x70, 0xf0, 0x00, 0x00, 0x40, 0x70, 0x40,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x70,
+ 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x40,
+ 0xf0, 0x40, 0xf0, 0x40, 0x00, 0x40, 0x40, 0x00,
+ 0x70, 0xf0, 0x00, 0xf0, 0x40, 0xf0, 0x70, 0x70,
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0x00, 0x40,
+ 0xf0, 0x40, 0x00, 0x40, 0x70, 0x40, 0xf0, 0x00,
+ 0x40, 0x40, 0x00, 0x70, 0xf0, 0x40, 0xf0, 0x70,
+ 0xf0, 0x40, 0x70, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40,
+ 0x00, 0x40, 0x70, 0x00, 0xf0, 0x70, 0x40, 0x40,
+ 0xf0, 0x40, 0x40, 0xf0, 0xf0, 0x40, 0x40, 0x70,
+ 0x00, 0x40, 0x70, 0xf0, 0x00, 0x00, 0x00, 0x40,
+ 0xf0, 0x40, 0x40, 0x40, 0x70, 0xf0, 0x70, 0x40,
+ 0xf0, 0xf0, 0x70, 0x00, 0xf0, 0x00, 0x40, 0x70,
+ 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0xf0, 0x70, 0x00, 0x00, 0xf0, 0x70, 0x00,
+ 0xf0, 0x70, 0x40, 0x00, 0x40, 0xf0, 0xf0, 0x40,
+ 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0xf0, 0x00, 0x40, 0x70, 0x40, 0x40,
+};
+
+static guchar m542[256] = {
+ 0x08, 0x0e, 0x08, 0x0f, 0x0f, 0x08, 0x0e, 0x0c,
+ 0x0f, 0x0f, 0x0f, 0x0c, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0e, 0x0a, 0x08, 0x0a, 0x0a, 0x0a,
+ 0x0c, 0x0c, 0x08, 0x0f, 0x0c, 0x0f, 0x0e, 0x0f,
+ 0x0f, 0x0a, 0x08, 0x0a, 0x0c, 0x0c, 0x0f, 0x0e,
+ 0x0e, 0x0f, 0x0c, 0x0f, 0x0e, 0x0e, 0x0e, 0x0a,
+ 0x0c, 0x0e, 0x0a, 0x0d, 0x0a, 0x0e, 0x0c, 0x0c,
+ 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0c, 0x0f, 0x08,
+ 0x0e, 0x0c, 0x0d, 0x0e, 0x08, 0x0c, 0x0e, 0x0c,
+ 0x0f, 0x08, 0x0f, 0x0a, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0a, 0x09, 0x0a, 0x0a, 0x0e, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x08, 0x0a, 0x0a, 0x08, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0e, 0x0c, 0x0e, 0x0c, 0x0e, 0x0c,
+ 0x0f, 0x08, 0x09, 0x0c, 0x0c, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0f, 0x0e, 0x0f, 0x0c, 0x0f, 0x0e, 0x0e,
+ 0x0e, 0x08, 0x0c, 0x0f, 0x0f, 0x0f, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x0e, 0x08, 0x0e, 0x08, 0x0f, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0e, 0x0f, 0x0c, 0x0f, 0x0a,
+ 0x0f, 0x0c, 0x0e, 0x08, 0x0a, 0x0e, 0x0e, 0x08,
+ 0x0c, 0x0a, 0x0e, 0x0a, 0x08, 0x0a, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0a, 0x0b, 0x0c, 0x0e, 0x0e,
+ 0x0f, 0x0e, 0x0e, 0x0f, 0x0f, 0x0e, 0x0c, 0x0e,
+ 0x0c, 0x0e, 0x08, 0x0f, 0x0a, 0x08, 0x0c, 0x0c,
+ 0x0f, 0x08, 0x0c, 0x0e, 0x0c, 0x0f, 0x0c, 0x0e,
+ 0x0f, 0x0f, 0x0e, 0x0c, 0x0f, 0x0c, 0x0e, 0x0e,
+ 0x0f, 0x0f, 0x08, 0x08, 0x0a, 0x0a, 0x0e, 0x0e,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0c, 0x0a,
+ 0x08, 0x0a, 0x0a, 0x08, 0x0a, 0x0a, 0x0e, 0x0a,
+ 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x0f, 0x0e, 0x0e,
+ 0x0f, 0x0e, 0x0e, 0x0c, 0x08, 0x0f, 0x0f, 0x08,
+ 0x08, 0x0c, 0x0f, 0x0e, 0x08, 0x08, 0x08, 0x0c,
+ 0x0c, 0x08, 0x0f, 0x0c, 0x0e, 0x0e, 0x08, 0x0e,
+};
+
+static guchar m551[256] = {
+ 0x40, 0x40, 0x00, 0x00, 0xf0, 0x40, 0xf0, 0xf0,
+ 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0xc0, 0x70,
+ 0xf0, 0x00, 0x00, 0x00, 0x70, 0x40, 0x70, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x70, 0xf0,
+ 0x00, 0xf0, 0x40, 0x70, 0x00, 0xc0, 0x40, 0x00,
+ 0x70, 0x70, 0x00, 0x40, 0x40, 0x40, 0xf0, 0x00,
+ 0x70, 0x40, 0x00, 0x00, 0xf0, 0x70, 0xf0, 0x40,
+ 0x00, 0x40, 0x40, 0x00, 0x70, 0x40, 0x00, 0xf0,
+ 0x40, 0x40, 0x00, 0xf0, 0x00, 0x00, 0xf0, 0x70,
+ 0x40, 0x00, 0x40, 0x40, 0x70, 0x70, 0xc0, 0x00,
+ 0x40, 0x40, 0x00, 0xf0, 0x40, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x70, 0xf0, 0x00, 0x70, 0x40,
+ 0xf0, 0x40, 0x40, 0x70, 0xf0, 0x00, 0xc0, 0xf0,
+ 0x40, 0xf0, 0xf0, 0x70, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x40,
+ 0xf0, 0xf0, 0xf0, 0x00, 0xf0, 0xf0, 0x70, 0x70,
+ 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0xf0,
+ 0x70, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0xf0, 0x00,
+ 0x70, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0x40, 0x70, 0xf0, 0x40, 0xf0,
+ 0x00, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x00,
+ 0x00, 0x40, 0x40, 0x40, 0xf0, 0x00, 0x00, 0x40,
+ 0x40, 0x00, 0xf0, 0x40, 0xf0, 0x00, 0x40, 0xf0,
+ 0x00, 0x00, 0x40, 0xf0, 0xf0, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x70,
+ 0x00, 0x70, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x40,
+};
+
+static guchar m552[256] = {
+ 0x08, 0x0e, 0x08, 0x0c, 0x0d, 0x0c, 0x0f, 0x0f,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0e,
+ 0x0f, 0x0e, 0x0c, 0x08, 0x0e, 0x08, 0x08, 0x0c,
+ 0x08, 0x08, 0x0c, 0x0a, 0x09, 0x08, 0x08, 0x08,
+ 0x0a, 0x08, 0x0a, 0x0a, 0x0a, 0x08, 0x0c, 0x0e,
+ 0x08, 0x08, 0x0e, 0x08, 0x0e, 0x0c, 0x0e, 0x0f,
+ 0x0e, 0x0f, 0x0c, 0x0e, 0x0c, 0x0d, 0x0c, 0x0e,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0e, 0x08, 0x0f, 0x0a,
+ 0x0c, 0x0e, 0x08, 0x0e, 0x0f, 0x0c, 0x0f, 0x08,
+ 0x0c, 0x0a, 0x0e, 0x0c, 0x08, 0x0c, 0x0c, 0x0d,
+ 0x0e, 0x0c, 0x0c, 0x09, 0x08, 0x0e, 0x0f, 0x0c,
+ 0x08, 0x08, 0x08, 0x08, 0x0e, 0x08, 0x0d, 0x0c,
+ 0x08, 0x0e, 0x0c, 0x0d, 0x0e, 0x0e, 0x0e, 0x0a,
+ 0x08, 0x08, 0x0e, 0x08, 0x0a, 0x0a, 0x0a, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x0e, 0x0e, 0x0e,
+ 0x0a, 0x08, 0x08, 0x0e, 0x0f, 0x0c, 0x0e, 0x0c,
+ 0x0f, 0x0e, 0x0e, 0x0e, 0x0f, 0x08, 0x09, 0x0f,
+ 0x0e, 0x0f, 0x0f, 0x0e, 0x0c, 0x0c, 0x0c, 0x0e,
+ 0x08, 0x0e, 0x0c, 0x0c, 0x0e, 0x0c, 0x08, 0x08,
+ 0x0f, 0x0f, 0x0d, 0x08, 0x0f, 0x0f, 0x08, 0x0e,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x0c, 0x09, 0x0d, 0x0d, 0x0d, 0x0c, 0x0d, 0x08,
+ 0x08, 0x0e, 0x0c, 0x0e, 0x08, 0x0e, 0x08, 0x0a,
+ 0x08, 0x0a, 0x08, 0x0e, 0x08, 0x0a, 0x0a, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0e, 0x0f, 0x0c, 0x0d,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0e, 0x0e, 0x0c, 0x0c,
+ 0x0c, 0x0e, 0x0e, 0x0e, 0x0f, 0x0c, 0x0e, 0x08,
+ 0x08, 0x0c, 0x0d, 0x0c, 0x0f, 0x0e, 0x08, 0x0f,
+ 0x08, 0x0e, 0x0c, 0x0f, 0x0f, 0x0e, 0x0e, 0x0c,
+ 0x0e, 0x0c, 0x0a, 0x0a, 0x0a, 0x08, 0x08, 0x0e,
+ 0x08, 0x08, 0x0e, 0x0a, 0x08, 0x0a, 0x0c, 0x0e,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0f, 0x0f, 0x0c,
+};
+
+static guchar m561[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xf0, 0x00,
+ 0x40, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x00, 0xf0, 0x00, 0x70, 0xf0,
+ 0x70, 0x40, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x70, 0x00, 0x00, 0x40, 0x00, 0x00, 0xf0,
+ 0x40, 0x70, 0xf0, 0x40, 0xf0, 0x40, 0xf0, 0x40,
+ 0x70, 0x40, 0x00, 0x40, 0x40, 0x40, 0x00, 0x40,
+ 0x40, 0x40, 0x70, 0x40, 0x40, 0x00, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0x40, 0x70, 0x40, 0x70, 0x40,
+ 0x70, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x40, 0x00,
+ 0x40, 0x40, 0x40, 0x40, 0x70, 0x00, 0x40, 0x00,
+ 0xf0, 0x40, 0x70, 0xf0, 0x70, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x40, 0x00, 0xf0, 0x40, 0x00, 0x00,
+ 0x70, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x40, 0x40, 0xf0, 0x70,
+ 0x40, 0x00, 0x70, 0x40, 0x40, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x70, 0x00, 0x70, 0x00, 0x00, 0xf0, 0x40, 0x40,
+ 0x40, 0x40, 0x00, 0x40, 0xc0, 0x40, 0xf0, 0x00,
+ 0x00, 0x40, 0x00, 0x40, 0xf0, 0x00, 0x70, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x00,
+ 0x70, 0x70, 0x70, 0x70, 0x00, 0x40, 0x00, 0x00,
+ 0x70, 0x40, 0xc0, 0x40, 0x40, 0xc0, 0x70, 0x40,
+ 0x40, 0xf0, 0x00, 0x70, 0x00, 0x00, 0x00, 0x70,
+ 0x70, 0x40, 0xf0, 0xf0, 0x40, 0x40, 0xf0, 0x40,
+ 0xf0, 0x40, 0x00, 0x70, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0x40, 0x00, 0x40, 0x70, 0x00,
+ 0xf0, 0x40, 0x70, 0x70, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0xf0, 0xf0, 0x00, 0x00, 0x70, 0x00, 0x70,
+};
+
+static guchar m562[256] = {
+ 0x0e, 0x0e, 0x0c, 0x08, 0x0c, 0x08, 0x0d, 0x08,
+ 0x0e, 0x0f, 0x08, 0x08, 0x0e, 0x0c, 0x0e, 0x0e,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0d,
+ 0x0a, 0x08, 0x08, 0x0e, 0x0c, 0x0c, 0x0a, 0x0e,
+ 0x08, 0x08, 0x08, 0x0a, 0x0a, 0x08, 0x08, 0x0e,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0e, 0x0a, 0x0c, 0x0d,
+ 0x0c, 0x0a, 0x0f, 0x0c, 0x0f, 0x0c, 0x0f, 0x08,
+ 0x0c, 0x0e, 0x0c, 0x0e, 0x08, 0x0c, 0x0c, 0x0e,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0e, 0x0a, 0x0e, 0x08,
+ 0x08, 0x08, 0x08, 0x0d, 0x0a, 0x08, 0x08, 0x0e,
+ 0x0e, 0x0e, 0x0c, 0x08, 0x0a, 0x08, 0x0c, 0x08,
+ 0x0c, 0x08, 0x0e, 0x0c, 0x0e, 0x0c, 0x0c, 0x08,
+ 0x0f, 0x0e, 0x0e, 0x0f, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0d, 0x08, 0x0e, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0a, 0x0a, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0f, 0x0c,
+ 0x08, 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x0e, 0x0e,
+ 0x0c, 0x08, 0x08, 0x0e, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x0a, 0x08, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x0c, 0x0d, 0x0a,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0e,
+ 0x08, 0x08, 0x08, 0x08, 0x0f, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0d, 0x08, 0x08,
+ 0x08, 0x0d, 0x08, 0x0c, 0x0e, 0x08, 0x08, 0x0e,
+ 0x08, 0x08, 0x0f, 0x0f, 0x08, 0x0e, 0x0f, 0x0e,
+ 0x0f, 0x0e, 0x0a, 0x08, 0x0e, 0x0c, 0x08, 0x0c,
+ 0x08, 0x08, 0x0c, 0x0e, 0x08, 0x0a, 0x0c, 0x08,
+ 0x0f, 0x0a, 0x08, 0x08, 0x0a, 0x0a, 0x08, 0x0c,
+ 0x08, 0x0f, 0x0f, 0x08, 0x08, 0x0a, 0x0a, 0x0e,
+};
+
+static guchar m571[256] = {
+ 0x70, 0x40, 0x40, 0xf0, 0xf0, 0x00, 0x00, 0x40,
+ 0xf0, 0x70, 0x40, 0xf0, 0x40, 0xf0, 0x00, 0x70,
+ 0x00, 0x40, 0xf0, 0xf0, 0x00, 0x40, 0xf0, 0x00,
+ 0xf0, 0x00, 0x40, 0x40, 0x70, 0x40, 0x00, 0xf0,
+ 0x40, 0x00, 0x40, 0x40, 0x40, 0x40, 0x70, 0x70,
+ 0xf0, 0x40, 0x40, 0x00, 0x40, 0xf0, 0x40, 0x40,
+ 0xf0, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x70,
+ 0x70, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x40, 0x40,
+ 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x40, 0x40, 0xf0,
+ 0x00, 0x00, 0xf0, 0x00, 0x40, 0xc0, 0xf0, 0x70,
+ 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0x40, 0x00, 0xf0, 0x40, 0xf0, 0x40,
+ 0x40, 0x70, 0xf0, 0x40, 0x00, 0x40, 0xc0, 0x40,
+ 0xc0, 0x40, 0x00, 0x40, 0x40, 0xc0, 0x00, 0x40,
+ 0x00, 0x40, 0x40, 0x40, 0xc0, 0x00, 0x40, 0x70,
+ 0x00, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x70, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0x40, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x40, 0x40,
+ 0xf0, 0x40, 0xf0, 0xf0, 0x70, 0x00, 0x00, 0x40,
+ 0x40, 0x40, 0x70, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x70, 0xc0,
+ 0xc0, 0x00, 0x00, 0xf0, 0x40, 0x00, 0xf0, 0x40,
+ 0x00, 0x00, 0x70, 0x70, 0x70, 0x40, 0x70, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0x40, 0x40, 0xf0,
+ 0xf0, 0x40, 0x00, 0x70, 0x40, 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0xc0, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x40, 0xf0,
+ 0x40, 0xf0, 0xf0, 0x00, 0xf0, 0x40, 0x40, 0x40,
+};
+
+static guchar m572[256] = {
+ 0x08, 0x0c, 0x0c, 0x0f, 0x0f, 0x08, 0x0a, 0x0c,
+ 0x0f, 0x0e, 0x0e, 0x0d, 0x0c, 0x0d, 0x08, 0x08,
+ 0x08, 0x08, 0x0d, 0x0d, 0x0c, 0x08, 0x0d, 0x08,
+ 0x0d, 0x08, 0x0c, 0x0c, 0x0e, 0x08, 0x0c, 0x0f,
+ 0x0c, 0x08, 0x0c, 0x0e, 0x08, 0x08, 0x08, 0x08,
+ 0x0f, 0x0e, 0x0e, 0x08, 0x0e, 0x0f, 0x0e, 0x0e,
+ 0x0f, 0x08, 0x08, 0x0e, 0x0c, 0x08, 0x08, 0x08,
+ 0x08, 0x0a, 0x0a, 0x0f, 0x08, 0x08, 0x0e, 0x08,
+ 0x0f, 0x0c, 0x0b, 0x08, 0x08, 0x0c, 0x08, 0x0f,
+ 0x08, 0x0c, 0x0f, 0x0c, 0x0e, 0x0f, 0x0f, 0x0e,
+ 0x0f, 0x0f, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0a,
+ 0x08, 0x08, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0f, 0x0c, 0x08, 0x0f, 0x08, 0x0f, 0x08,
+ 0x0e, 0x0e, 0x0f, 0x0e, 0x08, 0x0e, 0x09, 0x0e,
+ 0x0d, 0x0c, 0x0c, 0x0e, 0x0c, 0x0d, 0x0e, 0x0e,
+ 0x08, 0x08, 0x08, 0x0e, 0x0f, 0x0c, 0x08, 0x08,
+ 0x0c, 0x08, 0x0f, 0x0e, 0x0a, 0x0a, 0x0a, 0x08,
+ 0x09, 0x08, 0x08, 0x0f, 0x0e, 0x08, 0x08, 0x0c,
+ 0x08, 0x08, 0x0a, 0x0f, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0a, 0x0f, 0x0f, 0x0e, 0x0c, 0x0a, 0x0a,
+ 0x08, 0x0a, 0x08, 0x0a, 0x08, 0x0a, 0x0e, 0x08,
+ 0x08, 0x08, 0x0a, 0x08, 0x0a, 0x0c, 0x0c, 0x08,
+ 0x0e, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0e, 0x0f, 0x08, 0x08, 0x0c, 0x0d,
+ 0x09, 0x08, 0x08, 0x0f, 0x0c, 0x08, 0x0f, 0x0e,
+ 0x0c, 0x08, 0x0e, 0x08, 0x0e, 0x0e, 0x08, 0x08,
+ 0x0a, 0x0a, 0x0a, 0x08, 0x0c, 0x0a, 0x08, 0x0f,
+ 0x0f, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0c, 0x08, 0x08, 0x0c, 0x0e, 0x0c, 0x0a,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0d,
+ 0x0e, 0x0f, 0x0f, 0x0c, 0x0d, 0x0e, 0x08, 0x08,
+};
+
+static guchar m581[256] = {
+ 0xf0, 0x00, 0xf0, 0x40, 0x40, 0xf0, 0xf0, 0x00,
+ 0xc0, 0xc0, 0xf0, 0x70, 0x40, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
+ 0x00, 0x70, 0x00, 0x40, 0x00, 0x70, 0xc0, 0x40,
+ 0x40, 0xf0, 0x00, 0x00, 0xf0, 0x00, 0x40, 0xc0,
+ 0x00, 0x00, 0xf0, 0x00, 0x00, 0x40, 0x00, 0xf0,
+ 0xf0, 0xf0, 0x40, 0x00, 0xf0, 0xf0, 0x00, 0x00,
+ 0x00, 0x40, 0xf0, 0x00, 0x00, 0x70, 0x00, 0x40,
+ 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0xf0, 0xf0, 0x40, 0x40, 0x00, 0xc0,
+ 0x40, 0xf0, 0x70, 0x00, 0xf0, 0x40, 0x00, 0xf0,
+ 0xf0, 0x70, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x40,
+ 0x00, 0xc0, 0xf0, 0x00, 0xc0, 0x00, 0x00, 0x40,
+ 0x40, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x70, 0x00, 0x00, 0xf0, 0x00, 0x00,
+ 0x40, 0xf0, 0x00, 0x00, 0xc0, 0x00, 0xf0, 0x40,
+ 0x40, 0x40, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x40,
+ 0x40, 0xc0, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40,
+ 0x40, 0x00, 0x00, 0xf0, 0x40, 0x00, 0x40, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x80, 0xf0,
+ 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0xf0, 0xc0, 0x00, 0x70, 0x00, 0x00, 0xf0, 0x00,
+ 0x00, 0x40, 0x40, 0xf0, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0x70, 0xf0, 0xf0, 0x40, 0x00, 0xf0, 0x00,
+ 0x00, 0xf0, 0x40, 0x00, 0x40, 0xf0, 0x00, 0xf0,
+ 0x40, 0x00, 0x70, 0x00, 0x70, 0x40, 0xc0, 0x00,
+ 0x40, 0xf0, 0x40, 0xf0, 0x40, 0xf0, 0x40, 0x70,
+ 0xf0, 0xf0, 0x40, 0x00, 0x70, 0x40, 0xf0, 0xf0,
+ 0x00, 0x40, 0x40, 0x00, 0xf0, 0x70, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0xf0, 0xf0, 0x00, 0x70, 0xf0,
+ 0x70, 0x70, 0x70, 0x40, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0xf0, 0xf0, 0xf0, 0x70, 0xf0, 0x00, 0x00,
+};
+
+static guchar m582[256] = {
+ 0x0f, 0x0c, 0x0f, 0x08, 0x0c, 0x0d, 0x0f, 0x0e,
+ 0x0d, 0x0d, 0x0d, 0x0e, 0x0c, 0x0e, 0x0c, 0x08,
+ 0x0c, 0x0a, 0x08, 0x08, 0x0c, 0x0a, 0x08, 0x08,
+ 0x08, 0x0e, 0x08, 0x0c, 0x0c, 0x0c, 0x0f, 0x08,
+ 0x0e, 0x0f, 0x08, 0x0c, 0x0f, 0x0c, 0x08, 0x0d,
+ 0x0c, 0x0c, 0x0f, 0x08, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0f, 0x0d, 0x0c, 0x0c, 0x0d, 0x0f, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x09, 0x0c, 0x08, 0x0c, 0x08, 0x0c,
+ 0x08, 0x08, 0x08, 0x08, 0x0a, 0x08, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0d, 0x0d, 0x0e, 0x0e, 0x0c, 0x0d,
+ 0x08, 0x0f, 0x0c, 0x0c, 0x0f, 0x0c, 0x08, 0x0d,
+ 0x0f, 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x0f, 0x08,
+ 0x08, 0x09, 0x0d, 0x0c, 0x0d, 0x0e, 0x08, 0x08,
+ 0x0c, 0x08, 0x08, 0x0e, 0x0a, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0c, 0x08, 0x08, 0x0c, 0x0d, 0x0c, 0x08,
+ 0x08, 0x0d, 0x0c, 0x0c, 0x0d, 0x0c, 0x0f, 0x0c,
+ 0x0e, 0x0e, 0x0c, 0x0f, 0x08, 0x0f, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0a, 0x0f, 0x0c, 0x08, 0x08, 0x08,
+ 0x0c, 0x0a, 0x0a, 0x08, 0x0d, 0x0c, 0x0f, 0x0f,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x08,
+ 0x0f, 0x0f, 0x08, 0x0c, 0x0c, 0x08, 0x0d, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0d, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x0d, 0x0d, 0x0e, 0x0c, 0x0d, 0x0c,
+ 0x08, 0x0f, 0x0c, 0x08, 0x08, 0x0f, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0d, 0x0c,
+ 0x08, 0x0f, 0x0c, 0x0d, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x0d, 0x0d, 0x0c, 0x0c, 0x08, 0x0c, 0x0d, 0x0d,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0f, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0f, 0x0f, 0x08, 0x0a, 0x0d,
+ 0x0a, 0x08, 0x08, 0x0a, 0x0c, 0x08, 0x0a, 0x08,
+ 0x08, 0x0f, 0x0d, 0x09, 0x0c, 0x0d, 0x0c, 0x0c,
+};
+
+static guchar m591[256] = {
+ 0x00, 0x00, 0x70, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0x70, 0x70, 0x40, 0x40, 0x00, 0x00, 0xf0,
+ 0x70, 0x00, 0x40, 0x40, 0xc0, 0xf0, 0xf0, 0x00,
+ 0x70, 0xf0, 0xf0, 0x70, 0xf0, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0xf0, 0x40, 0x40, 0x70, 0x00, 0xf0,
+ 0x40, 0xf0, 0xf0, 0xf0, 0x70, 0xf0, 0xf0, 0x40,
+ 0x40, 0xf0, 0x70, 0x40, 0x00, 0x40, 0x40, 0xf0,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40,
+ 0x00, 0x00, 0x00, 0x40, 0xf0, 0x00, 0x40, 0xf0,
+ 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x40, 0x40, 0xf0, 0xf0, 0x00, 0xf0,
+ 0x70, 0x40, 0xf0, 0x40, 0x00, 0x40, 0x40, 0x40,
+ 0xf0, 0x40, 0xf0, 0x40, 0x00, 0x70, 0x00, 0xf0,
+ 0x70, 0x70, 0xf0, 0xc0, 0xf0, 0xc0, 0xf0, 0x40,
+ 0x00, 0x00, 0x40, 0xf0, 0xf0, 0x40, 0x40, 0x00,
+ 0xf0, 0x40, 0x00, 0x40, 0x40, 0xf0, 0x00, 0x00,
+ 0x00, 0x70, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xf0, 0x40, 0x40, 0x70, 0x40, 0x00,
+ 0x00, 0x00, 0x40, 0xf0, 0x00, 0x40, 0xf0, 0xc0,
+ 0x00, 0xf0, 0x00, 0x70, 0x00, 0x70, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x70, 0x40, 0xf0, 0x00, 0x40,
+ 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x40, 0x40,
+ 0x40, 0x00, 0x70, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0xf0, 0x40, 0xf0, 0x40, 0x00, 0xf0, 0x00,
+ 0x00, 0x40, 0x00, 0xc0, 0x40, 0x00, 0xf0, 0x00,
+ 0x40, 0xf0, 0x40, 0xf0, 0x00, 0x40, 0x00, 0x00,
+ 0xf0, 0xf0, 0x40, 0xf0, 0xf0, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0xf0, 0x00, 0xf0, 0xc0, 0x40, 0x40,
+ 0x00, 0x00, 0x00, 0x40, 0x40, 0x70, 0xf0, 0x40,
+ 0xf0, 0x00, 0xf0, 0x70, 0x80, 0x00, 0xc0, 0x40,
+ 0x00, 0x40, 0x40, 0x00, 0x40, 0x00, 0x70, 0x40,
+ 0x80, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0,
+};
+
+static guchar m592[256] = {
+ 0x08, 0x08, 0x0a, 0x0c, 0x0a, 0x08, 0x0c, 0x0a,
+ 0x08, 0x08, 0x08, 0x08, 0x0c, 0x0e, 0x0c, 0x0f,
+ 0x08, 0x08, 0x0c, 0x08, 0x0f, 0x0f, 0x0f, 0x0c,
+ 0x08, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x08, 0x0a,
+ 0x0c, 0x08, 0x0d, 0x08, 0x0e, 0x0e, 0x08, 0x0f,
+ 0x08, 0x0f, 0x0f, 0x0f, 0x0c, 0x0f, 0x0f, 0x0e,
+ 0x08, 0x0f, 0x08, 0x08, 0x0a, 0x08, 0x08, 0x0f,
+ 0x0e, 0x0a, 0x0a, 0x08, 0x0e, 0x08, 0x0d, 0x08,
+ 0x0c, 0x0a, 0x0a, 0x08, 0x0f, 0x0c, 0x08, 0x0f,
+ 0x0f, 0x0f, 0x0c, 0x0a, 0x08, 0x08, 0x0f, 0x0f,
+ 0x0d, 0x0f, 0x08, 0x0c, 0x0f, 0x0f, 0x0a, 0x0f,
+ 0x0e, 0x08, 0x0f, 0x08, 0x0c, 0x08, 0x08, 0x08,
+ 0x0f, 0x0c, 0x0f, 0x08, 0x08, 0x0a, 0x08, 0x0d,
+ 0x08, 0x0c, 0x0d, 0x0d, 0x09, 0x0d, 0x0d, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0f, 0x08, 0x0e, 0x0c,
+ 0x0f, 0x0e, 0x08, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c,
+ 0x0c, 0x0e, 0x0f, 0x0f, 0x0f, 0x0c, 0x0a, 0x0a,
+ 0x0a, 0x08, 0x0f, 0x08, 0x08, 0x0e, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0e, 0x0f, 0x08, 0x08, 0x0f, 0x0f,
+ 0x0c, 0x0f, 0x08, 0x08, 0x08, 0x0c, 0x0e, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x0e, 0x0f, 0x0c, 0x0c,
+ 0x0f, 0x0a, 0x0a, 0x0a, 0x09, 0x08, 0x0e, 0x0e,
+ 0x08, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x08, 0x0f, 0x0c, 0x0f, 0x0c, 0x0c, 0x0f, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0d, 0x08, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x09, 0x0e, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0f, 0x0e, 0x0f, 0x0f, 0x08, 0x0c, 0x0a,
+ 0x0e, 0x09, 0x0f, 0x0c, 0x0f, 0x0f, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0e, 0x0c, 0x0e, 0x0d, 0x08,
+ 0x0f, 0x0c, 0x0d, 0x08, 0x0f, 0x0c, 0x0d, 0x08,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x0f,
+};
+
+static guchar m5a1[256] = {
+ 0x40, 0xf0, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x70, 0x00, 0x00, 0x40, 0x40, 0x40, 0x00,
+ 0x00, 0xf0, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x00, 0x70, 0x80, 0xf0, 0x00, 0x40, 0xf0,
+ 0xf0, 0x00, 0x00, 0x40, 0x40, 0xf0, 0x00, 0x40,
+ 0x40, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x00, 0x70,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x70, 0xf0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00,
+ 0x70, 0xf0, 0x00, 0x00, 0x40, 0x40, 0xf0, 0x40,
+ 0x40, 0xf0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0xf0, 0x40, 0x00, 0x40, 0xf0, 0x40,
+ 0x00, 0x00, 0x70, 0x00, 0x70, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x40, 0xf0, 0xf0, 0x40, 0x00, 0x40, 0x40,
+ 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x40, 0x40, 0x00, 0x40, 0x00, 0x00,
+ 0x40, 0x00, 0x40, 0x40, 0x70, 0x70, 0x70, 0x40,
+ 0x00, 0xf0, 0xf0, 0x00, 0xc0, 0x00, 0x40, 0x00,
+ 0x40, 0xf0, 0x00, 0x70, 0xf0, 0x00, 0x00, 0x40,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70,
+ 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0xf0, 0x00, 0x70, 0x00, 0x40, 0xf0, 0x00,
+ 0x00, 0xf0, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0x00, 0x70, 0x70, 0x00, 0x40, 0x00, 0x00,
+};
+
+static guchar m5a2[256] = {
+ 0x0c, 0x0f, 0x08, 0x0f, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0e, 0x0c, 0x08, 0x0e, 0x08, 0x08, 0x0c,
+ 0x08, 0x0f, 0x08, 0x0e, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x08, 0x0d, 0x0f, 0x08, 0x0c, 0x0f,
+ 0x0f, 0x08, 0x08, 0x0e, 0x08, 0x0f, 0x08, 0x08,
+ 0x08, 0x0f, 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x08,
+ 0x08, 0x0a, 0x0a, 0x0c, 0x0a, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0f, 0x08, 0x0c, 0x08,
+ 0x0e, 0x0d, 0x0c, 0x0c, 0x0c, 0x08, 0x0f, 0x0c,
+ 0x0c, 0x0f, 0x0e, 0x08, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0e, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0f, 0x08, 0x0c, 0x0c, 0x0d, 0x0e,
+ 0x08, 0x0c, 0x0e, 0x08, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0c, 0x08, 0x08, 0x08, 0x0a, 0x0a, 0x0a, 0x0e,
+ 0x0c, 0x08, 0x0e, 0x0c, 0x0c, 0x0c, 0x08, 0x0e,
+ 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0f, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x08, 0x08, 0x0f, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x08, 0x0c, 0x08, 0x09, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0a, 0x08, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0e, 0x0e, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x0f, 0x0f, 0x08, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x0f, 0x0c, 0x08, 0x08,
+ 0x08, 0x08, 0x0a, 0x08, 0x0a, 0x0c, 0x0e, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0f, 0x0c, 0x0e, 0x08, 0x0c, 0x0f, 0x08,
+ 0x0c, 0x0f, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x08, 0x0a, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0c,
+};
+
+static guchar m5b1[256] = {
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x40, 0xf0, 0x00, 0xf0, 0xf0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x70, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0xf0, 0x00, 0x70, 0x40, 0x00, 0x00,
+ 0x70, 0x00, 0x70, 0x00, 0x40, 0x00, 0x70, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00,
+ 0xf0, 0x40, 0x00, 0xf0, 0x00, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00,
+ 0xf0, 0xf0, 0x40, 0x00, 0xf0, 0xf0, 0x40, 0xf0,
+ 0xf0, 0x00, 0xf0, 0x70, 0xf0, 0xf0, 0x40, 0xf0,
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x70, 0x70, 0x00,
+ 0x40, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x40, 0x40,
+ 0xf0, 0xf0, 0x00, 0x70, 0x00, 0xf0, 0x00, 0x00,
+ 0xf0, 0x00, 0xf0, 0x00, 0xc0, 0x40, 0x40, 0x40,
+ 0x70, 0x40, 0x00, 0x70, 0x40, 0xf0, 0x40, 0xf0,
+ 0xf0, 0xf0, 0x40, 0xf0, 0xf0, 0x70, 0x40, 0xf0,
+ 0x40, 0x40, 0x00, 0xc0, 0x40, 0xf0, 0xc0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x00, 0x70,
+ 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x00,
+ 0x40, 0x40, 0x00, 0x00, 0xc0, 0x40, 0xf0, 0x40,
+ 0xf0, 0x40, 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0x40,
+ 0xf0, 0xf0, 0x40, 0x00, 0x40, 0x00, 0x00, 0xf0,
+ 0xc0, 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0x00, 0x70, 0x00, 0x00, 0xf0, 0x40, 0x00, 0x40,
+ 0xf0, 0x00, 0xf0, 0xf0, 0xf0, 0x00, 0x40, 0xc0,
+ 0x40, 0x40, 0x40, 0x70, 0x00, 0x70, 0xf0, 0xf0,
+ 0x40, 0xf0, 0xf0, 0x00, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x00, 0xf0, 0x80, 0x00, 0xf0, 0xc0,
+ 0x70, 0x40, 0x00, 0x70, 0x40, 0xf0, 0xf0, 0x00,
+ 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x40, 0x70, 0x70,
+};
+
+static guchar m5b2[256] = {
+ 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x08, 0x0c,
+ 0x0c, 0x0f, 0x08, 0x0d, 0x0d, 0x08, 0x08, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0f, 0x0e,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0e, 0x08, 0x0e, 0x08, 0x08, 0x0a,
+ 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x08, 0x08, 0x0d, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0f, 0x0f, 0x08, 0x0e, 0x0f, 0x0f, 0x0c, 0x0f,
+ 0x0f, 0x0a, 0x0f, 0x0e, 0x0f, 0x0f, 0x08, 0x0f,
+ 0x08, 0x08, 0x0e, 0x0f, 0x0f, 0x0e, 0x0a, 0x08,
+ 0x08, 0x0f, 0x0a, 0x0d, 0x0e, 0x08, 0x0c, 0x08,
+ 0x0f, 0x0f, 0x0c, 0x0e, 0x08, 0x0f, 0x08, 0x0c,
+ 0x0d, 0x08, 0x0f, 0x0c, 0x09, 0x0e, 0x08, 0x0c,
+ 0x0a, 0x0e, 0x08, 0x0e, 0x0e, 0x0f, 0x08, 0x0f,
+ 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x08, 0x0c, 0x0f,
+ 0x08, 0x08, 0x0c, 0x0f, 0x08, 0x0f, 0x09, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0a, 0x0a, 0x08,
+ 0x0a, 0x0a, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0c,
+ 0x0c, 0x08, 0x0a, 0x0a, 0x0d, 0x0c, 0x0d, 0x08,
+ 0x0f, 0x08, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x08,
+ 0x0f, 0x0f, 0x08, 0x08, 0x08, 0x0a, 0x0a, 0x0f,
+ 0x0d, 0x0c, 0x0f, 0x09, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x08,
+ 0x0f, 0x0c, 0x0f, 0x0f, 0x0d, 0x08, 0x0c, 0x09,
+ 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x0a, 0x0f, 0x0f,
+ 0x0c, 0x0f, 0x0d, 0x0c, 0x0f, 0x0f, 0x0d, 0x0d,
+ 0x0f, 0x0d, 0x0c, 0x0d, 0x0d, 0x08, 0x0f, 0x0d,
+ 0x0e, 0x0c, 0x0c, 0x08, 0x08, 0x0d, 0x0d, 0x08,
+ 0x0f, 0x0a, 0x0f, 0x0a, 0x0a, 0x08, 0x08, 0x0a,
+};
+
+static guchar m5c1[256] = {
+ 0x00, 0xf0, 0x70, 0x00, 0xf0, 0x70, 0x70, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0xf0, 0xf0, 0xf0,
+ 0x00, 0xf0, 0x00, 0x70, 0x00, 0x00, 0xf0, 0x40,
+ 0x00, 0x80, 0x70, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x70, 0x00, 0x70, 0x40, 0xf0, 0x00, 0x40, 0x00,
+ 0xf0, 0x40, 0x00, 0x40, 0x40, 0x70, 0x40, 0x00,
+ 0x40, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0xf0, 0xf0,
+ 0xf0, 0x70, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x00,
+ 0xf0, 0x00, 0x70, 0xf0, 0x00, 0xf0, 0xf0, 0x70,
+ 0x70, 0xf0, 0x00, 0x70, 0x00, 0xf0, 0x00, 0x00,
+ 0x00, 0x40, 0x40, 0x80, 0x40, 0x00, 0x70, 0x00,
+ 0xf0, 0x70, 0xc0, 0x40, 0xf0, 0xf0, 0x00, 0x40,
+ 0x40, 0x40, 0x00, 0x00, 0xf0, 0x40, 0x70, 0xf0,
+ 0x40, 0xf0, 0x00, 0x00, 0x40, 0x40, 0x70, 0x00,
+ 0x00, 0xf0, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x40, 0x00, 0x40, 0x00, 0x70, 0x00, 0x00, 0x40,
+ 0xf0, 0xf0, 0x40, 0x00, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40,
+ 0x40, 0xf0, 0x40, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x70, 0xf0, 0x40, 0xf0, 0xf0, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0x40, 0xf0, 0x40, 0xc0, 0x70, 0xf0,
+ 0xf0, 0x00, 0xc0, 0x70, 0x70, 0x00, 0xf0, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x70,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0xf0, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x70, 0x70, 0x00, 0x00, 0x70, 0x40, 0xf0,
+ 0xf0, 0x40, 0x40, 0x00, 0xc0, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0x70, 0xf0, 0x00, 0xf0, 0x00, 0x00,
+};
+
+static guchar m5c2[256] = {
+ 0x08, 0x0f, 0x08, 0x0c, 0x0f, 0x08, 0x0a, 0x0d,
+ 0x0d, 0x0f, 0x0f, 0x0d, 0x0c, 0x0d, 0x0d, 0x0f,
+ 0x0c, 0x0f, 0x0c, 0x08, 0x0a, 0x0e, 0x0f, 0x08,
+ 0x0a, 0x09, 0x0e, 0x08, 0x0a, 0x0a, 0x08, 0x0c,
+ 0x08, 0x08, 0x0e, 0x08, 0x0f, 0x0e, 0x08, 0x0a,
+ 0x0d, 0x08, 0x0c, 0x08, 0x0e, 0x08, 0x08, 0x08,
+ 0x0c, 0x0f, 0x08, 0x0c, 0x0a, 0x08, 0x08, 0x0c,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0a, 0x0f, 0x0f,
+ 0x0f, 0x0e, 0x0a, 0x08, 0x0c, 0x0f, 0x0d, 0x0c,
+ 0x0f, 0x0a, 0x0a, 0x0f, 0x0c, 0x0d, 0x0f, 0x0e,
+ 0x0e, 0x0f, 0x08, 0x08, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x0c, 0x0e, 0x08, 0x09, 0x0c, 0x0c, 0x0a, 0x08,
+ 0x0f, 0x0a, 0x0d, 0x0e, 0x0d, 0x0f, 0x0a, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0c, 0x0e, 0x0f,
+ 0x08, 0x0f, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x08,
+ 0x08, 0x0f, 0x0e, 0x0c, 0x0c, 0x08, 0x0c, 0x0a,
+ 0x08, 0x0a, 0x0a, 0x08, 0x08, 0x08, 0x0c, 0x08,
+ 0x0e, 0x0c, 0x0c, 0x0c, 0x0e, 0x0e, 0x08, 0x0c,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x0e, 0x0c, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0d, 0x0e, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x0f, 0x0f, 0x0e, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0c, 0x0f,
+ 0x0f, 0x08, 0x09, 0x08, 0x08, 0x0a, 0x09, 0x0a,
+ 0x09, 0x0a, 0x08, 0x08, 0x0a, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0e, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0c,
+ 0x09, 0x0a, 0x08, 0x08, 0x0a, 0x0a, 0x0a, 0x08,
+ 0x0f, 0x08, 0x0e, 0x08, 0x0c, 0x0e, 0x0c, 0x09,
+ 0x0f, 0x0c, 0x08, 0x08, 0x0d, 0x08, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0f, 0x08, 0x0d, 0x08, 0x0c,
+};
+
+static guchar m5d1[256] = {
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0xf0,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0xc0, 0xf0, 0x00,
+ 0x00, 0xf0, 0x40, 0x00, 0xf0, 0x70, 0xf0, 0xf0,
+ 0x70, 0xf0, 0x70, 0x70, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x70, 0x40, 0x40, 0x00, 0x40, 0xc0,
+ 0x00, 0xf0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x40, 0x00, 0x40, 0xf0, 0xf0, 0x00, 0x70, 0x00,
+ 0xf0, 0x40, 0x70, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x70, 0x00, 0x00, 0x40,
+ 0x40, 0x40, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0x40, 0x00, 0xf0, 0x40, 0x00, 0xf0,
+ 0x40, 0x00, 0x00, 0x70, 0x00, 0x00, 0x70, 0x00,
+ 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0x70, 0x40, 0x70, 0x00, 0x00, 0xf0,
+ 0x40, 0x00, 0x40, 0xf0, 0x70, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0xf0, 0x00, 0x40,
+ 0xc0, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x80, 0x40, 0x70, 0x00, 0x70, 0x00,
+ 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x70,
+ 0xc0, 0x40, 0xf0, 0x00, 0xf0, 0xf0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x70, 0x00, 0x40, 0x70, 0xf0, 0x40, 0x00,
+ 0x40, 0x00, 0xf0, 0x70, 0x00, 0x00, 0xf0, 0x00,
+ 0x40, 0x40, 0x00, 0x70, 0x00, 0xf0, 0xf0, 0x00,
+ 0x40, 0xf0, 0x80, 0x70, 0x40, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0x40, 0x00, 0xf0, 0x00, 0x00, 0xf0, 0x00,
+ 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x00, 0xf0,
+ 0x40, 0x40, 0x00, 0x70, 0x00, 0xf0, 0xf0, 0x00,
+};
+
+static guchar m5d2[256] = {
+ 0x0c, 0x0c, 0x0a, 0x0a, 0x08, 0x08, 0x0e, 0x0f,
+ 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x0d, 0x0f, 0x0c,
+ 0x08, 0x0d, 0x0c, 0x08, 0x0f, 0x08, 0x0f, 0x0d,
+ 0x08, 0x0d, 0x0c, 0x0e, 0x08, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0e, 0x0c, 0x0e, 0x0f,
+ 0x0c, 0x0f, 0x08, 0x08, 0x08, 0x0a, 0x0e, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0e, 0x0a, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0e,
+ 0x08, 0x0c, 0x0e, 0x0f, 0x0f, 0x08, 0x0c, 0x08,
+ 0x0d, 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x08,
+ 0x0a, 0x0c, 0x08, 0x0a, 0x08, 0x0a, 0x0c, 0x08,
+ 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0f, 0x08, 0x0e, 0x0f, 0x08, 0x08, 0x0f,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0a, 0x08, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0e, 0x08, 0x0c, 0x08, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x09, 0x08, 0x0c, 0x08, 0x08,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0e, 0x0c, 0x08, 0x0c, 0x0f, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0d, 0x08, 0x08, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x0e,
+ 0x0d, 0x08, 0x0d, 0x08, 0x0d, 0x0d, 0x08, 0x08,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x0a, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0f, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0d, 0x08, 0x0c, 0x0c, 0x0d, 0x08,
+ 0x0c, 0x08, 0x08, 0x0a, 0x08, 0x0f, 0x0f, 0x0c,
+ 0x0c, 0x0f, 0x0f, 0x08, 0x08, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0a, 0x08, 0x0f, 0x08, 0x08, 0x0f, 0x0a,
+ 0x0c, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x08, 0x0f,
+ 0x08, 0x0c, 0x08, 0x08, 0x08, 0x0f, 0x0f, 0x0c,
+};
+
+static guchar m5e1[256] = {
+ 0x40, 0x00, 0xf0, 0xf0, 0x00, 0x00, 0xf0, 0x40,
+ 0x00, 0x00, 0x00, 0x70, 0xf0, 0x40, 0x00, 0x00,
+ 0x00, 0xf0, 0x40, 0x00, 0x40, 0x40, 0xf0, 0x00,
+ 0x40, 0xf0, 0x70, 0xf0, 0x00, 0xf0, 0x00, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0xf0, 0x00, 0xf0, 0x40, 0x70,
+ 0x70, 0x00, 0x40, 0xf0, 0x00, 0x40, 0xf0, 0x70,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x80,
+ 0xf0, 0x00, 0x00, 0x70, 0xf0, 0xf0, 0x00, 0xf0,
+ 0x00, 0x40, 0x00, 0x40, 0xf0, 0x00, 0x70, 0x00,
+ 0x40, 0x40, 0x00, 0x00, 0x70, 0xf0, 0x40, 0x70,
+ 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x40, 0xf0,
+ 0x00, 0xf0, 0xf0, 0xf0, 0x70, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00,
+ 0x40, 0x00, 0xf0, 0xf0, 0xf0, 0x70, 0x70, 0x80,
+ 0xf0, 0xf0, 0x70, 0xf0, 0xf0, 0xf0, 0xf0, 0x70,
+ 0x40, 0x70, 0x00, 0x70, 0xf0, 0x00, 0x00, 0xf0,
+ 0x00, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x40, 0xf0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x70, 0xf0,
+ 0x00, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x00,
+ 0xf0, 0x00, 0x40, 0x00, 0x40, 0x40, 0xf0, 0xf0,
+ 0x40, 0x00, 0x40, 0xf0, 0x40, 0xf0, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0x40, 0xc0, 0x40,
+ 0x00, 0x70, 0xf0, 0x70, 0x00, 0x00, 0x40, 0x00,
+ 0xf0, 0xf0, 0xf0, 0x40, 0x40, 0x00, 0x40, 0x70,
+ 0xf0, 0x40, 0x40, 0xf0, 0x40, 0x40, 0xf0, 0x00,
+ 0x00, 0x00, 0xf0, 0xf0, 0x40, 0x70, 0x40, 0xf0,
+ 0xf0, 0x70, 0xf0, 0xf0, 0x00, 0x40, 0x00, 0x00,
+ 0x70, 0x70, 0x00, 0x40, 0xf0, 0x00, 0x00, 0x00,
+ 0x70, 0x70, 0x00, 0xf0, 0x70, 0x00, 0xf0, 0xf0,
+ 0x70, 0x00, 0xf0, 0xf0, 0x70, 0x00, 0x70, 0x70,
+};
+
+static guchar m5e2[256] = {
+ 0x08, 0x0a, 0x0f, 0x0f, 0x0c, 0x0a, 0x0f, 0x08,
+ 0x0a, 0x08, 0x0c, 0x08, 0x0f, 0x08, 0x0c, 0x0a,
+ 0x0a, 0x0f, 0x08, 0x08, 0x0e, 0x0e, 0x0f, 0x0c,
+ 0x0e, 0x0f, 0x0e, 0x0f, 0x0a, 0x0f, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0a, 0x0a,
+ 0x0c, 0x0c, 0x08, 0x0d, 0x08, 0x0f, 0x0a, 0x08,
+ 0x08, 0x0a, 0x08, 0x0d, 0x0c, 0x08, 0x0d, 0x0e,
+ 0x0f, 0x08, 0x08, 0x0a, 0x0a, 0x0f, 0x0c, 0x09,
+ 0x0d, 0x0c, 0x0a, 0x0c, 0x0f, 0x0f, 0x08, 0x09,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x08, 0x08, 0x0c, 0x0e, 0x0f, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0e, 0x0c, 0x0c, 0x0a, 0x0d,
+ 0x0c, 0x0f, 0x0f, 0x0d, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0f, 0x0f, 0x0f, 0x0c, 0x0e, 0x09,
+ 0x0f, 0x0d, 0x0a, 0x0f, 0x0f, 0x0f, 0x0d, 0x0a,
+ 0x0e, 0x08, 0x0c, 0x08, 0x0f, 0x08, 0x0a, 0x0f,
+ 0x0c, 0x0c, 0x0f, 0x0e, 0x0c, 0x0c, 0x08, 0x0f,
+ 0x0a, 0x0a, 0x08, 0x0a, 0x0a, 0x0f, 0x0e, 0x0f,
+ 0x08, 0x0a, 0x0f, 0x0c, 0x0f, 0x08, 0x0a, 0x0a,
+ 0x0f, 0x08, 0x0c, 0x0c, 0x0c, 0x0e, 0x0f, 0x0f,
+ 0x0c, 0x08, 0x0c, 0x0d, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0e, 0x08, 0x08, 0x08, 0x08, 0x0f, 0x08,
+ 0x08, 0x0c, 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0f, 0x0f, 0x0c, 0x0c, 0x08, 0x0c, 0x08,
+ 0x09, 0x0e, 0x0e, 0x0f, 0x0c, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0f, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0d, 0x0c, 0x0d, 0x0d, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0c, 0x0a, 0x08, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0d, 0x0a, 0x08, 0x0f, 0x0f,
+ 0x08, 0x08, 0x0f, 0x09, 0x08, 0x08, 0x0e, 0x0e,
+};
+
+static guchar m5f1[256] = {
+ 0x00, 0xf0, 0x40, 0x70, 0xf0, 0x00, 0x40, 0x40,
+ 0x40, 0x70, 0xf0, 0x70, 0x70, 0x70, 0x40, 0xf0,
+ 0x70, 0xf0, 0x00, 0xf0, 0xf0, 0xf0, 0x70, 0xf0,
+ 0xf0, 0x40, 0x00, 0xf0, 0x40, 0x40, 0x00, 0xf0,
+ 0x00, 0x40, 0x40, 0x40, 0x40, 0x70, 0xf0, 0xf0,
+ 0x40, 0xf0, 0x00, 0x40, 0x40, 0x70, 0x40, 0x70,
+ 0x40, 0xf0, 0x00, 0x00, 0x40, 0xf0, 0x40, 0x70,
+ 0x70, 0x00, 0x80, 0x40, 0xf0, 0x40, 0x70, 0x40,
+ 0x40, 0x70, 0x00, 0x00, 0x40, 0x40, 0x00, 0x40,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x40, 0xf0, 0x00,
+ 0x40, 0x70, 0x00, 0x70, 0x40, 0x00, 0xf0, 0xf0,
+ 0x40, 0xf0, 0x00, 0xc0, 0x70, 0x70, 0x00, 0x00,
+ 0x40, 0x70, 0xf0, 0x40, 0x40, 0x00, 0xf0, 0xc0,
+ 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x40,
+ 0xf0, 0xf0, 0x40, 0x70, 0x40, 0x40, 0x00, 0xf0,
+ 0x40, 0xf0, 0x40, 0x00, 0xf0, 0x40, 0x40, 0xf0,
+ 0xf0, 0xf0, 0x70, 0x70, 0x70, 0xf0, 0x00, 0xf0,
+ 0x70, 0x40, 0xf0, 0xf0, 0xf0, 0x40, 0x00, 0x40,
+ 0xf0, 0xf0, 0xf0, 0x70, 0x00, 0x00, 0x40, 0xf0,
+ 0xf0, 0xf0, 0x00, 0x00, 0x40, 0x40, 0xf0, 0x00,
+ 0xf0, 0xf0, 0x40, 0x00, 0x40, 0x00, 0x00, 0x40,
+ 0xf0, 0xf0, 0xf0, 0x40, 0x40, 0x70, 0xf0, 0x40,
+ 0x40, 0x40, 0x00, 0x70, 0x70, 0x80, 0x00, 0x80,
+ 0x40, 0xf0, 0x00, 0x00, 0x70, 0xf0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0x40, 0xf0, 0x00, 0x40,
+ 0x40, 0x40, 0x00, 0x40, 0xf0, 0xf0, 0x00, 0x00,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x00, 0x00, 0x70, 0x70, 0x40, 0x00,
+ 0xf0, 0x40, 0x40, 0x00, 0x70, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0x40, 0xf0, 0x40, 0x40, 0x40, 0x40,
+ 0x70, 0x70, 0x40, 0x40, 0x00, 0xf0, 0x40, 0x00,
+ 0x70, 0x00, 0x40, 0x70, 0x40, 0xf0, 0x00, 0xf0,
+};
+
+static guchar m5f2[256] = {
+ 0x0a, 0x0f, 0x0e, 0x0a, 0x0f, 0x0c, 0x08, 0x0c,
+ 0x0e, 0x08, 0x0f, 0x0e, 0x08, 0x08, 0x08, 0x0f,
+ 0x08, 0x0b, 0x0c, 0x0f, 0x0d, 0x0f, 0x08, 0x0f,
+ 0x0f, 0x08, 0x0c, 0x0f, 0x08, 0x0c, 0x08, 0x0f,
+ 0x0a, 0x08, 0x0c, 0x0c, 0x0c, 0x0a, 0x0f, 0x0f,
+ 0x0c, 0x0f, 0x0a, 0x08, 0x08, 0x0e, 0x0c, 0x0a,
+ 0x0c, 0x0f, 0x08, 0x0c, 0x08, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0a, 0x0b, 0x08, 0x0f, 0x08, 0x08, 0x08,
+ 0x0e, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x08,
+ 0x0d, 0x0c, 0x0d, 0x0c, 0x0d, 0x08, 0x0d, 0x0c,
+ 0x0a, 0x08, 0x0a, 0x0a, 0x0c, 0x0a, 0x0f, 0x0f,
+ 0x0e, 0x0d, 0x08, 0x09, 0x08, 0x0e, 0x08, 0x08,
+ 0x08, 0x0a, 0x0f, 0x08, 0x0e, 0x0c, 0x0b, 0x0d,
+ 0x08, 0x0f, 0x0f, 0x0d, 0x0f, 0x0f, 0x08, 0x0c,
+ 0x0f, 0x0f, 0x08, 0x0e, 0x0c, 0x08, 0x0c, 0x0f,
+ 0x0c, 0x0f, 0x08, 0x0a, 0x0f, 0x0c, 0x0c, 0x0d,
+ 0x0f, 0x0f, 0x0e, 0x08, 0x0a, 0x0f, 0x0c, 0x0f,
+ 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x08, 0x08, 0x08,
+ 0x0f, 0x0d, 0x0f, 0x08, 0x08, 0x0a, 0x0c, 0x0f,
+ 0x0f, 0x0f, 0x08, 0x0c, 0x0e, 0x08, 0x0d, 0x0c,
+ 0x0d, 0x0f, 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x08,
+ 0x0f, 0x0d, 0x0f, 0x0c, 0x0c, 0x0e, 0x0f, 0x0c,
+ 0x08, 0x08, 0x0c, 0x08, 0x08, 0x0f, 0x0c, 0x0f,
+ 0x08, 0x0d, 0x08, 0x0c, 0x0e, 0x0f, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0f, 0x0a, 0x0f, 0x0a, 0x08,
+ 0x08, 0x0e, 0x08, 0x08, 0x0f, 0x0f, 0x08, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x08, 0x0c, 0x0c, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x08, 0x08, 0x08, 0x0e, 0x0c, 0x08,
+ 0x0f, 0x0e, 0x08, 0x0c, 0x0e, 0x0c, 0x08, 0x0a,
+ 0x0c, 0x08, 0x0e, 0x0f, 0x08, 0x0e, 0x0e, 0x0c,
+ 0x08, 0x0e, 0x08, 0x0c, 0x0c, 0x0f, 0x08, 0x0c,
+ 0x0e, 0x08, 0x0c, 0x0e, 0x08, 0x0f, 0x0a, 0x0f,
+};
+
+static guchar m601[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x70, 0xf0,
+ 0x70, 0x00, 0xf0, 0x40, 0x40, 0x70, 0xf0, 0x40,
+ 0x40, 0x70, 0x40, 0x70, 0xf0, 0xf0, 0x00, 0x40,
+ 0xf0, 0xf0, 0x00, 0x00, 0x40, 0xf0, 0x70, 0xf0,
+ 0xf0, 0x70, 0xf0, 0x70, 0x00, 0x40, 0x00, 0xf0,
+ 0x00, 0x70, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x70, 0x40,
+ 0x40, 0x40, 0x70, 0x70, 0x40, 0xf0, 0x00, 0x00,
+ 0xf0, 0x40, 0xf0, 0x00, 0x40, 0xf0, 0x40, 0x40,
+ 0x00, 0xf0, 0x70, 0x00, 0x00, 0xc0, 0x00, 0x70,
+ 0x70, 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x40,
+ 0xf0, 0xf0, 0xf0, 0x70, 0xf0, 0xf0, 0x00, 0xf0,
+ 0xf0, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x70, 0x40, 0x70, 0x70, 0x80, 0x40, 0x00,
+ 0x40, 0xf0, 0x40, 0x70, 0xf0, 0xf0, 0x40, 0x00,
+ 0x00, 0x40, 0x70, 0x40, 0xf0, 0x40, 0xf0, 0x70,
+ 0x40, 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x40, 0xf0,
+ 0xf0, 0x00, 0x40, 0xf0, 0xc0, 0x40, 0x70, 0xf0,
+ 0x40, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xc0, 0x40, 0xf0, 0xf0, 0xf0, 0x70, 0xf0, 0x40,
+ 0xf0, 0x00, 0x00, 0x40, 0xf0, 0xf0, 0x40, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x40, 0xf0, 0x70, 0xf0,
+ 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0xf0, 0x00, 0x70, 0x40, 0x40, 0x00, 0x00,
+ 0x70, 0x40, 0xf0, 0x40, 0xf0, 0x40, 0x40, 0xf0,
+ 0xf0, 0xf0, 0x40, 0x70, 0x00, 0x40, 0x00, 0x70,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0xf0, 0x40, 0xf0, 0x70, 0x40, 0xf0, 0x70,
+ 0x40, 0xf0, 0xf0, 0xf0, 0x40, 0x40, 0x00, 0x00,
+};
+
+static guchar m602[256] = {
+ 0x0e, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x08,
+ 0x08, 0x0c, 0x0e, 0x0c, 0x0c, 0x0e, 0x0e, 0x0f,
+ 0x0c, 0x0c, 0x0f, 0x0c, 0x0e, 0x0e, 0x0f, 0x0c,
+ 0x08, 0x0e, 0x0c, 0x0e, 0x0f, 0x0f, 0x0c, 0x08,
+ 0x0f, 0x0f, 0x0c, 0x08, 0x0c, 0x0f, 0x0e, 0x0f,
+ 0x0f, 0x0e, 0x0f, 0x0e, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0e, 0x08, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0a, 0x0a, 0x08, 0x08, 0x0a,
+ 0x0c, 0x0f, 0x0f, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0a, 0x0c, 0x0f, 0x08, 0x08,
+ 0x0f, 0x08, 0x0f, 0x0c, 0x0c, 0x0f, 0x08, 0x08,
+ 0x0c, 0x0f, 0x0e, 0x0c, 0x08, 0x0f, 0x0c, 0x0c,
+ 0x08, 0x08, 0x0f, 0x0f, 0x0f, 0x0d, 0x0c, 0x0e,
+ 0x0f, 0x0f, 0x0f, 0x0e, 0x0f, 0x0f, 0x0c, 0x0f,
+ 0x0f, 0x08, 0x0c, 0x0a, 0x08, 0x08, 0x0a, 0x08,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x08, 0x0e,
+ 0x0c, 0x0c, 0x08, 0x0e, 0x0e, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x08, 0x0f, 0x0f, 0x0c, 0x08,
+ 0x0c, 0x08, 0x0e, 0x08, 0x0f, 0x0c, 0x0f, 0x0c,
+ 0x08, 0x08, 0x0f, 0x0f, 0x0c, 0x0e, 0x08, 0x0f,
+ 0x0f, 0x08, 0x0c, 0x0f, 0x09, 0x08, 0x0a, 0x09,
+ 0x0e, 0x08, 0x08, 0x02, 0x02, 0x02, 0x08, 0x02,
+ 0x0d, 0x0e, 0x0f, 0x09, 0x0f, 0x0c, 0x0d, 0x0c,
+ 0x0f, 0x0c, 0x0c, 0x0e, 0x0f, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0f, 0x0e, 0x0d,
+ 0x0c, 0x0c, 0x0e, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0f, 0x08, 0x0c, 0x0c, 0x0e, 0x08, 0x08,
+ 0x0e, 0x0c, 0x0f, 0x0c, 0x0f, 0x0e, 0x08, 0x0f,
+ 0x0f, 0x0d, 0x0c, 0x08, 0x0c, 0x08, 0x0e, 0x0a,
+ 0x0a, 0x0a, 0x08, 0x02, 0x02, 0x02, 0x0a, 0x02,
+ 0x0f, 0x0d, 0x0c, 0x0f, 0x0e, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0f, 0x0f, 0x0d, 0x0c, 0x08, 0x0c, 0x0c,
+};
+
+static guchar m611[256] = {
+ 0x70, 0xf0, 0x40, 0x70, 0x00, 0x00, 0xf0, 0x40,
+ 0xf0, 0xf0, 0x40, 0x00, 0x40, 0xf0, 0xf0, 0xf0,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0xf0, 0x40, 0x40,
+ 0x00, 0x40, 0xf0, 0xf0, 0x40, 0x00, 0x40, 0xf0,
+ 0x00, 0x70, 0x40, 0x00, 0x00, 0x00, 0x00, 0xf0,
+ 0x70, 0x00, 0x40, 0x40, 0x70, 0x00, 0x00, 0x00,
+ 0xc0, 0x40, 0x00, 0x00, 0xf0, 0x40, 0x40, 0xc0,
+ 0x00, 0x40, 0x00, 0x00, 0xf0, 0x70, 0xf0, 0xf0,
+ 0x00, 0x40, 0xf0, 0x00, 0xf0, 0x40, 0x40, 0xf0,
+ 0xf0, 0x40, 0xf0, 0xf0, 0xf0, 0x70, 0x70, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x00,
+ 0xf0, 0xf0, 0x70, 0x00, 0x00, 0xf0, 0x40, 0xf0,
+ 0x40, 0x00, 0xf0, 0xf0, 0x80, 0x70, 0x00, 0xf0,
+ 0xf0, 0x00, 0x00, 0xf0, 0x40, 0x00, 0xf0, 0x70,
+ 0xf0, 0x70, 0x40, 0x70, 0x70, 0x70, 0xf0, 0xf0,
+ 0x40, 0x00, 0x00, 0x40, 0x40, 0x80, 0xf0, 0x40,
+ 0x40, 0xc0, 0xf0, 0x40, 0x40, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0xf0, 0x40, 0x00, 0x40, 0xf0, 0x00,
+ 0xf0, 0xf0, 0x40, 0x40, 0xf0, 0x00, 0x70, 0x40,
+ 0xc0, 0xf0, 0xf0, 0x00, 0x40, 0x40, 0x00, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x00, 0xf0,
+ 0x40, 0xf0, 0x40, 0xf0, 0xf0, 0x40, 0xf0, 0x00,
+ 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x00,
+ 0x40, 0x40, 0xf0, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x40, 0x40, 0x40, 0xf0, 0x00, 0x00, 0x70, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x70, 0x40, 0x40,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40,
+ 0x00, 0x40, 0x40, 0x70, 0x00, 0x40, 0xf0, 0x40,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x40,
+ 0x00, 0x00, 0xf0, 0x00, 0x70, 0x00, 0xf0, 0xf0,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x70, 0x70, 0xf0,
+};
+
+static guchar m612[256] = {
+ 0x0e, 0x0f, 0x08, 0x0c, 0x0c, 0x0c, 0x0f, 0x08,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x08, 0x0f, 0x0f, 0x0f,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x0c, 0x08, 0x0f, 0x0d, 0x0c, 0x0c, 0x08, 0x0f,
+ 0x0a, 0x08, 0x08, 0x0e, 0x02, 0x08, 0x02, 0x0f,
+ 0x0c, 0x0c, 0x08, 0x0e, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x09, 0x08, 0x0c, 0x08, 0x0d, 0x08, 0x0c, 0x0d,
+ 0x08, 0x08, 0x08, 0x0c, 0x09, 0x08, 0x0d, 0x0f,
+ 0x0c, 0x0c, 0x09, 0x08, 0x0d, 0x0c, 0x0c, 0x0d,
+ 0x0f, 0x0c, 0x0f, 0x0d, 0x0f, 0x0c, 0x0e, 0x0c,
+ 0x08, 0x02, 0x0c, 0x0d, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x0d, 0x09, 0x0c, 0x0c, 0x08, 0x0f, 0x0c, 0x0d,
+ 0x08, 0x0c, 0x0f, 0x0d, 0x09, 0x0c, 0x0c, 0x0f,
+ 0x0f, 0x08, 0x0c, 0x0d, 0x0c, 0x08, 0x0d, 0x08,
+ 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0d, 0x0f,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x09, 0x0d, 0x08,
+ 0x0c, 0x09, 0x0d, 0x0c, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x0c, 0x0d, 0x0e, 0x0c, 0x0c, 0x0f, 0x08,
+ 0x0d, 0x0d, 0x0c, 0x0c, 0x0f, 0x08, 0x0c, 0x08,
+ 0x09, 0x09, 0x0d, 0x0c, 0x08, 0x0e, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0d, 0x08, 0x08, 0x0f,
+ 0x0e, 0x0f, 0x0c, 0x0d, 0x0f, 0x0c, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0d, 0x0a,
+ 0x0c, 0x08, 0x0d, 0x08, 0x0c, 0x08, 0x0f, 0x0c,
+ 0x08, 0x0c, 0x0e, 0x0d, 0x08, 0x0c, 0x0c, 0x0d,
+ 0x0f, 0x0d, 0x0f, 0x0f, 0x0c, 0x0c, 0x08, 0x08,
+ 0x08, 0x02, 0x02, 0x08, 0x02, 0x08, 0x0c, 0x08,
+ 0x0c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0d, 0x08, 0x00, 0x0e, 0x0d, 0x0d,
+ 0x0d, 0x0c, 0x0d, 0x0c, 0x0d, 0x0c, 0x0c, 0x0f,
+};
+
+static guchar m621[256] = {
+ 0xf0, 0x40, 0x00, 0x40, 0x40, 0x00, 0x00, 0xc0,
+ 0xf0, 0x70, 0xf0, 0x00, 0xf0, 0xf0, 0xf0, 0x00,
+ 0xf0, 0xf0, 0xf0, 0x40, 0x70, 0x40, 0xf0, 0x00,
+ 0x00, 0x00, 0xf0, 0x70, 0x40, 0x70, 0x70, 0xf0,
+ 0x40, 0xf0, 0x40, 0x40, 0x00, 0x00, 0x70, 0x40,
+ 0x00, 0x40, 0xf0, 0x40, 0x00, 0x00, 0xf0, 0x70,
+ 0xf0, 0x80, 0x70, 0x70, 0xf0, 0x00, 0x80, 0x00,
+ 0x70, 0x40, 0x00, 0x70, 0x00, 0x40, 0x80, 0xf0,
+ 0xf0, 0xf0, 0x40, 0x40, 0x40, 0x00, 0x40, 0xf0,
+ 0xf0, 0xf0, 0x00, 0xf0, 0x40, 0xf0, 0x70, 0x00,
+ 0x40, 0x40, 0x40, 0xf0, 0x40, 0x70, 0x40, 0x00,
+ 0xf0, 0x00, 0x40, 0x70, 0x40, 0x00, 0x70, 0x00,
+ 0x70, 0x00, 0x00, 0x70, 0x40, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x40, 0xf0, 0x40,
+ 0x00, 0xf0, 0x00, 0x40, 0x00, 0x00, 0xf0, 0x00,
+ 0x00, 0xf0, 0x40, 0x00, 0xf0, 0x40, 0x70, 0xf0,
+ 0xf0, 0x00, 0x70, 0x70, 0xf0, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0xf0, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x40, 0xf0, 0xf0, 0x70, 0x70, 0xf0, 0x70, 0xf0,
+ 0xf0, 0x00, 0x00, 0xf0, 0x70, 0x00, 0x70, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x40, 0x00, 0x00, 0xf0, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0x00, 0x40, 0x00, 0xf0, 0x40, 0x40,
+ 0x00, 0xf0, 0x40, 0x70, 0xf0, 0xf0, 0x40, 0x40,
+ 0x00, 0x00, 0xf0, 0x00, 0x40, 0x70, 0x70, 0xf0,
+ 0xf0, 0xf0, 0x70, 0x00, 0xf0, 0xf0, 0x40, 0xf0,
+ 0xf0, 0x70, 0xf0, 0xf0, 0xf0, 0x40, 0xc0, 0xf0,
+ 0xf0, 0xf0, 0x40, 0xf0, 0xf0, 0x70, 0x00, 0x00,
+ 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0x00, 0xf0, 0x40, 0xf0, 0x40, 0x70, 0x70, 0xf0,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0xf0, 0xf0,
+};
+
+static guchar m622[256] = {
+ 0x0d, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x02, 0x0d,
+ 0x0f, 0x0c, 0x0f, 0x02, 0x0f, 0x0f, 0x0f, 0x0a,
+ 0x0f, 0x0f, 0x0f, 0x08, 0x0c, 0x0e, 0x0f, 0x02,
+ 0x0a, 0x0c, 0x0f, 0x0e, 0x08, 0x08, 0x08, 0x0f,
+ 0x0c, 0x0f, 0x0e, 0x0c, 0x0e, 0x0e, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0f, 0x0c, 0x0a, 0x0c, 0x0f, 0x08,
+ 0x0d, 0x09, 0x0c, 0x0e, 0x0f, 0x08, 0x0d, 0x0a,
+ 0x08, 0x08, 0x0c, 0x08, 0x08, 0x0e, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0c, 0x0e, 0x00, 0x08, 0x0c, 0x0f,
+ 0x0f, 0x0f, 0x0c, 0x0f, 0x0a, 0x0f, 0x0e, 0x08,
+ 0x0c, 0x0e, 0x0e, 0x0f, 0x0e, 0x00, 0x00, 0x08,
+ 0x0f, 0x0c, 0x0c, 0x0e, 0x0c, 0x08, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0e, 0x0a,
+ 0x08, 0x02, 0x02, 0x02, 0x02, 0x0e, 0x0f, 0x0e,
+ 0x0e, 0x0d, 0x0c, 0x0e, 0x0c, 0x08, 0x0f, 0x0c,
+ 0x08, 0x0f, 0x0c, 0x0c, 0x0f, 0x0c, 0x0e, 0x0f,
+ 0x0f, 0x0c, 0x08, 0x0c, 0x0f, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0f, 0x08, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0f, 0x0f, 0x0e, 0x0c, 0x0f, 0x0e, 0x0f,
+ 0x0f, 0x08, 0x0a, 0x0b, 0x08, 0x08, 0x08, 0x02,
+ 0x02, 0x02, 0x02, 0x08, 0x0a, 0x0a, 0x08, 0x08,
+ 0x0e, 0x0c, 0x0c, 0x0f, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x08, 0x0c, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x0c, 0x0f, 0x08, 0x0e, 0x0f, 0x0f, 0x0c, 0x0e,
+ 0x08, 0x08, 0x0f, 0x08, 0x0e, 0x0a, 0x0e, 0x0f,
+ 0x0f, 0x0f, 0x0e, 0x0c, 0x0f, 0x0f, 0x0e, 0x0d,
+ 0x0f, 0x0c, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0e, 0x0f, 0x0f, 0x08, 0x08, 0x0a,
+ 0x08, 0x08, 0x02, 0x02, 0x08, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x08, 0x0c, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0c, 0x0f, 0x0c, 0x0f, 0x0e, 0x0c, 0x0e, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0e, 0x0f, 0x0f,
+};
+
+static guchar m631[256] = {
+ 0x00, 0xf0, 0x70, 0x40, 0x40, 0x00, 0x00, 0xf0,
+ 0x70, 0xf0, 0x40, 0x40, 0x70, 0x40, 0x00, 0x00,
+ 0x40, 0xf0, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x40, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70,
+ 0x70, 0x40, 0x40, 0xf0, 0x00, 0x40, 0x00, 0xf0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0x40, 0xf0, 0x80, 0x40, 0xf0, 0xf0, 0x70,
+ 0x00, 0x40, 0x40, 0x40, 0x40, 0x00, 0x40, 0x00,
+ 0x00, 0xf0, 0x40, 0x40, 0xf0, 0x70, 0x40, 0xf0,
+ 0xf0, 0x00, 0x40, 0x40, 0x40, 0xf0, 0x00, 0x70,
+ 0x40, 0x00, 0x00, 0x40, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0xf0,
+ 0xf0, 0x70, 0x00, 0x70, 0x40, 0x40, 0xf0, 0x00,
+ 0x00, 0x40, 0xf0, 0x00, 0x40, 0x40, 0x70, 0xf0,
+ 0x40, 0x00, 0xf0, 0xf0, 0x40, 0x40, 0x00, 0xc0,
+ 0x70, 0x00, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x40,
+ 0xf0, 0xf0, 0x40, 0x00, 0xf0, 0x00, 0x70, 0x70,
+ 0x40, 0x00, 0xf0, 0x00, 0x40, 0x40, 0xf0, 0x00,
+ 0xf0, 0x40, 0x40, 0xf0, 0x00, 0x00, 0x40, 0x70,
+ 0xf0, 0xf0, 0xf0, 0x70, 0x40, 0xf0, 0x40, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0x70, 0x70, 0x40, 0x40, 0x40,
+ 0x00, 0x00, 0x70, 0x00, 0x70, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0x40, 0x70, 0x00,
+ 0xf0, 0x40, 0x00, 0x70, 0xf0, 0x40, 0xf0, 0x00,
+ 0x40, 0x70, 0x00, 0x00, 0x00, 0x00, 0x40, 0xf0,
+ 0xf0, 0x40, 0x70, 0x40, 0x40, 0x40, 0xf0, 0x00,
+ 0x00, 0x00, 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0xf0, 0x00, 0x70, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x70, 0x40, 0x00, 0x40, 0x80, 0xf0, 0x00,
+ 0x00, 0x00, 0x40, 0x40, 0xf0, 0x40, 0xf0, 0x80,
+ 0x40, 0x40, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m632[256] = {
+ 0x0c, 0x0f, 0x0e, 0x0c, 0x08, 0x08, 0x08, 0x0f,
+ 0x0e, 0x0f, 0x08, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x0f, 0x08, 0x0c, 0x0c, 0x0c, 0x0e, 0x08,
+ 0x00, 0x08, 0x02, 0x0a, 0x00, 0x02, 0x02, 0x0a,
+ 0x02, 0x0a, 0x02, 0x0a, 0x02, 0x02, 0x00, 0x00,
+ 0x0e, 0x0c, 0x0e, 0x0f, 0x0c, 0x0c, 0x08, 0x0f,
+ 0x08, 0x08, 0x0e, 0x0c, 0x0c, 0x08, 0x0c, 0x08,
+ 0x0c, 0x0e, 0x0f, 0x0d, 0x0c, 0x0f, 0x0d, 0x08,
+ 0x0c, 0x0c, 0x0e, 0x0e, 0x0c, 0x0e, 0x0e, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x0e, 0x0f, 0x0e, 0x0e, 0x0f,
+ 0x0f, 0x0c, 0x08, 0x08, 0x0c, 0x0f, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x02, 0x02,
+ 0x08, 0x02, 0x02, 0x02, 0x08, 0x0c, 0x08, 0x0f,
+ 0x0d, 0x0e, 0x08, 0x0c, 0x08, 0x0e, 0x0f, 0x0c,
+ 0x0c, 0x0e, 0x0d, 0x08, 0x08, 0x0c, 0x0e, 0x0f,
+ 0x0c, 0x08, 0x0f, 0x0f, 0x0c, 0x0c, 0x08, 0x09,
+ 0x0e, 0x0c, 0x0e, 0x0d, 0x0c, 0x0c, 0x08, 0x0e,
+ 0x0f, 0x0f, 0x0e, 0x08, 0x0f, 0x0c, 0x0e, 0x0e,
+ 0x0e, 0x0c, 0x0f, 0x08, 0x0c, 0x08, 0x0f, 0x0c,
+ 0x0f, 0x0c, 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0d, 0x0f, 0x0e, 0x0c, 0x0f, 0x08, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0c, 0x0e, 0x0e, 0x0e, 0x0c,
+ 0x0e, 0x0c, 0x08, 0x02, 0x02, 0x00, 0x08, 0x02,
+ 0x02, 0x00, 0x0a, 0x00, 0x02, 0x0c, 0x0e, 0x08,
+ 0x0d, 0x08, 0x0c, 0x0c, 0x0f, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x0e, 0x0e, 0x0f,
+ 0x0f, 0x08, 0x0e, 0x0c, 0x08, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0a, 0x0c,
+ 0x0e, 0x0f, 0x08, 0x0e, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0e, 0x0e, 0x0c, 0x08, 0x0f, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0e, 0x0c, 0x0f, 0x0c, 0x0f, 0x09,
+ 0x0a, 0x0c, 0x08, 0x00, 0x00, 0x02, 0x08, 0x02,
+};
+
+static guchar m641[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00,
+ 0x00, 0x40, 0x40, 0x00, 0x00, 0xf0, 0x00, 0xf0,
+ 0x40, 0x00, 0x40, 0x70, 0xc0, 0x00, 0xf0, 0xf0,
+ 0x40, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x00,
+ 0x40, 0x00, 0xc0, 0x00, 0x40, 0x40, 0x70, 0x00,
+ 0x70, 0x40, 0x40, 0x00, 0xf0, 0xf0, 0x00, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0x70, 0x40, 0x70, 0x00,
+ 0x00, 0x00, 0xf0, 0x00, 0x00, 0x40, 0xf0, 0x40,
+ 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x70, 0x40,
+ 0x00, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00,
+ 0xf0, 0x00, 0x40, 0x40, 0x40, 0x40, 0x00, 0x40,
+ 0xc0, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0x40, 0x00, 0xf0,
+ 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x70, 0x00,
+ 0xf0, 0x80, 0xf0, 0x40, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0x40, 0x00, 0x40,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x40, 0xc0, 0xf0, 0xf0, 0x00, 0x70, 0x00, 0x00,
+ 0x40, 0x40, 0xf0, 0x40, 0x00, 0x40, 0xf0, 0x40,
+ 0x00, 0x40, 0x00, 0x40, 0xf0, 0xf0, 0x40, 0x00,
+ 0x40, 0x70, 0x00, 0xf0, 0x40, 0xf0, 0xf0, 0x00,
+ 0xf0, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x70, 0x00, 0xf0, 0x70, 0x40, 0x40, 0x40,
+ 0x00, 0xf0, 0x70, 0x00, 0xc0, 0xf0, 0x00, 0xf0,
+ 0x00, 0x40, 0xc0, 0x40, 0x40, 0xf0, 0xc0, 0x00,
+ 0x40, 0x40, 0xf0, 0x00, 0xf0, 0x40, 0x00, 0x40,
+ 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0xf0, 0xf0, 0x70, 0x40, 0xc0, 0xf0, 0xf0,
+ 0x00, 0x40, 0x40, 0x00, 0xf0, 0x40, 0x00, 0x70,
+ 0x40, 0x70, 0xf0, 0x00, 0xf0, 0x40, 0x70, 0x40,
+ 0x00, 0x00, 0xf0, 0x40, 0x00, 0x70, 0xf0, 0x40,
+};
+
+static guchar m642[256] = {
+ 0x02, 0x02, 0x02, 0x08, 0x00, 0x02, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0c, 0x0e, 0x0e, 0x0d, 0x0c, 0x0f,
+ 0x0e, 0x08, 0x0c, 0x0e, 0x0f, 0x0c, 0x0d, 0x0d,
+ 0x0c, 0x08, 0x0c, 0x0e, 0x0f, 0x08, 0x0e, 0x0c,
+ 0x0e, 0x0e, 0x0d, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x08, 0x0e, 0x0c, 0x0f, 0x0f, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0b, 0x08, 0x08, 0x0e, 0x0d, 0x0e,
+ 0x0c, 0x0e, 0x00, 0x0c, 0x02, 0x02, 0x0a, 0x0a,
+ 0x02, 0x08, 0x02, 0x0c, 0x08, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0e, 0x0c, 0x0e, 0x08, 0x08, 0x08,
+ 0x0f, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0d, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x0e,
+ 0x0c, 0x0f, 0x08, 0x0c, 0x0c, 0x0e, 0x0c, 0x0d,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0f, 0x0f, 0x0c, 0x08, 0x0c, 0x08, 0x0c,
+ 0x08, 0x08, 0x0e, 0x08, 0x02, 0x0e, 0x08, 0x0e,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0b, 0x0f, 0x0d, 0x08, 0x0e, 0x0e, 0x0c,
+ 0x0c, 0x0e, 0x0d, 0x08, 0x0c, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0f, 0x0d, 0x0c, 0x08,
+ 0x08, 0x0e, 0x08, 0x0d, 0x0e, 0x0f, 0x0f, 0x08,
+ 0x0f, 0x0c, 0x0d, 0x0c, 0x08, 0x02, 0x08, 0x02,
+ 0x02, 0x08, 0x02, 0x0d, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0a, 0x0d, 0x0e, 0x0c, 0x0d, 0x0f, 0x08, 0x0d,
+ 0x08, 0x0c, 0x0d, 0x0c, 0x08, 0x0f, 0x0f, 0x0c,
+ 0x0e, 0x08, 0x0f, 0x08, 0x0d, 0x08, 0x0c, 0x0e,
+ 0x0f, 0x0c, 0x0d, 0x0c, 0x08, 0x00, 0x02, 0x08,
+ 0x0c, 0x09, 0x0f, 0x0c, 0x0e, 0x09, 0x0f, 0x09,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0d, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0d, 0x08, 0x08, 0x0c,
+ 0x0c, 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c,
+};
+
+static guchar m651[256] = {
+ 0xf0, 0x40, 0x00, 0x00, 0xc0, 0x70, 0x00, 0x00,
+ 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x40, 0x00,
+ 0xf0, 0x40, 0x00, 0x40, 0x70, 0xf0, 0x40, 0x40,
+ 0x00, 0x00, 0x40, 0xf0, 0x70, 0x00, 0x40, 0x00,
+ 0x00, 0x40, 0xf0, 0xf0, 0xf0, 0x00, 0x40, 0xf0,
+ 0x00, 0x40, 0x00, 0x00, 0x70, 0x70, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x40, 0xf0, 0x40, 0x40, 0xf0, 0xf0,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0xf0, 0x00, 0x40,
+ 0xf0, 0x40, 0x00, 0x00, 0x00, 0xf0, 0x80, 0xf0,
+ 0x40, 0xf0, 0x40, 0x00, 0x40, 0x70, 0xf0, 0xf0,
+ 0x70, 0x70, 0x00, 0x00, 0x00, 0x70, 0xf0, 0x40,
+ 0x40, 0x00, 0xf0, 0xf0, 0x00, 0x00, 0xf0, 0x40,
+ 0x00, 0x00, 0x00, 0x40, 0xf0, 0x80, 0x00, 0x00,
+ 0x70, 0x00, 0xf0, 0x00, 0xf0, 0xf0, 0x00, 0xf0,
+ 0xf0, 0x00, 0x40, 0x00, 0x00, 0x40, 0x80, 0x00,
+ 0x00, 0x40, 0xf0, 0xf0, 0x00, 0xc0, 0x00, 0xf0,
+ 0x70, 0x70, 0x40, 0x00, 0xf0, 0x00, 0x70, 0x00,
+ 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x00, 0xf0,
+ 0x40, 0xf0, 0x00, 0xf0, 0xf0, 0x40, 0x00, 0xf0,
+ 0x40, 0xf0, 0x00, 0x40, 0xf0, 0xf0, 0x40, 0xf0,
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x70, 0x40, 0xf0,
+ 0xf0, 0x00, 0x40, 0x40, 0x40, 0x00, 0x00, 0xf0,
+ 0x00, 0xf0, 0x00, 0x00, 0xf0, 0xf0, 0x00, 0x40,
+ 0x00, 0xf0, 0x40, 0x70, 0x70, 0xf0, 0x70, 0x00,
+ 0x40, 0x40, 0x00, 0xf0, 0xf0, 0x00, 0x40, 0xf0,
+ 0x40, 0x00, 0xf0, 0x00, 0x40, 0x00, 0x40, 0xf0,
+ 0x40, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00, 0x40,
+ 0xf0, 0x70, 0x70, 0x80, 0x00, 0xf0, 0xf0, 0x70,
+ 0xf0, 0xf0, 0x00, 0x00, 0xf0, 0xf0, 0x00, 0x00,
+ 0x40, 0xf0, 0x40, 0x00, 0xc0, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0xf0, 0xf0, 0x80, 0x80, 0x40, 0xc0,
+};
+
+static guchar m652[256] = {
+ 0x0f, 0x0c, 0x08, 0x0c, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0e, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x02, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0e, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0d, 0x0f, 0x0d, 0x0c, 0x0e, 0x0f,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0a, 0x0a, 0x0f, 0x0d,
+ 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0c, 0x0f, 0x0f,
+ 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0f, 0x0c, 0x08,
+ 0x0f, 0x0e, 0x0c, 0x08, 0x0a, 0x09, 0x09, 0x0f,
+ 0x08, 0x0f, 0x08, 0x0c, 0x0c, 0x0e, 0x0f, 0x0d,
+ 0x0c, 0x0e, 0x08, 0x02, 0x0c, 0x0e, 0x0f, 0x08,
+ 0x08, 0x08, 0x0f, 0x0f, 0x0c, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x00, 0x0c, 0x0a, 0x0f, 0x09, 0x08, 0x0c,
+ 0x0a, 0x08, 0x0f, 0x0c, 0x0f, 0x0d, 0x0c, 0x0f,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x01, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x09, 0x00, 0x0f,
+ 0x08, 0x08, 0x08, 0x0a, 0x0f, 0x08, 0x08, 0x00,
+ 0x0f, 0x0f, 0x0c, 0x02, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0c, 0x0c, 0x0f,
+ 0x0c, 0x0f, 0x0c, 0x08, 0x0f, 0x0f, 0x08, 0x0f,
+ 0x0c, 0x02, 0x0c, 0x0f, 0x0d, 0x0a, 0x0c, 0x0f,
+ 0x0f, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0d,
+ 0x0c, 0x0f, 0x08, 0x0c, 0x0f, 0x0f, 0x08, 0x0c,
+ 0x00, 0x0f, 0x0c, 0x0e, 0x0e, 0x0f, 0x0e, 0x08,
+ 0x08, 0x08, 0x08, 0x0f, 0x0f, 0x0c, 0x0e, 0x0f,
+ 0x0c, 0x08, 0x0f, 0x0c, 0x08, 0x00, 0x0e, 0x0f,
+ 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0b, 0x0c, 0x0e, 0x09, 0x08, 0x0f, 0x0f, 0x0a,
+ 0x0f, 0x0f, 0x08, 0x08, 0x0f, 0x0f, 0x0e, 0x0e,
+ 0x0e, 0x0f, 0x0c, 0x0c, 0x0d, 0x0c, 0x0a, 0x02,
+ 0x00, 0x08, 0x0f, 0x0d, 0x0d, 0x0d, 0x08, 0x09,
+};
+
+static guchar m661[256] = {
+ 0x40, 0x00, 0x70, 0x70, 0x40, 0x00, 0xf0, 0xf0,
+ 0x40, 0xc0, 0xf0, 0x00, 0xf0, 0x40, 0xf0, 0xf0,
+ 0x80, 0xc0, 0x40, 0xf0, 0xf0, 0xc0, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0x40, 0xc0, 0xf0,
+ 0xf0, 0x40, 0x40, 0x40, 0x40, 0xf0, 0x40, 0xf0,
+ 0xf0, 0x40, 0x40, 0x40, 0x40, 0xf0, 0x40, 0xf0,
+ 0xc0, 0xc0, 0x00, 0x40, 0xf0, 0x70, 0xf0, 0x40,
+ 0x00, 0x40, 0x80, 0x80, 0x70, 0x00, 0x00, 0x70,
+ 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0x40, 0x00,
+ 0x00, 0xf0, 0x40, 0xf0, 0x40, 0x00, 0x40, 0xf0,
+ 0x00, 0x40, 0x70, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x40, 0xc0, 0x00, 0xc0, 0x40, 0xf0, 0xf0, 0xf0,
+ 0x40, 0x40, 0x70, 0x00, 0xf0, 0x80, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x40, 0xc0, 0x40, 0x00, 0xf0, 0xf0,
+ 0x70, 0x00, 0x00, 0xc0, 0xf0, 0x40, 0xf0, 0xc0,
+ 0xc0, 0x40, 0xf0, 0x40, 0x40, 0x00, 0x40, 0x40,
+ 0x40, 0x70, 0x00, 0x70, 0xf0, 0x00, 0x00, 0xf0,
+ 0xf0, 0xf0, 0x00, 0x40, 0x40, 0x40, 0xf0, 0x00,
+ 0xc0, 0xf0, 0x40, 0x00, 0x00, 0x00, 0xf0, 0xf0,
+ 0xf0, 0x40, 0x40, 0x40, 0x40, 0xf0, 0x00, 0x40,
+ 0xc0, 0x00, 0xf0, 0x00, 0x40, 0x00, 0x70, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0x00, 0x40, 0xf0, 0x00,
+ 0x00, 0x40, 0xc0, 0x80, 0xf0, 0x40, 0x00, 0x00,
+ 0x70, 0xf0, 0x00, 0xc0, 0x70, 0x00, 0xf0, 0x40,
+ 0x40, 0x70, 0x40, 0x40, 0xf0, 0x00, 0x80, 0xf0,
+ 0x40, 0xf0, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x00, 0xf0, 0x70, 0x40, 0xf0, 0xf0, 0x00, 0x40,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00,
+ 0x40, 0x70, 0x00, 0x40, 0x40, 0x00, 0x40, 0x00,
+ 0xf0, 0x00, 0xf0, 0xf0, 0xf0, 0x70, 0x00, 0xf0,
+ 0xf0, 0xf0, 0xc0, 0x40, 0xf0, 0x70, 0xf0, 0xf0,
+};
+
+static guchar m662[256] = {
+ 0x0e, 0x08, 0x0e, 0x0e, 0x0c, 0x0c, 0x0f, 0x0d,
+ 0x0c, 0x0d, 0x0f, 0x0c, 0x0f, 0x0c, 0x0f, 0x0f,
+ 0x0d, 0x0d, 0x0c, 0x0f, 0x0f, 0x0f, 0x08, 0x08,
+ 0x08, 0x0a, 0x08, 0x08, 0x0c, 0x0e, 0x09, 0x0f,
+ 0x0f, 0x0c, 0x0c, 0x08, 0x0c, 0x0f, 0x0c, 0x0f,
+ 0x0f, 0x08, 0x08, 0x0c, 0x08, 0x0f, 0x0c, 0x0f,
+ 0x09, 0x0f, 0x0c, 0x0c, 0x0f, 0x0e, 0x0f, 0x08,
+ 0x08, 0x0c, 0x0d, 0x09, 0x0a, 0x00, 0x02, 0x00,
+ 0x08, 0x0f, 0x0d, 0x0f, 0x09, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0d, 0x0c, 0x0b, 0x0e, 0x08, 0x08, 0x0f,
+ 0x08, 0x0c, 0x0e, 0x02, 0x02, 0x02, 0x02, 0x0a,
+ 0x08, 0x0d, 0x0e, 0x0d, 0x0c, 0x0d, 0x0d, 0x0f,
+ 0x08, 0x0e, 0x0c, 0x08, 0x0f, 0x0d, 0x0f, 0x09,
+ 0x0f, 0x09, 0x0c, 0x09, 0x0c, 0x08, 0x0f, 0x0f,
+ 0x0e, 0x0c, 0x0c, 0x09, 0x0f, 0x08, 0x0f, 0x0f,
+ 0x0d, 0x0c, 0x0f, 0x0c, 0x0c, 0x08, 0x0e, 0x08,
+ 0x0c, 0x08, 0x02, 0x00, 0x0f, 0x08, 0x0c, 0x0f,
+ 0x0d, 0x0d, 0x0c, 0x0c, 0x0e, 0x0c, 0x09, 0x08,
+ 0x0d, 0x0f, 0x08, 0x08, 0x0c, 0x0c, 0x0f, 0x0f,
+ 0x0d, 0x0c, 0x08, 0x08, 0x08, 0x0f, 0x08, 0x0c,
+ 0x0d, 0x0c, 0x0d, 0x08, 0x08, 0x08, 0x08, 0x02,
+ 0x0e, 0x0c, 0x0c, 0x0d, 0x08, 0x08, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x09, 0x0f, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x0d, 0x08, 0x0c, 0x0f, 0x08,
+ 0x0c, 0x08, 0x08, 0x08, 0x0d, 0x08, 0x0d, 0x0d,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0c,
+ 0x08, 0x08, 0x0c, 0x08, 0x08, 0x08, 0x0d, 0x08,
+ 0x0c, 0x0f, 0x0c, 0x0e, 0x0f, 0x0f, 0x0c, 0x08,
+ 0x0d, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0f, 0x08,
+ 0x0c, 0x0e, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x08, 0x0c, 0x0f,
+ 0x0d, 0x0f, 0x09, 0x00, 0x0f, 0x00, 0x0f, 0x0f,
+};
+
+static guchar m671[256] = {
+ 0xf0, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x00, 0x40,
+ 0xf0, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x40, 0x70,
+ 0x00, 0x00, 0x00, 0x40, 0xf0, 0xf0, 0x70, 0xf0,
+ 0x00, 0x40, 0x00, 0xf0, 0x40, 0xf0, 0xf0, 0xf0,
+ 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0xf0, 0xf0,
+ 0xf0, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x00,
+ 0x00, 0xf0, 0x00, 0x40, 0xf0, 0x00, 0xf0, 0x70,
+ 0x70, 0x00, 0xf0, 0x00, 0x00, 0xf0, 0x40, 0x70,
+ 0x00, 0x70, 0x00, 0x00, 0x00, 0x40, 0xf0, 0x40,
+ 0x40, 0xf0, 0x00, 0x00, 0x40, 0x00, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x00, 0xf0, 0x40, 0x40, 0xf0, 0x00,
+ 0x00, 0x70, 0x00, 0x00, 0xf0, 0x40, 0xf0, 0xf0,
+ 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x40, 0x00,
+ 0x00, 0x00, 0x70, 0x00, 0x40, 0xf0, 0x40, 0xf0,
+ 0xf0, 0xf0, 0x70, 0xf0, 0x40, 0xf0, 0x40, 0xf0,
+ 0x00, 0x00, 0x00, 0xc0, 0x70, 0x00, 0xf0, 0xf0,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x70, 0x00, 0xf0,
+ 0x00, 0xf0, 0x00, 0xf0, 0x70, 0x00, 0x40, 0xc0,
+ 0xf0, 0x40, 0x00, 0xc0, 0x00, 0xf0, 0x40, 0xf0,
+ 0x40, 0x40, 0xf0, 0x40, 0xf0, 0xf0, 0x00, 0x00,
+ 0x70, 0x70, 0x70, 0x00, 0x00, 0x00, 0x70, 0x00,
+ 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0,
+ 0xc0, 0x40, 0x40, 0xf0, 0x70, 0x40, 0xf0, 0xf0,
+ 0xf0, 0x70, 0x00, 0x40, 0x40, 0x40, 0x80, 0x00,
+ 0x40, 0x70, 0x40, 0x40, 0xf0, 0x40, 0x70, 0x00,
+ 0x40, 0x40, 0x70, 0x00, 0x00, 0x00, 0x70, 0xf0,
+ 0xf0, 0xf0, 0xc0, 0xf0, 0xf0, 0x00, 0x00, 0x40,
+ 0x70, 0x40, 0xf0, 0x00, 0x40, 0xf0, 0x70, 0x00,
+ 0x00, 0x40, 0x70, 0x00, 0x70, 0x00, 0x40, 0x70,
+ 0x00, 0xf0, 0x00, 0x00, 0xf0, 0x00, 0x70, 0xf0,
+ 0xc0, 0xf0, 0x40, 0xf0, 0xf0, 0xf0, 0xc0, 0x40,
+ 0x00, 0x40, 0x00, 0xf0, 0x00, 0x00, 0xf0, 0x70,
+};
+
+static guchar m672[256] = {
+ 0x0f, 0x0c, 0x08, 0x0d, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0f, 0x0f, 0x0e, 0x0f, 0x08, 0x0f, 0x08, 0x0c,
+ 0x0e, 0x08, 0x0c, 0x0c, 0x0f, 0x0f, 0x08, 0x0f,
+ 0x0c, 0x08, 0x08, 0x0f, 0x08, 0x0f, 0x09, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0f, 0x0d,
+ 0x0f, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0d, 0x0a,
+ 0x00, 0x0f, 0x08, 0x0c, 0x0f, 0x0e, 0x09, 0x08,
+ 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c,
+ 0x0a, 0x00, 0x0a, 0x02, 0x08, 0x0c, 0x0f, 0x0c,
+ 0x0e, 0x0f, 0x08, 0x0c, 0x0e, 0x0c, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0c, 0x0f, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0f, 0x0c, 0x0f, 0x0f,
+ 0x0e, 0x0a, 0x08, 0x00, 0x00, 0x0a, 0x08, 0x00,
+ 0x02, 0x02, 0x0e, 0x08, 0x0c, 0x0f, 0x08, 0x0f,
+ 0x0f, 0x0d, 0x0e, 0x0f, 0x0c, 0x0f, 0x0c, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x0e, 0x0c, 0x0f, 0x0f,
+ 0x08, 0x0e, 0x08, 0x0c, 0x0e, 0x0c, 0x0c, 0x0f,
+ 0x08, 0x0f, 0x08, 0x0f, 0x0c, 0x0c, 0x0c, 0x09,
+ 0x0f, 0x0c, 0x0c, 0x0d, 0x0c, 0x0f, 0x08, 0x0f,
+ 0x0e, 0x0c, 0x0f, 0x08, 0x0f, 0x0f, 0x0a, 0x0c,
+ 0x00, 0x08, 0x0a, 0x0a, 0x08, 0x02, 0x08, 0x02,
+ 0x02, 0x00, 0x02, 0x02, 0x08, 0x02, 0x0c, 0x0f,
+ 0x0f, 0x08, 0x0c, 0x0f, 0x0c, 0x0e, 0x0f, 0x0f,
+ 0x0f, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x09, 0x08,
+ 0x0c, 0x0e, 0x0c, 0x0e, 0x0f, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x08, 0x08,
+ 0x0e, 0x0e, 0x0f, 0x0c, 0x0e, 0x0f, 0x0e, 0x0c,
+ 0x0a, 0x08, 0x0e, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x08, 0x0f, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0f,
+ 0x0f, 0x0f, 0x0c, 0x0f, 0x0f, 0x0d, 0x0d, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x09, 0x0c, 0x02, 0x09, 0x0e,
+};
+
+static guchar m681[256] = {
+ 0x00, 0x00, 0x70, 0x70, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0xf0, 0x40, 0x00, 0xf0, 0xf0,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x70, 0x40,
+ 0x00, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x40, 0x70, 0xf0, 0x70, 0x40, 0x40, 0x00, 0xc0,
+ 0x40, 0x40, 0x70, 0x40, 0x70, 0x00, 0x00, 0x00,
+ 0xf0, 0xf0, 0x00, 0x40, 0xf0, 0xf0, 0x00, 0x40,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0x40, 0x70, 0x00,
+ 0xf0, 0x00, 0x40, 0x00, 0x40, 0x70, 0xf0, 0x00,
+ 0xf0, 0xf0, 0x40, 0xf0, 0xf0, 0x40, 0x00, 0x40,
+ 0x40, 0x70, 0x00, 0x40, 0x70, 0x70, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x80, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x00, 0x70, 0x40, 0xf0, 0x70,
+ 0x00, 0x40, 0x40, 0x40, 0x40, 0x00, 0x70, 0xf0,
+ 0x00, 0xf0, 0x40, 0x70, 0x40, 0xf0, 0x40, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0xf0,
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x00, 0x40, 0xf0,
+ 0x40, 0x00, 0x40, 0x70, 0x40, 0xf0, 0x00, 0xf0,
+ 0x70, 0xc0, 0xf0, 0x40, 0x00, 0x40, 0x70, 0xf0,
+ 0xf0, 0x40, 0x40, 0x00, 0x00, 0xf0, 0x40, 0xf0,
+ 0xf0, 0xf0, 0x40, 0xf0, 0x00, 0xf0, 0xf0, 0x00,
+ 0x00, 0x70, 0x70, 0x40, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xf0, 0xc0, 0x70, 0x00,
+ 0x40, 0xf0, 0x70, 0xf0, 0x40, 0xf0, 0x00, 0x40,
+ 0x40, 0x40, 0xf0, 0x40, 0x70, 0xf0, 0x40, 0xf0,
+ 0xf0, 0x40, 0xf0, 0x00, 0x40, 0x40, 0x00, 0xf0,
+ 0xf0, 0x70, 0x00, 0x70, 0x00, 0x40, 0x00, 0xf0,
+ 0xc0, 0x00, 0x40, 0x40, 0x40, 0x40, 0xf0, 0x70,
+ 0x40, 0x40, 0xf0, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0xf0, 0xf0, 0x40, 0x40, 0x40, 0x00, 0x00,
+};
+
+static guchar m682[256] = {
+ 0x0a, 0x08, 0x00, 0x00, 0x08, 0x0a, 0x08, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02,
+ 0x08, 0x0a, 0x0d, 0x0f, 0x0c, 0x08, 0x0f, 0x0f,
+ 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x0e, 0x08, 0x0c,
+ 0x0c, 0x0f, 0x09, 0x08, 0x08, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0e, 0x0f, 0x0c, 0x08, 0x0c, 0x0c, 0x0d,
+ 0x08, 0x0c, 0x0e, 0x0e, 0x0c, 0x0c, 0x08, 0x0a,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x0f, 0x0f, 0x0a, 0x08,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x08,
+ 0x0f, 0x0e, 0x0a, 0x0c, 0x0e, 0x0c, 0x0f, 0x0c,
+ 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0a, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x0e, 0x08, 0x0d, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0e, 0x0c, 0x0f, 0x0e,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0f, 0x0c, 0x0e, 0x08, 0x0f, 0x0e, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x08, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0d, 0x08, 0x0c, 0x0f,
+ 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x0d, 0x08, 0x0d,
+ 0x0c, 0x0d, 0x0f, 0x0c, 0x0c, 0x08, 0x0a, 0x0f,
+ 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0f,
+ 0x0f, 0x0d, 0x0c, 0x0f, 0x0c, 0x0f, 0x09, 0x08,
+ 0x08, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00,
+ 0x02, 0x08, 0x02, 0x08, 0x0d, 0x09, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x08, 0x0f, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0f, 0x0c, 0x0d,
+ 0x0f, 0x08, 0x0f, 0x08, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0f, 0x0c, 0x08, 0x0e, 0x0c, 0x08, 0x0c, 0x0d,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0f, 0x0c,
+ 0x0e, 0x0e, 0x0d, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0f, 0x0c, 0x0e, 0x0c, 0x08, 0x08,
+};
+
+static guchar m691[256] = {
+ 0xf0, 0x70, 0x00, 0x00, 0x70, 0xf0, 0x40, 0x00,
+ 0x70, 0x40, 0x40, 0x70, 0x70, 0xf0, 0xf0, 0x70,
+ 0x40, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x70, 0x70, 0x70, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0x70, 0x70, 0x70, 0x00, 0x70, 0x70, 0x80,
+ 0x70, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x40, 0x00, 0x40, 0x70, 0x40, 0x70, 0x00,
+ 0x40, 0x70, 0x00, 0x40, 0x00, 0xf0, 0x00, 0xf0,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0xf0, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0x00, 0xc0,
+ 0x00, 0xf0, 0xf0, 0x40, 0x70, 0x70, 0xf0, 0x00,
+ 0xf0, 0xf0, 0xf0, 0xc0, 0x40, 0x40, 0x40, 0x00,
+ 0xc0, 0x40, 0x70, 0xf0, 0x40, 0xf0, 0xf0, 0xf0,
+ 0x40, 0x40, 0x40, 0x70, 0x70, 0xf0, 0x00, 0xf0,
+ 0xf0, 0xf0, 0x40, 0x40, 0x70, 0x70, 0x70, 0x40,
+ 0x40, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x70, 0x00, 0x00, 0x40, 0x70, 0x00,
+ 0x00, 0x70, 0x40, 0x00, 0x70, 0xf0, 0x40, 0x00,
+ 0x40, 0x00, 0x00, 0xf0, 0xf0, 0x00, 0x00, 0x00,
+ 0x70, 0x40, 0x00, 0x00, 0x00, 0xc0, 0x40, 0xf0,
+ 0x40, 0x00, 0x00, 0x40, 0x00, 0x40, 0xf0, 0x40,
+ 0x00, 0x70, 0x70, 0x00, 0xf0, 0x00, 0x00, 0x40,
+ 0x40, 0x00, 0x40, 0xf0, 0x40, 0x00, 0x70, 0x70,
+ 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x40, 0x00, 0x70,
+ 0x40, 0x00, 0x70, 0xf0, 0xf0, 0xf0, 0x70, 0x00,
+ 0xf0, 0x40, 0x00, 0x70, 0x00, 0x00, 0x40, 0x40,
+ 0x70, 0x70, 0x00, 0x00, 0x00, 0x70, 0x70, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x70,
+ 0xf0, 0x00, 0x80, 0x70, 0x00, 0x70, 0x40, 0x40,
+ 0x00, 0x40, 0x70, 0x40, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x70, 0x00, 0xf0, 0x00, 0xf0, 0x40, 0xf0,
+};
+
+static guchar m692[256] = {
+ 0x09, 0x0a, 0x08, 0x08, 0x0c, 0x0f, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0e, 0x0c, 0x0f, 0x0f, 0x0c,
+ 0x0e, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x08, 0x00, 0x00, 0x08, 0x08, 0x00, 0x08, 0x02,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x0c, 0x00, 0x09,
+ 0x00, 0x08, 0x0c, 0x08, 0x08, 0x02, 0x08, 0x0c,
+ 0x0f, 0x08, 0x0c, 0x0c, 0x0e, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0e, 0x08, 0x0c, 0x0c, 0x0f, 0x08, 0x0f,
+ 0x0c, 0x0c, 0x0e, 0x08, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0d, 0x0f, 0x09, 0x0c, 0x0f,
+ 0x0c, 0x0d, 0x0f, 0x0c, 0x0c, 0x0e, 0x0f, 0x0c,
+ 0x0f, 0x09, 0x0d, 0x0f, 0x08, 0x0c, 0x0e, 0x08,
+ 0x0d, 0x0c, 0x0c, 0x0f, 0x0c, 0x0d, 0x0f, 0x0d,
+ 0x0c, 0x0e, 0x08, 0x08, 0x0c, 0x0d, 0x0c, 0x0f,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x0a, 0x08, 0x00, 0x08,
+ 0x0a, 0x00, 0x0e, 0x0c, 0x02, 0x08, 0x0e, 0x02,
+ 0x02, 0x02, 0x08, 0x00, 0x08, 0x0e, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0e, 0x0f, 0x0c, 0x0c,
+ 0x0a, 0x0c, 0x0c, 0x0f, 0x0f, 0x08, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x0c, 0x0f,
+ 0x0e, 0x0c, 0x0c, 0x0e, 0x0c, 0x0e, 0x0d, 0x0c,
+ 0x0c, 0x0e, 0x08, 0x0c, 0x0f, 0x0c, 0x0c, 0x0e,
+ 0x08, 0x0c, 0x08, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0f, 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0e, 0x0d, 0x0f, 0x0d, 0x0e, 0x0c,
+ 0x0f, 0x08, 0x08, 0x0c, 0x0e, 0x08, 0x08, 0x08,
+ 0x08, 0x0c, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02,
+ 0x02, 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x08, 0x09, 0x0c, 0x0c, 0x0e, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0e, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0d, 0x0c, 0x0f, 0x0c, 0x0f,
+};
+
+static guchar m6a1[256] = {
+ 0x40, 0x40, 0xf0, 0x40, 0x00, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0xf0, 0x70, 0x70, 0x00, 0x00, 0x40,
+ 0x00, 0xc0, 0x70, 0xf0, 0x70, 0x40, 0x00, 0xf0,
+ 0x00, 0xf0, 0x40, 0x70, 0x00, 0x40, 0xf0, 0xf0,
+ 0x40, 0xf0, 0x70, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0x70, 0x70, 0x70, 0x00, 0x00, 0x70, 0x00,
+ 0x40, 0x00, 0x40, 0x00, 0x40, 0xf0, 0x70, 0x40,
+ 0xf0, 0xf0, 0xf0, 0x40, 0x00, 0xf0, 0x40, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x40, 0x70,
+ 0xf0, 0x40, 0x40, 0xf0, 0x00, 0x00, 0x40, 0x00,
+ 0x40, 0x40, 0xc0, 0x80, 0x00, 0x40, 0x40, 0x00,
+ 0xf0, 0xf0, 0x00, 0x40, 0x00, 0x00, 0x00, 0xf0,
+ 0x00, 0xf0, 0x70, 0x00, 0x40, 0x00, 0x70, 0x40,
+ 0x00, 0x00, 0x40, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x70, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x70,
+ 0xf0, 0x40, 0x00, 0x40, 0xf0, 0x00, 0x40, 0x40,
+ 0x00, 0xc0, 0x00, 0x40, 0x00, 0xf0, 0xf0, 0x00,
+ 0x70, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0,
+ 0x00, 0x00, 0x00, 0x40, 0xf0, 0x40, 0x40, 0x40,
+ 0x70, 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x70, 0x40, 0x70, 0x00, 0x70, 0x40,
+ 0x40, 0x40, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x40, 0x40,
+ 0x00, 0x70, 0xf0, 0xf0, 0x00, 0x00, 0x40, 0x00,
+ 0x40, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0x70, 0x00, 0xf0, 0x40, 0x40, 0x40, 0x00,
+ 0x00, 0x00, 0xf0, 0xf0, 0x40, 0x40, 0x70, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40,
+ 0x70, 0x00, 0x70, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0x70, 0xf0, 0x40, 0x40, 0x00, 0x00,
+};
+
+static guchar m6a2[256] = {
+ 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0f, 0x08, 0x08, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0d, 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x0e, 0x0d, 0x08, 0x0c, 0x08, 0x0c, 0x0d, 0x0f,
+ 0x0c, 0x0f, 0x08, 0x0d, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x08, 0x0a, 0x08, 0x08, 0x08, 0x00, 0x02,
+ 0x00, 0x02, 0x0c, 0x08, 0x0c, 0x0f, 0x08, 0x08,
+ 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0f, 0x0e, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0f, 0x08, 0x0c, 0x0e,
+ 0x0d, 0x0c, 0x08, 0x0d, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0c, 0x09, 0x09, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0f, 0x0f, 0x0c, 0x0e, 0x08, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0f, 0x0c, 0x08, 0x0c, 0x0a, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0a, 0x08, 0x00, 0x00, 0x00, 0x0c, 0x08,
+ 0x08, 0x02, 0x08, 0x00, 0x02, 0x08, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x08, 0x0c, 0x0f, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0d, 0x08, 0x08, 0x0c, 0x0d, 0x0f, 0x08,
+ 0x0e, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x0e, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x02, 0x08, 0x0a, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x08, 0x08, 0x0d, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x08, 0x08,
+ 0x08, 0x08, 0x0d, 0x0d, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x00, 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0d, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x08, 0x00, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x08, 0x09, 0x00,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x08, 0x08, 0x08,
+};
+
+static guchar m6b1[256] = {
+ 0x00, 0x00, 0x40, 0x40, 0xf0, 0x70, 0x40, 0x40,
+ 0x00, 0x40, 0xf0, 0x00, 0x80, 0x00, 0x00, 0x40,
+ 0x40, 0x40, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x40,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x70, 0x40, 0x70,
+ 0xf0, 0xf0, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x70,
+ 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0xf0, 0x00, 0x00, 0x40, 0x40, 0x70,
+ 0x70, 0x70, 0xf0, 0x40, 0x00, 0xf0, 0xf0, 0x40,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0xc0, 0xf0,
+ 0x00, 0x70, 0x40, 0x00, 0xf0, 0x40, 0xf0, 0x00,
+ 0xf0, 0x00, 0x40, 0x70, 0x70, 0x00, 0x40, 0x00,
+ 0x40, 0x70, 0x00, 0x70, 0x00, 0x40, 0x00, 0xf0,
+ 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0x80, 0xf0, 0x40,
+ 0x00, 0x70, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x70,
+ 0x40, 0x00, 0x80, 0x70, 0x70, 0x40, 0x00, 0x80,
+ 0xf0, 0x70, 0x00, 0xf0, 0x00, 0x40, 0x40, 0xf0,
+ 0x70, 0x00, 0x40, 0xf0, 0xf0, 0x40, 0xf0, 0x00,
+ 0x00, 0xf0, 0xf0, 0x70, 0x00, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xf0, 0x40,
+ 0xf0, 0x00, 0x00, 0x40, 0x00, 0x00, 0xf0, 0x40,
+ 0x40, 0x00, 0x40, 0x40, 0x70, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0x70, 0x70, 0x40, 0x40, 0xc0, 0xf0,
+ 0x40, 0x70, 0xf0, 0x70, 0x70, 0xf0, 0x00, 0xf0,
+ 0x40, 0x40, 0xf0, 0x70, 0xf0, 0x40, 0x40, 0xf0,
+ 0x70, 0x80, 0x00, 0x40, 0x40, 0xf0, 0xf0, 0x00,
+ 0x00, 0x40, 0x00, 0xf0, 0x40, 0xf0, 0x70, 0x80,
+ 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0x00, 0xc0, 0x80,
+ 0xf0, 0x00, 0x40, 0xf0, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x70,
+ 0x00, 0x40, 0x00, 0x70, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+};
+
+static guchar m6b2[256] = {
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x00, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x09, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x08, 0x08, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x00,
+ 0x0f, 0x0f, 0x0a, 0x0f, 0x0a, 0x0c, 0x08, 0x0a,
+ 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0f, 0x0c, 0x0c, 0x08, 0x0c, 0x0e,
+ 0x0c, 0x0e, 0x0f, 0x0c, 0x0c, 0x0d, 0x0f, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0e, 0x08, 0x0c, 0x0f, 0x0f,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0f, 0x0c, 0x0d, 0x08,
+ 0x0d, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x08, 0x0e, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0d,
+ 0x0c, 0x0d, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0e,
+ 0x08, 0x08, 0x0f, 0x08, 0x08, 0x0c, 0x08, 0x08,
+ 0x08, 0x08, 0x0d, 0x08, 0x08, 0x08, 0x0c, 0x0d,
+ 0x0d, 0x0e, 0x08, 0x0f, 0x02, 0x08, 0x0c, 0x0d,
+ 0x0c, 0x0a, 0x0e, 0x0f, 0x0f, 0x08, 0x0f, 0x02,
+ 0x0c, 0x0f, 0x0f, 0x0a, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x02, 0x02, 0x0c, 0x0c, 0x0f, 0x0c,
+ 0x0d, 0x0c, 0x02, 0x0e, 0x08, 0x08, 0x0d, 0x0c,
+ 0x0c, 0x0a, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x08, 0x0e, 0x0c, 0x08, 0x0c, 0x0d, 0x0d,
+ 0x0c, 0x00, 0x0d, 0x0e, 0x0a, 0x0f, 0x0c, 0x0f,
+ 0x08, 0x08, 0x0d, 0x08, 0x0d, 0x0c, 0x08, 0x0f,
+ 0x0c, 0x0b, 0x02, 0x0c, 0x0c, 0x0f, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0f, 0x08, 0x0f,
+ 0x0c, 0x08, 0x0f, 0x0f, 0x0f, 0x02, 0x0f, 0x0f,
+ 0x0d, 0x02, 0x0c, 0x0f, 0x08, 0x08, 0x0c, 0x00,
+ 0x0c, 0x0a, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0a, 0x0f, 0x0d, 0x08, 0x00, 0x0e,
+ 0x0c, 0x08, 0x0c, 0x0e, 0x08, 0x0a, 0x08, 0x0c,
+ 0x0c, 0x0e, 0x08, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c,
+};
+
+static guchar m6c1[256] = {
+ 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x00, 0x00,
+ 0xf0, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40, 0xf0,
+ 0x40, 0xf0, 0x40, 0xf0, 0x70, 0x00, 0x00, 0x70,
+ 0x00, 0x40, 0x00, 0x70, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0xf0, 0x70, 0x00, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0xf0, 0x40, 0x40, 0xf0,
+ 0xf0, 0x00, 0x40, 0x40, 0x00, 0x00, 0xf0, 0x40,
+ 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0x40, 0xf0, 0x40,
+ 0xf0, 0x00, 0x40, 0x00, 0x40, 0xf0, 0x00, 0xf0,
+ 0x00, 0x40, 0xf0, 0x40, 0x40, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0xf0, 0x00, 0xf0, 0x40, 0x00, 0xc0, 0x00, 0x40,
+ 0xf0, 0x00, 0xf0, 0x70, 0x40, 0x00, 0xc0, 0x00,
+ 0x40, 0x40, 0xf0, 0x40, 0x00, 0xf0, 0xf0, 0x00,
+ 0x00, 0xf0, 0xf0, 0xf0, 0x00, 0xc0, 0xc0, 0xc0,
+ 0xf0, 0x40, 0x00, 0x00, 0xf0, 0x70, 0x00, 0x00,
+ 0xf0, 0x00, 0xf0, 0xf0, 0xc0, 0xc0, 0xf0, 0x40,
+ 0x40, 0xf0, 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x40,
+ 0x00, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0xf0, 0x00,
+ 0x40, 0x70, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0xf0, 0xf0, 0x70, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0x00, 0xf0, 0xc0, 0x00, 0xf0, 0x70, 0x40, 0x00,
+ 0x00, 0xf0, 0xf0, 0x00, 0xf0, 0x40, 0x00, 0x40,
+ 0x40, 0x40, 0x40, 0xf0, 0x40, 0xf0, 0x40, 0xf0,
+ 0x00, 0x70, 0x40, 0xf0, 0x40, 0x70, 0x00, 0x00,
+ 0x40, 0xf0, 0xf0, 0xf0, 0x00, 0xf0, 0x00, 0x40,
+ 0xf0, 0x40, 0x70, 0xc0, 0x40, 0x00, 0xc0, 0xf0,
+ 0xf0, 0x70, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m6c2[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0e, 0x02,
+ 0x0d, 0x0c, 0x08, 0x0c, 0x0c, 0x0e, 0x00, 0x0f,
+ 0x0e, 0x0f, 0x08, 0x0f, 0x0e, 0x0e, 0x0e, 0x00,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x08, 0x0c, 0x08, 0x0e,
+ 0x0c, 0x0e, 0x02, 0x0d, 0x0e, 0x0c, 0x0e, 0x0e,
+ 0x0e, 0x02, 0x0e, 0x0c, 0x0c, 0x08, 0x0e, 0x0e,
+ 0x0e, 0x08, 0x0a, 0x0c, 0x0f, 0x02, 0x0c, 0x09,
+ 0x0f, 0x08, 0x00, 0x0c, 0x08, 0x02, 0x0d, 0x0c,
+ 0x0f, 0x0f, 0x0f, 0x0c, 0x08, 0x08, 0x0e, 0x02,
+ 0x00, 0x0a, 0x0e, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x0f, 0x08, 0x0c, 0x08, 0x0e, 0x0f, 0x08, 0x0f,
+ 0x08, 0x0c, 0x09, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0e, 0x08, 0x08, 0x02, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0e, 0x0f, 0x0c, 0x08, 0x0d, 0x08, 0x0c,
+ 0x0f, 0x0c, 0x0f, 0x0c, 0x0e, 0x08, 0x0f, 0x08,
+ 0x0c, 0x0a, 0x0d, 0x0c, 0x08, 0x0f, 0x0f, 0x08,
+ 0x0c, 0x0f, 0x0f, 0x0f, 0x0c, 0x0f, 0x0f, 0x0d,
+ 0x0f, 0x0e, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0e,
+ 0x0f, 0x08, 0x0d, 0x0f, 0x0f, 0x0d, 0x0d, 0x08,
+ 0x0c, 0x0f, 0x0d, 0x0f, 0x0c, 0x0c, 0x08, 0x0a,
+ 0x08, 0x0a, 0x08, 0x0a, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x02, 0x0a, 0x0f, 0x0c, 0x0e, 0x0f, 0x08,
+ 0x0c, 0x0e, 0x0a, 0x0f, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0f, 0x0f, 0x0c, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0c, 0x0d, 0x0d, 0x0c, 0x0f, 0x0e, 0x0c, 0x0c,
+ 0x08, 0x0f, 0x0f, 0x08, 0x0f, 0x0c, 0x08, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x0f, 0x0e, 0x0f, 0x0e, 0x0f,
+ 0x08, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0e, 0x08,
+ 0x0e, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0c,
+ 0x0f, 0x0c, 0x0a, 0x0f, 0x0c, 0x0c, 0x0f, 0x0f,
+ 0x0f, 0x0e, 0x0c, 0x0f, 0x08, 0x0e, 0x02, 0x02,
+ 0x02, 0x0c, 0x02, 0x02, 0x02, 0x02, 0x02, 0x08,
+};
+
+static guchar m6d1[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0xf0, 0xf0, 0x00, 0x40, 0x40,
+ 0x00, 0xc0, 0x70, 0x40, 0x00, 0x00, 0x00, 0xf0,
+ 0x00, 0xf0, 0x40, 0xf0, 0x00, 0x00, 0xf0, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40, 0xc0,
+ 0x40, 0xf0, 0xf0, 0x70, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0xf0, 0x70, 0x00, 0xf0, 0xf0, 0x00,
+ 0xf0, 0xc0, 0x00, 0xf0, 0x40, 0xf0, 0xf0, 0x40,
+ 0x00, 0xf0, 0x00, 0x00, 0x70, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0xf0, 0xf0, 0x00, 0xf0, 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0x00, 0xf0, 0x70, 0x40, 0xf0, 0x40,
+ 0x00, 0xf0, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0xf0,
+ 0xf0, 0xf0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0xf0, 0x00, 0xc0,
+ 0xf0, 0x80, 0x00, 0x00, 0xf0, 0x80, 0xf0, 0x00,
+ 0x00, 0xc0, 0x40, 0xf0, 0x40, 0xf0, 0x40, 0x40,
+ 0x40, 0x70, 0x00, 0x70, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0xf0,
+ 0x00, 0x00, 0xf0, 0x00, 0x40, 0xf0, 0x00, 0x40,
+ 0x70, 0x40, 0x00, 0x00, 0x70, 0x40, 0x00, 0x40,
+ 0xf0, 0x00, 0x00, 0x80, 0xc0, 0xf0, 0xf0, 0xf0,
+ 0x40, 0x00, 0x40, 0xf0, 0x70, 0x00, 0x40, 0xc0,
+ 0x00, 0xf0, 0x70, 0x00, 0x00, 0x70, 0x40, 0x00,
+ 0xf0, 0xf0, 0x80, 0x40, 0x00, 0x40, 0xf0, 0x40,
+ 0x40, 0xf0, 0x40, 0x00, 0x70, 0x40, 0x70, 0x00,
+ 0xf0, 0x40, 0xf0, 0xf0, 0x70, 0x00, 0xf0, 0x40,
+ 0x40, 0xf0, 0x00, 0xf0, 0x40, 0xf0, 0x40, 0xf0,
+ 0x80, 0xf0, 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x00,
+};
+
+static guchar m6d2[256] = {
+ 0x0c, 0x0e, 0x08, 0x0c, 0x0e, 0x08, 0x08, 0x0e,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0f, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x0d, 0x0e, 0x08, 0x08, 0x08, 0x0c, 0x0f,
+ 0x0c, 0x0f, 0x0e, 0x0f, 0x08, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x08, 0x08, 0x0f, 0x08, 0x0f,
+ 0x0c, 0x0d, 0x0f, 0x0e, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x0e, 0x0f, 0x0e, 0x0c, 0x0f, 0x0d, 0x0c,
+ 0x0d, 0x0f, 0x0c, 0x0f, 0x0e, 0x0f, 0x0f, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x0a, 0x08, 0x0a, 0x02, 0x02,
+ 0x02, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02,
+ 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x08, 0x08,
+ 0x0c, 0x0f, 0x0f, 0x08, 0x0b, 0x08, 0x0e, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0f, 0x0f, 0x08, 0x0d, 0x0c, 0x0f, 0x0e,
+ 0x0c, 0x08, 0x08, 0x08, 0x0f, 0x0c, 0x0c, 0x0f,
+ 0x0f, 0x0d, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x08, 0x0e, 0x0c, 0x0c, 0x0f, 0x0c, 0x0d,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x0f, 0x0d, 0x0f, 0x08,
+ 0x0c, 0x0f, 0x0c, 0x0f, 0x0e, 0x0f, 0x08, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0a, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x02, 0x02, 0x08, 0x02, 0x02,
+ 0x02, 0x02, 0x0e, 0x0e, 0x0c, 0x08, 0x0e, 0x0f,
+ 0x08, 0x08, 0x0f, 0x0c, 0x0c, 0x0f, 0x08, 0x0c,
+ 0x0e, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e,
+ 0x0f, 0x08, 0x0c, 0x09, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0e, 0x0c, 0x08, 0x0d,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0f, 0x0f, 0x0d, 0x0c, 0x0c, 0x0e, 0x0f, 0x0c,
+ 0x0e, 0x0f, 0x0c, 0x0c, 0x0e, 0x0c, 0x0e, 0x08,
+ 0x0d, 0x0c, 0x0d, 0x0f, 0x0e, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x0f, 0x0c, 0x0d, 0x0c, 0x0f,
+ 0x09, 0x0f, 0x0d, 0x0f, 0x0e, 0x0c, 0x08, 0x08,
+};
+
+static guchar m6e1[256] = {
+ 0x40, 0x00, 0x00, 0x00, 0x40, 0x70, 0x00, 0x70,
+ 0x70, 0x70, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x80,
+ 0x00, 0xf0, 0xf0, 0xf0, 0x00, 0x70, 0x40, 0xf0,
+ 0xf0, 0xf0, 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0x40,
+ 0x00, 0x70, 0x00, 0xf0, 0xf0, 0xf0, 0x70, 0xf0,
+ 0x00, 0x00, 0xc0, 0x00, 0x80, 0x00, 0xc0, 0x00,
+ 0xf0, 0x40, 0xf0, 0x40, 0xc0, 0x80, 0xf0, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0xc0, 0x40, 0x00, 0x00,
+ 0x40, 0x40, 0xf0, 0x40, 0x00, 0xf0, 0x70, 0x40,
+ 0x00, 0x40, 0x40, 0x40, 0x40, 0x00, 0xf0, 0x40,
+ 0xf0, 0x00, 0x00, 0xf0, 0xc0, 0x40, 0xc0, 0xf0,
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0xf0,
+ 0x40, 0x00, 0x00, 0xf0, 0x00, 0x00, 0xf0, 0xf0,
+ 0x00, 0x00, 0xf0, 0xc0, 0x00, 0x00, 0x70, 0x00,
+ 0x00, 0x00, 0x80, 0x40, 0x00, 0x40, 0x70, 0x70,
+ 0x70, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0x40, 0x00, 0x70,
+ 0xf0, 0x00, 0x00, 0x40, 0x00, 0x00, 0xf0, 0x00,
+ 0x70, 0x40, 0x00, 0x00, 0xf0, 0xf0, 0x00, 0xf0,
+ 0x40, 0x00, 0xf0, 0x00, 0x00, 0xf0, 0x00, 0x40,
+ 0x00, 0x00, 0xf0, 0x80, 0x00, 0x40, 0x40, 0xf0,
+ 0x00, 0xc0, 0x70, 0x40, 0x00, 0x00, 0xf0, 0x70,
+ 0x00, 0x00, 0xf0, 0x40, 0x00, 0x70, 0x00, 0x40,
+ 0x40, 0x40, 0xf0, 0x40, 0xf0, 0xf0, 0x00, 0x40,
+ 0x40, 0xf0, 0x40, 0xf0, 0xf0, 0x40, 0xc0, 0x40,
+ 0x00, 0xf0, 0x00, 0xf0, 0xf0, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x70, 0x40, 0x40, 0xf0,
+ 0x00, 0x00, 0x70, 0x00, 0xf0, 0x00, 0x00, 0x70,
+ 0xf0, 0x40, 0x00, 0x40, 0x00, 0x40, 0xf0, 0xf0,
+};
+
+static guchar m6e2[256] = {
+ 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x0e, 0x08, 0x08,
+ 0x08, 0x08, 0x0a, 0x08, 0x0a, 0x02, 0x02, 0x08,
+ 0x02, 0x02, 0x08, 0x00, 0x02, 0x08, 0x02, 0x0b,
+ 0x08, 0x0d, 0x0f, 0x0d, 0x0c, 0x0e, 0x08, 0x0d,
+ 0x0f, 0x0f, 0x0c, 0x0f, 0x0f, 0x0f, 0x0d, 0x0c,
+ 0x0c, 0x0a, 0x08, 0x0f, 0x0d, 0x0f, 0x0c, 0x0f,
+ 0x0c, 0x0c, 0x0f, 0x0c, 0x0f, 0x0c, 0x0d, 0x08,
+ 0x0f, 0x0c, 0x0f, 0x0c, 0x0d, 0x0d, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0f, 0x0f, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0d, 0x0c, 0x08, 0x0f, 0x0e, 0x08,
+ 0x08, 0x0c, 0x0c, 0x0e, 0x0e, 0x0c, 0x0f, 0x08,
+ 0x0f, 0x08, 0x0c, 0x0f, 0x0d, 0x0c, 0x0d, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x08, 0x0f, 0x08, 0x08, 0x0f, 0x0d,
+ 0x08, 0x0c, 0x0d, 0x0d, 0x0c, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x09, 0x08, 0x08, 0x08, 0x0a, 0x02,
+ 0x08, 0x00, 0x08, 0x02, 0x08, 0x02, 0x0a, 0x00,
+ 0x0c, 0x0e, 0x08, 0x08, 0x00, 0x0c, 0x0c, 0x0e,
+ 0x0f, 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0c,
+ 0x0e, 0x0c, 0x08, 0x0c, 0x0f, 0x0d, 0x0c, 0x0f,
+ 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0f, 0x0c, 0x0e,
+ 0x08, 0x08, 0x0f, 0x0d, 0x08, 0x08, 0x0c, 0x0f,
+ 0x0c, 0x0f, 0x0e, 0x0c, 0x0e, 0x08, 0x0f, 0x0e,
+ 0x08, 0x0c, 0x0f, 0x0a, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x0c, 0x0e, 0x0f, 0x0c, 0x0d, 0x0d, 0x0c, 0x0e,
+ 0x0c, 0x0d, 0x0c, 0x0f, 0x0d, 0x0c, 0x0d, 0x0e,
+ 0x0c, 0x0f, 0x0c, 0x0f, 0x0f, 0x0e, 0x0c, 0x0a,
+ 0x0c, 0x08, 0x0a, 0x08, 0x0c, 0x08, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x08, 0x02, 0x02, 0x0a, 0x00,
+ 0x0a, 0x0a, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0f, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0e, 0x08, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d,
+};
+
+static guchar m6f1[256] = {
+ 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x40, 0x70, 0x40, 0x00, 0x40, 0x40, 0x00, 0xf0,
+ 0x00, 0xf0, 0x00, 0x70, 0xf0, 0xf0, 0x40, 0x00,
+ 0x40, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x00, 0xf0, 0xf0, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x40, 0x40, 0xf0, 0xf0, 0x40, 0x00, 0x40,
+ 0x40, 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0xf0, 0x00, 0x00, 0x40, 0x40, 0x00, 0x70, 0xf0,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0xc0, 0x40, 0x40, 0xf0, 0x00, 0x00, 0xc0,
+ 0xf0, 0x40, 0xc0, 0xf0, 0x70, 0x40, 0xc0, 0xf0,
+ 0x00, 0x40, 0xc0, 0x00, 0xf0, 0x00, 0x70, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x40, 0xf0, 0xf0, 0x70,
+ 0xf0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0xf0, 0x00, 0xf0, 0xc0, 0xc0, 0x00,
+ 0x70, 0xf0, 0x70, 0x40, 0xf0, 0x00, 0x70, 0x40,
+ 0xc0, 0x00, 0x00, 0x40, 0x40, 0xc0, 0xf0, 0x00,
+ 0xc0, 0x70, 0x40, 0x40, 0xc0, 0x00, 0x40, 0xf0,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x40, 0x70, 0x00, 0xf0, 0xf0, 0x40, 0x40, 0xc0,
+ 0x40, 0x00, 0x70, 0x00, 0x00, 0x00, 0xc0, 0xc0,
+ 0x40, 0xf0, 0x00, 0xf0, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0xf0, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x40, 0x70, 0x40,
+ 0x40, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0xf0, 0x00, 0x00,
+ 0x70, 0x00, 0xc0, 0x70, 0x00, 0x00, 0x40, 0xf0,
+ 0xf0, 0xf0, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00,
+ 0x40, 0xc0, 0x00, 0xf0, 0xf0, 0x00, 0x70, 0xf0,
+ 0x40, 0xf0, 0x00, 0x70, 0x00, 0x40, 0x70, 0x00,
+ 0x00, 0x40, 0x70, 0x00, 0x40, 0x40, 0xf0, 0x00,
+};
+
+static guchar m6f2[256] = {
+ 0x0c, 0x0d, 0x0f, 0x0c, 0x08, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0e, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0f,
+ 0x08, 0x09, 0x0c, 0x0e, 0x0f, 0x0f, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x0d, 0x0d, 0x0a, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0e, 0x0e, 0x0f, 0x0d, 0x0e, 0x0c, 0x0e,
+ 0x0c, 0x0f, 0x0d, 0x0e, 0x08, 0x0c, 0x0e, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0e, 0x0d,
+ 0x0c, 0x0d, 0x08, 0x0c, 0x08, 0x08, 0x0a, 0x02,
+ 0x08, 0x08, 0x08, 0x02, 0x08, 0x02, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x0d, 0x0c, 0x08, 0x0d,
+ 0x0f, 0x08, 0x0d, 0x0d, 0x0a, 0x0c, 0x0f, 0x0d,
+ 0x0c, 0x0c, 0x0f, 0x0c, 0x0d, 0x08, 0x0e, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0f, 0x0c,
+ 0x0d, 0x08, 0x0e, 0x0c, 0x0a, 0x08, 0x0c, 0x0c,
+ 0x0e, 0x08, 0x0f, 0x0c, 0x0f, 0x0d, 0x0d, 0x0c,
+ 0x0c, 0x09, 0x0c, 0x08, 0x0f, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0e, 0x08, 0x0c, 0x0e, 0x0f, 0x0f, 0x08,
+ 0x0d, 0x08, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0d,
+ 0x08, 0x08, 0x08, 0x00, 0x02, 0x08, 0x0c, 0x08,
+ 0x0c, 0x0e, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d,
+ 0x0c, 0x0d, 0x0c, 0x0f, 0x0c, 0x08, 0x0e, 0x08,
+ 0x0c, 0x0f, 0x0c, 0x08, 0x0c, 0x0c, 0x09, 0x0c,
+ 0x0f, 0x0d, 0x0f, 0x0d, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0e, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x02, 0x02, 0x08, 0x0c, 0x0d, 0x08, 0x08,
+ 0x0c, 0x08, 0x09, 0x0c, 0x0c, 0x0c, 0x0e, 0x0d,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x08, 0x0d, 0x0d, 0x0c, 0x0e, 0x0f,
+ 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x08, 0x08, 0x0c,
+ 0x08, 0x00, 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0c,
+};
+
+static guchar m701[256] = {
+ 0x40, 0xf0, 0x00, 0x00, 0x00, 0xc0, 0xc0, 0x40,
+ 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x40, 0x00, 0xf0,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x40,
+ 0xf0, 0x00, 0xf0, 0xf0, 0x80, 0xf0, 0xf0, 0xf0,
+ 0x40, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x70, 0xf0,
+ 0x80, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0xc0,
+ 0x70, 0x00, 0x70, 0x00, 0x40, 0x00, 0x00, 0xc0,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0x40, 0x40, 0xf0, 0x00, 0x40, 0x00,
+ 0x80, 0xf0, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0x40, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0x40, 0x00, 0x40, 0x70,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0xf0, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0xc0, 0x40, 0x00,
+ 0x00, 0x70, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0xf0, 0x00, 0x40, 0x40, 0x40, 0x40,
+ 0xc0, 0xf0, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xc0, 0xf0, 0xf0, 0x70, 0xf0,
+ 0x40, 0x40, 0x00, 0xf0, 0x40, 0x00, 0x00, 0xc0,
+ 0xf0, 0xf0, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x00, 0x40, 0xf0, 0x00, 0x00, 0x00, 0xf0,
+ 0x00, 0x40, 0x00, 0x40, 0x40, 0x40, 0x40, 0x00,
+ 0xc0, 0xf0, 0x00, 0x00, 0x40, 0xf0, 0x00, 0xf0,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0x40, 0x00, 0x00, 0xf0, 0x00, 0x00,
+};
+
+static guchar m702[256] = {
+ 0x0c, 0x0d, 0x08, 0x08, 0x0c, 0x0d, 0x0d, 0x0c,
+ 0x08, 0x0d, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x08, 0x0f, 0x08, 0x08, 0x0c, 0x0d, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0f, 0x0f, 0x0d, 0x0d, 0x09, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x08, 0x0c, 0x0d,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x08, 0x0d,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0f, 0x08, 0x00, 0x02,
+ 0x01, 0x0d, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x0c, 0x08, 0x0d, 0x0e, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0a, 0x02, 0x08, 0x0a,
+ 0x0f, 0x0c, 0x08, 0x08, 0x0c, 0x0a, 0x0e, 0x08,
+ 0x0f, 0x08, 0x0c, 0x08, 0x0f, 0x0d, 0x0a, 0x0a,
+ 0x02, 0x08, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x08, 0x0a, 0x0f, 0x08, 0x08, 0x08, 0x0f, 0x08,
+ 0x08, 0x0c, 0x0f, 0x0c, 0x0e, 0x0e, 0x0e, 0x08,
+ 0x0d, 0x0f, 0x0d, 0x08, 0x02, 0x02, 0x00, 0x0c,
+ 0x08, 0x0d, 0x08, 0x08, 0x0d, 0x08, 0x08, 0x08,
+ 0x08, 0x0c, 0x08, 0x0f, 0x0f, 0x0f, 0x0e, 0x0f,
+ 0x0c, 0x0e, 0x08, 0x0f, 0x0c, 0x0c, 0x08, 0x0f,
+ 0x0f, 0x0b, 0x0c, 0x0a, 0x02, 0x02, 0x0c, 0x08,
+ 0x0a, 0x02, 0x02, 0x02, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x08, 0x0e, 0x0d, 0x08, 0x0c, 0x0c, 0x0d,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0c,
+ 0x0f, 0x0f, 0x0c, 0x0a, 0x0c, 0x0d, 0x0c, 0x0b,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0e, 0x08, 0x02, 0x02,
+ 0x02, 0x02, 0x08, 0x02, 0x02, 0x02, 0x08, 0x0e,
+ 0x0c, 0x09, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0e,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x0f, 0x08, 0x0c,
+};
+
+static guchar m711[256] = {
+ 0x00, 0x00, 0x00, 0x40, 0xc0, 0x40, 0x40, 0x40,
+ 0x00, 0xf0, 0x00, 0x40, 0xc0, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0xf0, 0x00, 0x70, 0x00, 0xc0, 0x00,
+ 0x40, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40,
+ 0xc0, 0x40, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40, 0xc0,
+ 0x00, 0xf0, 0xc0, 0x40, 0xf0, 0x00, 0xf0, 0x00,
+ 0xc0, 0x00, 0x40, 0x00, 0x00, 0x70, 0xf0, 0x40,
+ 0x00, 0xf0, 0x40, 0x00, 0xc0, 0x00, 0xc0, 0x00,
+ 0x40, 0x00, 0x70, 0x00, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0x40, 0xf0, 0x00, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x00, 0x40,
+ 0x70, 0x80, 0xf0, 0x00, 0x40, 0x00, 0x00, 0xf0,
+ 0x00, 0x00, 0xc0, 0x00, 0xf0, 0x70, 0x00, 0x00,
+ 0x00, 0xf0, 0x40, 0x40, 0x00, 0x00, 0x00, 0xf0,
+ 0x40, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x40,
+ 0x40, 0xf0, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0xc0, 0x00, 0x00, 0x00, 0xf0, 0x40,
+ 0x40, 0xc0, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0xf0, 0xf0, 0x00, 0x40, 0x40, 0x00, 0xf0, 0x00,
+ 0xf0, 0x00, 0xf0, 0x40, 0xf0, 0xf0, 0x40, 0x70,
+ 0x00, 0x40, 0x40, 0x00, 0x40, 0x00, 0x00, 0xf0,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0xf0, 0xf0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
+ 0x40, 0x70, 0x00, 0xf0, 0xf0, 0x00, 0xc0, 0xf0,
+};
+
+static guchar m712[256] = {
+ 0x0c, 0x08, 0x0c, 0x08, 0x0d, 0x08, 0x0c, 0x08,
+ 0x08, 0x0f, 0x0e, 0x0c, 0x0d, 0x0c, 0x0c, 0x08,
+ 0x0e, 0x08, 0x08, 0x0e, 0x00, 0x02, 0x02, 0x0c,
+ 0x02, 0x0f, 0x0f, 0x0c, 0x0c, 0x08, 0x0d, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x0c, 0x0f, 0x08,
+ 0x0c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x0e,
+ 0x0f, 0x0e, 0x0c, 0x08, 0x08, 0x08, 0x0f, 0x08,
+ 0x08, 0x08, 0x0c, 0x08, 0x00, 0x08, 0x00, 0x08,
+ 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0a, 0x0c, 0x0d,
+ 0x08, 0x0d, 0x0b, 0x0c, 0x0f, 0x0c, 0x0f, 0x08,
+ 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x00, 0x0d, 0x08,
+ 0x0c, 0x0d, 0x0c, 0x08, 0x0f, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0d, 0x0f, 0x0f,
+ 0x0e, 0x0d, 0x0c, 0x08, 0x0d, 0x08, 0x0f, 0x08,
+ 0x0c, 0x08, 0x0e, 0x02, 0x08, 0x08, 0x08, 0x08,
+ 0x0e, 0x08, 0x0a, 0x0c, 0x08, 0x0f, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0f, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0d, 0x0f, 0x08, 0x08, 0x08, 0x08, 0x0f,
+ 0x0c, 0x08, 0x0d, 0x08, 0x0f, 0x08, 0x08, 0x0c,
+ 0x0a, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x0e, 0x0c, 0x09, 0x08, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x08, 0x0f, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x0e, 0x08, 0x0e, 0x08, 0x08,
+ 0x0c, 0x0f, 0x09, 0x08, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0d, 0x0c, 0x0c, 0x08, 0x08, 0x0f, 0x0c,
+ 0x0d, 0x08, 0x0d, 0x08, 0x0f, 0x0f, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0d,
+ 0x0e, 0x0c, 0x0c, 0x08, 0x0c, 0x0f, 0x0d, 0x0f,
+ 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x0d, 0x0f, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x00, 0x00,
+ 0x0c, 0x0e, 0x08, 0x0d, 0x0d, 0x0c, 0x0d, 0x0d,
+};
+
+static guchar m721[256] = {
+ 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
+ 0xf0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0xf0, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0xf0, 0x40, 0xf0, 0xf0, 0x00, 0x40,
+ 0xf0, 0x00, 0xf0, 0x00, 0x40, 0xf0, 0xf0, 0x00,
+ 0x40, 0x40, 0xf0, 0xf0, 0x70, 0xf0, 0xf0, 0x70,
+ 0xf0, 0x40, 0x40, 0x40, 0x00, 0x40, 0xf0, 0xf0,
+ 0xf0, 0x00, 0x00, 0x70, 0xf0, 0x00, 0x40, 0x40,
+ 0x40, 0x00, 0xf0, 0x40, 0x00, 0x40, 0x40, 0x00,
+ 0xf0, 0xf0, 0x40, 0xf0, 0x40, 0xf0, 0x40, 0xf0,
+ 0x40, 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x00, 0xf0,
+ 0x40, 0xf0, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0xf0, 0x00, 0x70, 0x00, 0x00, 0x40,
+ 0x40, 0xf0, 0x00, 0x40, 0x40, 0xf0, 0x70, 0x40,
+ 0xf0, 0xf0, 0x70, 0x00, 0x40, 0x00, 0x00, 0x70,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0x00, 0x70, 0x40, 0x00, 0x00, 0x70, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0,
+ 0x40, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x40, 0xf0,
+ 0x00, 0x40, 0x70, 0x00, 0x40, 0x00, 0x70, 0x00,
+ 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x80, 0x40, 0xf0, 0x70, 0xf0, 0x00, 0x70, 0x40,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0xf0, 0x00, 0x70, 0x00, 0x00, 0x40, 0x40, 0xf0,
+ 0x40, 0xf0, 0x00, 0x70, 0x00, 0x00, 0x00, 0x40,
+ 0x70, 0xf0, 0x70, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0xf0, 0x00, 0x00, 0x70, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x70,
+ 0xf0, 0xf0, 0x40, 0x40, 0xf0, 0xf0, 0x40, 0x00,
+};
+
+static guchar m722[256] = {
+ 0x09, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0f, 0x0c,
+ 0x08, 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0d, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x08,
+ 0x08, 0x0c, 0x0c, 0x0d, 0x08, 0x0e, 0x0c, 0x0c,
+ 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0e, 0x0c, 0x0f, 0x08, 0x0f, 0x0d, 0x08, 0x08,
+ 0x0f, 0x02, 0x09, 0x00, 0x08, 0x0f, 0x0f, 0x02,
+ 0x0e, 0x0e, 0x0d, 0x0f, 0x08, 0x0f, 0x0d, 0x0e,
+ 0x09, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0d, 0x0f,
+ 0x0f, 0x0c, 0x0c, 0x0c, 0x0f, 0x02, 0x08, 0x0c,
+ 0x08, 0x08, 0x0f, 0x0c, 0x08, 0x08, 0x0e, 0x08,
+ 0x0d, 0x0f, 0x0c, 0x0f, 0x00, 0x0f, 0x0c, 0x0f,
+ 0x0c, 0x0f, 0x0f, 0x0c, 0x08, 0x08, 0x0a, 0x0f,
+ 0x08, 0x0f, 0x0c, 0x08, 0x0c, 0x08, 0x0e, 0x0e,
+ 0x0c, 0x08, 0x0f, 0x0c, 0x0c, 0x02, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0a, 0x0c, 0x0c, 0x0d, 0x0e, 0x0e,
+ 0x0f, 0x0f, 0x08, 0x08, 0x0e, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x02, 0x0e, 0x0c, 0x0e, 0x0c, 0x0a,
+ 0x0c, 0x0c, 0x0e, 0x0c, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0a,
+ 0x00, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0f, 0x0a, 0x0c, 0x0f,
+ 0x0e, 0x08, 0x08, 0x08, 0x0e, 0x0c, 0x0a, 0x02,
+ 0x02, 0x0a, 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0d, 0x0e, 0x0f, 0x0e, 0x0f, 0x0c, 0x0c, 0x08,
+ 0x02, 0x0c, 0x0c, 0x0c, 0x0c, 0x0a, 0x0f, 0x08,
+ 0x0f, 0x0c, 0x0e, 0x08, 0x0c, 0x08, 0x0c, 0x0f,
+ 0x0c, 0x0f, 0x0c, 0x08, 0x0c, 0x08, 0x02, 0x0c,
+ 0x0e, 0x0f, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x08,
+ 0x0e, 0x0f, 0x0c, 0x0c, 0x0a, 0x0a, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x0e, 0x0e, 0x08, 0x0c, 0x0e,
+ 0x0f, 0x0d, 0x0e, 0x0e, 0x0f, 0x0d, 0x0c, 0x0c,
+};
+
+static guchar m731[256] = {
+ 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40,
+ 0x00, 0x00, 0xf0, 0x40, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0xf0, 0x70,
+ 0x40, 0x40, 0x00, 0xf0, 0xf0, 0xf0, 0x40, 0x70,
+ 0x00, 0x00, 0x40, 0x00, 0x40, 0xf0, 0x00, 0x40,
+ 0x40, 0xf0, 0xf0, 0xf0, 0x40, 0x00, 0x70, 0x70,
+ 0x00, 0x40, 0x40, 0x00, 0x70, 0x40, 0xf0, 0xf0,
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0x40, 0xf0, 0xf0,
+ 0x00, 0x00, 0x00, 0x40, 0xf0, 0xf0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x70, 0x70,
+ 0xc0, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x40, 0xf0,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0x70, 0x00, 0x00, 0x40, 0x40,
+ 0xf0, 0x40, 0xf0, 0x40, 0x40, 0x00, 0x40, 0x40,
+ 0xf0, 0x40, 0xf0, 0x00, 0x00, 0xf0, 0x00, 0x40,
+ 0xf0, 0x40, 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0x00, 0x40, 0xf0, 0x40, 0xc0, 0xf0,
+ 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xc0, 0x00,
+ 0x40, 0x00, 0x00, 0x40, 0x80, 0x40, 0xf0, 0xc0,
+ 0xc0, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0xc0,
+ 0x40, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x80,
+ 0x00, 0xf0, 0x40, 0x40, 0x00, 0xc0, 0x00, 0x00,
+ 0x00, 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0xc0, 0x00, 0x70, 0x40, 0x40, 0x00, 0x40,
+ 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x70, 0xc0, 0xf0, 0x40, 0xc0, 0xf0, 0x70, 0xc0,
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0xc0, 0x00,
+ 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xf0, 0x00,
+ 0xf0, 0x40, 0x00, 0xc0, 0x80, 0xf0, 0xc0, 0x40,
+ 0x00, 0xc0, 0xf0, 0x00, 0x00, 0xf0, 0x70, 0x00,
+ 0x00, 0x70, 0x00, 0x00, 0x40, 0x40, 0x00, 0xc0,
+ 0x70, 0xc0, 0x40, 0x40, 0x00, 0xc0, 0xf0, 0x40,
+};
+
+static guchar m732[256] = {
+ 0x0c, 0x0e, 0x08, 0x02, 0x08, 0x08, 0x08, 0x0c,
+ 0x0c, 0x08, 0x0f, 0x0c, 0x0c, 0x08, 0x0a, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0e, 0x08, 0x0a, 0x0f, 0x0e,
+ 0x0c, 0x0c, 0x08, 0x0f, 0x0f, 0x0f, 0x0e, 0x00,
+ 0x00, 0x02, 0x0e, 0x0c, 0x08, 0x0f, 0x0c, 0x0c,
+ 0x08, 0x0f, 0x0b, 0x0b, 0x0a, 0x0c, 0x0a, 0x08,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0e, 0x0c, 0x0d, 0x0f,
+ 0x0a, 0x02, 0x0c, 0x0c, 0x0c, 0x08, 0x0f, 0x0f,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x0d, 0x08, 0x08,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0e, 0x0c, 0x08,
+ 0x0f, 0x0c, 0x0f, 0x08, 0x08, 0x08, 0x08, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x00, 0x00, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0d, 0x0c, 0x0e, 0x02, 0x0c, 0x0e,
+ 0x0d, 0x08, 0x0d, 0x0c, 0x08, 0x0d, 0x0c, 0x0c,
+ 0x0d, 0x08, 0x0d, 0x0d, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0d, 0x0f,
+ 0x0c, 0x0f, 0x0c, 0x0f, 0x08, 0x08, 0x0f, 0x08,
+ 0x08, 0x0a, 0x0c, 0x0c, 0x0d, 0x0c, 0x0f, 0x0d,
+ 0x09, 0x00, 0x00, 0x02, 0x08, 0x0c, 0x08, 0x0f,
+ 0x0c, 0x0c, 0x0e, 0x08, 0x0c, 0x0c, 0x0c, 0x09,
+ 0x0c, 0x0f, 0x08, 0x0e, 0x0c, 0x0d, 0x02, 0x0a,
+ 0x02, 0x00, 0x0f, 0x0f, 0x0c, 0x0c, 0x0c, 0x0e,
+ 0x0c, 0x0d, 0x0a, 0x0e, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0f, 0x08, 0x0f, 0x0c, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0b, 0x0f, 0x0c, 0x0d, 0x0f, 0x08, 0x0b,
+ 0x0a, 0x02, 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x0d, 0x0f, 0x08,
+ 0x0f, 0x08, 0x08, 0x0d, 0x09, 0x0f, 0x09, 0x0e,
+ 0x0c, 0x0f, 0x0d, 0x0c, 0x08, 0x0f, 0x0c, 0x08,
+ 0x00, 0x00, 0x02, 0x08, 0x0c, 0x0c, 0x0c, 0x09,
+ 0x0c, 0x09, 0x0c, 0x08, 0x0c, 0x0d, 0x0d, 0x0c,
+};
+
+static guchar m741[256] = {
+ 0x40, 0xc0, 0x00, 0xf0, 0x40, 0xf0, 0xf0, 0xc0,
+ 0x00, 0xf0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0xc0, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0xf0, 0x00, 0x40, 0xf0, 0xc0, 0x00,
+ 0xc0, 0x40, 0xc0, 0xc0, 0xc0, 0x40, 0xc0, 0xc0,
+ 0xc0, 0x40, 0x70, 0xf0, 0xf0, 0xf0, 0xf0, 0x00,
+ 0x80, 0x40, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0,
+ 0xc0, 0xf0, 0x00, 0xc0, 0xc0, 0x00, 0x40, 0x40,
+ 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x40, 0x00, 0x00, 0xf0, 0x00, 0xc0,
+ 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0xf0, 0xf0,
+ 0xf0, 0x00, 0xc0, 0x70, 0xf0, 0x80, 0x40, 0x40,
+ 0xc0, 0xf0, 0xf0, 0x40, 0x00, 0x40, 0x40, 0xf0,
+ 0x70, 0x40, 0x40, 0x70, 0x00, 0x00, 0x70, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00,
+ 0x40, 0x40, 0x80, 0xf0, 0x00, 0x40, 0x40, 0xc0,
+ 0x00, 0xc0, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x40,
+ 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xc0, 0x40, 0x40, 0x00, 0xc0, 0x00, 0xf0, 0xc0,
+ 0x40, 0xc0, 0x70, 0xc0, 0x00, 0x80, 0x40, 0xf0,
+ 0xc0, 0x40, 0xc0, 0x40, 0x00, 0x00, 0x40, 0x40,
+ 0xf0, 0x40, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00,
+ 0x00, 0xc0, 0x00, 0x40, 0x00, 0xf0, 0x00, 0xc0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
+ 0x40, 0x40, 0xf0, 0x00, 0x40, 0x00, 0x00, 0xf0,
+ 0x40, 0x00, 0x00, 0x40, 0xf0, 0x00, 0x00, 0x00,
+ 0xc0, 0x00, 0xc0, 0x40, 0xf0, 0x00, 0x40, 0x40,
+ 0xf0, 0x00, 0xf0, 0xf0, 0x40, 0x00, 0xf0, 0x70,
+ 0x40, 0x70, 0x40, 0x40, 0x00, 0x00, 0xf0, 0x40,
+ 0x70, 0x70, 0x70, 0x00, 0x40, 0x00, 0x70, 0xf0,
+ 0x70, 0x00, 0x40, 0x40, 0x40, 0x00, 0x00, 0x40,
+};
+
+static guchar m742[256] = {
+ 0x0c, 0x0d, 0x08, 0x0f, 0x0c, 0x0f, 0x0f, 0x0d,
+ 0x0c, 0x0f, 0x0e, 0x0c, 0x0c, 0x0c, 0x00, 0x02,
+ 0x02, 0x00, 0x08, 0x09, 0x08, 0x08, 0x0c, 0x08,
+ 0x08, 0x08, 0x0e, 0x0f, 0x08, 0x0c, 0x08, 0x08,
+ 0x0d, 0x0d, 0x0f, 0x0c, 0x0c, 0x0f, 0x0f, 0x08,
+ 0x0f, 0x0c, 0x0f, 0x0d, 0x0f, 0x0c, 0x0f, 0x0d,
+ 0x0f, 0x0c, 0x0c, 0x0f, 0x0f, 0x0f, 0x0f, 0x00,
+ 0x09, 0x08, 0x0d, 0x08, 0x0a, 0x08, 0x08, 0x0d,
+ 0x0d, 0x0f, 0x0c, 0x09, 0x0d, 0x08, 0x0c, 0x08,
+ 0x08, 0x08, 0x0c, 0x0d, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0f, 0x08, 0x0f,
+ 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x0f,
+ 0x09, 0x08, 0x0d, 0x0c, 0x0d, 0x09, 0x08, 0x0c,
+ 0x09, 0x0d, 0x0d, 0x08, 0x08, 0x0e, 0x0c, 0x0d,
+ 0x0e, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0a, 0x02,
+ 0x08, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0f, 0x0c,
+ 0x0e, 0x0e, 0x09, 0x0f, 0x08, 0x0c, 0x0c, 0x0f,
+ 0x0c, 0x0d, 0x0c, 0x0f, 0x08, 0x08, 0x02, 0x08,
+ 0x0e, 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x08, 0x0f, 0x08, 0x0f, 0x0d,
+ 0x0c, 0x0d, 0x08, 0x0d, 0x08, 0x0d, 0x0c, 0x0f,
+ 0x0f, 0x0e, 0x0d, 0x0c, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0d, 0x0c, 0x0d, 0x08, 0x08, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x09, 0x0e, 0x0c, 0x08, 0x0d, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x09, 0x08,
+ 0x08, 0x08, 0x0d, 0x0c, 0x08, 0x08, 0x08, 0x0d,
+ 0x08, 0x08, 0x02, 0x08, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0d, 0x0c, 0x0f, 0x0c, 0x0e, 0x0c,
+ 0x0f, 0x0c, 0x0f, 0x0f, 0x0e, 0x0c, 0x0f, 0x00,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x0f, 0x0a,
+ 0x00, 0x00, 0x00, 0x08, 0x0e, 0x0c, 0x0e, 0x0f,
+ 0x08, 0x08, 0x08, 0x0c, 0x00, 0x0c, 0x0c, 0x0e,
+};
+
+static guchar m751[256] = {
+ 0x00, 0x80, 0x00, 0x70, 0xf0, 0x70, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x70, 0x00,
+ 0x00, 0xf0, 0x40, 0x70, 0x00, 0xf0, 0x40, 0x40,
+ 0xf0, 0x00, 0xf0, 0x80, 0x70, 0x00, 0x70, 0xf0,
+ 0x40, 0x40, 0x00, 0xf0, 0x40, 0xf0, 0xf0, 0x40,
+ 0xf0, 0x40, 0x40, 0xf0, 0xf0, 0x00, 0x00, 0x40,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x40, 0xf0,
+ 0xf0, 0x40, 0xf0, 0x70, 0x70, 0x40, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0x40, 0x70, 0x00, 0x70, 0xc0,
+ 0x40, 0x70, 0x70, 0x70, 0xf0, 0x70, 0x40, 0xf0,
+ 0x40, 0xf0, 0x40, 0x80, 0xf0, 0x00, 0x00, 0x40,
+ 0x00, 0xf0, 0x70, 0xf0, 0xf0, 0xf0, 0x40, 0x40,
+ 0x70, 0x40, 0xf0, 0x00, 0x70, 0xf0, 0xf0, 0x70,
+ 0x00, 0x70, 0xf0, 0x70, 0x00, 0x70, 0x00, 0xc0,
+ 0xf0, 0x40, 0x00, 0x70, 0x70, 0x80, 0xf0, 0x70,
+ 0xf0, 0x40, 0xc0, 0x40, 0x40, 0x40, 0x40, 0xf0,
+ 0x00, 0x40, 0x70, 0x00, 0x00, 0x40, 0xf0, 0xf0,
+ 0x00, 0x70, 0xf0, 0xf0, 0x00, 0x00, 0xf0, 0xf0,
+ 0x40, 0xf0, 0x40, 0x40, 0x70, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x70, 0x00, 0x40, 0xf0, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x70, 0x40, 0xf0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0xf0, 0xf0, 0x40, 0xf0, 0x00, 0x00,
+ 0xf0, 0xf0, 0x40, 0x00, 0xf0, 0xf0, 0xf0, 0x40,
+ 0x40, 0x40, 0xf0, 0x70, 0x40, 0xf0, 0x40, 0xf0,
+ 0x00, 0x00, 0x70, 0x00, 0x40, 0xf0, 0x40, 0x40,
+ 0x00, 0x00, 0xf0, 0x00, 0xf0, 0xf0, 0x00, 0x40,
+ 0xf0, 0xf0, 0x00, 0xf0, 0x40, 0x00, 0x70, 0x40,
+ 0x40, 0x40, 0xf0, 0x70, 0x40, 0x00, 0x00, 0x40,
+ 0x00, 0x70, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40,
+ 0xf0, 0x40, 0xf0, 0x70, 0xf0, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0xf0, 0x00, 0xf0, 0x00, 0x70, 0x70,
+};
+
+static guchar m752[256] = {
+ 0x0c, 0x09, 0x0c, 0x0c, 0x0f, 0x00, 0x08, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0e, 0x08, 0x0e,
+ 0x0c, 0x0f, 0x0c, 0x0e, 0x0c, 0x0d, 0x0c, 0x0c,
+ 0x0f, 0x02, 0x0f, 0x09, 0x0e, 0x0c, 0x08, 0x0f,
+ 0x08, 0x0c, 0x0c, 0x09, 0x08, 0x0f, 0x0d, 0x08,
+ 0x0f, 0x0e, 0x0c, 0x0f, 0x0f, 0x0e, 0x0c, 0x0e,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x02, 0x08, 0x0f,
+ 0x0f, 0x0c, 0x0f, 0x0a, 0x00, 0x0c, 0x0e, 0x0c,
+ 0x0e, 0x08, 0x08, 0x08, 0x08, 0x02, 0x08, 0x0d,
+ 0x0e, 0x08, 0x08, 0x0e, 0x0f, 0x08, 0x0e, 0x0f,
+ 0x08, 0x09, 0x00, 0x09, 0x0f, 0x08, 0x08, 0x08,
+ 0x08, 0x0f, 0x0e, 0x0f, 0x0f, 0x0d, 0x08, 0x0c,
+ 0x00, 0x08, 0x0d, 0x0c, 0x0c, 0x0f, 0x0f, 0x08,
+ 0x08, 0x00, 0x0f, 0x0c, 0x0c, 0x08, 0x08, 0x0d,
+ 0x0d, 0x08, 0x0a, 0x00, 0x02, 0x09, 0x0d, 0x0c,
+ 0x0f, 0x0e, 0x09, 0x08, 0x08, 0x0c, 0x0c, 0x0f,
+ 0x0c, 0x08, 0x00, 0x0a, 0x0c, 0x08, 0x0f, 0x0d,
+ 0x08, 0x08, 0x0d, 0x0f, 0x0c, 0x00, 0x09, 0x0f,
+ 0x0c, 0x0f, 0x0a, 0x08, 0x0e, 0x0c, 0x02, 0x02,
+ 0x0c, 0x0e, 0x0e, 0x08, 0x08, 0x0f, 0x08, 0x02,
+ 0x02, 0x02, 0x0c, 0x0e, 0x0e, 0x0f, 0x08, 0x0c,
+ 0x08, 0x08, 0x0c, 0x0f, 0x02, 0x00, 0x02, 0x02,
+ 0x0e, 0x0b, 0x0f, 0x0f, 0x0a, 0x0f, 0x0c, 0x08,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x0f, 0x0f, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0f, 0x0a, 0x0e, 0x0f, 0x08, 0x0f,
+ 0x02, 0x02, 0x0e, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0f, 0x08, 0x0f, 0x0f, 0x02, 0x0c,
+ 0x0f, 0x0d, 0x0c, 0x0f, 0x08, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x0c, 0x0f, 0x0e, 0x0e, 0x08, 0x0e, 0x0e,
+ 0x02, 0x00, 0x02, 0x02, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0f, 0x0e, 0x0d, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0e, 0x0d, 0x0c, 0x0f, 0x0c, 0x0c, 0x0e,
+};
+
+static guchar m761[256] = {
+ 0xc0, 0x70, 0x40, 0x40, 0x40, 0x00, 0x00, 0x40,
+ 0x40, 0x70, 0x40, 0x70, 0x40, 0xf0, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0xc0, 0x00, 0x40, 0x40, 0x40, 0x40, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0x40, 0xf0, 0x40, 0xc0, 0x70,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x70, 0x00, 0x40, 0x40, 0x70, 0x40, 0x00, 0x00,
+ 0x40, 0x40, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0xf0, 0x40, 0x40, 0x40, 0x70, 0x70,
+ 0x70, 0x40, 0x40, 0x40, 0xf0, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0xf0, 0x00, 0x00, 0x40, 0xf0, 0x00,
+ 0x70, 0x40, 0x00, 0x00, 0x70, 0x00, 0x00, 0x40,
+ 0x00, 0xf0, 0x70, 0x00, 0xc0, 0x40, 0x00, 0x70,
+ 0x70, 0xf0, 0x70, 0x00, 0xf0, 0x40, 0x40, 0x40,
+ 0xf0, 0x40, 0xf0, 0x00, 0x40, 0x00, 0x70, 0x00,
+ 0xf0, 0x00, 0x70, 0xf0, 0xf0, 0xf0, 0xf0, 0x00,
+ 0x70, 0x40, 0x00, 0x70, 0xf0, 0x40, 0xf0, 0xf0,
+ 0x70, 0x00, 0x00, 0x70, 0x40, 0x40, 0xf0, 0x00,
+ 0xf0, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x70, 0x00,
+ 0x00, 0x70, 0x70, 0x40, 0x40, 0x40, 0x00, 0x40,
+ 0x40, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x00, 0x40, 0x00, 0x00, 0x40, 0xf0, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x70,
+ 0x70, 0x70, 0xf0, 0x00, 0x00, 0x40, 0x00, 0xf0,
+ 0x00, 0x40, 0xf0, 0xf0, 0x00, 0x40, 0xf0, 0x00,
+ 0xf0, 0x40, 0xf0, 0x40, 0x40, 0x70, 0x40, 0x00,
+ 0x00, 0x00, 0xf0, 0x00, 0x40, 0x00, 0xf0, 0x70,
+ 0x00, 0x40, 0x00, 0xf0, 0xf0, 0x00, 0xf0, 0xf0,
+ 0x40, 0xf0, 0x00, 0xf0, 0xf0, 0x70, 0x40, 0xf0,
+ 0x40, 0x00, 0x70, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x40, 0x40, 0xf0, 0x00, 0xf0, 0x00, 0x40, 0x00,
+ 0xf0, 0x40, 0x00, 0x70, 0xc0, 0x00, 0xf0, 0x00,
+};
+
+static guchar m762[256] = {
+ 0x0f, 0x0e, 0x08, 0x0e, 0x00, 0x02, 0x00, 0x08,
+ 0x0c, 0x0c, 0x0e, 0x0c, 0x0e, 0x0d, 0x08, 0x0c,
+ 0x0e, 0x0c, 0x08, 0x0c, 0x0c, 0x0e, 0x0c, 0x02,
+ 0x02, 0x0f, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x0f, 0x0d, 0x0f, 0x0c, 0x0f, 0x0e, 0x0f, 0x0c,
+ 0x0c, 0x0e, 0x02, 0x02, 0x08, 0x0e, 0x08, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x0e, 0x0e, 0x0e, 0x08, 0x08,
+ 0x0e, 0x08, 0x0c, 0x09, 0x0e, 0x0c, 0x02, 0x02,
+ 0x0a, 0x08, 0x0d, 0x0e, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0f, 0x02, 0x09, 0x08,
+ 0x0c, 0x08, 0x0d, 0x0c, 0x0a, 0x08, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0e, 0x00, 0x02, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x02, 0x0d, 0x0c, 0x00, 0x08,
+ 0x08, 0x0d, 0x0c, 0x02, 0x0d, 0x0c, 0x0c, 0x0a,
+ 0x0d, 0x0c, 0x0d, 0x08, 0x08, 0x0c, 0x08, 0x08,
+ 0x0f, 0x0c, 0x00, 0x0f, 0x0d, 0x0f, 0x0f, 0x0c,
+ 0x08, 0x0c, 0x0e, 0x08, 0x0f, 0x08, 0x0f, 0x0f,
+ 0x0e, 0x0c, 0x0c, 0x0e, 0x08, 0x08, 0x0f, 0x0c,
+ 0x09, 0x02, 0x0c, 0x0f, 0x08, 0x0c, 0x0e, 0x08,
+ 0x08, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x0e, 0x08, 0x0c, 0x08,
+ 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x02, 0x02, 0x08, 0x0e, 0x0c, 0x08, 0x08,
+ 0x0c, 0x08, 0x0d, 0x0c, 0x08, 0x0c, 0x0c, 0x0f,
+ 0x08, 0x08, 0x0f, 0x0d, 0x0c, 0x0e, 0x0f, 0x08,
+ 0x0f, 0x0c, 0x0f, 0x08, 0x08, 0x0e, 0x0e, 0x02,
+ 0x02, 0x02, 0x0f, 0x0c, 0x0e, 0x08, 0x0b, 0x0a,
+ 0x0a, 0x08, 0x0c, 0x0f, 0x0d, 0x0c, 0x0d, 0x0f,
+ 0x08, 0x0d, 0x08, 0x0d, 0x0d, 0x0e, 0x0c, 0x0d,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0f, 0x0e,
+ 0x0c, 0x0e, 0x0f, 0x0c, 0x0f, 0x0c, 0x08, 0x0c,
+ 0x0f, 0x0e, 0x0c, 0x0c, 0x0f, 0x08, 0x0f, 0x08,
+};
+
+static guchar m771[256] = {
+ 0x40, 0xf0, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x70,
+ 0xf0, 0xf0, 0x40, 0xf0, 0x70, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x40, 0x70, 0x40, 0x00, 0xf0, 0x70,
+ 0xf0, 0x00, 0x40, 0x00, 0x70, 0x70, 0x70, 0x00,
+ 0x40, 0xf0, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0xf0,
+ 0xf0, 0x40, 0xf0, 0x00, 0xf0, 0x40, 0x40, 0x00,
+ 0xf0, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x70,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0xc0, 0x40, 0x40,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x00, 0x70, 0xf0, 0x40, 0x00, 0x40, 0x40,
+ 0x40, 0xf0, 0x40, 0xf0, 0x40, 0x70, 0xf0, 0x40,
+ 0x70, 0x00, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0x40, 0x00, 0x00, 0x40, 0xf0, 0xf0,
+ 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0x40, 0x40, 0x70, 0x00,
+ 0x00, 0xf0, 0x00, 0x00, 0x40, 0x40, 0x40, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xf0, 0x40,
+ 0x70, 0x00, 0x40, 0x00, 0x00, 0xf0, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x00, 0xf0, 0xf0, 0x40, 0x40,
+ 0xf0, 0x40, 0x00, 0xf0, 0x00, 0x40, 0x70, 0x00,
+ 0x00, 0x70, 0x00, 0xf0, 0xf0, 0x70, 0x40, 0xf0,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
+ 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x00, 0xf0,
+ 0x00, 0x40, 0x70, 0xf0, 0xf0, 0x00, 0x40, 0x40,
+ 0x40, 0x00, 0xf0, 0xf0, 0x40, 0xf0, 0x40, 0x70,
+ 0x00, 0xf0, 0x40, 0x00, 0x40, 0xf0, 0xf0, 0xf0,
+ 0x40, 0x40, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x40, 0x70, 0x00, 0x00, 0x00,
+};
+
+static guchar m772[256] = {
+ 0x08, 0x0f, 0x08, 0x0c, 0x0f, 0x0c, 0x08, 0x0e,
+ 0x0f, 0x0f, 0x0c, 0x0f, 0x00, 0x02, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x08,
+ 0x08, 0x0e, 0x0e, 0x0c, 0x08, 0x0c, 0x09, 0x0e,
+ 0x0f, 0x08, 0x0e, 0x0c, 0x08, 0x0c, 0x0a, 0x0c,
+ 0x0e, 0x0f, 0x08, 0x08, 0x00, 0x0e, 0x08, 0x0e,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0e, 0x0f,
+ 0x0f, 0x0c, 0x0f, 0x0c, 0x0f, 0x0c, 0x0c, 0x08,
+ 0x0b, 0x0a, 0x08, 0x0a, 0x0c, 0x0c, 0x0c, 0x0e,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c,
+ 0x02, 0x02, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0e, 0x0f, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0e, 0x0f, 0x08, 0x0e, 0x0f, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x0f, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x0f, 0x08, 0x08, 0x0c, 0x0e, 0x0f, 0x0f,
+ 0x0e, 0x0c, 0x0c, 0x0c, 0x0e, 0x0e, 0x00, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0d, 0x0e, 0x0e, 0x0e, 0x0c,
+ 0x08, 0x0f, 0x02, 0x00, 0x08, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0e,
+ 0x0e, 0x0c, 0x0e, 0x0c, 0x08, 0x0f, 0x08, 0x0e,
+ 0x0c, 0x0a, 0x0e, 0x0c, 0x0f, 0x0d, 0x08, 0x08,
+ 0x0f, 0x0c, 0x0c, 0x0f, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x08, 0x08, 0x0c, 0x0f, 0x0d, 0x0e, 0x08, 0x0f,
+ 0x08, 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x08, 0x0c,
+ 0x00, 0x0c, 0x0c, 0x00, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0f, 0x08, 0x0c, 0x08,
+ 0x0c, 0x08, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0e,
+ 0x0c, 0x0f, 0x08, 0x02, 0x0e, 0x0f, 0x0f, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x08, 0x08, 0x0a, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0a, 0x02,
+};
+
+static guchar m781[256] = {
+ 0x00, 0x00, 0xf0, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x70, 0x40, 0x40, 0x00,
+ 0x00, 0x40, 0xf0, 0x00, 0x70, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x70, 0x40, 0x40, 0x40, 0x00, 0xf0, 0xf0, 0xf0,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x40, 0x00,
+ 0x40, 0x00, 0xf0, 0x00, 0xf0, 0x40, 0x00, 0x40,
+ 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x40, 0x40, 0xf0, 0x00, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x80,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0xf0, 0x40, 0x00,
+ 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x40, 0xf0, 0xf0, 0x00, 0x40, 0xf0,
+ 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0xf0, 0x00, 0x40, 0x00,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x70, 0xf0,
+ 0x00, 0x00, 0x40, 0x00, 0xf0, 0xf0, 0xf0, 0x40,
+ 0x00, 0xf0, 0x00, 0x70, 0x40, 0x70, 0x00, 0xf0,
+ 0x40, 0x00, 0x70, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x00, 0x40, 0x00, 0xf0, 0x40, 0x00, 0x00, 0xf0,
+ 0x40, 0xf0, 0x70, 0x00, 0x40, 0x40, 0x00, 0x70,
+ 0x40, 0x40, 0x40, 0x40, 0x00, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0xf0, 0xc0, 0xf0, 0x40, 0x70, 0x40,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x70, 0x40,
+ 0x40, 0x40, 0xf0, 0xf0, 0x40, 0x00, 0xc0, 0x00,
+ 0xf0, 0x70, 0x40, 0x40, 0x70, 0x40, 0x40, 0x00,
+ 0x00, 0x00, 0x70, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0x40, 0x40, 0x00, 0x00, 0x40, 0x00, 0x40, 0x70,
+ 0xf0, 0x00, 0x40, 0x00, 0xf0, 0x00, 0x00, 0xf0,
+ 0x00, 0x00, 0x40, 0x40, 0x70, 0x80, 0x40, 0x40,
+ 0x00, 0x00, 0x40, 0xc0, 0x00, 0x70, 0x00, 0x40,
+};
+
+static guchar m782[256] = {
+ 0x02, 0x02, 0x0f, 0x0c, 0x08, 0x0c, 0x0c, 0x08,
+ 0x08, 0x0e, 0x08, 0x08, 0x0e, 0x0e, 0x0c, 0x0c,
+ 0x0c, 0x0e, 0x0f, 0x0c, 0x0e, 0x08, 0x02, 0x02,
+ 0x0a, 0x08, 0x02, 0x08, 0x02, 0x0e, 0x08, 0x0e,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x08, 0x0f, 0x0f, 0x0f,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0f, 0x0e, 0x0c, 0x0c,
+ 0x0e, 0x0c, 0x0d, 0x0c, 0x0f, 0x0c, 0x08, 0x0e,
+ 0x0e, 0x02, 0x02, 0x02, 0x02, 0x08, 0x02, 0x00,
+ 0x02, 0x00, 0x08, 0x0c, 0x08, 0x0f, 0x08, 0x0a,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0e, 0x0c, 0x0e, 0x09,
+ 0x0e, 0x08, 0x0e, 0x08, 0x08, 0x02, 0x02, 0x02,
+ 0x08, 0x00, 0x00, 0x00, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0d,
+ 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x08, 0x02,
+ 0x08, 0x0c, 0x08, 0x0c, 0x0f, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x08, 0x0e, 0x08, 0x08, 0x0f, 0x0b, 0x0f, 0x0c,
+ 0x08, 0x0f, 0x08, 0x0e, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x0e, 0x0c, 0x0e, 0x02, 0x0a, 0x08, 0x0c, 0x0e,
+ 0x0c, 0x0e, 0x0c, 0x0f, 0x0c, 0x0e, 0x08, 0x0f,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0e, 0x0a, 0x0e, 0x0e, 0x0e, 0x00, 0x00, 0x08,
+ 0x08, 0x0a, 0x0d, 0x0d, 0x0d, 0x08, 0x0e, 0x08,
+ 0x08, 0x0f, 0x08, 0x0c, 0x0c, 0x0f, 0x08, 0x08,
+ 0x0c, 0x0e, 0x0f, 0x0f, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x0f, 0x0c, 0x08, 0x08, 0x0e, 0x0e, 0x08, 0x00,
+ 0x08, 0x02, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0f, 0x0c, 0x0c, 0x08, 0x0f, 0x0c, 0x08, 0x0d,
+ 0x08, 0x08, 0x0e, 0x0c, 0x0e, 0x01, 0x08, 0x0e,
+ 0x08, 0x0c, 0x0e, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m791[256] = {
+ 0x40, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x40, 0x70, 0x70, 0x00, 0x00, 0x00, 0x80, 0x00,
+ 0x00, 0x70, 0x40, 0x00, 0x40, 0x00, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x70, 0x40,
+ 0x00, 0x40, 0xf0, 0xf0, 0xf0, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0xf0, 0x40, 0x70, 0x40, 0xf0, 0x40,
+ 0xf0, 0xf0, 0x00, 0x00, 0x40, 0x40, 0x40, 0xf0,
+ 0xf0, 0xf0, 0x40, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0xf0, 0x40, 0x00, 0x70, 0x40, 0x70, 0xf0, 0xf0,
+ 0x40, 0x00, 0xf0, 0xc0, 0xc0, 0xf0, 0xf0, 0x70,
+ 0xf0, 0x00, 0x70, 0x00, 0x00, 0xf0, 0x00, 0x40,
+ 0xf0, 0x40, 0x00, 0x40, 0x00, 0xf0, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x40, 0xf0, 0x40, 0x40, 0x00, 0x40, 0xf0,
+ 0x70, 0xf0, 0x00, 0x00, 0x70, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x70, 0x40, 0x40, 0xf0, 0xf0, 0xf0,
+ 0x00, 0xc0, 0x00, 0x40, 0x40, 0x40, 0x40, 0x00,
+ 0x40, 0x00, 0x00, 0x40, 0x40, 0x70, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0,
+ 0x40, 0x40, 0xf0, 0x40, 0x00, 0x00, 0xf0, 0x40,
+ 0x70, 0xc0, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0xf0, 0x70, 0x40, 0x00, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x40, 0x00, 0x40, 0x00, 0x00, 0x40,
+ 0x40, 0xf0, 0xc0, 0xf0, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0xf0, 0xf0, 0x00, 0x40, 0xf0, 0x40, 0x00,
+ 0xf0, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0xf0,
+ 0x40, 0x70, 0x40, 0x70, 0xf0, 0x40, 0xf0, 0xf0,
+ 0x00, 0xf0, 0x40, 0x40, 0x70, 0x40, 0x00, 0x00,
+ 0x70, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x00,
+};
+
+static guchar m792[256] = {
+ 0x08, 0x0f, 0x0c, 0x00, 0x0c, 0x0e, 0x08, 0x08,
+ 0x08, 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x0d, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x08, 0x01, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0e, 0x08,
+ 0x08, 0x0c, 0x08, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0c, 0x08, 0x00, 0x02, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0f, 0x02, 0x0a, 0x0c, 0x0f, 0x0c,
+ 0x0f, 0x0f, 0x0c, 0x00, 0x0c, 0x0c, 0x0e, 0x0d,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x0c, 0x08, 0x00, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0f, 0x0f,
+ 0x08, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0e,
+ 0x0f, 0x0c, 0x0a, 0x0c, 0x0c, 0x0f, 0x08, 0x0e,
+ 0x0f, 0x0c, 0x0c, 0x0c, 0x08, 0x0f, 0x08, 0x02,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0a,
+ 0x02, 0x0c, 0x0f, 0x08, 0x0c, 0x0c, 0x08, 0x0d,
+ 0x0a, 0x0f, 0x0c, 0x08, 0x0a, 0x02, 0x08, 0x08,
+ 0x0c, 0x08, 0x0e, 0x0c, 0x08, 0x0d, 0x0d, 0x0f,
+ 0x0c, 0x09, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0e, 0x0c, 0x0c, 0x08, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0d, 0x0f,
+ 0x0c, 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0d, 0x08,
+ 0x0c, 0x0d, 0x0c, 0x0f, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0e, 0x0e, 0x08, 0x0f, 0x0f, 0x0d,
+ 0x0f, 0x0f, 0x08, 0x0a, 0x08, 0x0c, 0x0a, 0x08,
+ 0x0c, 0x0f, 0x09, 0x0f, 0x08, 0x0e, 0x0c, 0x0c,
+ 0x08, 0x0f, 0x0f, 0x08, 0x08, 0x0f, 0x0c, 0x08,
+ 0x0f, 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x0c, 0x08, 0x08, 0x0e, 0x0f, 0x08, 0x0f, 0x0f,
+ 0x08, 0x0f, 0x0c, 0x0e, 0x0c, 0x0e, 0x0c, 0x02,
+ 0x0a, 0x08, 0x08, 0x08, 0x00, 0x08, 0x0c, 0x0c,
+ 0x0e, 0x08, 0x0c, 0x0f, 0x08, 0x02, 0x00, 0x08,
+};
+
+static guchar m7a1[256] = {
+ 0xf0, 0x00, 0x40, 0x40, 0x00, 0x80, 0x00, 0x40,
+ 0xf0, 0x40, 0x40, 0xf0, 0x40, 0xf0, 0x70, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0xf0, 0x40, 0x00, 0xf0,
+ 0x70, 0xf0, 0xf0, 0x40, 0xf0, 0x00, 0x40, 0xf0,
+ 0xf0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0xf0, 0x40,
+ 0x40, 0xf0, 0x70, 0x00, 0x40, 0x40, 0x80, 0xf0,
+ 0x40, 0x40, 0x40, 0xf0, 0xf0, 0xf0, 0x70, 0xf0,
+ 0xf0, 0x00, 0x70, 0x70, 0x40, 0x40, 0xf0, 0x40,
+ 0x40, 0xf0, 0x00, 0x00, 0x40, 0xf0, 0xf0, 0x70,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0xf0,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x40,
+ 0x40, 0xf0, 0xf0, 0x70, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0xf0, 0x40, 0xf0, 0x00, 0x40, 0x00, 0x00,
+ 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x40, 0xf0, 0x00,
+ 0x40, 0xf0, 0xf0, 0x00, 0x00, 0xf0, 0x40, 0xf0,
+ 0x40, 0xf0, 0x40, 0x70, 0xf0, 0x40, 0x40, 0x00,
+ 0xf0, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0xf0, 0xf0, 0x40, 0xf0, 0x70, 0x70,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xf0,
+ 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x00, 0xf0, 0xf0,
+ 0x70, 0x00, 0x00, 0x40, 0x00, 0x40, 0x70, 0x00,
+ 0x00, 0x40, 0xf0, 0x40, 0x40, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x70, 0xf0, 0xf0, 0x40, 0xf0,
+ 0x70, 0x40, 0xf0, 0xf0, 0x40, 0x70, 0x40, 0x70,
+ 0x00, 0x40, 0x70, 0x70, 0x00, 0x70, 0x00, 0x80,
+ 0x00, 0xf0, 0x70, 0x40, 0x70, 0xf0, 0x00, 0xf0,
+ 0xf0, 0x70, 0x70, 0xf0, 0x00, 0xf0, 0x70, 0x00,
+ 0x40, 0x40, 0xf0, 0x40, 0x40, 0xf0, 0x00, 0xf0,
+ 0x70, 0x40, 0x00, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x70, 0xf0, 0xf0, 0x40, 0x00, 0x40, 0x40, 0xf0,
+};
+
+static guchar m7a2[256] = {
+ 0x0f, 0x08, 0x0e, 0x0e, 0x0c, 0x0d, 0x0a, 0x08,
+ 0x0d, 0x08, 0x0c, 0x0f, 0x0c, 0x0f, 0x0a, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x08, 0x0f,
+ 0x0c, 0x0d, 0x0f, 0x0c, 0x0d, 0x08, 0x0e, 0x0d,
+ 0x0f, 0x08, 0x0c, 0x02, 0x00, 0x08, 0x0c, 0x08,
+ 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x08, 0x0d, 0x0c,
+ 0x0c, 0x0d, 0x08, 0x0a, 0x08, 0x08, 0x09, 0x0f,
+ 0x08, 0x0e, 0x08, 0x0f, 0x0f, 0x0f, 0x08, 0x0f,
+ 0x0d, 0x08, 0x08, 0x00, 0x0c, 0x08, 0x0f, 0x0c,
+ 0x0c, 0x09, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x08,
+ 0x08, 0x02, 0x00, 0x08, 0x0c, 0x08, 0x0c, 0x0f,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x0c,
+ 0x0c, 0x0d, 0x0d, 0x00, 0x08, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0d, 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0f, 0x0c, 0x08, 0x08, 0x0f, 0x0c, 0x0f, 0x02,
+ 0x0e, 0x0f, 0x0f, 0x0c, 0x08, 0x09, 0x0c, 0x0f,
+ 0x0e, 0x0f, 0x08, 0x0a, 0x0f, 0x0c, 0x0e, 0x0c,
+ 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x02, 0x00, 0x0c,
+ 0x0c, 0x0a, 0x0f, 0x09, 0x0c, 0x0f, 0x0e, 0x0e,
+ 0x0f, 0x0c, 0x08, 0x08, 0x02, 0x02, 0x0c, 0x0f,
+ 0x0e, 0x08, 0x0c, 0x0c, 0x08, 0x02, 0x02, 0x08,
+ 0x0e, 0x0d, 0x0d, 0x0c, 0x0e, 0x02, 0x0d, 0x0d,
+ 0x08, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0d, 0x08, 0x08, 0x08, 0x0c, 0x0e,
+ 0x0c, 0x0c, 0x08, 0x00, 0x0d, 0x0d, 0x08, 0x0d,
+ 0x08, 0x08, 0x0d, 0x0f, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x02, 0x09,
+ 0x0c, 0x0f, 0x08, 0x08, 0x08, 0x09, 0x02, 0x0f,
+ 0x0f, 0x08, 0x08, 0x0f, 0x0c, 0x0f, 0x0e, 0x08,
+ 0x08, 0x08, 0x09, 0x0c, 0x08, 0x0f, 0x0c, 0x0f,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0d, 0x0c,
+ 0x08, 0x0f, 0x0f, 0x0c, 0x08, 0x0e, 0x08, 0x0f,
+};
+
+static guchar m7b1[256] = {
+ 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x40,
+ 0x70, 0x00, 0x70, 0x70, 0x00, 0x00, 0x00, 0xf0,
+ 0x00, 0xf0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x70, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xf0, 0x40,
+ 0xf0, 0x00, 0x00, 0x40, 0x00, 0x70, 0xf0, 0x40,
+ 0x70, 0x40, 0x40, 0x40, 0xf0, 0xc0, 0x40, 0x40,
+ 0x40, 0x40, 0x00, 0x70, 0x40, 0x70, 0x70, 0x00,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0x70, 0xf0, 0x40,
+ 0x70, 0xf0, 0x00, 0xf0, 0xf0, 0xf0, 0x40, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0x00, 0xf0, 0x40, 0xf0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
+ 0xc0, 0x00, 0x00, 0x00, 0x40, 0x70, 0x40, 0x70,
+ 0x00, 0x40, 0x40, 0x00, 0xf0, 0x40, 0xf0, 0x40,
+ 0x70, 0x70, 0x40, 0x40, 0x70, 0xf0, 0x00, 0x40,
+ 0x00, 0x00, 0x70, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x70, 0xf0,
+ 0x00, 0x40, 0x00, 0xf0, 0x00, 0x70, 0x40, 0xf0,
+ 0x40, 0x40, 0x70, 0x00, 0xf0, 0xf0, 0x40, 0xf0,
+ 0x70, 0x70, 0xf0, 0x40, 0x70, 0xf0, 0x40, 0x70,
+ 0x40, 0xf0, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x70, 0x00, 0x40, 0xf0, 0x00, 0x40,
+ 0x40, 0xf0, 0x40, 0x00, 0xf0, 0x40, 0x40, 0x00,
+ 0xf0, 0x00, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00,
+ 0xf0, 0xf0, 0x40, 0x00, 0xf0, 0x40, 0xf0, 0xf0,
+ 0x40, 0xf0, 0x40, 0x70, 0x70, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0x40, 0x40, 0x00, 0x70, 0x00, 0x00,
+ 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x70, 0x70, 0x00,
+ 0x40, 0xf0, 0x40, 0x00, 0x00, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x70, 0x40, 0x40, 0x70, 0x70,
+ 0x40, 0x40, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00,
+};
+
+static guchar m7b2[256] = {
+ 0x0c, 0x0c, 0x00, 0x02, 0x0e, 0x0c, 0x0e, 0x00,
+ 0x0e, 0x0c, 0x0e, 0x0a, 0x08, 0x08, 0x0c, 0x0f,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x0a, 0x02, 0x08, 0x08,
+ 0x0c, 0x0f, 0x0c, 0x0f, 0x08, 0x0c, 0x0f, 0x08,
+ 0x0f, 0x08, 0x0c, 0x0c, 0x0e, 0x0e, 0x0f, 0x08,
+ 0x0e, 0x08, 0x0e, 0x0e, 0x0f, 0x0d, 0x0e, 0x0c,
+ 0x0c, 0x0e, 0x0c, 0x0e, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0e, 0x09, 0x02, 0x0c, 0x02, 0x08, 0x02, 0x08,
+ 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x0e, 0x0d, 0x0e,
+ 0x0c, 0x0f, 0x0c, 0x0f, 0x0f, 0x0d, 0x0c, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08,
+ 0x0e, 0x08, 0x02, 0x02, 0x00, 0x02, 0x08, 0x08,
+ 0x0f, 0x0c, 0x0a, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x08, 0x08, 0x09, 0x0c, 0x0f, 0x08,
+ 0x0c, 0x0e, 0x0e, 0x0c, 0x0c, 0x0f, 0x0c, 0x0e,
+ 0x0c, 0x0a, 0x00, 0x0a, 0x00, 0x01, 0x02, 0x00,
+ 0x02, 0x08, 0x0c, 0x08, 0x0c, 0x0e, 0x08, 0x0d,
+ 0x0c, 0x08, 0x0c, 0x0d, 0x0c, 0x0e, 0x0c, 0x0d,
+ 0x0e, 0x0c, 0x08, 0x08, 0x0f, 0x0f, 0x0c, 0x0f,
+ 0x0c, 0x0c, 0x01, 0x0c, 0x0e, 0x0f, 0x08, 0x08,
+ 0x0c, 0x0f, 0x0a, 0x08, 0x0c, 0x08, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x0e, 0x0f, 0x08, 0x0c,
+ 0x08, 0x0f, 0x08, 0x08, 0x0f, 0x0c, 0x08, 0x0c,
+ 0x0f, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x08,
+ 0x0d, 0x0f, 0x08, 0x08, 0x0d, 0x08, 0x0f, 0x0f,
+ 0x08, 0x0d, 0x0c, 0x0c, 0x0e, 0x08, 0x0c, 0x00,
+ 0x08, 0x02, 0x01, 0x02, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0e, 0x0e, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x0d, 0x0e, 0x0c, 0x0c, 0x0d, 0x0e, 0x0e, 0x0c,
+ 0x0c, 0x0d, 0x0e, 0x0c, 0x08, 0x08, 0x02, 0x00,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0e,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0a, 0x0c, 0x0e, 0x0c,
+};
+
+static guchar m7c1[256] = {
+ 0x70, 0x40, 0x40, 0x40, 0x40, 0x00, 0x40, 0xf0,
+ 0x00, 0x40, 0x00, 0x40, 0x40, 0x70, 0x40, 0x40,
+ 0x00, 0x70, 0xf0, 0x70, 0x70, 0x00, 0x00, 0x70,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x80, 0x70,
+ 0x40, 0xf0, 0x00, 0x70, 0x00, 0x40, 0x40, 0xf0,
+ 0x40, 0x00, 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x70,
+ 0x70, 0x40, 0x40, 0x00, 0x00, 0xf0, 0xf0, 0xf0,
+ 0x70, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0xf0, 0xf0, 0x00, 0x70,
+ 0x70, 0x40, 0x40, 0x40, 0x70, 0x40, 0x70, 0x00,
+ 0x70, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x70,
+ 0xf0, 0x40, 0x00, 0x40, 0xf0, 0x70, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0x00, 0xf0, 0x40, 0x40, 0x00,
+ 0x40, 0x00, 0x40, 0xf0, 0x00, 0x70, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x70, 0x00,
+ 0x00, 0x70, 0x70, 0xf0, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0xf0, 0x00, 0x70, 0x00, 0x70, 0x00, 0x40,
+ 0x70, 0x00, 0xf0, 0x00, 0x40, 0xf0, 0x00, 0xf0,
+ 0xf0, 0x00, 0x00, 0x70, 0x00, 0x00, 0x40, 0xf0,
+ 0x40, 0x70, 0x70, 0x00, 0x70, 0xf0, 0x40, 0xf0,
+ 0x70, 0x00, 0x00, 0x70, 0x00, 0x70, 0xf0, 0x00,
+ 0x40, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0xf0, 0x40, 0x40, 0x40, 0x70, 0xf0, 0x40,
+ 0x70, 0x00, 0x70, 0x00, 0x40, 0x70, 0x00, 0x40,
+ 0x40, 0x40, 0xf0, 0x00, 0x00, 0x40, 0x70, 0x40,
+ 0x00, 0x00, 0x70, 0x40, 0x40, 0x40, 0xf0, 0x40,
+ 0x70, 0x40, 0x40, 0x00, 0x70, 0x40, 0xf0, 0xf0,
+ 0xf0, 0x00, 0x70, 0x00, 0x00, 0x00, 0x40, 0xf0,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x70, 0x00, 0x70, 0x40, 0x70, 0x00,
+ 0x70, 0x00, 0x70, 0xf0, 0x00, 0x00, 0xf0, 0x00,
+};
+
+static guchar m7c2[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0f,
+ 0x08, 0x0c, 0x0c, 0x0e, 0x0e, 0x0c, 0x0c, 0x0e,
+ 0x0c, 0x0c, 0x09, 0x00, 0x08, 0x08, 0x02, 0x00,
+ 0x08, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0d, 0x0e,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x0c, 0x0e, 0x0f,
+ 0x0c, 0x0c, 0x0f, 0x0d, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0c, 0x00, 0x08, 0x0c, 0x08, 0x08, 0x08, 0x0c,
+ 0x0e, 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x0d, 0x0f,
+ 0x0e, 0x02, 0x08, 0x0d, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0f, 0x08, 0x08,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0c,
+ 0x0d, 0x00, 0x08, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x08, 0x08, 0x0c, 0x0f, 0x0a, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0a, 0x0a, 0x0e, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x00, 0x09, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0f, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x00,
+ 0x00, 0x0e, 0x0f, 0x08, 0x0c, 0x0f, 0x0c, 0x0f,
+ 0x0f, 0x08, 0x08, 0x08, 0x0a, 0x0a, 0x0e, 0x0f,
+ 0x08, 0x0c, 0x0e, 0x0c, 0x0a, 0x0f, 0x08, 0x0d,
+ 0x0c, 0x08, 0x02, 0x08, 0x08, 0x00, 0x0b, 0x0c,
+ 0x08, 0x0f, 0x0f, 0x0f, 0x0c, 0x0c, 0x08, 0x08,
+ 0x08, 0x0f, 0x0c, 0x0c, 0x0e, 0x0e, 0x0f, 0x0c,
+ 0x08, 0x0a, 0x08, 0x08, 0x08, 0x0e, 0x08, 0x0a,
+ 0x0e, 0x08, 0x0f, 0x0c, 0x0e, 0x0a, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0e, 0x0f, 0x0e,
+ 0x00, 0x0e, 0x08, 0x08, 0x0e, 0x0c, 0x0d, 0x0f,
+ 0x0f, 0x08, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0d,
+ 0x0e, 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x0e,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0e, 0x00, 0x08, 0x0f, 0x08, 0x0c, 0x0d, 0x08,
+};
+
+static guchar m7d1[256] = {
+ 0xf0, 0x00, 0xf0, 0x40, 0xf0, 0xf0, 0xf0, 0xc0,
+ 0xc0, 0x40, 0xf0, 0xf0, 0x00, 0xf0, 0x00, 0x40,
+ 0xf0, 0x40, 0x40, 0x40, 0xf0, 0x70, 0x40, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x40, 0x40, 0x00,
+ 0xf0, 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x40, 0xf0, 0xf0, 0x40, 0xf0, 0xf0,
+ 0xf0, 0x40, 0x70, 0xf0, 0x00, 0xf0, 0x00, 0x00,
+ 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x40, 0x40, 0x70,
+ 0x40, 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x40,
+ 0x40, 0x00, 0x00, 0x70, 0x70, 0x40, 0x70, 0x70,
+ 0xf0, 0x40, 0x00, 0x40, 0x00, 0x00, 0x70, 0x40,
+ 0x00, 0x40, 0x40, 0x70, 0x40, 0x40, 0xf0, 0x00,
+ 0x00, 0xf0, 0xf0, 0x70, 0x00, 0x40, 0xf0, 0x40,
+ 0xf0, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xf0, 0x00,
+ 0x40, 0xf0, 0xf0, 0xf0, 0x00, 0x70, 0xf0, 0x00,
+ 0x40, 0xf0, 0x40, 0x40, 0x00, 0x70, 0x00, 0xc0,
+ 0x00, 0x40, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00,
+ 0x40, 0x70, 0x00, 0x40, 0x40, 0x40, 0x80, 0xf0,
+ 0x00, 0x40, 0x00, 0xf0, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x70, 0x70, 0x70, 0xf0, 0x40, 0x40, 0x70,
+ 0x80, 0x00, 0xf0, 0x70, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x00, 0x40, 0x70, 0xf0, 0xf0, 0x70, 0x70,
+ 0x70, 0xf0, 0xf0, 0x40, 0xf0, 0xf0, 0x40, 0x40,
+ 0xf0, 0x40, 0xf0, 0xf0, 0x00, 0xf0, 0xf0, 0xf0,
+ 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0xf0,
+ 0x00, 0x00, 0xf0, 0xf0, 0x40, 0x40, 0x40, 0x70,
+ 0x00, 0x70, 0x70, 0x00, 0x00, 0x70, 0x80, 0x40,
+ 0xf0, 0x40, 0xf0, 0x00, 0x70, 0xf0, 0xf0, 0x00,
+ 0xf0, 0xf0, 0x40, 0x80, 0x70, 0x40, 0x40, 0x00,
+ 0xf0, 0xf0, 0x40, 0x40, 0xf0, 0x40, 0x00, 0xf0,
+ 0x00, 0x40, 0x70, 0x00, 0xf0, 0x40, 0x40, 0x00,
+ 0x00, 0x40, 0x40, 0xf0, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m7d2[256] = {
+ 0x0d, 0x0c, 0x0d, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0c, 0x0f, 0x0d, 0x0c, 0x0d, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0d, 0x0f, 0x08, 0x08, 0x08, 0x00, 0x02,
+ 0x0c, 0x0c, 0x08, 0x0f, 0x0d, 0x08, 0x0d, 0x0f,
+ 0x0d, 0x0c, 0x0c, 0x0d, 0x08, 0x0d, 0x0c, 0x08,
+ 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c,
+ 0x08, 0x08, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0d, 0x0c,
+ 0x08, 0x0d, 0x0d, 0x0c, 0x08, 0x08, 0x0d, 0x0c,
+ 0x0d, 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0d, 0x0d, 0x0d, 0x08, 0x08, 0x09, 0x02,
+ 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0d,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x0d, 0x0d,
+ 0x08, 0x0c, 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x08,
+ 0x08, 0x08, 0x00, 0x00, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0d, 0x0c, 0x08, 0x08, 0x0e, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0d, 0x0d, 0x0e, 0x0c,
+ 0x0c, 0x0d, 0x0d, 0x08, 0x0d, 0x0d, 0x08, 0x0c,
+ 0x0d, 0x0c, 0x0d, 0x0d, 0x0c, 0x0d, 0x0d, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x08, 0x0c, 0x0d, 0x0d, 0x0c, 0x08, 0x0c, 0x08,
+ 0x08, 0x08, 0x0c, 0x08, 0x00, 0x00, 0x09, 0x0c,
+ 0x0d, 0x0c, 0x0d, 0x0c, 0x08, 0x0d, 0x0d, 0x0c,
+ 0x0d, 0x0d, 0x08, 0x0d, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0d, 0x0d, 0x0c, 0x08, 0x0d, 0x08, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0d, 0x08, 0x08, 0x08, 0x08,
+};
+
+static guchar m7e1[256] = {
+ 0x40, 0x70, 0x00, 0x00, 0x70, 0x70, 0x00, 0x00,
+ 0x40, 0xf0, 0xf0, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0x70, 0x00, 0x00, 0xc0, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0xf0, 0x40, 0xc0, 0xf0, 0xf0,
+ 0x40, 0xf0, 0x70, 0xf0, 0x00, 0x00, 0x70, 0x40,
+ 0x40, 0x00, 0x00, 0xf0, 0x40, 0x40, 0xf0, 0xc0,
+ 0x00, 0xf0, 0x70, 0x40, 0x00, 0x70, 0x40, 0xf0,
+ 0x00, 0x70, 0x70, 0x70, 0x00, 0xf0, 0xf0, 0x40,
+ 0x00, 0xf0, 0x00, 0xf0, 0x40, 0x40, 0xf0, 0xc0,
+ 0x00, 0x00, 0x70, 0x70, 0x00, 0x70, 0x40, 0x00,
+ 0x40, 0x00, 0xc0, 0x00, 0xf0, 0xf0, 0x70, 0x00,
+ 0x40, 0x70, 0x70, 0x00, 0x00, 0x70, 0xf0, 0x40,
+ 0x00, 0xc0, 0x40, 0x00, 0x00, 0x40, 0x70, 0x70,
+ 0x00, 0xf0, 0xf0, 0xc0, 0x00, 0xf0, 0x40, 0x40,
+ 0xf0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0xf0, 0x00, 0x70, 0xf0, 0x70, 0x40, 0x70,
+ 0x00, 0x40, 0xf0, 0x70, 0x00, 0x00, 0x40, 0x40,
+ 0x70, 0x70, 0x40, 0x00, 0xf0, 0x40, 0x70, 0xf0,
+ 0x70, 0x40, 0x70, 0xf0, 0x70, 0x40, 0xf0, 0x00,
+ 0xc0, 0x00, 0x40, 0xf0, 0xf0, 0x40, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m7e2[256] = {
+ 0x08, 0x08, 0x08, 0x0c, 0x08, 0x00, 0x00, 0x08,
+ 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c,
+ 0x08, 0x08, 0x0c, 0x0d, 0x0c, 0x0d, 0x0d, 0x0d,
+ 0x0c, 0x0d, 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x08,
+ 0x00, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0d, 0x0d,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0d, 0x0d, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x0d, 0x0c, 0x0c, 0x0d, 0x0f,
+ 0x0c, 0x0c, 0x00, 0x00, 0x0c, 0x08, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0d, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x00,
+ 0x0c, 0x0d, 0x0d, 0x0d, 0x08, 0x0d, 0x08, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0f, 0x00, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x0c, 0x08, 0x0d,
+ 0x00, 0x0c, 0x08, 0x0d, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0f, 0x0d, 0x08, 0x08, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+};
+
+static guchar m7f1[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00,
+ 0xf0, 0x00, 0xf0, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x00, 0x00, 0x00, 0x40, 0x40, 0x70, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0xf0, 0x70, 0x70, 0x40,
+ 0xf0, 0x70, 0x40, 0x40, 0xf0, 0xf0, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x40, 0x40, 0x40, 0x00, 0x70,
+ 0x70, 0x40, 0x00, 0x40, 0x40, 0x40, 0x40, 0x70,
+ 0x70, 0x70, 0xf0, 0xf0, 0x00, 0x40, 0xf0, 0x00,
+ 0xf0, 0x40, 0xf0, 0x00, 0x00, 0xf0, 0x00, 0xf0,
+ 0x70, 0xf0, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x40, 0x00, 0x70, 0x70, 0x00, 0xf0, 0x70, 0x70,
+ 0xf0, 0x00, 0xf0, 0x40, 0xf0, 0x40, 0xf0, 0x40,
+ 0x40, 0x40, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x40,
+ 0x00, 0x00, 0xf0, 0x00, 0x40, 0x70, 0xf0, 0x00,
+ 0x00, 0x40, 0x40, 0x70, 0xf0, 0x00, 0x40, 0x00,
+ 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x70, 0x70,
+ 0x00, 0x00, 0xf0, 0x00, 0x40, 0x00, 0x70, 0x00,
+ 0xf0, 0xf0, 0x00, 0x00, 0x40, 0xf0, 0x00, 0x40,
+ 0x40, 0xf0, 0x00, 0x40, 0x00, 0xf0, 0x70, 0x00,
+ 0x40, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xc0, 0x40,
+ 0x00, 0x00, 0xf0, 0x00, 0xf0, 0xf0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xc0,
+ 0xf0, 0xf0, 0x00, 0x40, 0x00, 0x40, 0x70, 0x00,
+ 0x40, 0xf0, 0x00, 0xf0, 0x40, 0x00, 0x40, 0x40,
+ 0xf0, 0x00, 0x40, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0x40, 0x70, 0xf0, 0x40, 0x40, 0x40,
+};
+
+static guchar m7f2[256] = {
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0f, 0x08,
+ 0x0f, 0x0c, 0x0f, 0x08, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x08, 0x02, 0x0c, 0x0e, 0x0e, 0x08, 0x08,
+ 0x0c, 0x00, 0x0c, 0x0c, 0x0d, 0x0c, 0x08, 0x0c,
+ 0x0f, 0x0e, 0x08, 0x00, 0x0f, 0x0f, 0x00, 0x0a,
+ 0x0e, 0x08, 0x02, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e,
+ 0x0c, 0x0e, 0x0a, 0x0c, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0e, 0x0f, 0x0d, 0x0c, 0x0c, 0x0f, 0x08,
+ 0x0d, 0x0a, 0x0f, 0x0c, 0x02, 0x0d, 0x0c, 0x0d,
+ 0x08, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x08, 0x02, 0x08, 0x0c, 0x08, 0x0d, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0f, 0x0c, 0x0f, 0x0c, 0x0f, 0x08,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0f, 0x0c, 0x0c, 0x08,
+ 0x08, 0x08, 0x0f, 0x0c, 0x0c, 0x0e, 0x0f, 0x02,
+ 0x0c, 0x0e, 0x0c, 0x08, 0x0f, 0x0c, 0x0c, 0x0e,
+ 0x0d, 0x0d, 0x00, 0x08, 0x0c, 0x0c, 0x08, 0x0e,
+ 0x0e, 0x0c, 0x0f, 0x0c, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x0e, 0x0f, 0x0c, 0x0e,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x08, 0x0f, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0f, 0x0c, 0x0f, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x08, 0x0f, 0x0f, 0x08, 0x0c,
+ 0x02, 0x00, 0x00, 0x0c, 0x0c, 0x08, 0x0c, 0x0f,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x08, 0x0e, 0x0e, 0x08,
+ 0x0c, 0x0f, 0x0c, 0x0d, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0f, 0x0e, 0x0c, 0x0e, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0d, 0x08, 0x0e, 0x0f, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m801[256] = {
+ 0xf0, 0xf0, 0x00, 0xf0, 0x70, 0xf0, 0xf0, 0x40,
+ 0x40, 0x80, 0x40, 0x70, 0xf0, 0x40, 0x40, 0x40,
+ 0xf0, 0x40, 0x70, 0x40, 0x40, 0xf0, 0x40, 0xf0,
+ 0xf0, 0x70, 0x00, 0x00, 0x70, 0x40, 0x40, 0x40,
+ 0x40, 0x70, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x40, 0x80, 0x40, 0x00,
+ 0x40, 0x00, 0x00, 0xf0, 0x40, 0x40, 0xf0, 0x40,
+ 0x00, 0x40, 0x40, 0x70, 0x40, 0xf0, 0x40, 0xf0,
+ 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xf0, 0x00,
+ 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x70,
+ 0x40, 0x70, 0x70, 0x00, 0x40, 0x00, 0x40, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0xf0,
+ 0xf0, 0x40, 0xf0, 0xf0, 0x70, 0x40, 0x70, 0xf0,
+ 0x00, 0x70, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0,
+ 0x00, 0x40, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0x40, 0xf0, 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0xf0, 0x00,
+ 0x70, 0x00, 0x70, 0xf0, 0x40, 0xf0, 0x40, 0x00,
+ 0x00, 0xf0, 0xf0, 0x00, 0x00, 0xf0, 0x40, 0x40,
+ 0x00, 0xf0, 0xf0, 0x40, 0x70, 0x70, 0x00, 0xf0,
+ 0x00, 0xf0, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x00, 0x70, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0xf0, 0x40, 0xf0, 0x40,
+ 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x70, 0x40,
+ 0x40, 0x70, 0xf0, 0xf0, 0x00, 0x70, 0xf0, 0x00,
+ 0x40, 0xf0, 0x00, 0x00, 0xf0, 0xf0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x70,
+ 0x40, 0xf0, 0x40, 0x40, 0xf0, 0x00, 0x40, 0x00,
+ 0xf0, 0x40, 0x40, 0x00, 0x70, 0xf0, 0x40, 0x00,
+};
+
+static guchar m802[256] = {
+ 0x0f, 0x0f, 0x08, 0x0f, 0x0e, 0x0f, 0x0f, 0x0c,
+ 0x08, 0x09, 0x08, 0x0e, 0x0f, 0x0e, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x0e, 0x08, 0x0e, 0x0f, 0x0e, 0x0f,
+ 0x0f, 0x0e, 0x08, 0x0c, 0x0e, 0x08, 0x0c, 0x0c,
+ 0x0a, 0x0c, 0x02, 0x08, 0x0c, 0x0a, 0x0e, 0x02,
+ 0x0e, 0x0e, 0x0e, 0x08, 0x0c, 0x09, 0x08, 0x08,
+ 0x0c, 0x0a, 0x08, 0x0f, 0x0c, 0x0e, 0x0f, 0x0e,
+ 0x0a, 0x0c, 0x08, 0x0a, 0x08, 0x0f, 0x0c, 0x0f,
+ 0x08, 0x08, 0x02, 0x0f, 0x08, 0x08, 0x0f, 0x0c,
+ 0x0c, 0x08, 0x0f, 0x02, 0x02, 0x02, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0e, 0x08, 0x02, 0x08, 0x0d, 0x08,
+ 0x0f, 0x08, 0x0f, 0x08, 0x0c, 0x0c, 0x0d, 0x08,
+ 0x08, 0x08, 0x00, 0x08, 0x0c, 0x08, 0x08, 0x0c,
+ 0x08, 0x02, 0x0a, 0x08, 0x0c, 0x08, 0x08, 0x0d,
+ 0x0d, 0x0e, 0x0d, 0x0d, 0x08, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x0d, 0x0d, 0x0f,
+ 0x02, 0x08, 0x0c, 0x02, 0x0f, 0x0d, 0x0f, 0x0f,
+ 0x08, 0x0f, 0x0c, 0x0f, 0x0f, 0x08, 0x08, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0e, 0x08, 0x0c, 0x0f, 0x08,
+ 0x0e, 0x0c, 0x0e, 0x0f, 0x0e, 0x0f, 0x08, 0x0a,
+ 0x02, 0x0f, 0x0f, 0x0c, 0x0a, 0x0f, 0x08, 0x08,
+ 0x08, 0x0f, 0x0f, 0x0e, 0x08, 0x0e, 0x0e, 0x0f,
+ 0x08, 0x0f, 0x0f, 0x08, 0x0f, 0x0c, 0x08, 0x0a,
+ 0x0c, 0x08, 0x0f, 0x08, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x0e, 0x0f, 0x0f, 0x0c, 0x0a, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0f, 0x0e, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0e, 0x0e,
+ 0x0c, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x08,
+ 0x0c, 0x0f, 0x08, 0x0c, 0x0f, 0x0f, 0x0c, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x0e, 0x08, 0x0e,
+ 0x0e, 0x0f, 0x0e, 0x0e, 0x0f, 0x0c, 0x0a, 0x08,
+ 0x0f, 0x0c, 0x0e, 0x0c, 0x0e, 0x0f, 0x0c, 0x08,
+};
+
+static guchar m811[256] = {
+ 0x00, 0x00, 0xf0, 0x40, 0x00, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0x70, 0xf0, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0xc0, 0x00, 0xf0, 0xf0, 0x40, 0x00, 0x40, 0x00,
+ 0x40, 0x00, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x40,
+ 0x00, 0xf0, 0x00, 0x80, 0x40, 0x00, 0x00, 0xf0,
+ 0x40, 0x70, 0x00, 0x70, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0xf0, 0x40, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x70, 0x40,
+ 0x00, 0x00, 0x40, 0xf0, 0x40, 0x00, 0xf0, 0x00,
+ 0xf0, 0xf0, 0x40, 0x70, 0xf0, 0xf0, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x40,
+ 0x40, 0x40, 0x00, 0xf0, 0x00, 0x40, 0x70, 0x40,
+ 0xf0, 0xf0, 0x00, 0x00, 0x70, 0x00, 0x00, 0x40,
+ 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0,
+ 0xf0, 0x40, 0x70, 0x70, 0x40, 0x40, 0x40, 0x00,
+ 0xf0, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x40, 0xf0,
+ 0x40, 0x00, 0x00, 0x70, 0x00, 0x70, 0x40, 0x00,
+ 0x40, 0x00, 0xf0, 0x40, 0xf0, 0xf0, 0x40, 0x00,
+ 0xf0, 0x00, 0x40, 0xf0, 0x70, 0x00, 0x00, 0x00,
+ 0xf0, 0x70, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x70, 0x00, 0x40, 0xf0, 0x40, 0xf0, 0x00, 0x00,
+ 0x70, 0x00, 0xf0, 0x40, 0x00, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0x00, 0xf0, 0x40, 0x00, 0x40, 0xf0, 0x00,
+ 0x70, 0x70, 0x40, 0x40, 0x00, 0xf0, 0x40, 0x40,
+ 0x00, 0x70, 0x00, 0x70, 0x00, 0x40, 0x00, 0x40,
+ 0xf0, 0x70, 0x70, 0x40, 0x00, 0x40, 0x40, 0xf0,
+ 0x70, 0x40, 0x00, 0xf0, 0x40, 0xf0, 0x00, 0xf0,
+ 0xf0, 0x00, 0xf0, 0x40, 0x40, 0xf0, 0x00, 0x00,
+ 0x40, 0x40, 0x40, 0xf0, 0xf0, 0x40, 0x40, 0x00,
+ 0x40, 0x40, 0xf0, 0xf0, 0xf0, 0x40, 0xf0, 0x40,
+};
+
+static guchar m812[256] = {
+ 0x0c, 0x0c, 0x0f, 0x08, 0x08, 0x0d, 0x0f, 0x09,
+ 0x0d, 0x0a, 0x0f, 0x00, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x0a, 0x0c, 0x0e, 0x08,
+ 0x0f, 0x0c, 0x0b, 0x0d, 0x08, 0x0c, 0x0e, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0d, 0x08, 0x0d, 0x0e, 0x0c, 0x08, 0x0f,
+ 0x0c, 0x0a, 0x02, 0x00, 0x08, 0x00, 0x02, 0x00,
+ 0x02, 0x0d, 0x0c, 0x08, 0x08, 0x0c, 0x0f, 0x08,
+ 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0e, 0x0c,
+ 0x0a, 0x08, 0x0e, 0x0f, 0x0e, 0x0c, 0x0d, 0x0c,
+ 0x0f, 0x0f, 0x0c, 0x0e, 0x0f, 0x0f, 0x00, 0x00,
+ 0x08, 0x02, 0x02, 0x0c, 0x0c, 0x08, 0x0c, 0x08,
+ 0x0e, 0x0c, 0x0c, 0x08, 0x0c, 0x0f, 0x0d, 0x0e,
+ 0x08, 0x0e, 0x08, 0x0d, 0x08, 0x0a, 0x0e, 0x0c,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x0e, 0x08, 0x0c, 0x0c,
+ 0x0d, 0x0f, 0x0f, 0x02, 0x02, 0x0a, 0x02, 0x0f,
+ 0x0f, 0x08, 0x0e, 0x0c, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x08, 0x0f,
+ 0x08, 0x02, 0x08, 0x08, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0e, 0x0c, 0x0d, 0x0e, 0x0f, 0x0f, 0x0c, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x0f, 0x00, 0x00, 0x0e, 0x0c,
+ 0x0f, 0x0c, 0x0a, 0x0c, 0x0c, 0x08, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0d, 0x00, 0x0c,
+ 0x08, 0x0c, 0x0f, 0x0e, 0x0c, 0x0d, 0x0d, 0x0d,
+ 0x0f, 0x0a, 0x0f, 0x0e, 0x0c, 0x0c, 0x0f, 0x0c,
+ 0x08, 0x0c, 0x0e, 0x08, 0x0e, 0x0d, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x00, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x00, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x08, 0x0d, 0x0c, 0x0f,
+ 0x0d, 0x0c, 0x0f, 0x08, 0x0e, 0x0f, 0x0c, 0x08,
+ 0x08, 0x08, 0x0c, 0x0f, 0x0f, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0f, 0x0f, 0x08, 0x0f, 0x0c,
+};
+
+static guchar m821[256] = {
+ 0x40, 0x70, 0x70, 0x40, 0x00, 0xf0, 0x00, 0xf0,
+ 0xf0, 0x70, 0xf0, 0x00, 0xf0, 0xf0, 0x70, 0x40,
+ 0x70, 0x00, 0xf0, 0x40, 0x40, 0x00, 0x70, 0x70,
+ 0x70, 0x40, 0x40, 0xf0, 0xf0, 0x40, 0xf0, 0xf0,
+ 0x00, 0xc0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x70, 0xf0, 0xf0, 0xf0, 0x00, 0x70, 0x00,
+ 0x00, 0x00, 0x40, 0x70, 0x40, 0xf0, 0xf0, 0xf0,
+ 0x70, 0xf0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x00, 0x00, 0x40, 0x40, 0xc0, 0x40, 0xf0,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x70, 0xf0, 0x70, 0x00, 0x40, 0x70, 0x00, 0x70,
+ 0x40, 0x00, 0x70, 0x40, 0xf0, 0x00, 0xf0, 0x40,
+ 0x70, 0x00, 0x70, 0x70, 0x00, 0x40, 0xf0, 0xf0,
+ 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x00, 0xf0, 0x70,
+ 0xf0, 0x00, 0x00, 0x40, 0x00, 0x40, 0xf0, 0x40,
+ 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40,
+ 0x00, 0x40, 0x40, 0xf0, 0x00, 0xf0, 0xc0, 0x00,
+ 0x00, 0x40, 0xf0, 0x00, 0x40, 0x00, 0x40, 0x00,
+ 0x40, 0xf0, 0xc0, 0x40, 0x00, 0xf0, 0x00, 0xf0,
+ 0x40, 0x40, 0x00, 0x40, 0x40, 0xf0, 0xf0, 0x40,
+ 0x40, 0xc0, 0x40, 0x70, 0xf0, 0xf0, 0xc0, 0xf0,
+ 0x40, 0xf0, 0x40, 0xf0, 0x40, 0x00, 0x00, 0xc0,
+ 0xf0, 0xf0, 0x40, 0xf0, 0xc0, 0xf0, 0x40, 0xc0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0xf0, 0xf0, 0x70, 0xf0, 0xc0, 0x00, 0xf0,
+ 0x00, 0x70, 0x40, 0xf0, 0x70, 0x00, 0xf0, 0xf0,
+ 0x40, 0xf0, 0x40, 0x70, 0x40, 0xf0, 0xf0, 0xf0,
+ 0x40, 0x00, 0x40, 0x70, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0xf0, 0x00, 0x70, 0x70, 0x00, 0x40, 0x40,
+ 0x00, 0x70, 0x70, 0x70, 0x00, 0xc0, 0xc0, 0x00,
+};
+
+static guchar m822[256] = {
+ 0x0e, 0x0e, 0x0e, 0x08, 0x0e, 0x0f, 0x02, 0x0d,
+ 0x0d, 0x0c, 0x0d, 0x0c, 0x0f, 0x0f, 0x08, 0x08,
+ 0x0e, 0x0c, 0x0f, 0x08, 0x0e, 0x0c, 0x0c, 0x00,
+ 0x08, 0x08, 0x08, 0x0f, 0x0f, 0x0c, 0x0f, 0x0f,
+ 0x0c, 0x0f, 0x0e, 0x02, 0x08, 0x0c, 0x08, 0x08,
+ 0x0e, 0x08, 0x0f, 0x0f, 0x0f, 0x0a, 0x00, 0x0e,
+ 0x02, 0x02, 0x0c, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f,
+ 0x0e, 0x0f, 0x0c, 0x02, 0x0c, 0x0c, 0x02, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x08, 0x0e, 0x0d, 0x08, 0x0f,
+ 0x08, 0x0e, 0x08, 0x0e, 0x08, 0x00, 0x0c, 0x0e,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0d, 0x0e, 0x0c, 0x0c, 0x00, 0x0c, 0x0e,
+ 0x00, 0x0c, 0x08, 0x0c, 0x0d, 0x08, 0x0d, 0x08,
+ 0x0e, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0f, 0x0f,
+ 0x02, 0x0d, 0x0f, 0x0a, 0x0e, 0x0c, 0x09, 0x0c,
+ 0x0d, 0x0a, 0x02, 0x08, 0x0c, 0x0e, 0x0f, 0x0e,
+ 0x0c, 0x08, 0x02, 0x0c, 0x0e, 0x0c, 0x08, 0x08,
+ 0x02, 0x08, 0x0e, 0x0f, 0x08, 0x0f, 0x0f, 0x0e,
+ 0x0c, 0x0e, 0x0f, 0x0c, 0x0c, 0x08, 0x08, 0x02,
+ 0x0e, 0x0f, 0x0d, 0x0c, 0x0a, 0x0f, 0x0c, 0x0f,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0e, 0x0f, 0x0b, 0x0c,
+ 0x0e, 0x0f, 0x0a, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0e, 0x0f, 0x08, 0x0f, 0x0e, 0x0c, 0x0c, 0x0f,
+ 0x0f, 0x0f, 0x0c, 0x0d, 0x0d, 0x0f, 0x0e, 0x09,
+ 0x0c, 0x0a, 0x0c, 0x0c, 0x0a, 0x08, 0x00, 0x02,
+ 0x02, 0x08, 0x0a, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x08, 0x0f, 0x0f, 0x0e, 0x0f, 0x0f, 0x0c, 0x0f,
+ 0x0a, 0x0c, 0x08, 0x0f, 0x0e, 0x08, 0x0f, 0x0f,
+ 0x0e, 0x0f, 0x08, 0x0e, 0x0e, 0x0f, 0x0f, 0x0d,
+ 0x0c, 0x08, 0x0c, 0x0e, 0x0c, 0x0c, 0x08, 0x0e,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0a,
+ 0x08, 0x0e, 0x0c, 0x0e, 0x08, 0x09, 0x0d, 0x08,
+};
+
+static guchar m831[256] = {
+ 0x40, 0xc0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x40,
+ 0x40, 0xf0, 0x40, 0x40, 0x00, 0x00, 0x70, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xf0,
+ 0x70, 0x00, 0x00, 0x40, 0x70, 0x40, 0x40, 0x40,
+ 0x00, 0x40, 0x40, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x00, 0x00, 0xf0, 0x40, 0x40, 0x40, 0xf0,
+ 0x40, 0xf0, 0x70, 0x40, 0xf0, 0xf0, 0xf0, 0x40,
+ 0xf0, 0xf0, 0x40, 0x00, 0x40, 0x40, 0x00, 0x00,
+ 0xf0, 0x00, 0x40, 0x40, 0x40, 0x70, 0x00, 0xc0,
+ 0x00, 0xf0, 0xf0, 0x00, 0x00, 0x40, 0x40, 0xf0,
+ 0x70, 0xc0, 0xf0, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0xf0, 0x00, 0x70, 0x00, 0xf0,
+ 0x40, 0x00, 0x00, 0xf0, 0x70, 0x40, 0x00, 0x40,
+ 0x40, 0x00, 0x40, 0x00, 0x40, 0x70, 0x40, 0x70,
+ 0x00, 0xf0, 0xf0, 0x00, 0x00, 0x40, 0xf0, 0x00,
+ 0x00, 0x00, 0x40, 0x70, 0x40, 0x40, 0xf0, 0x00,
+ 0xc0, 0x40, 0x70, 0x40, 0x40, 0x40, 0xf0, 0x70,
+ 0x70, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x70, 0xc0, 0xf0, 0xf0, 0x40, 0x40, 0x00, 0x00,
+ 0x00, 0x70, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x40,
+ 0x40, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x40,
+ 0x00, 0xc0, 0xf0, 0x00, 0xf0, 0x00, 0x70, 0x40,
+ 0x40, 0x40, 0x00, 0xf0, 0x40, 0x00, 0xf0, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x00, 0x70,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0xf0, 0x40, 0xf0, 0x00, 0x00, 0x00, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0x00, 0xf0, 0x00, 0x40, 0x70,
+ 0x40, 0xc0, 0x00, 0x70, 0x40, 0xf0, 0x00, 0x00,
+};
+
+static guchar m832[256] = {
+ 0x0c, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e,
+ 0x0e, 0x0f, 0x08, 0x08, 0x0e, 0x0c, 0x0a, 0x02,
+ 0x08, 0x02, 0x08, 0x00, 0x02, 0x02, 0x0c, 0x0f,
+ 0x08, 0x0c, 0x0a, 0x0e, 0x0e, 0x08, 0x0c, 0x08,
+ 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0e,
+ 0x0f, 0x0c, 0x0c, 0x0f, 0x0e, 0x0e, 0x08, 0x0f,
+ 0x08, 0x0f, 0x0c, 0x0e, 0x0f, 0x0f, 0x0f, 0x0c,
+ 0x0f, 0x0f, 0x0e, 0x0c, 0x0e, 0x08, 0x08, 0x0c,
+ 0x0f, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0a, 0x0f,
+ 0x0c, 0x0f, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x0e, 0x0f, 0x0f, 0x0c, 0x0e, 0x08, 0x0c, 0x08,
+ 0x08, 0x00, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02,
+ 0x02, 0x02, 0x08, 0x0a, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x08, 0x08, 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x0e, 0x08, 0x0c, 0x0f, 0x0e, 0x0e, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0a, 0x0e, 0x0c,
+ 0x0c, 0x0f, 0x0d, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x08, 0x0e, 0x0e, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x0f, 0x0c, 0x0c, 0x0e, 0x0a, 0x0c, 0x0f, 0x08,
+ 0x0e, 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0f, 0x0f, 0x0f, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0e, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x0a,
+ 0x02, 0x0a, 0x02, 0x00, 0x02, 0x0f, 0x08, 0x0c,
+ 0x0e, 0x0f, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0e,
+ 0x0c, 0x0d, 0x0f, 0x0c, 0x0f, 0x08, 0x0c, 0x0e,
+ 0x08, 0x0c, 0x08, 0x09, 0x0e, 0x0c, 0x0f, 0x0c,
+ 0x0e, 0x0c, 0x08, 0x0c, 0x0f, 0x0e, 0x0c, 0x0e,
+ 0x0e, 0x0e, 0x0c, 0x0c, 0x0c, 0x0e, 0x08, 0x0c,
+ 0x0c, 0x0f, 0x0e, 0x0d, 0x0c, 0x08, 0x0c, 0x0d,
+ 0x0f, 0x0f, 0x0f, 0x0c, 0x0d, 0x0c, 0x0c, 0x08,
+ 0x0e, 0x0f, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c,
+};
+
+static guchar m841[256] = {
+ 0x00, 0x40, 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x70,
+ 0x00, 0x00, 0xc0, 0x70, 0xf0, 0xf0, 0xf0, 0x40,
+ 0x00, 0x40, 0x00, 0x70, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0x70, 0x00, 0xf0, 0x00, 0x00, 0x40,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
+ 0xf0, 0x40, 0x00, 0x00, 0x70, 0xf0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x70, 0x40,
+ 0x40, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x70, 0x40,
+ 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x40, 0xf0,
+ 0x40, 0x40, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0x40, 0xf0, 0x70, 0xf0, 0x40, 0x40, 0xf0, 0x40,
+ 0x00, 0x70, 0x40, 0xf0, 0xf0, 0x70, 0x70, 0xf0,
+ 0x40, 0x70, 0x00, 0x40, 0x40, 0xf0, 0x40, 0x70,
+ 0x40, 0x70, 0xf0, 0x00, 0x40, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x70, 0x00, 0x70, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x00, 0x40, 0x40, 0xf0, 0x40, 0x00, 0x00,
+ 0x00, 0xf0, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x70,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x40, 0x40, 0x40, 0x00, 0x00, 0x70, 0x00, 0x40,
+ 0x00, 0x40, 0xf0, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0xf0, 0x70, 0x40, 0xf0, 0xf0, 0x40, 0x40, 0xf0,
+ 0xc0, 0x70, 0xc0, 0x00, 0xf0, 0x00, 0xf0, 0x40,
+ 0x40, 0xf0, 0x70, 0xf0, 0x40, 0xf0, 0x00, 0x40,
+ 0x70, 0xf0, 0x00, 0x40, 0x00, 0x00, 0x70, 0x00,
+ 0x00, 0x70, 0xf0, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x00, 0xf0, 0x00, 0xf0, 0x40,
+ 0x40, 0x40, 0x40, 0x00, 0xf0, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x40, 0xf0, 0x40, 0x00, 0x70,
+};
+
+static guchar m842[256] = {
+ 0x08, 0x0e, 0x08, 0x0f, 0x0f, 0x08, 0x0e, 0x0c,
+ 0x08, 0x0c, 0x0d, 0x0e, 0x0f, 0x0f, 0x0f, 0x0e,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x08,
+ 0x0a, 0x00, 0x00, 0x0c, 0x02, 0x02, 0x08, 0x08,
+ 0x00, 0x08, 0x00, 0x0c, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x0d, 0x08, 0x0c, 0x0d, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x0c, 0x0c, 0x0e, 0x0f, 0x08, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x08, 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0e, 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x0f,
+ 0x08, 0x0e, 0x0e, 0x0f, 0x0a, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x08, 0x0f, 0x08, 0x0c, 0x0d, 0x0c,
+ 0x0c, 0x0e, 0x08, 0x0f, 0x0f, 0x0e, 0x0c, 0x0d,
+ 0x0c, 0x0a, 0x08, 0x0e, 0x0c, 0x0f, 0x0e, 0x0c,
+ 0x0e, 0x0c, 0x0f, 0x08, 0x08, 0x0c, 0x0c, 0x08,
+ 0x08, 0x08, 0x0e, 0x08, 0x08, 0x00, 0x0c, 0x02,
+ 0x0a, 0x02, 0x00, 0x02, 0x02, 0x0c, 0x0e, 0x0c,
+ 0x0d, 0x0c, 0x08, 0x08, 0x0d, 0x08, 0x08, 0x0e,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x08, 0x0c, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0e,
+ 0x0c, 0x0c, 0x0f, 0x08, 0x0e, 0x08, 0x0c, 0x08,
+ 0x0f, 0x0e, 0x0e, 0x0d, 0x0d, 0x0a, 0x08, 0x0f,
+ 0x0d, 0x0e, 0x0d, 0x08, 0x0f, 0x0c, 0x0d, 0x0c,
+ 0x08, 0x0f, 0x0e, 0x0d, 0x0c, 0x0f, 0x0c, 0x0c,
+ 0x0e, 0x0f, 0x0c, 0x0e, 0x0c, 0x08, 0x0e, 0x0c,
+ 0x08, 0x00, 0x09, 0x0c, 0x00, 0x02, 0x08, 0x02,
+ 0x02, 0x08, 0x08, 0x02, 0x08, 0x02, 0x02, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x08, 0x0d, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0e,
+};
+
+static guchar m851[256] = {
+ 0x70, 0x00, 0x40, 0x40, 0x00, 0x00, 0x70, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00,
+ 0x40, 0xf0, 0x00, 0xf0, 0xf0, 0x70, 0x00, 0xf0,
+ 0xf0, 0x00, 0xf0, 0x00, 0x40, 0x00, 0xc0, 0x70,
+ 0x00, 0xf0, 0x40, 0xc0, 0x40, 0xc0, 0x70, 0x40,
+ 0x00, 0x00, 0x40, 0x40, 0xf0, 0xf0, 0x00, 0xc0,
+ 0x00, 0x00, 0x40, 0x40, 0x40, 0x70, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xc0,
+ 0x70, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x40, 0x00,
+ 0x70, 0xf0, 0x70, 0x70, 0x00, 0x00, 0xf0, 0x40,
+ 0x40, 0x40, 0x40, 0xc0, 0x00, 0x70, 0x40, 0x70,
+ 0x70, 0xc0, 0x70, 0x00, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0xf0, 0xf0, 0xf0, 0x40, 0x00, 0xf0, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x40, 0x40, 0x40, 0x00, 0x40, 0x70, 0x40,
+ 0x70, 0x40, 0x00, 0x00, 0xf0, 0x40, 0x40, 0xf0,
+ 0x70, 0x40, 0x70, 0x40, 0x40, 0x00, 0x00, 0xc0,
+ 0x70, 0xf0, 0x00, 0x40, 0xf0, 0x00, 0x00, 0x70,
+ 0x40, 0x70, 0x00, 0xf0, 0x70, 0x40, 0x00, 0x40,
+ 0x40, 0x00, 0x40, 0x00, 0x70, 0x40, 0xf0, 0x40,
+ 0xf0, 0xf0, 0xf0, 0x70, 0x70, 0x40, 0x70, 0xf0,
+ 0x80, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40,
+ 0x40, 0x70, 0xf0, 0x00, 0x40, 0x40, 0x40, 0x40,
+ 0x00, 0xf0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0xf0, 0x40, 0x40, 0x00, 0xf0, 0xc0, 0xf0,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00,
+ 0x40, 0x00, 0x40, 0x00, 0xf0, 0xf0, 0x00, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x40, 0x00,
+ 0x40, 0xf0, 0xf0, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0xf0,
+ 0x00, 0x70, 0xf0, 0xf0, 0x40, 0x00, 0x70, 0xc0,
+};
+
+static guchar m852[256] = {
+ 0x0c, 0x08, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0f, 0x0c, 0x0f, 0x0d, 0x0c, 0x0c, 0x0f,
+ 0x0d, 0x0c, 0x0f, 0x08, 0x0c, 0x0c, 0x0d, 0x0e,
+ 0x0c, 0x0f, 0x08, 0x0d, 0x0c, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x0f, 0x0d, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x00, 0x02,
+ 0x0a, 0x02, 0x02, 0x0e, 0x02, 0x0f, 0x0c, 0x09,
+ 0x0c, 0x0d, 0x08, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0f, 0x0e, 0x08, 0x08, 0x0c, 0x0d, 0x08,
+ 0x08, 0x0c, 0x08, 0x0d, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x0f, 0x08, 0x0c, 0x08, 0x0c, 0x0e, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0c, 0x08,
+ 0x08, 0x0c, 0x02, 0x08, 0x02, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0e, 0x0e, 0x0e, 0x0c, 0x08, 0x0e, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0e, 0x0c, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x0c, 0x0d, 0x08, 0x08, 0x0d, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x08, 0x0d, 0x0c,
+ 0x0f, 0x0d, 0x0f, 0x08, 0x08, 0x00, 0x02, 0x0f,
+ 0x0f, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e,
+ 0x0c, 0x0e, 0x0d, 0x08, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0b, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x08, 0x0c, 0x08, 0x0d, 0x0d, 0x0f,
+ 0x0e, 0x0c, 0x0c, 0x02, 0x08, 0x0f, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0f, 0x0d, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0f, 0x0d, 0x0c, 0x08,
+ 0x0c, 0x0f, 0x0d, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0d, 0x0f, 0x08, 0x0c, 0x0c, 0x0f,
+};
+
+static guchar m861[256] = {
+ 0x40, 0x00, 0xf0, 0x00, 0x40, 0x40, 0xf0, 0xf0,
+ 0x00, 0x00, 0xf0, 0x70, 0x00, 0x40, 0x40, 0x00,
+ 0x40, 0x40, 0x40, 0x70, 0x00, 0x00, 0xf0, 0xf0,
+ 0x40, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x40, 0x70, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x70,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x40, 0x00, 0x40, 0x00, 0x40, 0x40, 0x00, 0xf0,
+ 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xf0, 0x00,
+ 0xf0, 0x00, 0x40, 0x40, 0xf0, 0xf0, 0x40, 0x40,
+ 0x40, 0x40, 0x70, 0x80, 0xf0, 0x40, 0xf0, 0xf0,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0xf0,
+ 0x00, 0x40, 0x00, 0x70, 0x40, 0x00, 0x00, 0x40,
+ 0x00, 0x70, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x00, 0xf0, 0x40, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0xf0, 0x70, 0xf0, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0xf0, 0x00, 0x70, 0x40, 0x00,
+ 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x40,
+ 0x40, 0xf0, 0x70, 0x70, 0x00, 0x40, 0x00, 0x70,
+ 0x70, 0x40, 0x00, 0x40, 0x40, 0x40, 0x70, 0x40,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x40, 0x40, 0x00, 0x40, 0x70, 0x40, 0x70, 0xf0,
+ 0x00, 0x70, 0x00, 0xf0, 0x00, 0x70, 0x70, 0x00,
+ 0x00, 0x40, 0x40, 0x00, 0xf0, 0x40, 0x00, 0x40,
+ 0x00, 0xf0, 0x40, 0xf0, 0x40, 0x00, 0x70, 0xf0,
+ 0x40, 0x00, 0x00, 0x40, 0xf0, 0x40, 0x00, 0x40,
+ 0x00, 0x70, 0x00, 0x00, 0x70, 0xf0, 0x70, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0x70, 0x40, 0x70, 0x40, 0x40, 0xf0, 0x00,
+};
+
+static guchar m862[256] = {
+ 0x0c, 0x0c, 0x09, 0x08, 0x0c, 0x0e, 0x0d, 0x0d,
+ 0x08, 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x08, 0x08,
+ 0x08, 0x0e, 0x00, 0x08, 0x00, 0x08, 0x0b, 0x0d,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e,
+ 0x08, 0x0e, 0x0c, 0x08, 0x0c, 0x0d, 0x0c, 0x08,
+ 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0e, 0x0c, 0x0c, 0x0c, 0x0e, 0x08, 0x0c, 0x0d,
+ 0x0c, 0x08, 0x08, 0x0c, 0x00, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0e, 0x0f, 0x02,
+ 0x0f, 0x0a, 0x0c, 0x0c, 0x0f, 0x0d, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0a, 0x0d, 0x0d, 0x08, 0x0f, 0x0d,
+ 0x08, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x08, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x0a, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x0e, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x0c,
+ 0x08, 0x0f, 0x0e, 0x0e, 0x0e, 0x0a, 0x0a, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x08, 0x0f, 0x0e, 0x0f, 0x0e, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0f, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0e, 0x0e, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0c, 0x0f, 0x0f, 0x0c, 0x08, 0x0e,
+ 0x0e, 0x0f, 0x0e, 0x00, 0x02, 0x08, 0x08, 0x0e,
+ 0x0e, 0x0e, 0x08, 0x0c, 0x0e, 0x0e, 0x0e, 0x0c,
+ 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0e, 0x0f,
+ 0x0c, 0x0e, 0x0a, 0x0f, 0x0c, 0x00, 0x02, 0x0a,
+ 0x0e, 0x0e, 0x08, 0x0c, 0x0f, 0x08, 0x0c, 0x0c,
+ 0x0e, 0x0f, 0x0c, 0x0f, 0x0c, 0x0c, 0x0e, 0x0f,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0f, 0x08, 0x0c, 0x08,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x0f, 0x0a, 0x00,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0e, 0x0c, 0x0c, 0x08, 0x08, 0x0f, 0x08,
+};
+
+static guchar m871[256] = {
+ 0xf0, 0x00, 0xf0, 0xf0, 0x40, 0x40, 0x70, 0x40,
+ 0xf0, 0x70, 0x70, 0x40, 0x00, 0x70, 0x40, 0x40,
+ 0x40, 0x70, 0x70, 0x40, 0x40, 0x00, 0x00, 0x00,
+ 0xf0, 0x40, 0xf0, 0x00, 0xf0, 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0x70, 0x00, 0x00,
+ 0x40, 0x70, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0x40, 0x00, 0x70, 0x00, 0x00, 0x70,
+ 0x00, 0x40, 0x40, 0x70, 0x40, 0x40, 0x40, 0x70,
+ 0x40, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x70, 0x00, 0x70, 0x70, 0x40, 0xf0, 0x00,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0xf0, 0x00, 0xf0,
+ 0x40, 0x70, 0x00, 0x00, 0x00, 0x40, 0x00, 0xf0,
+ 0x70, 0x40, 0x00, 0x70, 0x40, 0x40, 0xf0, 0x00,
+ 0xf0, 0x00, 0x70, 0x00, 0x00, 0x00, 0x70, 0x40,
+ 0x00, 0x40, 0x40, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0xf0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0xf0, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x00, 0x40, 0x40, 0xf0, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0xf0,
+ 0x40, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x70, 0x40, 0x40, 0x40, 0x70,
+ 0x00, 0x40, 0x00, 0xf0, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0xf0, 0x70, 0x00, 0x70, 0x40, 0x40,
+ 0x70, 0x40, 0x00, 0x00, 0xf0, 0x00, 0x70, 0x70,
+ 0x40, 0x40, 0x40, 0x70, 0x00, 0x00, 0x40, 0x00,
+ 0x70, 0x00, 0x70, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0x40, 0x40, 0x00, 0x40, 0x00, 0x00, 0x40,
+ 0xf0, 0x00, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x40, 0x80, 0x40, 0x00, 0xf0,
+ 0x00, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x70, 0x70,
+ 0x40, 0xf0, 0x40, 0xf0, 0x00, 0x00, 0xf0, 0x40,
+};
+
+static guchar m872[256] = {
+ 0x0f, 0x0c, 0x0f, 0x0f, 0x0c, 0x0c, 0x0c, 0x0e,
+ 0x0f, 0x0e, 0x0e, 0x0c, 0x0c, 0x0e, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0e, 0x0e, 0x08, 0x0a, 0x08, 0x02,
+ 0x0f, 0x0c, 0x0f, 0x0c, 0x0f, 0x08, 0x0e, 0x08,
+ 0x0c, 0x0e, 0x0e, 0x0e, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x0c, 0x0e, 0x0c, 0x08, 0x0c, 0x0c, 0x0e, 0x08,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0e, 0x0c, 0x08, 0x0e,
+ 0x0c, 0x08, 0x0c, 0x0e, 0x0c, 0x08, 0x0e, 0x0e,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0a,
+ 0x02, 0x02, 0x08, 0x00, 0x0e, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0d, 0x0c, 0x0f,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0e, 0x0c, 0x0c, 0x0e, 0x0e, 0x0e, 0x0d, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0a, 0x08, 0x08, 0x0c, 0x0f, 0x0c, 0x0f, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0e, 0x0a, 0x0a, 0x02, 0x00,
+ 0x00, 0x0c, 0x0f, 0x0e, 0x0c, 0x0e, 0x08, 0x0c,
+ 0x0e, 0x0c, 0x08, 0x0a, 0x08, 0x0f, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x08, 0x0c, 0x0e,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x08, 0x08, 0x0d, 0x0c, 0x0c, 0x08, 0x00, 0x00,
+ 0x02, 0x08, 0x0c, 0x0e, 0x0e, 0x0e, 0x0c, 0x0e,
+ 0x0c, 0x08, 0x0c, 0x0f, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0f, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x0e, 0x08, 0x0c, 0x0c, 0x0d, 0x0c, 0x0e, 0x08,
+ 0x0c, 0x0c, 0x0e, 0x0e, 0x0c, 0x08, 0x08, 0x00,
+ 0x00, 0x0e, 0x0e, 0x0e, 0x0c, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0e, 0x0c, 0x0d, 0x0c, 0x0a, 0x0d,
+ 0x08, 0x08, 0x0d, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0f, 0x0c, 0x0d, 0x0c, 0x08, 0x0f, 0x0c,
+};
+
+static guchar m881[256] = {
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0xf0, 0x40, 0x00,
+ 0x00, 0x40, 0x40, 0x40, 0x00, 0x70, 0x70, 0x70,
+ 0x40, 0x70, 0x40, 0x40, 0x40, 0x70, 0x70, 0x00,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x40, 0xc0,
+ 0x00, 0x70, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x70,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x40, 0xf0, 0x40, 0x00, 0x00, 0x40, 0xf0, 0x00,
+ 0x00, 0x70, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0xf0, 0x40, 0x70, 0x40, 0x70, 0x40, 0xf0, 0x00,
+ 0x40, 0x40, 0x40, 0x40, 0xf0, 0xf0, 0x40, 0x00,
+ 0x00, 0x40, 0xf0, 0xf0, 0x00, 0x40, 0x40, 0xf0,
+ 0x40, 0xf0, 0x40, 0xf0, 0x40, 0xf0, 0x70, 0x40,
+ 0x40, 0xf0, 0xf0, 0xf0, 0x40, 0x00, 0x00, 0x00,
+ 0xf0, 0x40, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x40, 0xf0, 0x00, 0x00, 0x70, 0x00, 0xf0,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0x70, 0xf0, 0xf0,
+ 0x40, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x00, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x00,
+ 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0xf0,
+ 0x40, 0x70, 0x40, 0x40, 0x40, 0x00, 0xf0, 0x40,
+ 0x40, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x40, 0xf0, 0x00, 0x00, 0x70, 0x00,
+ 0x70, 0x70, 0x00, 0x00, 0xf0, 0x70, 0x00, 0x70,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x70,
+ 0x40, 0xf0, 0xf0, 0x70, 0x70, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0xf0,
+ 0x00, 0x40, 0x40, 0x40, 0xf0, 0xf0, 0x00, 0x00,
+ 0x70, 0xf0, 0x00, 0x40, 0xf0, 0xf0, 0x40, 0xf0,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x40, 0x40, 0x70, 0xf0, 0xf0, 0xc0, 0x00, 0x40,
+ 0xf0, 0x70, 0x00, 0x00, 0x70, 0xf0, 0x70, 0x00,
+};
+
+static guchar m882[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x08, 0x0d, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0e, 0x0c, 0x0e, 0x0e, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0e, 0x0c, 0x0c, 0x08, 0x0d,
+ 0x0c, 0x0e, 0x0f, 0x0d, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0e, 0x0c, 0x08, 0x0c, 0x0d, 0x0c,
+ 0x0c, 0x0e, 0x08, 0x0d, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x08, 0x0c, 0x0e, 0x0a, 0x09, 0x08,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0f, 0x0f, 0x0c, 0x08,
+ 0x08, 0x08, 0x0d, 0x0d, 0x02, 0x0c, 0x0c, 0x0f,
+ 0x08, 0x0f, 0x0c, 0x0d, 0x08, 0x0d, 0x08, 0x08,
+ 0x08, 0x0f, 0x0f, 0x0f, 0x02, 0x02, 0x08, 0x0c,
+ 0x0f, 0x0e, 0x0c, 0x0f, 0x02, 0x0c, 0x0a, 0x0c,
+ 0x0f, 0x0c, 0x0f, 0x08, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x08, 0x0c, 0x08, 0x08, 0x0c, 0x0e, 0x0f, 0x0f,
+ 0x0c, 0x0f, 0x0f, 0x0c, 0x0a, 0x02, 0x00, 0x08,
+ 0x0f, 0x0c, 0x08, 0x0f, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0f, 0x0c, 0x08, 0x0c, 0x0f, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0a, 0x08, 0x0d, 0x0c,
+ 0x08, 0x0c, 0x0e, 0x08, 0x0e, 0x08, 0x08, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0f, 0x0c, 0x02, 0x00, 0x00,
+ 0x00, 0x0e, 0x0c, 0x08, 0x09, 0x08, 0x0c, 0x0e,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0e, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0f, 0x0f, 0x00, 0x00, 0x02, 0x02, 0x00,
+ 0x00, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0d,
+ 0x0c, 0x08, 0x0e, 0x08, 0x0f, 0x0f, 0x0c, 0x0c,
+ 0x0e, 0x0f, 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0f,
+ 0x08, 0x0d, 0x02, 0x02, 0x02, 0x02, 0x08, 0x0c,
+ 0x0f, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0e, 0x0e, 0x0c, 0x0f, 0x0f, 0x09, 0x0c, 0x0c,
+ 0x0f, 0x0e, 0x0c, 0x0c, 0x0e, 0x0d, 0x0e, 0x08,
+};
+
+static guchar m891[256] = {
+ 0x00, 0x40, 0x70, 0x00, 0x70, 0x00, 0x40, 0xf0,
+ 0x00, 0x00, 0x70, 0x00, 0x70, 0x40, 0x40, 0x40,
+ 0xf0, 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x40, 0x00,
+ 0xc0, 0xc0, 0x40, 0x00, 0x40, 0x70, 0x70, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x40,
+ 0x40, 0x00, 0xf0, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0x40, 0x00, 0x00, 0x40, 0xf0, 0x00,
+ 0xf0, 0x40, 0x40, 0xf0, 0x00, 0x00, 0x40, 0x00,
+ 0x40, 0xf0, 0x40, 0x70, 0xf0, 0x40, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x70, 0x70, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x40,
+ 0x00, 0x00, 0x40, 0x40, 0x40, 0x00, 0x70, 0xf0,
+ 0x70, 0x40, 0x40, 0x40, 0xf0, 0x00, 0x70, 0x00,
+ 0x00, 0x00, 0xf0, 0x40, 0x00, 0x70, 0x40, 0x70,
+ 0x40, 0x00, 0xf0, 0x40, 0x70, 0x40, 0x00, 0x70,
+ 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x70, 0xf0,
+ 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xf0, 0xf0,
+ 0x70, 0x40, 0x70, 0xf0, 0x00, 0x40, 0x00, 0xf0,
+ 0x40, 0x00, 0x00, 0xf0, 0x40, 0x40, 0xf0, 0x70,
+ 0x70, 0x00, 0x70, 0x40, 0x40, 0x00, 0x00, 0x40,
+ 0x40, 0xf0, 0x00, 0x00, 0x00, 0x40, 0x70, 0x70,
+ 0x00, 0xf0, 0xf0, 0x00, 0x70, 0x00, 0x00, 0x70,
+ 0x40, 0x00, 0xf0, 0x70, 0x40, 0x40, 0x40, 0x40,
+ 0x00, 0x00, 0xf0, 0x00, 0x40, 0xf0, 0x00, 0x70,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xf0, 0x00, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x00, 0x70, 0x00, 0x70, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x70, 0x70,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x40, 0xf0, 0x00, 0x40, 0x00,
+ 0xf0, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40,
+};
+
+static guchar m892[256] = {
+ 0x08, 0x0c, 0x0e, 0x08, 0x00, 0x0c, 0x0c, 0x0d,
+ 0x08, 0x0c, 0x0e, 0x0c, 0x0c, 0x08, 0x0c, 0x08,
+ 0x0f, 0x0c, 0x0f, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0f, 0x0e, 0x0a, 0x00, 0x00, 0x0c, 0x0c,
+ 0x08, 0x0e, 0x0c, 0x0c, 0x08, 0x0f, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0f, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x0c, 0x02, 0x0c, 0x0f, 0x0c,
+ 0x0d, 0x08, 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x08, 0x0f, 0x0c, 0x08, 0x0f, 0x00, 0x0c, 0x00,
+ 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x00, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0e, 0x08,
+ 0x00, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0c, 0x0e, 0x0c, 0x08, 0x0c, 0x0f,
+ 0x00, 0x0f, 0x0c, 0x0f, 0x08, 0x0c, 0x0f, 0x09,
+ 0x0c, 0x08, 0x08, 0x0d, 0x08, 0x08, 0x08, 0x0d,
+ 0x08, 0x08, 0x08, 0x0d, 0x08, 0x0c, 0x0d, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x08,
+ 0x08, 0x09, 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x08, 0x0d, 0x08, 0x08, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0d, 0x08, 0x08, 0x0d, 0x0c, 0x0c,
+ 0x0d, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02,
+ 0x02, 0x02, 0x0f, 0x0c, 0x0c, 0x0c, 0x0e, 0x08,
+ 0x08, 0x0c, 0x0e, 0x0c, 0x0e, 0x0c, 0x02, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0e, 0x0e, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0e, 0x0c, 0x0c, 0x08, 0x02,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+};
+
+static guchar m8a1[256] = {
+ 0xf0, 0x00, 0xf0, 0xf0, 0x40, 0x40, 0x00, 0x40,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x40,
+ 0x70, 0x40, 0x40, 0xf0, 0x40, 0x40, 0xf0, 0xf0,
+ 0xf0, 0x00, 0x00, 0xf0, 0x00, 0xf0, 0x40, 0xf0,
+ 0x40, 0x00, 0x40, 0xf0, 0x40, 0xf0, 0x40, 0x00,
+ 0x00, 0x00, 0xf0, 0x40, 0x40, 0xf0, 0x00, 0x40,
+ 0x00, 0xf0, 0x00, 0x70, 0xf0, 0x40, 0xf0, 0x40,
+ 0x00, 0x00, 0xf0, 0xf0, 0x70, 0x40, 0x40, 0x00,
+ 0x40, 0x70, 0x00, 0x40, 0x00, 0x40, 0x70, 0x40,
+ 0x70, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0xf0, 0x70, 0x70, 0x40, 0xf0, 0xf0, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0xf0, 0x40, 0x40, 0xf0, 0x00,
+ 0xf0, 0x40, 0xf0, 0xf0, 0x00, 0x40, 0xf0, 0x40,
+ 0x00, 0xf0, 0x00, 0x70, 0x70, 0xf0, 0xf0, 0x00,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0xc0, 0x40, 0x40,
+ 0x00, 0xc0, 0x40, 0x40, 0x70, 0x00, 0x40, 0x40,
+ 0x40, 0x00, 0x70, 0x40, 0x70, 0xf0, 0x40, 0xf0,
+ 0x00, 0x70, 0x00, 0x40, 0xf0, 0xf0, 0x00, 0x40,
+ 0x40, 0x70, 0x40, 0xf0, 0x00, 0xf0, 0x40, 0x40,
+ 0xf0, 0x40, 0x70, 0x00, 0x00, 0x00, 0xf0, 0x40,
+ 0xf0, 0xf0, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x40,
+ 0xf0, 0x40, 0x80, 0x00, 0x70, 0x70, 0x40, 0x40,
+ 0xf0, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0xf0, 0x00, 0x40, 0xf0, 0x00, 0xc0, 0xf0,
+ 0x00, 0x00, 0xf0, 0x40, 0xf0, 0x00, 0x40, 0xf0,
+ 0x40, 0x40, 0x40, 0xf0, 0x70, 0xf0, 0x00, 0xf0,
+ 0x00, 0x40, 0xf0, 0x40, 0x40, 0x40, 0xf0, 0x40,
+ 0x00, 0x00, 0x70, 0xf0, 0xf0, 0x40, 0x70, 0x40,
+ 0x70, 0xf0, 0x70, 0x00, 0x70, 0x00, 0xf0, 0xf0,
+ 0x00, 0x00, 0x80, 0xf0, 0x40, 0xf0, 0xf0, 0x00,
+ 0x40, 0xf0, 0x00, 0x70, 0x40, 0x40, 0xc0, 0xf0,
+ 0xf0, 0x00, 0xf0, 0x00, 0x40, 0x00, 0xf0, 0x40,
+};
+
+static guchar m8a2[256] = {
+ 0x0f, 0x00, 0x0d, 0x0d, 0x0c, 0x08, 0x08, 0x0e,
+ 0x0d, 0x08, 0x0d, 0x08, 0x0d, 0x08, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0d, 0x0d,
+ 0x0d, 0x08, 0x00, 0x0d, 0x08, 0x0d, 0x0c, 0x0d,
+ 0x08, 0x08, 0x0c, 0x0d, 0x08, 0x0d, 0x08, 0x0c,
+ 0x08, 0x08, 0x0d, 0x08, 0x0c, 0x0d, 0x08, 0x08,
+ 0x0c, 0x0d, 0x08, 0x00, 0x0d, 0x08, 0x0d, 0x08,
+ 0x08, 0x0c, 0x0d, 0x0d, 0x0c, 0x08, 0x0e, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0e, 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x08, 0x0d, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0d, 0x08, 0x08, 0x0d, 0x00,
+ 0x0d, 0x0c, 0x0d, 0x0d, 0x08, 0x08, 0x0d, 0x08,
+ 0x0c, 0x0d, 0x08, 0x0c, 0x0c, 0x0d, 0x0d, 0x08,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0c, 0x0d, 0x0c, 0x0c,
+ 0x08, 0x0f, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0d,
+ 0x08, 0x02, 0x02, 0x0c, 0x0d, 0x0d, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0f, 0x08, 0x0d, 0x0c, 0x08,
+ 0x0d, 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x0d, 0x08,
+ 0x0d, 0x0d, 0x08, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c,
+ 0x0d, 0x08, 0x0d, 0x0c, 0x08, 0x08, 0x00, 0x08,
+ 0x0d, 0x08, 0x0d, 0x08, 0x08, 0x08, 0x0c, 0x08,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x0d, 0x0c, 0x0d, 0x0d,
+ 0x0c, 0x08, 0x0d, 0x0c, 0x0d, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x08, 0x0d, 0x08, 0x0d, 0x08, 0x0d,
+ 0x08, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x00, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x08, 0x0d, 0x0d,
+ 0x0c, 0x08, 0x09, 0x0d, 0x08, 0x0d, 0x0d, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d,
+ 0x0d, 0x08, 0x0d, 0x0c, 0x0c, 0x08, 0x0d, 0x0c,
+};
+
+static guchar m8b1[256] = {
+ 0xf0, 0xf0, 0xf0, 0x00, 0xf0, 0x40, 0x40, 0x70,
+ 0x00, 0x00, 0x40, 0x40, 0x70, 0x00, 0xf0, 0x00,
+ 0xf0, 0x40, 0x00, 0x00, 0xf0, 0x00, 0xf0, 0xf0,
+ 0x00, 0xf0, 0xf0, 0xf0, 0x40, 0xf0, 0x40, 0x40,
+ 0xf0, 0x70, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00,
+ 0xf0, 0x00, 0x00, 0xf0, 0xf0, 0x40, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0xf0, 0x00, 0x00, 0x40, 0x00, 0x70, 0x00,
+ 0x00, 0xf0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00,
+ 0x40, 0xf0, 0x00, 0x00, 0x70, 0x40, 0xf0, 0xf0,
+ 0x00, 0x00, 0x40, 0x40, 0x40, 0x00, 0x70, 0x00,
+ 0xf0, 0x40, 0xf0, 0x70, 0xf0, 0x00, 0x40, 0x70,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0x00, 0x70, 0xf0, 0x40, 0x00, 0xf0,
+ 0xf0, 0x70, 0x70, 0x00, 0xf0, 0x00, 0x40, 0xf0,
+ 0x40, 0x40, 0x00, 0x00, 0x40, 0xf0, 0x40, 0x00,
+ 0xf0, 0x40, 0x00, 0x70, 0x40, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0xf0, 0x40, 0x70, 0x40, 0x70, 0x40,
+ 0xf0, 0x00, 0xf0, 0xf0, 0x40, 0x40, 0xf0, 0x00,
+ 0x00, 0x70, 0xf0, 0x00, 0x40, 0x00, 0x40, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m8b2[256] = {
+ 0x0d, 0x0d, 0x0d, 0x08, 0x0d, 0x0c, 0x0c, 0x0e,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0d, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0d, 0x0d,
+ 0x0c, 0x0d, 0x0d, 0x0d, 0x0c, 0x0d, 0x0c, 0x08,
+ 0x0d, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x0d, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0d, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0d, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x08, 0x0d, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0d, 0x00, 0x0d, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0f, 0x0c, 0x08, 0x0d,
+ 0x0d, 0x08, 0x00, 0x08, 0x0d, 0x08, 0x08, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0c, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x08, 0x0d, 0x0c, 0x0c, 0x08, 0x0c, 0x00,
+ 0x09, 0x08, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x08,
+ 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+};
+
+static guchar m8c1[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0,
+ 0x40, 0x40, 0x70, 0x00, 0x00, 0x40, 0x40, 0xf0,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0x40, 0xf0, 0x40,
+ 0xf0, 0x40, 0xf0, 0x40, 0xf0, 0x00, 0x70, 0x40,
+ 0x70, 0x40, 0x00, 0x40, 0x40, 0xf0, 0x00, 0x40,
+ 0x40, 0x40, 0xf0, 0x40, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0xf0, 0x70, 0x40, 0x40, 0x00, 0x40, 0x00,
+ 0x40, 0x40, 0xf0, 0xf0, 0x70, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x00,
+ 0x70, 0xf0, 0xf0, 0x40, 0x70, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0xf0, 0x00, 0x00, 0x70, 0x40, 0x40,
+ 0x00, 0x70, 0xf0, 0x40, 0xf0, 0x70, 0x70, 0x00,
+ 0x40, 0x00, 0x40, 0x40, 0x70, 0x00, 0x00, 0x00,
+ 0x70, 0x40, 0x00, 0x40, 0x40, 0xf0, 0xf0, 0x00,
+ 0xf0, 0xf0, 0xf0, 0x00, 0x40, 0x00, 0x00, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x70, 0xf0,
+ 0xf0, 0x00, 0x70, 0xf0, 0xf0, 0x00, 0xf0, 0xf0,
+ 0xf0, 0x40, 0x40, 0xf0, 0xf0, 0xf0, 0x00, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0x40, 0xf0,
+ 0xf0, 0x40, 0xf0, 0x40, 0x00, 0x70, 0x70, 0x40,
+ 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0x40, 0xf0, 0x70, 0xf0, 0x40, 0xf0, 0x00,
+ 0xf0, 0x40, 0xf0, 0xf0, 0xf0, 0x00, 0xf0, 0x00,
+ 0x40, 0x00, 0xf0, 0x00, 0x40, 0xf0, 0x00, 0x40,
+ 0x40, 0x00, 0x40, 0x00, 0x80, 0x40, 0x00, 0x40,
+ 0x40, 0x00, 0x70, 0xf0, 0xf0, 0xf0, 0x40, 0x40,
+};
+
+static guchar m8c2[256] = {
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0f,
+ 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x08, 0x0f, 0x0c, 0x0c, 0x08, 0x0c, 0x0f, 0x0e,
+ 0x0d, 0x0e, 0x0d, 0x0c, 0x0f, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x0f, 0x0c, 0x0c,
+ 0x08, 0x08, 0x0f, 0x08, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0f, 0x0e, 0x08, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0f, 0x0f, 0x0c, 0x0c, 0x00, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0f, 0x0f, 0x0c, 0x08, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0f, 0x08, 0x0c, 0x0e, 0x0c, 0x08,
+ 0x08, 0x0e, 0x0f, 0x08, 0x0f, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x08, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x08, 0x0c, 0x0d, 0x0d, 0x08,
+ 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x08, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x08, 0x08, 0x0d,
+ 0x0d, 0x08, 0x0c, 0x0d, 0x0d, 0x0c, 0x0d, 0x0d,
+ 0x0d, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0c, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c, 0x08, 0x0d,
+ 0x0d, 0x08, 0x0d, 0x08, 0x0c, 0x08, 0x00, 0x0c,
+ 0x08, 0x0d, 0x0c, 0x0d, 0x08, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0d, 0x08, 0x0d, 0x0c, 0x0d, 0x0c,
+ 0x0d, 0x0c, 0x0d, 0x0d, 0x0d, 0x0c, 0x0d, 0x0c,
+ 0x0c, 0x08, 0x0d, 0x08, 0x0c, 0x0d, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0d, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0c, 0x08,
+};
+
+static guchar m8d1[256] = {
+ 0x00, 0x40, 0x00, 0x40, 0xf0, 0xf0, 0x00, 0xf0,
+ 0xf0, 0x40, 0xf0, 0x70, 0x00, 0xf0, 0x00, 0x70,
+ 0x70, 0x00, 0x40, 0xf0, 0x70, 0x00, 0xf0, 0x40,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40, 0xf0, 0x70,
+ 0x00, 0x40, 0x00, 0xf0, 0x40, 0x70, 0x40, 0x00,
+ 0xf0, 0x70, 0x00, 0xf0, 0xf0, 0x00, 0x00, 0xf0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x70, 0x40, 0x00, 0x40, 0xf0, 0x00, 0x00,
+ 0x40, 0x00, 0xf0, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x40, 0x00,
+ 0xf0, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0xf0, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0xf0, 0x40, 0x40, 0x00, 0xf0, 0x00,
+ 0x40, 0x00, 0x70, 0x00, 0x00, 0x40, 0xc0, 0x40,
+ 0x40, 0x00, 0x40, 0xf0, 0xf0, 0x00, 0x40, 0xf0,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x70, 0x40,
+ 0x00, 0x40, 0x70, 0xf0, 0x00, 0xf0, 0x00, 0x70,
+ 0x00, 0xf0, 0x00, 0xf0, 0x40, 0x40, 0x00, 0x40,
+ 0xf0, 0x00, 0x70, 0x70, 0x40, 0x00, 0x00, 0xf0,
+ 0x40, 0x40, 0x40, 0xf0, 0x40, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0x40, 0x00, 0x70,
+};
+
+static guchar m8d2[256] = {
+ 0x0c, 0x08, 0x0c, 0x08, 0x0d, 0x0d, 0x0c, 0x0d,
+ 0x0d, 0x0c, 0x0d, 0x08, 0x08, 0x0d, 0x08, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0d, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x02, 0x02, 0x02,
+ 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00,
+ 0x02, 0x02, 0x02, 0x02, 0x0f, 0x08, 0x0f, 0x0e,
+ 0x0c, 0x0c, 0x00, 0x0f, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x0f, 0x08, 0x0c, 0x0f, 0x0f, 0x02, 0x0e, 0x0f,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0e, 0x08, 0x08, 0x0e, 0x0f, 0x08, 0x08,
+ 0x08, 0x0c, 0x0f, 0x0a, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x08,
+ 0x08, 0x0d, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x0e,
+ 0x0c, 0x0c, 0x08, 0x0f, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x02, 0x0c, 0x0f, 0x0e, 0x0e, 0x0c, 0x0c,
+ 0x02, 0x0c, 0x0f, 0x08, 0x0e, 0x08, 0x0f, 0x0e,
+ 0x08, 0x0c, 0x0c, 0x0a, 0x02, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0f, 0x0f, 0x0c, 0x0e, 0x0f,
+ 0x0c, 0x0e, 0x08, 0x0c, 0x08, 0x0c, 0x0e, 0x0e,
+ 0x0c, 0x0c, 0x0e, 0x0f, 0x0c, 0x0f, 0x02, 0x0e,
+ 0x0c, 0x0d, 0x0c, 0x0f, 0x0e, 0x00, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x0e, 0x0e, 0x0e, 0x08, 0x0c, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x02, 0x00, 0x02,
+ 0x02, 0x02, 0x0e, 0x02, 0x0c, 0x0e, 0x0c, 0x0c,
+};
+
+static guchar m8e1[256] = {
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x40, 0x00,
+ 0x70, 0x70, 0xf0, 0x40, 0x00, 0x00, 0x00, 0xf0,
+ 0xf0, 0x40, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xf0, 0x70,
+ 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x40, 0x00, 0x40, 0x70, 0xf0, 0x40, 0x40,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x40, 0x40, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0x40, 0x70, 0x40, 0x40, 0x40,
+ 0x70, 0x00, 0x00, 0x00, 0x40, 0x70, 0x00, 0x00,
+ 0x00, 0xf0, 0x00, 0x40, 0x40, 0x40, 0x40, 0xf0,
+ 0xf0, 0x40, 0x40, 0x70, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x40,
+ 0x40, 0x40, 0x70, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0x00, 0x40, 0x40, 0x40, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0x40, 0x40, 0x70, 0x70, 0x00, 0xf0,
+ 0x00, 0x40, 0xf0, 0x70, 0x00, 0xf0, 0x00, 0x00,
+ 0x40, 0x70, 0x40, 0x70, 0x70, 0x40, 0x00, 0x00,
+ 0x00, 0x70, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0x70, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x40, 0xf0, 0xf0, 0xf0, 0x40, 0x40, 0x70,
+ 0x70, 0x70, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x70, 0x00,
+ 0xc0, 0x40, 0x00, 0x40, 0x40, 0x70, 0x70, 0x40,
+ 0x70, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x40,
+ 0x00, 0x40, 0xf0, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x70, 0x40, 0x00, 0x00, 0xf0,
+ 0x00, 0x00, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x40, 0x00,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0xf0, 0x40, 0x40, 0xf0, 0x70, 0x70, 0xf0, 0x00,
+};
+
+static guchar m8e2[256] = {
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x08, 0x0e, 0x0f, 0x08, 0x02, 0x0c, 0x00, 0x0f,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0f, 0x0e,
+ 0x0c, 0x0c, 0x0e, 0x0e, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0e, 0x0b, 0x0c, 0x02, 0x00, 0x0e, 0x02,
+ 0x0d, 0x0e, 0x08, 0x0c, 0x0c, 0x0f, 0x0c, 0x08,
+ 0x0c, 0x0e, 0x0a, 0x08, 0x0c, 0x0e, 0x0c, 0x0c,
+ 0x0e, 0x0e, 0x0f, 0x08, 0x0f, 0x0c, 0x08, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0e, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0a, 0x02, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x08, 0x0e, 0x0c, 0x08, 0x0e, 0x0e, 0x08, 0x0e,
+ 0x0a, 0x08, 0x0e, 0x0c, 0x0f, 0x08, 0x0f, 0x08,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0e, 0x00, 0x00, 0x02,
+ 0x00, 0x0f, 0x0c, 0x08, 0x0c, 0x0e, 0x0c, 0x0f,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0d, 0x0c, 0x02,
+ 0x0e, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x00, 0x02, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0d, 0x0f, 0x0f, 0x08, 0x08, 0x0a,
+ 0x08, 0x08, 0x0e, 0x08, 0x08, 0x00, 0x08, 0x08,
+ 0x08, 0x08, 0x0e, 0x00, 0x08, 0x0c, 0x08, 0x08,
+ 0x0d, 0x08, 0x0c, 0x08, 0x08, 0x00, 0x08, 0x08,
+ 0x00, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0a, 0x0c,
+ 0x08, 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x08, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x00, 0x00, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0d, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0d, 0x0c, 0x08, 0x0d, 0x0c,
+};
+
+static guchar m8f1[256] = {
+ 0x40, 0x00, 0x40, 0xf0, 0x00, 0xf0, 0x00, 0x40,
+ 0x40, 0xf0, 0x70, 0x00, 0x70, 0x00, 0x00, 0x40,
+ 0x40, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0x40,
+ 0x40, 0x70, 0x00, 0xf0, 0xf0, 0xf0, 0xc0, 0xf0,
+ 0x40, 0x40, 0x00, 0x40, 0x00, 0x40, 0xf0, 0xc0,
+ 0x40, 0xf0, 0xf0, 0x00, 0x40, 0x40, 0x40, 0xf0,
+ 0x00, 0x00, 0x00, 0xf0, 0x40, 0x40, 0x40, 0x40,
+ 0xf0, 0xf0, 0x40, 0xf0, 0x00, 0x00, 0xf0, 0xf0,
+ 0x40, 0x40, 0x70, 0x40, 0xf0, 0xf0, 0x70, 0x40,
+ 0x00, 0xf0, 0x00, 0x00, 0x70, 0xf0, 0xf0, 0x40,
+ 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x70,
+ 0x40, 0x00, 0x00, 0x00, 0x70, 0xc0, 0x40, 0xf0,
+ 0x00, 0x70, 0xf0, 0x70, 0x70, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x40, 0x70, 0x70,
+ 0x40, 0x40, 0x00, 0xf0, 0x40, 0x40, 0xc0, 0x70,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x70, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x40, 0x40, 0x70,
+ 0x40, 0x00, 0x70, 0x70, 0x70, 0x00, 0x40, 0x70,
+ 0x40, 0x40, 0xf0, 0x00, 0x70, 0xf0, 0x40, 0x00,
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0x40, 0xf0, 0x00,
+ 0x40, 0xf0, 0x40, 0x40, 0xf0, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x70, 0x40, 0x40, 0x70, 0xf0, 0x00,
+ 0x40, 0x70, 0xf0, 0xf0, 0x00, 0xf0, 0x40, 0x70,
+ 0xf0, 0x40, 0x80, 0x00, 0x70, 0x40, 0x40, 0xf0,
+ 0x70, 0xf0, 0x70, 0x40, 0x00, 0xf0, 0x40, 0x00,
+};
+
+static guchar m8f2[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x00, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0d, 0x09,
+ 0x08, 0x0d, 0x0d, 0x08, 0x0c, 0x08, 0x0c, 0x0d,
+ 0x08, 0x08, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0d, 0x08, 0x0d, 0x08, 0x08, 0x0d, 0x0d,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x08, 0x0c, 0x00, 0x0d, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x0f, 0x0f, 0x08, 0x0a, 0x0e,
+ 0x08, 0x08, 0x08, 0x0f, 0x08, 0x08, 0x0d, 0x08,
+ 0x0f, 0x02, 0x08, 0x02, 0x08, 0x0d, 0x0c, 0x0d,
+ 0x0f, 0x0f, 0x0d, 0x08, 0x0c, 0x08, 0x0a, 0x08,
+ 0x08, 0x0a, 0x00, 0x08, 0x08, 0x02, 0x0a, 0x0c,
+ 0x08, 0x0a, 0x0f, 0x08, 0x0e, 0x0f, 0x0c, 0x0a,
+ 0x0a, 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x0f, 0x08,
+ 0x0a, 0x0f, 0x0c, 0x0e, 0x0f, 0x0e, 0x0c, 0x0c,
+ 0x0a, 0x0a, 0x00, 0x02, 0x0a, 0x02, 0x02, 0x0a,
+ 0x0c, 0x0c, 0x0e, 0x0c, 0x0e, 0x0e, 0x0f, 0x08,
+ 0x0e, 0x0a, 0x0f, 0x0f, 0x08, 0x0f, 0x0e, 0x08,
+ 0x0f, 0x08, 0x01, 0x02, 0x0c, 0x0c, 0x0c, 0x0f,
+ 0x0e, 0x0b, 0x0c, 0x0c, 0x0c, 0x0f, 0x0c, 0x0c,
+};
+
+static guchar m901[256] = {
+ 0xf0, 0xf0, 0xc0, 0xf0, 0x40, 0xf0, 0xf0, 0x00,
+ 0xc0, 0x00, 0x00, 0xf0, 0x40, 0xf0, 0x70, 0xf0,
+ 0xf0, 0xf0, 0x00, 0x70, 0xf0, 0xf0, 0x70, 0xf0,
+ 0x40, 0xf0, 0xf0, 0x40, 0x00, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x70,
+ 0x40, 0x40, 0x40, 0x00, 0x40, 0x40, 0xf0, 0x40,
+ 0x00, 0xf0, 0xf0, 0x40, 0x40, 0xf0, 0x70, 0x40,
+ 0xf0, 0x70, 0x00, 0x00, 0xf0, 0x00, 0xf0, 0x40,
+ 0x00, 0xf0, 0xf0, 0x40, 0x40, 0x70, 0x00, 0xf0,
+ 0x00, 0x70, 0xf0, 0xf0, 0x40, 0xf0, 0xf0, 0x70,
+ 0xf0, 0xf0, 0x70, 0xf0, 0xf0, 0xf0, 0x70, 0x00,
+ 0x70, 0xf0, 0x00, 0x40, 0xf0, 0xc0, 0xf0, 0x00,
+ 0xf0, 0xf0, 0x40, 0xf0, 0x00, 0x70, 0x40, 0x40,
+ 0x70, 0xf0, 0x00, 0x00, 0x40, 0xf0, 0xf0, 0xf0,
+ 0x40, 0x00, 0xf0, 0x00, 0x40, 0xf0, 0x70, 0xf0,
+ 0xf0, 0x40, 0xf0, 0x00, 0xf0, 0xf0, 0x00, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0x00, 0xf0,
+ 0xc0, 0x70, 0xf0, 0x40, 0x40, 0x00, 0x40, 0xf0,
+ 0x40, 0xf0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40,
+ 0x40, 0xc0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x40, 0xc0, 0xf0, 0x00, 0x40, 0xf0, 0x00,
+ 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0,
+ 0xc0, 0xf0, 0x40, 0x40, 0x40, 0xf0, 0x40, 0x00,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0xf0, 0x00, 0x40, 0x40, 0x40, 0x00, 0x40,
+ 0x40, 0x00, 0xf0, 0x00, 0x40, 0x00, 0x70, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x40,
+ 0x40, 0x40, 0x00, 0x70, 0x40, 0x40, 0x80, 0x40,
+ 0x00, 0xf0, 0x70, 0x00, 0x70, 0x40, 0x00, 0x00,
+ 0xf0, 0x00, 0x00, 0x40, 0x00, 0xf0, 0x00, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0x40, 0xf0, 0x40, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x40,
+};
+
+static guchar m902[256] = {
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0e, 0x0f, 0x0f, 0x08,
+ 0x09, 0x0a, 0x02, 0x0f, 0x0c, 0x0f, 0x08, 0x0f,
+ 0x0f, 0x0f, 0x0a, 0x08, 0x0f, 0x0d, 0x0e, 0x0f,
+ 0x08, 0x0d, 0x0f, 0x0e, 0x0c, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0d, 0x0c, 0x08, 0x02, 0x00,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x0e, 0x0f, 0x0e,
+ 0x08, 0x0d, 0x0d, 0x08, 0x0c, 0x0f, 0x0e, 0x08,
+ 0x0f, 0x00, 0x08, 0x0a, 0x0f, 0x0c, 0x0f, 0x0c,
+ 0x08, 0x0f, 0x0f, 0x08, 0x0e, 0x08, 0x08, 0x0f,
+ 0x08, 0x0c, 0x0d, 0x0d, 0x08, 0x0f, 0x0d, 0x0e,
+ 0x0f, 0x0f, 0x0e, 0x0f, 0x0d, 0x0d, 0x00, 0x02,
+ 0x0e, 0x0d, 0x08, 0x0e, 0x0d, 0x0d, 0x0d, 0x08,
+ 0x0d, 0x09, 0x0e, 0x0f, 0x08, 0x02, 0x08, 0x0c,
+ 0x0e, 0x0d, 0x08, 0x0c, 0x08, 0x0f, 0x0f, 0x0d,
+ 0x0c, 0x08, 0x0d, 0x0c, 0x0e, 0x0f, 0x0c, 0x0d,
+ 0x0d, 0x0c, 0x0d, 0x0c, 0x0d, 0x0f, 0x0c, 0x0f,
+ 0x0f, 0x0d, 0x0f, 0x0f, 0x0d, 0x0c, 0x0c, 0x0d,
+ 0x0f, 0x08, 0x0d, 0x0e, 0x08, 0x0c, 0x08, 0x0d,
+ 0x0c, 0x0f, 0x08, 0x0a, 0x0c, 0x0f, 0x08, 0x0e,
+ 0x0c, 0x0f, 0x08, 0x0e, 0x08, 0x02, 0x0c, 0x0c,
+ 0x0c, 0x0e, 0x0f, 0x0f, 0x08, 0x0c, 0x0f, 0x0c,
+ 0x09, 0x08, 0x0f, 0x08, 0x02, 0x08, 0x0a, 0x0f,
+ 0x0f, 0x0f, 0x0c, 0x0e, 0x0e, 0x0f, 0x0e, 0x08,
+ 0x0f, 0x0a, 0x02, 0x02, 0x08, 0x0c, 0x0e, 0x0c,
+ 0x08, 0x0f, 0x08, 0x0c, 0x0a, 0x0e, 0x08, 0x0e,
+ 0x0c, 0x08, 0x0f, 0x0c, 0x08, 0x08, 0x0e, 0x02,
+ 0x02, 0x02, 0x08, 0x02, 0x0c, 0x0c, 0x0c, 0x0e,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x0e, 0x0e, 0x09, 0x0c,
+ 0x0c, 0x0f, 0x0e, 0x0c, 0x0c, 0x0c, 0x02, 0x02,
+ 0x0f, 0x0c, 0x0c, 0x0e, 0x0c, 0x0f, 0x08, 0x0e,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0d, 0x08, 0x08,
+ 0x02, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0e, 0x0c,
+};
+
+static guchar m911[256] = {
+ 0x40, 0x00, 0xf0, 0x00, 0x40, 0x40, 0x40, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x40, 0x00, 0xf0, 0x00, 0x40, 0x80, 0x40, 0x40,
+ 0x40, 0xf0, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00,
+ 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x00, 0xc0,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x40,
+ 0x70, 0x40, 0xf0, 0x00, 0x40, 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x40,
+ 0x00, 0x00, 0xf0, 0x40, 0x70, 0x00, 0x70, 0x40,
+ 0x70, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0xf0, 0x70, 0x40, 0x70, 0x00, 0x40,
+ 0x00, 0xf0, 0xf0, 0x00, 0xf0, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x70, 0x70, 0x40, 0xf0, 0x00, 0xf0,
+ 0xf0, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x70, 0x40, 0x00, 0x40, 0x40, 0xf0,
+ 0x00, 0xf0, 0x40, 0xf0, 0x00, 0xf0, 0x40, 0x00,
+ 0x70, 0x40, 0xf0, 0x40, 0x40, 0x40, 0x00, 0x70,
+ 0x40, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x00,
+ 0x00, 0x40, 0x70, 0x00, 0x70, 0x00, 0x40, 0x00,
+ 0x40, 0x00, 0x70, 0xf0, 0xc0, 0x40, 0xc0, 0xf0,
+ 0x40, 0xc0, 0x40, 0x40, 0xf0, 0xf0, 0x40, 0x00,
+ 0x70, 0x00, 0x70, 0x40, 0x40, 0x40, 0x00, 0x40,
+ 0xf0, 0x70, 0x40, 0x40, 0x00, 0x40, 0x70, 0xf0,
+ 0x70, 0xf0, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0x00, 0x40, 0x40, 0x00, 0x70, 0xc0,
+ 0xf0, 0x40, 0x40, 0x70, 0xf0, 0xf0, 0x40, 0x70,
+ 0x00, 0x70, 0x00, 0xf0, 0x40, 0x40, 0x70, 0xf0,
+ 0x00, 0x40, 0xc0, 0x00, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0xf0, 0x70, 0x40,
+ 0x00, 0x40, 0x00, 0x40, 0x70, 0x40, 0x00, 0x70,
+};
+
+static guchar m912[256] = {
+ 0x0c, 0x0c, 0x0f, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x09, 0x0c, 0x0c,
+ 0x0c, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x0c, 0x0e, 0x0e, 0x0c, 0x08, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0e,
+ 0x0c, 0x0e, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x00, 0x0e, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0e,
+ 0x0e, 0x08, 0x0f, 0x0c, 0x08, 0x0c, 0x0c, 0x0e,
+ 0x0c, 0x08, 0x0e, 0x00, 0x08, 0x02, 0x02, 0x0c,
+ 0x0c, 0x0e, 0x0f, 0x0e, 0x0e, 0x0e, 0x00, 0x08,
+ 0x0c, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0e, 0x0e,
+ 0x0a, 0x02, 0x0e, 0x0c, 0x0e, 0x0f, 0x0a, 0x0f,
+ 0x0f, 0x0e, 0x0c, 0x08, 0x08, 0x02, 0x02, 0x02,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0f,
+ 0x08, 0x0f, 0x0c, 0x0f, 0x0a, 0x0f, 0x08, 0x0c,
+ 0x0e, 0x0e, 0x0f, 0x0c, 0x08, 0x08, 0x08, 0x00,
+ 0x08, 0x0c, 0x0e, 0x0e, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0e, 0x0e, 0x00, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x08, 0x0e, 0x0d, 0x0d, 0x0e, 0x0f, 0x0f,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x0f, 0x0f, 0x08, 0x0c,
+ 0x00, 0x0c, 0x0e, 0x08, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0f,
+ 0x00, 0x0f, 0x02, 0x0d, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0d, 0x0f, 0x00, 0x0c, 0x0c, 0x0c, 0x08, 0x0d,
+ 0x0d, 0x0c, 0x0c, 0x08, 0x0f, 0x0d, 0x08, 0x08,
+ 0x08, 0x00, 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x08, 0x0c,
+};
+
+static guchar m921[256] = {
+ 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40,
+ 0x00, 0x40, 0x40, 0x00, 0x40, 0xf0, 0x70, 0x00,
+ 0xc0, 0xf0, 0xc0, 0x40, 0x70, 0x70, 0x40, 0x80,
+ 0x40, 0x00, 0x00, 0x00, 0x40, 0x40, 0xf0, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x00,
+ 0x40, 0x70, 0x00, 0x00, 0x70, 0x00, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0x40, 0xf0, 0x40, 0x40, 0x70,
+ 0x40, 0x40, 0xc0, 0x00, 0x40, 0x00, 0x40, 0xf0,
+ 0xc0, 0x00, 0x40, 0x40, 0x70, 0xf0, 0x40, 0x40,
+ 0x70, 0xf0, 0x40, 0x70, 0x00, 0x40, 0x40, 0x40,
+ 0x70, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0,
+ 0x40, 0x40, 0x70, 0xf0, 0x40, 0x40, 0xf0, 0x00,
+ 0x40, 0x40, 0xf0, 0x00, 0xf0, 0xc0, 0xf0, 0x40,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x40, 0x70, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x00, 0x40, 0x40, 0x40, 0x70, 0x40,
+ 0xf0, 0x00, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x00,
+ 0x40, 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0xf0, 0x40, 0xf0, 0x00, 0x70, 0xf0, 0x40,
+ 0xf0, 0x40, 0x70, 0x70, 0xf0, 0x00, 0x00, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x40,
+ 0x40, 0x00, 0x00, 0x40, 0x00, 0x70, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x80, 0x00, 0x00, 0xc0, 0xf0,
+ 0x40, 0xf0, 0x40, 0x40, 0x40, 0x40, 0x00, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x00, 0x40, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0x40, 0xc0, 0x40, 0x40, 0xf0,
+ 0x40, 0x00, 0xf0, 0x40, 0x00, 0x40, 0x00, 0x40,
+ 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x40,
+ 0x40, 0x40, 0x00, 0x40, 0xf0, 0x40, 0x00, 0x40,
+ 0x40, 0x70, 0xf0, 0x00, 0x40, 0x70, 0x40, 0x00,
+ 0x40, 0x00, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x40, 0x70, 0x40, 0xf0, 0x00, 0x00, 0x40,
+};
+
+static guchar m922[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0d, 0x08, 0x0c,
+ 0x0d, 0x0d, 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0d, 0x08,
+ 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x08, 0x08, 0x0c, 0x0c, 0x0d,
+ 0x0d, 0x08, 0x08, 0x08, 0x08, 0x0d, 0x0c, 0x08,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0d,
+ 0x08, 0x08, 0x0c, 0x0d, 0x08, 0x08, 0x0d, 0x08,
+ 0x0c, 0x0c, 0x09, 0x0c, 0x0d, 0x0d, 0x0d, 0x0c,
+ 0x08, 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x00, 0x0c, 0x08, 0x0a, 0x08, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x0d, 0x08, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x08,
+ 0x08, 0x0d, 0x08, 0x0d, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0a, 0x08,
+ 0x08, 0x08, 0x0c, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d,
+ 0x08, 0x0d, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x08, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0c,
+};
+
+static guchar m931[256] = {
+ 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x70, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x70,
+ 0xf0, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00,
+ 0xf0, 0x70, 0xf0, 0x00, 0x40, 0x40, 0xc0, 0xc0,
+ 0xf0, 0xc0, 0xf0, 0x70, 0xc0, 0x40, 0xf0, 0x40,
+ 0xf0, 0x40, 0x40, 0xf0, 0x70, 0x00, 0xf0, 0xf0,
+ 0x00, 0x00, 0x70, 0x40, 0x40, 0x70, 0x40, 0x40,
+ 0x00, 0x00, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x40,
+ 0xc0, 0x40, 0x80, 0xf0, 0x00, 0xf0, 0x00, 0x00,
+ 0x40, 0x40, 0x40, 0x00, 0xf0, 0x40, 0x70, 0x40,
+ 0x40, 0x00, 0x40, 0xf0, 0x70, 0x00, 0x40, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x40, 0x00, 0x70, 0x40, 0xf0, 0x40,
+ 0x40, 0x40, 0x00, 0x40, 0x40, 0xf0, 0x40, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0xf0, 0x40, 0xf0, 0x40,
+ 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x40, 0x40, 0xf0, 0x40, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x00, 0xf0, 0x40, 0xf0, 0x70,
+ 0x40, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0xc0, 0x40, 0x00, 0x40, 0xf0,
+ 0x40, 0x40, 0x00, 0x40, 0xf0, 0xf0, 0x70, 0x00,
+ 0xf0, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x00,
+ 0x00, 0x70, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0xf0, 0x40, 0x40, 0x40, 0x40,
+ 0x70, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00,
+ 0x70, 0xf0, 0x00, 0x40, 0x00, 0x00, 0x70, 0x70,
+ 0x70, 0x40, 0x00, 0x00, 0x40, 0x70, 0xc0, 0x40,
+ 0x00, 0xf0, 0x40, 0x00, 0xf0, 0x70, 0x40, 0x40,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x00, 0x40, 0x00, 0x00,
+};
+
+static guchar m932[256] = {
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x08, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0d, 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0c, 0x0d, 0x0c, 0x0d, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0d, 0x0d,
+ 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x02, 0x08,
+ 0x08, 0x00, 0x08, 0x08, 0x00, 0x08, 0x0c, 0x0c,
+ 0x09, 0x0c, 0x0d, 0x0d, 0x0c, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x08, 0x08, 0x0e, 0x08, 0x0c, 0x0c, 0x09, 0x08,
+ 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x0d, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0d, 0x08,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0c, 0x0c, 0x0e,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0d, 0x0c, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x09, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x08, 0x08, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x0c, 0x08, 0x0c, 0x0d, 0x0c, 0x08, 0x08, 0x0c,
+ 0x0c, 0x08, 0x0e, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x08, 0x0d, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x08, 0x0c, 0x08,
+ 0x00, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m941[256] = {
+ 0x00, 0x40, 0x40, 0x70, 0xc0, 0x00, 0x00, 0x70,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x70, 0x00, 0x00, 0x70, 0x70, 0x40, 0x40, 0x40,
+ 0xf0, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x70, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0x40, 0x40, 0x40, 0xf0, 0x70, 0x00,
+ 0xf0, 0x00, 0x70, 0x40, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x70, 0x00, 0x40, 0xf0, 0x40, 0x00, 0x00,
+ 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0xf0, 0x70, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x70, 0xf0, 0x40, 0x00, 0x70, 0x40,
+ 0x70, 0x40, 0x70, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x70, 0x40, 0x00, 0x40, 0x40, 0x40,
+ 0x70, 0x40, 0x40, 0x00, 0x00, 0x70, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0xf0, 0x70, 0xf0,
+ 0x00, 0x70, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m942[256] = {
+ 0x0c, 0x08, 0x08, 0x0c, 0x09, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0d, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x0c, 0x00, 0x08, 0x08, 0x08, 0x09, 0x00, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0a, 0x0c,
+ 0x0c, 0x00, 0x08, 0x08, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0d, 0x0d, 0x00, 0x08, 0x0c, 0x08, 0x0c,
+ 0x08, 0x08, 0x08, 0x09, 0x08, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0e, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x0d, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02,
+ 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00,
+ 0x02, 0x02, 0x02, 0x00, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02,
+};
+
+static guchar m951[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0xf0, 0x00, 0x70, 0xf0, 0x40, 0x00, 0x00, 0x70,
+ 0x40, 0xf0, 0x70, 0xf0, 0x40, 0x40, 0x40, 0xf0,
+ 0x00, 0xf0, 0x80, 0xf0, 0xf0, 0x00, 0x70, 0x00,
+ 0xf0, 0x70, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x70, 0x40, 0x70, 0xf0, 0xf0, 0xf0, 0x40, 0x70,
+ 0xf0, 0x40, 0x00, 0x40, 0x40, 0xf0, 0x00, 0x00,
+ 0x00, 0x80, 0x70, 0x00, 0x40, 0x00, 0x40, 0x00,
+ 0x00, 0x70, 0x40, 0xf0, 0xf0, 0x40, 0x70, 0x40,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x40, 0xf0,
+ 0x40, 0x40, 0xf0, 0x40, 0x70, 0x70, 0x00, 0x00,
+ 0x40, 0x40, 0x40, 0x40, 0xf0, 0xf0, 0xf0, 0x00,
+ 0x70, 0x40, 0x40, 0x00, 0xf0, 0x40, 0x40, 0x40,
+ 0x40, 0xf0, 0xf0, 0x00, 0x40, 0x70, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m952[256] = {
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02,
+ 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x02, 0x0d,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x02,
+ 0x0d, 0x08, 0x0c, 0x0d, 0x08, 0x08, 0x0c, 0x08,
+ 0x0c, 0x0d, 0x00, 0x0d, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x08, 0x08, 0x08,
+ 0x0d, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0d, 0x0d, 0x0d, 0x08, 0x08,
+ 0x0d, 0x0c, 0x00, 0x0c, 0x0c, 0x0d, 0x0c, 0x08,
+ 0x0c, 0x0d, 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x08, 0x00,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x08,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x08, 0x08,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x0a, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00,
+};
+
+static guchar m961[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x40, 0x00,
+ 0x00, 0xf0, 0x40, 0x00, 0x40, 0x40, 0x40, 0x00,
+ 0x70, 0x00, 0xf0, 0x00, 0x40, 0x00, 0xf0, 0x70,
+ 0x00, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0x40, 0x40, 0x40, 0xf0, 0x40, 0x40, 0x00, 0xf0,
+ 0xf0, 0x40, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0x00, 0x70,
+ 0xf0, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0x00, 0x40, 0xf0, 0xf0, 0xf0, 0x70, 0x70, 0x00,
+ 0x00, 0x00, 0xf0, 0x00, 0x70, 0x00, 0x40, 0x00,
+ 0xf0, 0x00, 0x70, 0xf0, 0x40, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0x00, 0x70, 0x40, 0x40, 0xf0, 0x40, 0x40,
+ 0x00, 0x40, 0x40, 0x40, 0x40, 0xf0, 0xf0, 0x00,
+ 0x70, 0x40, 0xf0, 0xf0, 0x00, 0xf0, 0xf0, 0x70,
+ 0x00, 0x40, 0x00, 0x00, 0xf0, 0xf0, 0x40, 0x70,
+ 0xf0, 0xf0, 0x40, 0xf0, 0xf0, 0x40, 0x00, 0x40,
+ 0x70, 0x00, 0x00, 0xf0, 0x40, 0x40, 0x40, 0xf0,
+ 0xf0, 0x40, 0xf0, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x70, 0xf0, 0x70, 0x40, 0x70, 0x00, 0x70, 0xf0,
+ 0x70, 0x70, 0x40, 0xf0, 0x70, 0x00, 0x00, 0x00,
+ 0xf0, 0xf0, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0x00, 0xf0, 0x40, 0xf0, 0xf0, 0xf0, 0xf0, 0x00,
+ 0x00, 0x70, 0x40, 0x00, 0x00, 0xf0, 0xf0, 0x00,
+ 0x40, 0xf0, 0x40, 0xf0, 0xf0, 0x40, 0x40, 0x40,
+ 0x00, 0x00, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0xc0, 0xf0, 0x70, 0x00, 0x00, 0x00, 0xc0,
+ 0xf0, 0x40, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0xf0,
+ 0x00, 0xf0, 0x40, 0xf0, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m962[256] = {
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x02, 0x02, 0x00, 0x0f, 0x0a, 0x0c, 0x02,
+ 0x0c, 0x0f, 0x0e, 0x0c, 0x0c, 0x08, 0x08, 0x08,
+ 0x0c, 0x08, 0x0f, 0x08, 0x0c, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x0e, 0x0f, 0x0a, 0x0a, 0x02, 0x02, 0x08,
+ 0x08, 0x0c, 0x0c, 0x0f, 0x0e, 0x0e, 0x08, 0x0f,
+ 0x0f, 0x08, 0x0f, 0x0c, 0x0f, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x0c, 0x0f, 0x0f, 0x0f, 0x0c, 0x0c,
+ 0x0f, 0x0c, 0x08, 0x0c, 0x0e, 0x0a, 0x08, 0x08,
+ 0x0c, 0x08, 0x08, 0x0f, 0x0d, 0x0d, 0x0d, 0x0f,
+ 0x08, 0x0e, 0x0f, 0x0d, 0x0f, 0x00, 0x00, 0x02,
+ 0x02, 0x02, 0x0f, 0x0c, 0x0e, 0x0c, 0x08, 0x0c,
+ 0x0d, 0x0c, 0x0e, 0x0d, 0x0e, 0x0f, 0x0f, 0x0f,
+ 0x0d, 0x08, 0x00, 0x08, 0x0c, 0x0d, 0x0c, 0x08,
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0f, 0x0f, 0x0c,
+ 0x0e, 0x0c, 0x0d, 0x0f, 0x08, 0x0f, 0x0d, 0x0a,
+ 0x02, 0x0c, 0x0c, 0x0c, 0x0f, 0x0d, 0x08, 0x0e,
+ 0x0f, 0x0f, 0x08, 0x0d, 0x0f, 0x08, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0c, 0x09, 0x0c, 0x08, 0x08, 0x0f,
+ 0x0d, 0x0c, 0x0d, 0x08, 0x0c, 0x08, 0x0c, 0x08,
+ 0x0e, 0x0d, 0x08, 0x0e, 0x0c, 0x08, 0x0a, 0x09,
+ 0x0c, 0x0e, 0x08, 0x0d, 0x0e, 0x0a, 0x0a, 0x0c,
+ 0x0f, 0x0f, 0x0c, 0x0c, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0c, 0x0f, 0x0c, 0x0d, 0x0f, 0x0f, 0x0f, 0x0a,
+ 0x08, 0x08, 0x0e, 0x0c, 0x0c, 0x0f, 0x0d, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x02, 0x0c, 0x0d, 0x0d, 0x08, 0x0c, 0x08, 0x08,
+ 0x0f, 0x0f, 0x0f, 0x08, 0x08, 0x08, 0x08, 0x0f,
+ 0x0d, 0x0c, 0x0d, 0x02, 0x08, 0x0c, 0x0f, 0x0f,
+ 0x0c, 0x0f, 0x0c, 0x0d, 0x08, 0x0c, 0x02, 0x0c,
+};
+
+static guchar m971[256] = {
+ 0xf0, 0x00, 0x40, 0x40, 0x70, 0x40, 0xf0, 0xf0,
+ 0x70, 0x40, 0x70, 0x00, 0x00, 0x70, 0x70, 0x70,
+ 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xf0, 0x00,
+ 0x00, 0xf0, 0x40, 0x40, 0xf0, 0x40, 0xf0, 0x00,
+ 0x00, 0x40, 0x40, 0x40, 0x70, 0x00, 0x00, 0xf0,
+ 0x40, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x40, 0xf0, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x70, 0x00,
+ 0x00, 0x40, 0xf0, 0x40, 0xf0, 0x00, 0x70, 0x00,
+ 0xf0, 0x70, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x80, 0x70, 0x00, 0x00, 0x40, 0xf0, 0x40,
+ 0x40, 0x70, 0x40, 0x40, 0xf0, 0x00, 0xf0, 0x00,
+ 0x70, 0xf0, 0xf0, 0x40, 0x70, 0x00, 0x70, 0x40,
+ 0x70, 0xf0, 0x40, 0x70, 0x00, 0xf0, 0x40, 0x00,
+ 0x00, 0x70, 0x00, 0x40, 0xf0, 0x00, 0x40, 0xc0,
+ 0x40, 0x70, 0xf0, 0x40, 0x70, 0x40, 0x00, 0x40,
+ 0x40, 0x70, 0x00, 0x00, 0x70, 0x70, 0x70, 0x00,
+ 0x00, 0x40, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x70, 0x40, 0x40, 0x00, 0x70, 0x00, 0x40, 0x40,
+ 0xf0, 0x00, 0x40, 0x70, 0x00, 0x00, 0x70, 0x00,
+ 0xf0, 0x00, 0x00, 0xf0, 0x40, 0xf0, 0x40, 0x00,
+ 0x00, 0x40, 0x40, 0x70, 0x70, 0x40, 0x40, 0x00,
+ 0x40, 0x40, 0x40, 0x00, 0x40, 0x00, 0x40, 0x40,
+ 0x00, 0x40, 0x00, 0x70, 0x40, 0x40, 0xf0, 0x40,
+ 0x70, 0x40, 0x40, 0xf0, 0x40, 0x40, 0x40, 0x00,
+ 0x40, 0x40, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x40,
+ 0x40, 0x40, 0x00, 0x40, 0xf0, 0x40, 0x40, 0x00,
+ 0x40, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x40,
+ 0x00, 0x40, 0x70, 0xf0, 0x40, 0x70, 0xf0, 0x40,
+ 0x40, 0x00, 0x40, 0xf0, 0x00, 0x00, 0x00, 0xf0,
+};
+
+static guchar m972[256] = {
+ 0x0f, 0x02, 0x0c, 0x08, 0x0e, 0x0c, 0x0f, 0x0f,
+ 0x0e, 0x0e, 0x08, 0x0c, 0x08, 0x0e, 0x0e, 0x0e,
+ 0x0c, 0x0d, 0x0c, 0x0f, 0x08, 0x08, 0x0f, 0x08,
+ 0x0c, 0x0d, 0x08, 0x08, 0x0f, 0x0c, 0x0f, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x02, 0x0c, 0x0c,
+ 0x0f, 0x08, 0x0f, 0x08, 0x08, 0x0c, 0x08, 0x08,
+ 0x0e, 0x0f, 0x0c, 0x08, 0x08, 0x0d, 0x0e, 0x0c,
+ 0x08, 0x08, 0x0d, 0x0c, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x08, 0x0c, 0x00, 0x08, 0x00, 0x00,
+ 0x08, 0x09, 0x0e, 0x02, 0x00, 0x08, 0x0f, 0x08,
+ 0x0c, 0x0a, 0x0c, 0x0e, 0x0d, 0x08, 0x0f, 0x08,
+ 0x0e, 0x0f, 0x0f, 0x08, 0x08, 0x02, 0x0c, 0x08,
+ 0x0c, 0x0f, 0x0c, 0x08, 0x0c, 0x09, 0x0c, 0x08,
+ 0x0c, 0x00, 0x0c, 0x0e, 0x0f, 0x08, 0x0e, 0x0d,
+ 0x0c, 0x08, 0x0d, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x00, 0x08,
+ 0x0c, 0x08, 0x0c, 0x0f, 0x08, 0x0f, 0x0c, 0x0d,
+ 0x00, 0x02, 0x02, 0x08, 0x0e, 0x08, 0x08, 0x0c,
+ 0x0e, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0f, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0d, 0x08, 0x0c, 0x0f, 0x0c, 0x0f, 0x0c, 0x02,
+ 0x08, 0x08, 0x0a, 0x0c, 0x0a, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x08, 0x08, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x02, 0x02,
+ 0x00, 0x02, 0x02, 0x02, 0x02, 0x0e, 0x08, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0f, 0x08, 0x0a, 0x0f, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0c, 0x0c, 0x0d,
+};
+
+static guchar m981[256] = {
+ 0x80, 0xf0, 0xf0, 0xf0, 0x00, 0xf0, 0xf0, 0x40,
+ 0xf0, 0x00, 0xc0, 0x00, 0xf0, 0x40, 0x40, 0x70,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0x00, 0x40, 0xf0,
+ 0xf0, 0x40, 0x70, 0x00, 0x40, 0x00, 0x40, 0x00,
+ 0x40, 0x70, 0x00, 0x40, 0x70, 0x40, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x70, 0xf0, 0x40, 0x40,
+ 0xc0, 0x00, 0x40, 0x40, 0x70, 0x40, 0x00, 0x70,
+ 0xf0, 0x80, 0x00, 0xf0, 0x70, 0x70, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0xf0, 0x40,
+ 0x00, 0x00, 0x40, 0x70, 0xf0, 0xf0, 0xf0, 0x70,
+ 0x00, 0x40, 0x40, 0x40, 0xf0, 0x70, 0x40, 0x40,
+ 0xf0, 0x40, 0xc0, 0x70, 0x00, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0xc0, 0x40, 0xf0,
+ 0x00, 0x00, 0x40, 0xf0, 0x40, 0x00, 0x00, 0xf0,
+ 0x70, 0x70, 0x00, 0x70, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x00, 0x70, 0x40, 0x00, 0x40, 0x40, 0xf0,
+ 0x40, 0xf0, 0x00, 0x00, 0x40, 0x00, 0x70, 0x40,
+ 0x40, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x70, 0xf0, 0x40, 0x70, 0x80,
+ 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x00, 0x00, 0xf0,
+ 0x00, 0xc0, 0xf0, 0x40, 0x00, 0x40, 0x40, 0x40,
+ 0x00, 0x70, 0x40, 0x70, 0x00, 0xf0, 0xf0, 0xf0,
+ 0x00, 0x00, 0x70, 0x40, 0xf0, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0x00,
+};
+
+static guchar m982[256] = {
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0c, 0x0d, 0x0d, 0x0c,
+ 0x0d, 0x08, 0x0d, 0x08, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x08, 0x08, 0x0c, 0x0d,
+ 0x0d, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x00, 0x0d, 0x08, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0d, 0x0d, 0x08, 0x0d, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0d, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0d, 0x0d, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x09, 0x08, 0x08, 0x0c,
+ 0x0d, 0x0c, 0x09, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0d,
+ 0x08, 0x0c, 0x0c, 0x0d, 0x08, 0x08, 0x08, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x00, 0x02,
+ 0x02, 0x02, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02,
+ 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x0d, 0x0c, 0x00, 0x08, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x08, 0x0d, 0x0c, 0x0c, 0x00, 0x08, 0x0c, 0x08,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x00, 0x0c, 0x09,
+ 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x02, 0x00,
+ 0x00, 0x02, 0x02, 0x02, 0x00, 0x02, 0x00, 0x00,
+ 0x02, 0x02, 0x02, 0x0d, 0x09, 0x08, 0x02, 0x0f,
+ 0x00, 0x09, 0x0d, 0x0c, 0x08, 0x0c, 0x08, 0x0e,
+ 0x02, 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x01, 0x0d,
+ 0x08, 0x08, 0x0c, 0x08, 0x0d, 0x08, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0d, 0x0d, 0x0d, 0x08,
+};
+
+static guchar m991[256] = {
+ 0x00, 0x00, 0x40, 0xf0, 0x00, 0x70, 0x00, 0x40,
+ 0x40, 0xf0, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x00,
+ 0xf0, 0x40, 0x70, 0xf0, 0x70, 0x40, 0x40, 0x40,
+ 0xf0, 0x00, 0x40, 0x40, 0x40, 0x70, 0xf0, 0x40,
+ 0xf0, 0x70, 0x40, 0x00, 0x70, 0x00, 0x40, 0x40,
+ 0xf0, 0x00, 0x00, 0x40, 0x70, 0x00, 0x70, 0x00,
+ 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x40, 0x40, 0x40, 0x70, 0x70, 0x00,
+ 0x40, 0x40, 0x70, 0x00, 0x00, 0xf0, 0x40, 0x40,
+ 0x40, 0xf0, 0x00, 0xf0, 0xf0, 0xc0, 0x40, 0x00,
+ 0x70, 0xf0, 0xf0, 0x00, 0xc0, 0x70, 0x00, 0xf0,
+ 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x70,
+ 0x70, 0xf0, 0x00, 0x40, 0x00, 0xc0, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40, 0x00,
+ 0xf0, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0x00,
+ 0x40, 0xc0, 0x40, 0xf0, 0xf0, 0x40, 0x00, 0x00,
+ 0x00, 0xc0, 0x40, 0x00, 0x70, 0x40, 0x00, 0x40,
+ 0x00, 0xf0, 0x00, 0x40, 0x70, 0x70, 0x70, 0x00,
+ 0x70, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0xf0, 0xf0, 0x40, 0x40, 0xf0, 0x00, 0x00,
+ 0x70, 0xc0, 0x40, 0x70, 0x40, 0xf0, 0x40, 0xf0,
+ 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x40, 0x40, 0xf0, 0x70, 0x00,
+ 0x40, 0xf0, 0x70, 0x00, 0x40, 0x40, 0x00, 0x00,
+ 0x70, 0x40, 0x00, 0x70, 0x00, 0x40, 0x40, 0xf0,
+};
+
+static guchar m992[256] = {
+ 0x0c, 0x08, 0x0c, 0x0d, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0d, 0x0d, 0x08, 0x0d, 0x02, 0x08, 0x00,
+ 0x0f, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x0d, 0x0c,
+ 0x09, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x08,
+ 0x08, 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0d, 0x08, 0x0c,
+ 0x0c, 0x0d, 0x08, 0x0d, 0x0d, 0x09, 0x0c, 0x08,
+ 0x0c, 0x0d, 0x0d, 0x0c, 0x0f, 0x0e, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x00, 0x02, 0x00, 0x02, 0x02, 0x00, 0x02,
+ 0x02, 0x02, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x02, 0x02, 0x00, 0x02, 0x00, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0f, 0x0e,
+ 0x0e, 0x0f, 0x08, 0x08, 0x0c, 0x0d, 0x0c, 0x08,
+ 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0f, 0x0c, 0x0c,
+ 0x0f, 0x08, 0x08, 0x0c, 0x0d, 0x0d, 0x0d, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x0d, 0x0d, 0x0c, 0x08, 0x08,
+ 0x08, 0x0d, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x08,
+ 0x08, 0x0d, 0x0c, 0x0c, 0x08, 0x00, 0x08, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x08, 0x0c, 0x0c, 0x0d, 0x08, 0x0d,
+ 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0d, 0x0c, 0x08,
+ 0x0c, 0x0d, 0x00, 0x08, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+};
+
+static guchar m9a1[256] = {
+ 0x00, 0xf0, 0x40, 0x40, 0x40, 0x70, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x40, 0x40, 0x00, 0xf0, 0xf0,
+ 0x40, 0x40, 0x70, 0x70, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x40, 0x00, 0x40, 0x40, 0x40, 0x00, 0x00, 0x40,
+ 0x70, 0x00, 0x00, 0xf0, 0x00, 0x40, 0x40, 0x00,
+ 0xf0, 0x00, 0x00, 0x40, 0x00, 0x40, 0xc0, 0xf0,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00,
+ 0xf0, 0x40, 0x70, 0xf0, 0x40, 0xf0, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x40, 0x40, 0xf0, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0xf0, 0x40, 0xf0,
+ 0x00, 0x00, 0xf0, 0xf0, 0x00, 0x40, 0x00, 0xf0,
+ 0x00, 0x00, 0xf0, 0x00, 0x70, 0xf0, 0x00, 0x00,
+ 0x00, 0xf0, 0xf0, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf0, 0x00, 0x40, 0x00, 0x40, 0x70, 0x40, 0x40,
+ 0x70, 0x00, 0x40, 0x00, 0x40, 0x40, 0x40, 0x00,
+ 0xf0, 0x40, 0x00, 0x40, 0x70, 0x00, 0x40, 0x40,
+ 0x70, 0x40, 0x00, 0x40, 0x70, 0x00, 0x40, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x70,
+ 0x40, 0x70, 0x40, 0xf0, 0xf0, 0x40, 0x40, 0x40,
+ 0xf0, 0x00, 0x00, 0x40, 0x40, 0x00, 0x70, 0x70,
+ 0x40, 0x00, 0x70, 0x70, 0x40, 0xc0, 0x70, 0x40,
+ 0x00, 0x40, 0x70, 0x70, 0x40, 0x70, 0xf0, 0x70,
+ 0x00, 0x70, 0x40, 0x40, 0x70, 0x40, 0x00, 0x70,
+ 0x00, 0x40, 0x40, 0x70, 0x00, 0x40, 0x00, 0x40,
+};
+
+static guchar m9a2[256] = {
+ 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x09, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0d, 0x0d,
+ 0x08, 0x0c, 0x00, 0x00, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x00, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0d, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0c, 0x0c, 0x0d, 0x0c, 0x0d, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0d,
+ 0x08, 0x0c, 0x0d, 0x0d, 0x08, 0x08, 0x0c, 0x0d,
+ 0x0c, 0x08, 0x0d, 0x08, 0x0c, 0x0d, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0d, 0x0c, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x02, 0x02,
+ 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02,
+ 0x0f, 0x08, 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0e, 0x0e, 0x08, 0x0c, 0x0c, 0x08, 0x0a, 0x0e,
+ 0x0f, 0x0c, 0x0a, 0x0c, 0x0e, 0x08, 0x0c, 0x0c,
+ 0x0e, 0x0e, 0x0e, 0x08, 0x08, 0x02, 0x0c, 0x0c,
+ 0x08, 0x08, 0x0c, 0x02, 0x02, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0e, 0x0c, 0x0f, 0x0d, 0x0c, 0x0c, 0x08,
+ 0x0f, 0x08, 0x08, 0x08, 0x0c, 0x08, 0x08, 0x0e,
+ 0x08, 0x0e, 0x08, 0x0c, 0x08, 0x09, 0x0e, 0x0c,
+ 0x08, 0x08, 0x08, 0x0e, 0x0c, 0x0e, 0x0d, 0x0e,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0e, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x08,
+};
+
+static guchar m9b1[256] = {
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x70, 0x00,
+ 0x40, 0x40, 0x00, 0x40, 0x40, 0x40, 0x40, 0x00,
+ 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x70, 0x40, 0xf0, 0x40, 0x40, 0x00, 0x00, 0x70,
+ 0x40, 0x00, 0x70, 0x70, 0x00, 0x70, 0x40, 0xf0,
+ 0x70, 0x70, 0xf0, 0x40, 0x00, 0x40, 0x70, 0x70,
+ 0x00, 0xf0, 0x70, 0x40, 0x40, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x40, 0x70, 0xf0, 0x40, 0x00, 0x00,
+ 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x40, 0x40, 0x70, 0x70, 0xf0,
+ 0x00, 0x70, 0x00, 0x00, 0xf0, 0x40, 0x40, 0x40,
+ 0x70, 0x00, 0xf0, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x40, 0x00,
+ 0x40, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0xf0,
+ 0x00, 0x00, 0x00, 0x40, 0x70, 0x40, 0x00, 0x40,
+ 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x40, 0x00, 0x00, 0x70, 0x40, 0x40, 0x40, 0x40,
+ 0x00, 0x40, 0x40, 0x40, 0x00, 0x40, 0xf0, 0x40,
+ 0x40, 0xf0, 0x70, 0x70, 0x40, 0x00, 0x70, 0x70,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0xf0,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x70, 0x40, 0x70, 0xf0, 0x40, 0x70, 0xf0, 0x00,
+ 0x40, 0x40, 0x40, 0x00, 0x70, 0x00, 0x00, 0x40,
+ 0x40, 0x70, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40,
+ 0x70, 0x40, 0x00, 0x00, 0x00, 0x00, 0x70, 0x40,
+ 0x40, 0xf0, 0x70, 0x00, 0x00, 0x00, 0x40, 0x70,
+ 0x40, 0x70, 0x70, 0x00, 0x70, 0x00, 0xf0, 0x40,
+ 0x40, 0x00, 0x00, 0x70, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x70, 0x70, 0x70, 0xf0, 0x40, 0x00, 0x40,
+ 0xf0, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0x70, 0x70, 0x70, 0x40, 0x00, 0x70, 0x00, 0x40,
+ 0x40, 0x40, 0x40, 0x00, 0x00, 0x40, 0x00, 0x40,
+};
+
+static guchar m9b2[256] = {
+ 0x08, 0x0c, 0x08, 0x0e, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0e, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0a,
+ 0x0c, 0x0c, 0x0c, 0x02, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x08, 0x08, 0x08, 0x0c, 0x0e,
+ 0x0c, 0x08, 0x0c, 0x0e, 0x0c, 0x0c, 0x08, 0x0d,
+ 0x0c, 0x0c, 0x09, 0x0c, 0x08, 0x08, 0x0c, 0x0e,
+ 0x08, 0x0d, 0x0e, 0x0c, 0x08, 0x0c, 0x00, 0x0c,
+ 0x08, 0x08, 0x0c, 0x0e, 0x0f, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0c, 0x02,
+ 0x0e, 0x0a, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0f,
+ 0x08, 0x0e, 0x0c, 0x08, 0x0f, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x08, 0x08, 0x00, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x08, 0x08, 0x0d,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x00, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x08, 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x08,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x00,
+ 0x08, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x09,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x08, 0x0c, 0x0d, 0x08, 0x0c, 0x0d, 0x0c,
+ 0x08, 0x08, 0x00, 0x08, 0x00, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x08, 0x08, 0x08, 0x00, 0x08,
+ 0x00, 0x00, 0x08, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x08,
+ 0x0c, 0x00, 0x00, 0x08, 0x08, 0x00, 0x08, 0x0c,
+ 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x08, 0x08,
+};
+
+static guchar m9c1[256] = {
+ 0x40, 0x00, 0x40, 0x00, 0x70, 0x00, 0x70, 0x00,
+ 0x70, 0x70, 0x70, 0x40, 0x70, 0xf0, 0x00, 0x40,
+ 0xf0, 0x40, 0xf0, 0x70, 0x70, 0xf0, 0x40, 0x00,
+ 0x40, 0x40, 0x40, 0x70, 0x40, 0x00, 0x40, 0x00,
+ 0x00, 0x70, 0x40, 0x40, 0x70, 0xf0, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x00, 0x00, 0x70, 0x70, 0x70,
+ 0x70, 0x40, 0xf0, 0x00, 0x00, 0x40, 0x40, 0x40,
+ 0x00, 0x70, 0x70, 0xf0, 0x00, 0x40, 0x70, 0x00,
+ 0x00, 0x40, 0x00, 0x40, 0x40, 0x40, 0x70, 0xf0,
+ 0x70, 0xc0, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x40, 0x00, 0x70, 0x40, 0x40, 0x00, 0x40, 0xf0,
+ 0x40, 0x00, 0x70, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x70, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70,
+ 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x40, 0x00, 0x40, 0x00, 0x00, 0x40, 0x70, 0x40,
+ 0x70, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40, 0xf0,
+ 0x00, 0xf0, 0x00, 0x70, 0x70, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x40, 0xf0, 0xf0, 0x00, 0xf0, 0x40,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m9c2[256] = {
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0d, 0x0c, 0x08,
+ 0x09, 0x08, 0x0d, 0x0c, 0x0c, 0x09, 0x08, 0x0c,
+ 0x00, 0x00, 0x00, 0x08, 0x0c, 0x0c, 0x08, 0x08,
+ 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x08, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x08,
+ 0x00, 0x0c, 0x0d, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x0c, 0x09,
+ 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x00, 0x00, 0x00, 0x08, 0x0c, 0x0c, 0x08,
+ 0x00, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x02, 0x00, 0x00, 0x02,
+ 0x00, 0x02, 0x02, 0x00, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, 0x02, 0x00,
+ 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x02, 0x00, 0x02, 0x02, 0x00,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x0d, 0x0c, 0x0d,
+ 0x08, 0x0d, 0x0c, 0x08, 0x08, 0x0c, 0x08, 0x08,
+ 0x00, 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0d, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+};
+
+static guchar m9d1[256] = {
+ 0x00, 0x00, 0x40, 0x70, 0x00, 0x00, 0x70, 0x70,
+ 0x70, 0xf0, 0x00, 0x40, 0x00, 0x00, 0x70, 0x00,
+ 0x00, 0x40, 0x70, 0x00, 0x00, 0x70, 0x00, 0x40,
+ 0x40, 0x00, 0x00, 0xf0, 0x40, 0x40, 0x40, 0x70,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0xf0, 0x00,
+ 0xf0, 0x00, 0x70, 0x70, 0x70, 0x00, 0x00, 0x40,
+ 0x40, 0x00, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0xf0, 0x40, 0x40, 0x70, 0x70,
+ 0x00, 0x70, 0x40, 0x40, 0x70, 0x40, 0x70, 0x40,
+ 0x70, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0xf0, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x70, 0x00, 0x00, 0x70, 0xf0, 0x70, 0x40,
+ 0xf0, 0xf0, 0x40, 0x40, 0x70, 0x40, 0x00, 0x00,
+ 0x00, 0x40, 0x40, 0x40, 0xf0, 0x00, 0x00, 0x70,
+ 0x40, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x00, 0x70, 0x40, 0x40, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x40, 0x70,
+ 0x00, 0x70, 0x40, 0x00, 0x00, 0x40, 0x40, 0x70,
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x40,
+ 0x40, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0x40, 0x70, 0x40, 0x00, 0x40, 0xf0,
+ 0x00, 0x40, 0x70, 0x00, 0xf0, 0x40, 0x00, 0x00,
+ 0x70, 0x40, 0x70, 0x70, 0x40, 0x00, 0x00, 0x40,
+ 0x00, 0x70, 0x70, 0x40, 0xf0, 0x00, 0x70, 0x40,
+ 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x70, 0x40, 0x40, 0x40, 0xc0,
+ 0x00, 0x70, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x70, 0x40,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0x70, 0x40, 0x70,
+ 0x40, 0x00, 0xf0, 0x40, 0x40, 0x00, 0x00, 0x00,
+ 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x70, 0x40, 0x00,
+};
+
+static guchar m9d2[256] = {
+ 0x0c, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08,
+ 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0d, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x08, 0x0c, 0x0d, 0x08,
+ 0x0d, 0x0c, 0x08, 0x00, 0x00, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x08, 0x0d, 0x08, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x00, 0x08,
+ 0x00, 0x08, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0c,
+ 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x0c,
+ 0x0d, 0x0d, 0x08, 0x08, 0x08, 0x00, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x08, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x00,
+ 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0d, 0x0c, 0x00, 0x0c, 0x0c, 0x08, 0x0d,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x09, 0x0c, 0x00, 0x0c,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x08, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0d, 0x0d, 0x0d, 0x0c, 0x08, 0x0c, 0x0c, 0x0c,
+};
+
+static guchar m9e1[256] = {
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x40, 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00,
+ 0x00, 0x40, 0xf0, 0x70, 0x40, 0x40, 0xf0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00,
+ 0x70, 0xf0, 0x40, 0x40, 0x40, 0xf0, 0x00, 0xf0,
+ 0x40, 0x70, 0x40, 0x40, 0x40, 0x40, 0x00, 0x40,
+ 0x70, 0x00, 0x00, 0x70, 0x70, 0x00, 0x40, 0x40,
+ 0x00, 0x70, 0xf0, 0xf0, 0x00, 0x70, 0x40, 0xf0,
+ 0x40, 0x00, 0x00, 0x40, 0x00, 0xf0, 0x40, 0xf0,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0xf0, 0x70, 0x00,
+ 0x40, 0x70, 0x70, 0x00, 0x40, 0x70, 0x40, 0x40,
+ 0x40, 0x00, 0x00, 0x40, 0xc0, 0xc0, 0x00, 0x00,
+ 0x70, 0x70, 0x70, 0xf0, 0x70, 0x00, 0xf0, 0x70,
+ 0x00, 0x00, 0x00, 0x80, 0x70, 0x00, 0x40, 0x00,
+ 0x40, 0x00, 0x00, 0x40, 0x70, 0xf0, 0xf0, 0x70,
+ 0x70, 0x80, 0x70, 0x00, 0xf0, 0x40, 0x00, 0x00,
+ 0xf0, 0x70, 0x00, 0xf0, 0xf0, 0x70, 0xf0, 0x40,
+ 0x70, 0x00, 0x00, 0x00, 0x40, 0x70, 0x00, 0x40,
+ 0xf0, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x70,
+ 0x40, 0x40, 0x40, 0x00, 0xf0, 0x40, 0x70, 0x70,
+ 0x40, 0x70, 0x00, 0x70, 0x70, 0x70, 0x00, 0x40,
+};
+
+static guchar m9e2[256] = {
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x0c,
+ 0x08, 0x0c, 0x0d, 0x0c, 0x08, 0x0c, 0x0d, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x00,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02,
+ 0x00, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x00, 0x02, 0x02, 0x02,
+ 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02,
+ 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02,
+ 0x02, 0x00, 0x02, 0x02, 0x02, 0x00, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00,
+ 0x02, 0x02, 0x00, 0x02, 0x00, 0x0d, 0x08, 0x08,
+ 0x00, 0x0d, 0x0c, 0x08, 0x0c, 0x0d, 0x02, 0x0f,
+ 0x0c, 0x08, 0x0e, 0x0c, 0x08, 0x08, 0x0c, 0x0e,
+ 0x0e, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x08,
+ 0x08, 0x0c, 0x0f, 0x0f, 0x0c, 0x08, 0x08, 0x0d,
+ 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x08, 0x0f,
+ 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x0d, 0x0a, 0x0c,
+ 0x08, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x0c, 0x08,
+ 0x0c, 0x08, 0x08, 0x08, 0x0f, 0x0d, 0x0c, 0x0c,
+ 0x02, 0x00, 0x00, 0x0f, 0x0c, 0x0a, 0x0f, 0x08,
+ 0x0c, 0x08, 0x0c, 0x0d, 0x0a, 0x08, 0x08, 0x08,
+ 0x0c, 0x02, 0x08, 0x08, 0x0c, 0x0f, 0x0f, 0x0e,
+ 0x0c, 0x0f, 0x08, 0x0c, 0x0f, 0x0c, 0x0c, 0x08,
+ 0x0f, 0x08, 0x0c, 0x0f, 0x0f, 0x0e, 0x0d, 0x0e,
+ 0x0e, 0x00, 0x0a, 0x08, 0x0c, 0x0e, 0x0c, 0x0e,
+ 0x0d, 0x02, 0x02, 0x0c, 0x08, 0x0c, 0x0c, 0x0e,
+ 0x0c, 0x08, 0x0c, 0x0c, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x08, 0x0e, 0x0c, 0x0e, 0x0e, 0x0c, 0x0a, 0x0c,
+};
+
+static guchar m9f1[256] = {
+ 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0xf0,
+ 0xf0, 0x40, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x40,
+ 0x40, 0x40, 0x40, 0xf0, 0x40, 0x70, 0x40, 0x40,
+ 0x00, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0xf0, 0x70, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x00, 0x40, 0x40, 0x70, 0x00, 0x00, 0x40,
+ 0x00, 0x40, 0x40, 0x00, 0x40, 0x00, 0x00, 0x40,
+ 0x00, 0x40, 0x40, 0xf0, 0x40, 0x40, 0x70, 0x40,
+ 0x00, 0x40, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x00, 0x00, 0xf0, 0xf0, 0x00, 0x00, 0xf0, 0x70,
+ 0x00, 0x00, 0xf0, 0x40, 0x70, 0x40, 0x40, 0x40,
+ 0x40, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0xf0,
+ 0x70, 0xf0, 0x70, 0x70, 0x00, 0x00, 0x70, 0xf0,
+ 0x40, 0x40, 0xf0, 0x00, 0xf0, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x70, 0x40, 0x00, 0x40, 0x70, 0xf0,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x40,
+ 0xc0, 0x40, 0x40, 0x00, 0x40, 0xf0, 0x40, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0xf0, 0x70, 0x40, 0x00,
+ 0x70, 0x40, 0x40, 0x40, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar m9f2[256] = {
+ 0x0c, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x0d,
+ 0x09, 0x0c, 0x0c, 0x02, 0x00, 0x02, 0x0f, 0x0c,
+ 0x0e, 0x08, 0x0c, 0x0f, 0x08, 0x0c, 0x0c, 0x0a,
+ 0x0c, 0x0e, 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x08,
+ 0x0f, 0x00, 0x0e, 0x0c, 0x0c, 0x0c, 0x08, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x0c, 0x0e,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e,
+ 0x0c, 0x0a, 0x08, 0x0f, 0x08, 0x0e, 0x0e, 0x08,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0a, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0c,
+ 0x0a, 0x02, 0x0d, 0x08, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0d, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x08, 0x08, 0x0d, 0x0c, 0x0d, 0x08, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x08, 0x0c, 0x0c, 0x0c, 0x0d,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x08, 0x08, 0x0c, 0x02,
+ 0x02, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x0d, 0x08, 0x08,
+ 0x0d, 0x0c, 0x0c, 0x08, 0x0c, 0x0d, 0x08, 0x08,
+ 0x0c, 0x0a, 0x02, 0x02, 0x0d, 0x08, 0x08, 0x02,
+ 0x0e, 0x08, 0x0c, 0x08, 0x0c, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mac1[256] = {
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00,
+ 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+};
+
+static guchar mac2[256] = {
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+};
+
+static guchar mad1[256] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+};
+
+static guchar mad2[256] = {
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+};
+
+static guchar mae1[256] = {
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+};
+
+static guchar mae2[256] = {
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+};
+
+static guchar maf1[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar maf2[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mb01[256] = {
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mb02[256] = {
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mb11[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+};
+
+static guchar mb12[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+};
+
+static guchar mb21[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x80,
+};
+
+static guchar mb22[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01,
+};
+
+static guchar mb31[256] = {
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+};
+
+static guchar mb32[256] = {
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+};
+
+static guchar mb41[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mb42[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mb51[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mb52[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mb61[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mb62[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mb71[256] = {
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+};
+
+static guchar mb72[256] = {
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+};
+
+static guchar mb81[256] = {
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+};
+
+static guchar mb82[256] = {
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+};
+
+static guchar mb91[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00,
+ 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mb92[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mba1[256] = {
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+};
+
+static guchar mba2[256] = {
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+};
+
+static guchar mbb1[256] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+};
+
+static guchar mbb2[256] = {
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+};
+
+static guchar mbc1[256] = {
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+};
+
+static guchar mbc2[256] = {
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+};
+
+static guchar mbd1[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mbd2[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mbe1[256] = {
+ 0x80, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mbe2[256] = {
+ 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mbf1[256] = {
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mbf2[256] = {
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mc01[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mc02[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mc11[256] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+};
+
+static guchar mc12[256] = {
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+};
+
+static guchar mc21[256] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00,
+};
+
+static guchar mc22[256] = {
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
+};
+
+static guchar mc31[256] = {
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mc32[256] = {
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mc41[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80,
+};
+
+static guchar mc42[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
+};
+
+static guchar mc51[256] = {
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80,
+};
+
+static guchar mc52[256] = {
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01,
+};
+
+static guchar mc61[256] = {
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+};
+
+static guchar mc62[256] = {
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+};
+
+static guchar mc71[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mc72[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mc81[256] = {
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80,
+ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+};
+
+static guchar mc82[256] = {
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+};
+
+static guchar mc91[256] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00,
+ 0x00, 0x80, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+};
+
+static guchar mc92[256] = {
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+};
+
+static guchar mca1[256] = {
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mca2[256] = {
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mcb1[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mcb2[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mcc1[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mcc2[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mcd1[256] = {
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+};
+
+static guchar mcd2[256] = {
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+};
+
+static guchar mce1[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mce2[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mcf1[256] = {
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+};
+
+static guchar mcf2[256] = {
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+};
+
+static guchar md01[256] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar md02[256] = {
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar md11[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar md12[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar md21[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar md22[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar md31[256] = {
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+};
+
+static guchar md32[256] = {
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+};
+
+static guchar md41[256] = {
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+};
+
+static guchar md42[256] = {
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+};
+
+static guchar md51[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar md52[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar md61[256] = {
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+};
+
+static guchar md62[256] = {
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+};
+
+static guchar md71[256] = {
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar md72[256] = {
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mf62[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+};
+
+static guchar mf72[256] = {
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+};
+
+static guchar mf82[256] = {
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mf91[256] = {
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+};
+
+static guchar mf92[256] = {
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+};
+
+static guchar mfa1[256] = {
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mfa2[256] = {
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x04, 0x04, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mfe2[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x0c, 0x08, 0x04, 0x04, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x04,
+ 0x0c, 0x0c, 0x0c, 0x00, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x00,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mff1[256] = {
+ 0x00, 0xf0, 0x80, 0xf0, 0xf0, 0xf0, 0xf0, 0x80,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x80, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xc0, 0x00,
+ 0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xa0, 0xa0, 0xa0, 0xf0, 0x00, 0xf0, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static guchar mff2[256] = {
+ 0x00, 0x0f, 0x03, 0x0f, 0x0f, 0x0f, 0x0f, 0x03,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x03, 0x0f, 0x03, 0x03, 0x0f,
+ 0x03, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x07, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0f, 0x0f, 0x01, 0x07, 0x00, 0x0f, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static const struct {
+ const guchar *bits0;
+ const guchar *bits1;
+ const guchar *bits2;
+} camel_charmap[256] = {
+ { m000, m001, m002, }, { m010, m011, m012, }, { m020, m021, m022, }, { m030, m031, m032, },
+ { m040, m041, m042, }, { m050, m051, m052, }, { m060, m061, m062, }, { NULL, m071, m072, },
+ { NULL, m081, m082, }, { NULL, m091, m092, }, { NULL, m0a1, m0a2, }, { NULL, m0b1, m0b2, },
+ { NULL, m0c1, m0c2, }, { NULL, m0d1, m0d2, }, { NULL, m0e1, m0e2, }, { NULL, m0f1, m0f2, },
+ { NULL, m101, m102, }, { NULL, m111, m112, }, { NULL, m121, m122, }, { NULL, m131, m132, },
+ { NULL, m141, m142, }, { NULL, m151, m152, }, { NULL, m161, m162, }, { NULL, m171, m172, },
+ { NULL, m181, m182, }, { NULL, m191, m192, }, { NULL, m1a1, m1a2, }, { NULL, m1b1, m1b2, },
+ { NULL, m1c1, m1c2, }, { NULL, m1d1, m1d2, }, { NULL, m1e1, m1e2, }, { NULL, m1f1, m1f2, },
+ { m200, m201, m202, }, { m210, m211, m212, }, { m220, m221, m222, }, { m230, m231, m232, },
+ { NULL, m241, m242, }, { m250, m251, m252, }, { NULL, m261, m262, }, { NULL, m271, m272, },
+ { NULL, m281, m282, }, { NULL, m291, m292, }, { NULL, m2a1, m2a2, }, { NULL, m2b1, m2b2, },
+ { NULL, m2c1, m2c2, }, { NULL, m2d1, m2d2, }, { NULL, m2e1, m2e2, }, { NULL, m2f1, m2f2, },
+ { NULL, m301, m302, }, { NULL, m311, m312, }, { NULL, m321, m322, }, { NULL, m331, m332, },
+ { NULL, NULL, m342, }, { NULL, NULL, m352, }, { NULL, NULL, m362, }, { NULL, NULL, m372, },
+ { NULL, NULL, m382, }, { NULL, NULL, m392, }, { NULL, NULL, m3a2, }, { NULL, NULL, m3b2, },
+ { NULL, NULL, m3c2, }, { NULL, NULL, m3d2, }, { NULL, NULL, m3e2, }, { NULL, NULL, m3f2, },
+ { NULL, NULL, m402, }, { NULL, NULL, m412, }, { NULL, NULL, m422, }, { NULL, NULL, m432, },
+ { NULL, NULL, m442, }, { NULL, NULL, m452, }, { NULL, NULL, m462, }, { NULL, NULL, m472, },
+ { NULL, NULL, m482, }, { NULL, NULL, m492, }, { NULL, NULL, m4a2, }, { NULL, NULL, m4b2, },
+ { NULL, NULL, m4c2, }, { NULL, NULL, m4d2, }, { NULL, m4e1, m4e2, }, { NULL, m4f1, m4f2, },
+ { NULL, m501, m502, }, { NULL, m511, m512, }, { NULL, m521, m522, }, { NULL, m531, m532, },
+ { NULL, m541, m542, }, { NULL, m551, m552, }, { NULL, m561, m562, }, { NULL, m571, m572, },
+ { NULL, m581, m582, }, { NULL, m591, m592, }, { NULL, m5a1, m5a2, }, { NULL, m5b1, m5b2, },
+ { NULL, m5c1, m5c2, }, { NULL, m5d1, m5d2, }, { NULL, m5e1, m5e2, }, { NULL, m5f1, m5f2, },
+ { NULL, m601, m602, }, { NULL, m611, m612, }, { NULL, m621, m622, }, { NULL, m631, m632, },
+ { NULL, m641, m642, }, { NULL, m651, m652, }, { NULL, m661, m662, }, { NULL, m671, m672, },
+ { NULL, m681, m682, }, { NULL, m691, m692, }, { NULL, m6a1, m6a2, }, { NULL, m6b1, m6b2, },
+ { NULL, m6c1, m6c2, }, { NULL, m6d1, m6d2, }, { NULL, m6e1, m6e2, }, { NULL, m6f1, m6f2, },
+ { NULL, m701, m702, }, { NULL, m711, m712, }, { NULL, m721, m722, }, { NULL, m731, m732, },
+ { NULL, m741, m742, }, { NULL, m751, m752, }, { NULL, m761, m762, }, { NULL, m771, m772, },
+ { NULL, m781, m782, }, { NULL, m791, m792, }, { NULL, m7a1, m7a2, }, { NULL, m7b1, m7b2, },
+ { NULL, m7c1, m7c2, }, { NULL, m7d1, m7d2, }, { NULL, m7e1, m7e2, }, { NULL, m7f1, m7f2, },
+ { NULL, m801, m802, }, { NULL, m811, m812, }, { NULL, m821, m822, }, { NULL, m831, m832, },
+ { NULL, m841, m842, }, { NULL, m851, m852, }, { NULL, m861, m862, }, { NULL, m871, m872, },
+ { NULL, m881, m882, }, { NULL, m891, m892, }, { NULL, m8a1, m8a2, }, { NULL, m8b1, m8b2, },
+ { NULL, m8c1, m8c2, }, { NULL, m8d1, m8d2, }, { NULL, m8e1, m8e2, }, { NULL, m8f1, m8f2, },
+ { NULL, m901, m902, }, { NULL, m911, m912, }, { NULL, m921, m922, }, { NULL, m931, m932, },
+ { NULL, m941, m942, }, { NULL, m951, m952, }, { NULL, m961, m962, }, { NULL, m971, m972, },
+ { NULL, m981, m982, }, { NULL, m991, m992, }, { NULL, m9a1, m9a2, }, { NULL, m9b1, m9b2, },
+ { NULL, m9c1, m9c2, }, { NULL, m9d1, m9d2, }, { NULL, m9e1, m9e2, }, { NULL, m9f1, m9f2, },
+ { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, },
+ { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, },
+ { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, },
+ { NULL, mac1, mac2, }, { NULL, mad1, mad2, }, { NULL, mae1, mae2, }, { NULL, maf1, maf2, },
+ { NULL, mb01, mb02, }, { NULL, mb11, mb12, }, { NULL, mb21, mb22, }, { NULL, mb31, mb32, },
+ { NULL, mb41, mb42, }, { NULL, mb51, mb52, }, { NULL, mb61, mb62, }, { NULL, mb71, mb72, },
+ { NULL, mb81, mb82, }, { NULL, mb91, mb92, }, { NULL, mba1, mba2, }, { NULL, mbb1, mbb2, },
+ { NULL, mbc1, mbc2, }, { NULL, mbd1, mbd2, }, { NULL, mbe1, mbe2, }, { NULL, mbf1, mbf2, },
+ { NULL, mc01, mc02, }, { NULL, mc11, mc12, }, { NULL, mc21, mc22, }, { NULL, mc31, mc32, },
+ { NULL, mc41, mc42, }, { NULL, mc51, mc52, }, { NULL, mc61, mc62, }, { NULL, mc71, mc72, },
+ { NULL, mc81, mc82, }, { NULL, mc91, mc92, }, { NULL, mca1, mca2, }, { NULL, mcb1, mcb2, },
+ { NULL, mcc1, mcc2, }, { NULL, mcd1, mcd2, }, { NULL, mce1, mce2, }, { NULL, mcf1, mcf2, },
+ { NULL, md01, md02, }, { NULL, md11, md12, }, { NULL, md21, md22, }, { NULL, md31, md32, },
+ { NULL, md41, md42, }, { NULL, md51, md52, }, { NULL, md61, md62, }, { NULL, md71, md72, },
+ { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, },
+ { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, },
+ { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, },
+ { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, },
+ { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, },
+ { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, },
+ { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, NULL, },
+ { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, mf62, }, { NULL, NULL, mf72, },
+ { NULL, NULL, mf82, }, { NULL, mf91, mf92, }, { NULL, mfa1, mfa2, }, { NULL, NULL, NULL, },
+ { NULL, NULL, NULL, }, { NULL, NULL, NULL, }, { NULL, NULL, mfe2, }, { NULL, mff1, mff2, },
+};
+
+static const struct {
+ const gchar *name;
+ guint bit;
+} camel_charinfo[] = {
+ { "iso-8859-2", 0x00000001 },
+ { "iso-8859-4", 0x00000002 },
+ { "koi8-r", 0x00000004 },
+ { "koi8-u", 0x00000008 },
+ { "iso-8859-5", 0x00000010 },
+ { "iso-8859-6", 0x00000020 },
+ { "iso-8859-7", 0x00000040 },
+ { "iso-8859-8", 0x00000080 },
+ { "iso-8859-9", 0x00000100 },
+ { "iso-8859-13", 0x00000200 },
+ { "iso-8859-15", 0x00000400 },
+ { "windows-1251", 0x00000800 },
+ { "iso-2022-jp", 0x00001000 },
+ { "Shift-JIS", 0x00002000 },
+ { "euc-jp", 0x00004000 },
+ { "euc-kr", 0x00008000 },
+ { "iso-2022-kr", 0x00010000 },
+ { "gb2312", 0x00020000 },
+ { "Big5", 0x00040000 },
+ { "euc-tw", 0x00080000 },
+};
+
+#define charset_mask(x) \
+ (camel_charmap[(x) >> 8].bits0 ? camel_charmap[(x) >> 8].bits0[(x) & 0xff] << 0 : 0) \
+ | (camel_charmap[(x) >> 8].bits1 ? camel_charmap[(x) >> 8].bits1[(x) & 0xff] << 8 : 0) \
+ | (camel_charmap[(x) >> 8].bits2 ? camel_charmap[(x) >> 8].bits2[(x) & 0xff] << 16 : 0)
+
diff --git a/src/camel/camel-charset-map.c b/src/camel/camel-charset-map.c
new file mode 100644
index 000000000..38f416765
--- /dev/null
+++ b/src/camel/camel-charset-map.c
@@ -0,0 +1,451 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ * Dan Winship <danw@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#ifdef HAVE_CODESET
+#include <langinfo.h>
+#endif
+#include <iconv.h>
+#include <errno.h>
+
+#include "camel-iconv.h"
+
+/*
+ * if you want to build the charset map, compile this with something like:
+ * gcc -DBUILD_MAP camel-charset-map.c `pkg-config --cflags --libs glib-2.0`
+ * (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
+
+static struct {
+ gchar *name; /* charset name */
+ gint multibyte; /* charset type */
+ guint 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, 0 }, /* Central/Eastern European */
+ { "iso-8859-4", 0, 0 }, /* Baltic */
+ { "koi8-r", 0, 0 }, /* Russian */
+ { "koi8-u", 0, 0 }, /* Ukranian */
+ { "iso-8859-5", 0, 0 }, /* Least-popular Russian encoding */
+ { "iso-8859-6", 0, 0 }, /* Arabic */
+ { "iso-8859-7", 0, 0 }, /* Greek */
+ { "iso-8859-8", 0, 0 }, /* Hebrew; Visual */
+ { "iso-8859-9", 0, 0 }, /* Turkish */
+ { "iso-8859-13", 0, 0 }, /* Baltic again */
+ { "iso-8859-15", 0, 0 }, /* New-and-improved iso-8859-1, but most
+ * programs that support this support UTF8
+ */
+ { "windows-1251", 0, 0 }, /* Russian */
+
+ /* These are the multibyte character sets which are commonly
+ * supported by other mail clients. Note: order for multibyte
+ * charsets does not affect priority unlike the 8bit charsets
+ * listed above.
+ */
+ { "iso-2022-jp", 1, 0 }, /* Japanese designed for use over the Net */
+ { "Shift-JIS", 1, 0 }, /* Japanese as used by Windows and MacOS systems */
+ { "euc-jp", 1, 0 }, /* Japanese traditionally used on Unix systems */
+ { "euc-kr", 1, 0 }, /* Korean */
+ { "iso-2022-kr", 1, 0 }, /* Korean (less popular than euc-kr) */
+ { "gb2312", 1, 0 }, /* Simplified Chinese */
+ { "Big5", 1, 0 }, /* Traditional Chinese */
+ { "euc-tw", 1, 0 },
+ { NULL, 0, 0 }
+};
+
+guint encoding_map[256 * 256];
+
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+#define UCS "UCS-4BE"
+#else
+#define UCS "UCS-4LE"
+#endif
+
+static guint
+block_hash (gconstpointer v)
+{
+ const gchar *p = v;
+ guint32 h = *p++;
+ gint i;
+
+ for (i = 0; i < 256; i++)
+ h = (h << 5) - h + *p++;
+
+ return h;
+}
+
+static gint
+block_equal (gconstpointer v1,
+ gconstpointer v2)
+{
+ return !memcmp (v1, v2, 256);
+}
+
+gint main (gint argc, gchar **argv)
+{
+ guchar *block = NULL;
+ guint bit = 0x01;
+ GHashTable *table_hash;
+ gsize inleft, outleft;
+ gchar *inbuf, *outbuf;
+ guint32 out[128], c;
+ gchar in[128];
+ gint i, j, k;
+ gint bytes;
+ GIConv cd;
+
+ /* dont count the terminator */
+ bytes = (G_N_ELEMENTS (tables) + 7 - 1) / 8;
+ g_return_val_if_fail (bytes <= 4, -1);
+
+ for (i = 0; i < 128; i++)
+ in[i] = i + 128;
+
+ for (j = 0; tables[j].name && !tables[j].multibyte; j++) {
+ cd = iconv_open (UCS, tables[j].name);
+ inbuf = in;
+ inleft = sizeof (in);
+ outbuf = (gchar *) out;
+ outleft = sizeof (out);
+ while (iconv (cd, &inbuf, &inleft, &outbuf, &outleft) == -1) {
+ if (errno == EILSEQ) {
+ inbuf++;
+ inleft--;
+ } else {
+ g_warning (
+ "iconv (%s->UCS4, ..., %d, ..., %d): %s",
+ tables[j].name, inleft, outleft,
+ g_strerror (errno));
+ exit (1);
+ }
+ }
+ iconv_close (cd);
+
+ for (i = 0; i < 128 - outleft / 4; i++) {
+ encoding_map[i] |= bit;
+ encoding_map[out[i]] |= bit;
+ }
+
+ tables[j].bit = bit;
+ bit <<= 1;
+ }
+
+ /* Mutibyte tables */
+ for (; tables[j].name && tables[j].multibyte; j++) {
+ cd = iconv_open (tables[j].name, UCS);
+ if (cd == (GIConv) -1)
+ continue;
+
+ for (c = 128, i = 0; c < 65535 && i < 65535; c++) {
+ inbuf = (gchar *) &c;
+ inleft = sizeof (c);
+ outbuf = in;
+ outleft = sizeof (in);
+
+ if (iconv (cd, &inbuf, &inleft, &outbuf, &outleft) != (gsize) -1) {
+ /* this is a legal character in charset table[j].name */
+ iconv (cd, NULL, NULL, &outbuf, &outleft);
+ encoding_map[i++] |= bit;
+ encoding_map[c] |= bit;
+ } else {
+ /* reset the iconv descriptor */
+ iconv (cd, NULL, NULL, NULL, NULL);
+ }
+ }
+
+ iconv_close (cd);
+
+ tables[j].bit = bit;
+ bit <<= 1;
+ }
+
+ printf ("/* This file is automatically generated: DO NOT EDIT */\n\n");
+
+ table_hash = g_hash_table_new_full (block_hash, block_equal, g_free, g_free);
+
+ for (i = 0; i < 256; i++) {
+ for (k = 0; k < bytes; k++) {
+ gchar name[32], *alias;
+ gint has_bits = FALSE;
+
+ if (!block) {
+ /* we reuse malloc'd blocks that are not added to the
+ * hash table to avoid unnecessary malloc/free's */
+ block = g_malloc (256);
+ }
+
+ for (j = 0; j < 256; j++) {
+ if ((block[j] = (encoding_map[i * 256 + j] >> (k * 8)) & 0xff))
+ has_bits = TRUE;
+ }
+
+ if (!has_bits)
+ continue;
+
+ g_snprintf (name, sizeof (name), "m%02x%x", i, k);
+
+ if ((alias = g_hash_table_lookup (table_hash, block))) {
+ /* this block is identical to an earlier block, just alias it */
+ printf ("#define %s %s\n\n", name, alias);
+ } else {
+ /* unique block, dump it */
+ g_hash_table_insert (table_hash, block, g_strdup (name));
+
+ printf ("static guchar %s[256] = {\n\t", name);
+ for (j = 0; j < 256; j++) {
+ printf ("0x%02x, ", block[j]);
+ if (((j + 1) & 7) == 0 && j < 255)
+ printf ("\n\t");
+ }
+ printf ("\n};\n\n");
+
+ /* force the next loop to malloc a new block */
+ block = NULL;
+ }
+ }
+ }
+
+ g_hash_table_destroy (table_hash);
+ g_free (block);
+
+ printf ("static const struct {\n");
+ for (k = 0; k < bytes; k++)
+ printf ("\tconst guchar *bits%d;\n", k);
+
+ printf ("} camel_charmap[256] = {\n\t");
+ for (i = 0; i < 256; i++) {
+ 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 ("NULL, ");
+ }
+
+ printf ("}, ");
+ if (((i + 1) & 3) == 0 && i < 255)
+ printf ("\n\t");
+ }
+ printf ("\n};\n\n");
+
+ printf (
+ "static const struct {\n"
+ "\tconst gchar *name;\n"
+ "\tguint bit;\n"
+ "} camel_charinfo[] = {\n");
+ for (j = 0; tables[j].name; j++)
+ printf (
+ "\t{ \"%s\", 0x%08x },\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 "camel-utf8.h"
+
+void
+camel_charset_init (CamelCharset *c)
+{
+ c->mask = (guint) ~0;
+ c->level = 0;
+}
+
+void
+camel_charset_step (CamelCharset *cc,
+ const gchar *in,
+ gint len)
+{
+ const guchar *inptr = (const guchar *) in;
+ const guchar *inend = inptr + len;
+ register guint mask;
+ register gint level;
+ register guint32 c;
+
+ mask = cc->mask;
+ level = cc->level;
+
+ /* check what charset a given string will fit in */
+ while ((c = camel_utf8_getc_limit (&inptr, inend)) != 0xffff) {
+ if (c < 0xffff) {
+ mask &= charset_mask (c);
+
+ if (c >= 128 && c < 256)
+ level = MAX (level, 1);
+ else if (c >= 256)
+ level = 2;
+ } else {
+ mask = 0;
+ level = 2;
+ break;
+ }
+ }
+
+ cc->mask = mask;
+ cc->level = level;
+}
+
+/* gets the best charset from the mask of chars in it */
+static const gchar *
+camel_charset_best_mask (guint mask)
+{
+ const gchar *locale_lang, *lang;
+ gint i;
+
+ locale_lang = camel_iconv_locale_language ();
+ for (i = 0; i < G_N_ELEMENTS (camel_charinfo); i++) {
+ if (camel_charinfo[i].bit & mask) {
+ lang = camel_iconv_charset_language (camel_charinfo[i].name);
+
+ if (!locale_lang || (lang && !strncmp (locale_lang, lang, 2)))
+ return camel_charinfo[i].name;
+ }
+ }
+
+ return "UTF-8";
+}
+
+const gchar *
+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 gchar *
+camel_charset_best (const gchar *in,
+ gint 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 gchar *
+camel_charset_iso_to_windows (const gchar *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/src/camel/camel-charset-map.h b/src/camel/camel-charset-map.h
new file mode 100644
index 000000000..6942ce15e
--- /dev/null
+++ b/src/camel/camel-charset-map.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_CHARSET_MAP_H
+#define CAMEL_CHARSET_MAP_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _CamelCharset CamelCharset;
+
+struct _CamelCharset {
+ guint mask;
+ gint level;
+};
+
+void camel_charset_init (CamelCharset *c);
+void camel_charset_step (CamelCharset *cc, const gchar *in, gint len);
+
+const gchar *camel_charset_best_name (CamelCharset *charset);
+
+/* helper function */
+const gchar *camel_charset_best (const gchar *in, gint len);
+
+const gchar *camel_charset_iso_to_windows (const gchar *isocharset);
+
+G_END_DECLS
+
+#endif /* CAMEL_CHARSET_MAP_H */
diff --git a/src/camel/camel-cipher-context.c b/src/camel/camel-cipher-context.c
new file mode 100644
index 000000000..80bcc6c05
--- /dev/null
+++ b/src/camel/camel-cipher-context.c
@@ -0,0 +1,1631 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-cipher-context.h"
+#include "camel-debug.h"
+#include "camel-session.h"
+#include "camel-stream.h"
+#include "camel-operation.h"
+
+#include "camel-mime-utils.h"
+#include "camel-medium.h"
+#include "camel-multipart.h"
+#include "camel-multipart-encrypted.h"
+#include "camel-multipart-signed.h"
+#include "camel-mime-message.h"
+#include "camel-mime-filter-canon.h"
+#include "camel-stream-filter.h"
+
+#define CAMEL_CIPHER_CONTEXT_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_CIPHER_CONTEXT, CamelCipherContextPrivate))
+
+#define CIPHER_LOCK(ctx) \
+ g_mutex_lock (&((CamelCipherContext *) ctx)->priv->lock)
+#define CIPHER_UNLOCK(ctx) \
+ g_mutex_unlock (&((CamelCipherContext *) ctx)->priv->lock);
+
+#define d(x)
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _CamelCipherContextPrivate {
+ CamelSession *session;
+ GMutex lock;
+};
+
+struct _AsyncContext {
+ /* arguments */
+ CamelCipherHash hash;
+ CamelMimePart *ipart;
+ CamelMimePart *opart;
+ CamelStream *stream;
+ GPtrArray *strings;
+ gchar *userid;
+};
+
+enum {
+ PROP_0,
+ PROP_SESSION
+};
+
+G_DEFINE_TYPE (CamelCipherContext, camel_cipher_context, G_TYPE_OBJECT)
+
+G_DEFINE_BOXED_TYPE (CamelCipherValidity,
+ camel_cipher_validity,
+ camel_cipher_validity_clone,
+ camel_cipher_validity_free)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+ if (async_context->ipart != NULL)
+ g_object_unref (async_context->ipart);
+
+ if (async_context->opart != NULL)
+ g_object_unref (async_context->opart);
+
+ if (async_context->stream != NULL)
+ g_object_unref (async_context->stream);
+
+ if (async_context->strings != NULL) {
+ g_ptr_array_foreach (
+ async_context->strings, (GFunc) g_free, NULL);
+ g_ptr_array_free (async_context->strings, TRUE);
+ }
+
+ g_free (async_context->userid);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static void
+cipher_context_set_session (CamelCipherContext *context,
+ CamelSession *session)
+{
+ g_return_if_fail (CAMEL_IS_SESSION (session));
+ g_return_if_fail (context->priv->session == NULL);
+
+ context->priv->session = g_object_ref (session);
+}
+
+static void
+cipher_context_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_SESSION:
+ cipher_context_set_session (
+ CAMEL_CIPHER_CONTEXT (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cipher_context_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_SESSION:
+ g_value_set_object (
+ value, camel_cipher_context_get_session (
+ CAMEL_CIPHER_CONTEXT (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cipher_context_dispose (GObject *object)
+{
+ CamelCipherContextPrivate *priv;
+
+ priv = CAMEL_CIPHER_CONTEXT_GET_PRIVATE (object);
+
+ if (priv->session != NULL) {
+ g_object_unref (priv->session);
+ priv->session = NULL;
+ }
+
+ /* Chain up to parent's dispose () method. */
+ G_OBJECT_CLASS (camel_cipher_context_parent_class)->dispose (object);
+}
+
+static void
+cipher_context_finalize (GObject *object)
+{
+ CamelCipherContextPrivate *priv;
+
+ priv = CAMEL_CIPHER_CONTEXT_GET_PRIVATE (object);
+
+ g_mutex_clear (&priv->lock);
+
+ /* Chain up to parent's finalize () method. */
+ G_OBJECT_CLASS (camel_cipher_context_parent_class)->finalize (object);
+}
+
+static const gchar *
+cipher_context_hash_to_id (CamelCipherContext *context,
+ CamelCipherHash hash)
+{
+ return NULL;
+}
+
+static CamelCipherHash
+cipher_context_id_to_hash (CamelCipherContext *context,
+ const gchar *id)
+{
+ return CAMEL_CIPHER_HASH_DEFAULT;
+}
+
+static gboolean
+cipher_context_sign_sync (CamelCipherContext *ctx,
+ const gchar *userid,
+ CamelCipherHash hash,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Signing is not supported by this cipher"));
+
+ return FALSE;
+}
+
+static CamelCipherValidity *
+cipher_context_verify_sync (CamelCipherContext *context,
+ CamelMimePart *sigpart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Verifying is not supported by this cipher"));
+
+ return NULL;
+}
+
+static gboolean
+cipher_context_encrypt_sync (CamelCipherContext *context,
+ const gchar *userid,
+ GPtrArray *recipients,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Encryption is not supported by this cipher"));
+
+ return FALSE;
+}
+
+static CamelCipherValidity *
+cipher_context_decrypt_sync (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Decryption is not supported by this cipher"));
+
+ return NULL;
+}
+
+static void
+camel_cipher_context_class_init (CamelCipherContextClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelCipherContextPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = cipher_context_set_property;
+ object_class->get_property = cipher_context_get_property;
+ object_class->dispose = cipher_context_dispose;
+ object_class->finalize = cipher_context_finalize;
+
+ class->hash_to_id = cipher_context_hash_to_id;
+ class->id_to_hash = cipher_context_id_to_hash;
+
+ class->sign_sync = cipher_context_sign_sync;
+ class->verify_sync = cipher_context_verify_sync;
+ class->encrypt_sync = cipher_context_encrypt_sync;
+ class->decrypt_sync = cipher_context_decrypt_sync;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SESSION,
+ g_param_spec_object (
+ "session",
+ "Session",
+ NULL,
+ CAMEL_TYPE_SESSION,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+camel_cipher_context_init (CamelCipherContext *context)
+{
+ context->priv = CAMEL_CIPHER_CONTEXT_GET_PRIVATE (context);
+ g_mutex_init (&context->priv->lock);
+}
+
+/* Helper for camel_cipher_context_sign() */
+static void
+cipher_context_sign_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_cipher_context_sign_sync (
+ CAMEL_CIPHER_CONTEXT (source_object),
+ async_context->userid,
+ async_context->hash,
+ async_context->ipart,
+ async_context->opart,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_cipher_context_sign_sync:
+ * @context: a #CamelCipherContext
+ * @userid: a private key to use to sign the stream
+ * @hash: preferred Message-Integrity-Check hash algorithm
+ * @ipart: input #CamelMimePart
+ * @opart: output #CamelMimePart
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Converts the (unsigned) part @ipart into a new self-contained MIME
+ * part @opart. This may be a multipart/signed part, or a simple part
+ * for enveloped types.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_cipher_context_sign_sync (CamelCipherContext *context,
+ const gchar *userid,
+ CamelCipherHash hash,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelCipherContextClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), FALSE);
+
+ class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context);
+ g_return_val_if_fail (class->sign_sync != NULL, FALSE);
+
+ CIPHER_LOCK (context);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ CIPHER_UNLOCK (context);
+ return FALSE;
+ }
+
+ camel_operation_push_message (cancellable, _("Signing message"));
+
+ success = class->sign_sync (
+ context, userid, hash, ipart, opart, cancellable, error);
+ CAMEL_CHECK_GERROR (context, sign_sync, success, error);
+
+ camel_operation_pop_message (cancellable);
+
+ CIPHER_UNLOCK (context);
+
+ return success;
+}
+
+/**
+ * camel_cipher_context_sign:
+ * @context: a #CamelCipherContext
+ * @userid: a private key to use to sign the stream
+ * @hash: preferred Message-Integrity-Check hash algorithm
+ * @ipart: input #CamelMimePart
+ * @opart: output #CamelMimePart
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously converts the (unsigned) part @ipart into a new
+ * self-contained MIME part @opart. This may be a multipart/signed part,
+ * or a simple part for enveloped types.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_cipher_context_sign_finish() to get the result of the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_cipher_context_sign (CamelCipherContext *context,
+ const gchar *userid,
+ CamelCipherHash hash,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_CIPHER_CONTEXT (context));
+ g_return_if_fail (CAMEL_IS_MIME_PART (ipart));
+ g_return_if_fail (CAMEL_IS_MIME_PART (opart));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->userid = g_strdup (userid);
+ async_context->hash = hash;
+ async_context->ipart = g_object_ref (ipart);
+ async_context->opart = g_object_ref (opart);
+
+ task = g_task_new (context, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_cipher_context_sign);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, cipher_context_sign_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_cipher_context_sign_finish:
+ * @context: a #CamelCipherContext
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_cipher_context_sign().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_cipher_context_sign_finish (CamelCipherContext *context,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, context), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_cipher_context_sign), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_cipher_context_verify_sync:
+ * @context: a #CamelCipherContext
+ * @ipart: the #CamelMimePart to verify
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Verifies the signature.
+ *
+ * Returns: a #CamelCipherValidity structure containing information
+ * about the integrity of the input stream, or %NULL on failure to
+ * execute at all
+ **/
+CamelCipherValidity *
+camel_cipher_context_verify_sync (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelCipherContextClass *class;
+ CamelCipherValidity *valid;
+
+ g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), NULL);
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (ipart), NULL);
+
+ class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context);
+ g_return_val_if_fail (class->verify_sync != NULL, NULL);
+
+ CIPHER_LOCK (context);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ CIPHER_UNLOCK (context);
+ return NULL;
+ }
+
+ valid = class->verify_sync (context, ipart, cancellable, error);
+ CAMEL_CHECK_GERROR (context, verify_sync, valid != NULL, error);
+
+ CIPHER_UNLOCK (context);
+
+ return valid;
+}
+
+/* Helper for camel_cipher_context_verify() */
+static void
+cipher_context_verify_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelCipherValidity *validity;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ validity = camel_cipher_context_verify_sync (
+ CAMEL_CIPHER_CONTEXT (source_object),
+ async_context->ipart,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_warn_if_fail (validity == NULL);
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_pointer (
+ task, validity,
+ (GDestroyNotify) camel_cipher_validity_free);
+ }
+}
+
+/**
+ * camel_cipher_context_verify:
+ * @context: a #CamelCipherContext
+ * @ipart: the #CamelMimePart to verify
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously verifies the signature.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_cipher_context_verify_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_cipher_context_verify (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_CIPHER_CONTEXT (context));
+ g_return_if_fail (CAMEL_IS_MIME_PART (ipart));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->ipart = g_object_ref (ipart);
+
+ task = g_task_new (context, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_cipher_context_verify);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, cipher_context_verify_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_cipher_context_verify_finish:
+ * @context: a #CamelCipherContext
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_cipher_context_verify().
+ *
+ * Returns: a #CamelCipherValidity structure containing information
+ * about the integrity of the input stream, or %NULL on failure to
+ * execute at all
+ *
+ * Since: 3.0
+ **/
+CamelCipherValidity *
+camel_cipher_context_verify_finish (CamelCipherContext *context,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, context), NULL);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_cipher_context_verify), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/**
+ * camel_cipher_context_encrypt_sync:
+ * @context: a #CamelCipherContext
+ * @userid: key ID (or email address) to use when signing, or %NULL to not sign
+ * @recipients: (element-type utf8): an array of recipient key IDs and/or email addresses
+ * @ipart: clear-text #CamelMimePart
+ * @opart: cipher-text #CamelMimePart
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Encrypts (and optionally signs) the clear-text @ipart and writes the
+ * resulting cipher-text to @opart.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_cipher_context_encrypt_sync (CamelCipherContext *context,
+ const gchar *userid,
+ GPtrArray *recipients,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelCipherContextClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (ipart), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (opart), FALSE);
+
+ class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context);
+ g_return_val_if_fail (class->encrypt_sync != NULL, FALSE);
+
+ CIPHER_LOCK (context);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ CIPHER_UNLOCK (context);
+ return FALSE;
+ }
+
+ camel_operation_push_message (cancellable, _("Encrypting message"));
+
+ success = class->encrypt_sync (
+ context, userid, recipients,
+ ipart, opart, cancellable, error);
+ CAMEL_CHECK_GERROR (context, encrypt_sync, success, error);
+
+ camel_operation_pop_message (cancellable);
+
+ CIPHER_UNLOCK (context);
+
+ return success;
+}
+
+/* Helper for camel_cipher_context_encrypt_thread() */
+static void
+cipher_context_encrypt_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_cipher_context_encrypt_sync (
+ CAMEL_CIPHER_CONTEXT (source_object),
+ async_context->userid,
+ async_context->strings,
+ async_context->ipart,
+ async_context->opart,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_cipher_context_encrypt:
+ * @context: a #CamelCipherContext
+ * @userid: key id (or email address) to use when signing, or %NULL to not sign
+ * @recipients: (element-type utf8): an array of recipient key IDs and/or email addresses
+ * @ipart: clear-text #CamelMimePart
+ * @opart: cipher-text #CamelMimePart
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously encrypts (and optionally signs) the clear-text @ipart and
+ * writes the resulting cipher-text to @opart.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_cipher_context_encrypt_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_cipher_context_encrypt (CamelCipherContext *context,
+ const gchar *userid,
+ GPtrArray *recipients,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+ guint ii;
+
+ g_return_if_fail (CAMEL_IS_CIPHER_CONTEXT (context));
+ g_return_if_fail (CAMEL_IS_MIME_PART (ipart));
+ g_return_if_fail (CAMEL_IS_MIME_PART (opart));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->userid = g_strdup (userid);
+ async_context->strings = g_ptr_array_new ();
+ async_context->ipart = g_object_ref (ipart);
+ async_context->opart = g_object_ref (opart);
+
+ for (ii = 0; ii < recipients->len; ii++)
+ g_ptr_array_add (
+ async_context->strings,
+ g_strdup (recipients->pdata[ii]));
+
+ task = g_task_new (context, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_cipher_context_encrypt);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, cipher_context_encrypt_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_cipher_context_encrypt_finish:
+ * @context: a #CamelCipherContext
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_cipher_context_encrypt().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_cipher_context_encrypt_finish (CamelCipherContext *context,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, context), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_cipher_context_encrypt), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_cipher_context_decrypt_sync:
+ * @context: a #CamelCipherContext
+ * @ipart: cipher-text #CamelMimePart
+ * @opart: clear-text #CamelMimePart
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Decrypts @ipart into @opart.
+ *
+ * Returns: a validity/encryption status, or %NULL on error
+ *
+ * Since: 3.0
+ **/
+CamelCipherValidity *
+camel_cipher_context_decrypt_sync (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelCipherContextClass *class;
+ CamelCipherValidity *valid;
+
+ g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), NULL);
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (ipart), NULL);
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (opart), NULL);
+
+ class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context);
+ g_return_val_if_fail (class->decrypt_sync != NULL, NULL);
+
+ CIPHER_LOCK (context);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ CIPHER_UNLOCK (context);
+ return NULL;
+ }
+
+ camel_operation_push_message (cancellable, _("Decrypting message"));
+
+ valid = class->decrypt_sync (
+ context, ipart, opart, cancellable, error);
+ CAMEL_CHECK_GERROR (context, decrypt_sync, valid != NULL, error);
+
+ camel_operation_pop_message (cancellable);
+
+ CIPHER_UNLOCK (context);
+
+ return valid;
+}
+
+/* Helper for camel_cipher_context_decrypt() */
+static void
+cipher_context_decrypt_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelCipherValidity *validity;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ validity = camel_cipher_context_decrypt_sync (
+ CAMEL_CIPHER_CONTEXT (source_object),
+ async_context->ipart,
+ async_context->opart,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_warn_if_fail (validity == NULL);
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_pointer (
+ task, validity,
+ (GDestroyNotify) camel_cipher_validity_free);
+ }
+}
+
+/**
+ * camel_cipher_context_decrypt:
+ * @context: a #CamelCipherContext
+ * @ipart: cipher-text #CamelMimePart
+ * @opart: clear-text #CamelMimePart
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously decrypts @ipart into @opart.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_cipher_context_decrypt_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_cipher_context_decrypt (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_CIPHER_CONTEXT (context));
+ g_return_if_fail (CAMEL_IS_MIME_PART (ipart));
+ g_return_if_fail (CAMEL_IS_MIME_PART (opart));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->ipart = g_object_ref (ipart);
+ async_context->opart = g_object_ref (opart);
+
+ task = g_task_new (context, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_cipher_context_decrypt);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, cipher_context_decrypt_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_cipher_context_decrypt_finish:
+ * @context: a #CamelCipherContext
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_cipher_context_decrypt().
+ *
+ * Returns: a validity/encryption status, or %NULL on error
+ *
+ * Since: 3.0
+ **/
+CamelCipherValidity *
+camel_cipher_context_decrypt_finish (CamelCipherContext *context,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, context), NULL);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_cipher_context_decrypt), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/* a couple of util functions */
+CamelCipherHash
+camel_cipher_context_id_to_hash (CamelCipherContext *context,
+ const gchar *id)
+{
+ CamelCipherContextClass *class;
+
+ g_return_val_if_fail (
+ CAMEL_IS_CIPHER_CONTEXT (context),
+ CAMEL_CIPHER_HASH_DEFAULT);
+
+ class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context);
+ g_return_val_if_fail (
+ class->id_to_hash != NULL, CAMEL_CIPHER_HASH_DEFAULT);
+
+ return class->id_to_hash (context, id);
+}
+
+const gchar *
+camel_cipher_context_hash_to_id (CamelCipherContext *context,
+ CamelCipherHash hash)
+{
+ CamelCipherContextClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), NULL);
+
+ class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context);
+ g_return_val_if_fail (class->hash_to_id != NULL, NULL);
+
+ return class->hash_to_id (context, hash);
+}
+
+/* Cipher Validity stuff */
+static void
+ccv_certinfo_property_free (gpointer ptr)
+{
+ CamelCipherCertInfoProperty *property = ptr;
+
+ if (property) {
+ g_free (property->name);
+ if (property->value_free)
+ property->value_free (property->value);
+ g_free (property);
+ }
+}
+
+static void
+ccv_certinfo_free (CamelCipherCertInfo *info)
+{
+ g_return_if_fail (info != NULL);
+
+ g_free (info->name);
+ g_free (info->email);
+
+ if (info->cert_data && info->cert_data_free)
+ info->cert_data_free (info->cert_data);
+
+ g_slist_free_full (info->properties, ccv_certinfo_property_free);
+ g_free (info);
+}
+
+CamelCipherValidity *
+camel_cipher_validity_new (void)
+{
+ CamelCipherValidity *validity;
+
+ validity = g_malloc (sizeof (*validity));
+ camel_cipher_validity_init (validity);
+
+ return validity;
+}
+
+void
+camel_cipher_validity_init (CamelCipherValidity *validity)
+{
+ g_return_if_fail (validity != NULL);
+
+ memset (validity, 0, sizeof (*validity));
+ g_queue_init (&validity->children);
+ g_queue_init (&validity->sign.signers);
+ g_queue_init (&validity->encrypt.encrypters);
+}
+
+gboolean
+camel_cipher_validity_get_valid (CamelCipherValidity *validity)
+{
+ return validity != NULL
+ && validity->sign.status == CAMEL_CIPHER_VALIDITY_SIGN_GOOD;
+}
+
+void
+camel_cipher_validity_set_valid (CamelCipherValidity *validity,
+ gboolean valid)
+{
+ g_return_if_fail (validity != NULL);
+
+ validity->sign.status = valid ? CAMEL_CIPHER_VALIDITY_SIGN_GOOD : CAMEL_CIPHER_VALIDITY_SIGN_BAD;
+}
+
+gchar *
+camel_cipher_validity_get_description (CamelCipherValidity *validity)
+{
+ g_return_val_if_fail (validity != NULL, NULL);
+
+ return validity->sign.description;
+}
+
+void
+camel_cipher_validity_set_description (CamelCipherValidity *validity,
+ const gchar *description)
+{
+ g_return_if_fail (validity != NULL);
+
+ g_free (validity->sign.description);
+ validity->sign.description = g_strdup (description);
+}
+
+void
+camel_cipher_validity_clear (CamelCipherValidity *validity)
+{
+ g_return_if_fail (validity != NULL);
+
+ /* TODO: this doesn't free children/clear key lists */
+ g_free (validity->sign.description);
+ g_free (validity->encrypt.description);
+ camel_cipher_validity_init (validity);
+}
+
+CamelCipherValidity *
+camel_cipher_validity_clone (CamelCipherValidity *vin)
+{
+ CamelCipherValidity *vo;
+ GList *head, *link;
+
+ g_return_val_if_fail (vin != NULL, NULL);
+
+ vo = camel_cipher_validity_new ();
+ vo->sign.status = vin->sign.status;
+ vo->sign.description = g_strdup (vin->sign.description);
+ vo->encrypt.status = vin->encrypt.status;
+ vo->encrypt.description = g_strdup (vin->encrypt.description);
+
+ head = g_queue_peek_head_link (&vin->sign.signers);
+ for (link = head; link != NULL; link = g_list_next (link)) {
+ CamelCipherCertInfo *info = link->data;
+ gint index;
+
+ if (info->cert_data && info->cert_data_clone && info->cert_data_free)
+ index = camel_cipher_validity_add_certinfo_ex (
+ vo, CAMEL_CIPHER_VALIDITY_SIGN,
+ info->name,
+ info->email,
+ info->cert_data_clone (info->cert_data),
+ info->cert_data_free,
+ info->cert_data_clone);
+ else
+ index = camel_cipher_validity_add_certinfo (
+ vo, CAMEL_CIPHER_VALIDITY_SIGN,
+ info->name,
+ info->email);
+
+ if (index != -1 && info->properties) {
+ GSList *link;
+
+ for (link = info->properties; link; link = g_slist_next (link)) {
+ CamelCipherCertInfoProperty *property = link->data;
+ gpointer value;
+
+ if (!property)
+ continue;
+
+ value = property->value_clone ? property->value_clone (property->value) : property->value;
+ camel_cipher_validity_set_certinfo_property (vo, CAMEL_CIPHER_VALIDITY_SIGN, index,
+ property->name, value, property->value_free, property->value_clone);
+ }
+ }
+ }
+
+ head = g_queue_peek_head_link (&vin->encrypt.encrypters);
+ for (link = head; link != NULL; link = g_list_next (link)) {
+ CamelCipherCertInfo *info = link->data;
+ gint index;
+
+ if (info->cert_data && info->cert_data_clone && info->cert_data_free)
+ index = camel_cipher_validity_add_certinfo_ex (
+ vo, CAMEL_CIPHER_VALIDITY_SIGN,
+ info->name,
+ info->email,
+ info->cert_data_clone (info->cert_data),
+ info->cert_data_free,
+ info->cert_data_clone);
+ else
+ index = camel_cipher_validity_add_certinfo (
+ vo, CAMEL_CIPHER_VALIDITY_ENCRYPT,
+ info->name,
+ info->email);
+
+ if (index != -1 && info->properties) {
+ GSList *link;
+
+ for (link = info->properties; link; link = g_slist_next (link)) {
+ CamelCipherCertInfoProperty *property = link->data;
+ gpointer value;
+
+ if (!property)
+ continue;
+
+ value = property->value_clone ? property->value_clone (property->value) : property->value;
+ camel_cipher_validity_set_certinfo_property (vo, CAMEL_CIPHER_VALIDITY_ENCRYPT, index,
+ property->name, value, property->value_free, property->value_clone);
+ }
+ }
+ }
+
+ return vo;
+}
+
+/**
+ * camel_cipher_validity_add_certinfo:
+ * @vin:
+ * @mode:
+ * @name:
+ * @email:
+ *
+ * Add a cert info to the signer or encrypter info.
+ *
+ * Returns: Index of the added certinfo; -1 on error
+ **/
+gint
+camel_cipher_validity_add_certinfo (CamelCipherValidity *vin,
+ CamelCipherValidityMode mode,
+ const gchar *name,
+ const gchar *email)
+{
+ return camel_cipher_validity_add_certinfo_ex (vin, mode, name, email, NULL, NULL, NULL);
+}
+
+/**
+ * camel_cipher_validity_add_certinfo_ex:
+ *
+ * Add a cert info to the signer or encrypter info, with extended data set.
+ *
+ * Returns: Index of the added certinfo; -1 on error
+ *
+ * Since: 2.30
+ **/
+gint
+camel_cipher_validity_add_certinfo_ex (CamelCipherValidity *vin,
+ CamelCipherValidityMode mode,
+ const gchar *name,
+ const gchar *email,
+ gpointer cert_data,
+ GDestroyNotify cert_data_free,
+ CamelCipherCloneFunc cert_data_clone)
+{
+ CamelCipherCertInfo *info;
+ GQueue *queue;
+
+ g_return_val_if_fail (vin != NULL, -1);
+ if (cert_data) {
+ g_return_val_if_fail (cert_data_free != NULL, -1);
+ g_return_val_if_fail (cert_data_clone != NULL, -1);
+ }
+
+ info = g_malloc0 (sizeof (*info));
+ info->name = g_strdup (name);
+ info->email = g_strdup (email);
+ if (cert_data) {
+ info->cert_data = cert_data;
+ info->cert_data_free = cert_data_free;
+ info->cert_data_clone = cert_data_clone;
+ }
+
+ if (mode == CAMEL_CIPHER_VALIDITY_SIGN)
+ queue = &vin->sign.signers;
+ else
+ queue = &vin->encrypt.encrypters;
+
+ g_queue_push_tail (queue, info);
+
+ return (gint) (g_queue_get_length (queue) - 1);
+}
+
+/**
+ * camel_cipher_validity_get_certinfo_property:
+ * @vin: a #CamelCipherValidity
+ * @mode: which cipher validity part to use
+ * @info_index: a 0-based index of the requested #CamelCipherCertInfo
+ * @name: a property name
+ *
+ * Gets a named property @name value for the given @info_index of the @mode validity part.
+ *
+ * Returns: Value of a named property of a #CamelCipherCertInfo, or %NULL when no such
+ * property exists. The returned value is owned by the associated #CamelCipherCertInfo
+ * and is valid until the cert info is freed.
+ *
+ * Since: 3.22
+ **/
+gpointer
+camel_cipher_validity_get_certinfo_property (CamelCipherValidity *vin,
+ CamelCipherValidityMode mode,
+ gint info_index,
+ const gchar *name)
+{
+ GQueue *queue;
+ CamelCipherCertInfo *cert_info;
+
+ g_return_val_if_fail (vin != NULL, NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ if (mode == CAMEL_CIPHER_VALIDITY_SIGN)
+ queue = &vin->sign.signers;
+ else
+ queue = &vin->encrypt.encrypters;
+
+ g_return_val_if_fail (info_index >= 0 && info_index < g_queue_get_length (queue), NULL);
+
+ cert_info = g_queue_peek_nth (queue, info_index);
+
+ g_return_val_if_fail (cert_info != NULL, NULL);
+
+ return camel_cipher_certinfo_get_property (cert_info, name);
+}
+
+/**
+ * camel_cipher_validity_set_certinfo_property:
+ * @vin: a #CamelCipherValidity
+ * @mode: which cipher validity part to use
+ * @info_index: a 0-based index of the requested #CamelCipherCertInfo
+ * @name: a property name
+ * @value: (nullable): a property value, or %NULL
+ * @value_free: (nullable): a free function for the @value
+ * @value_clone: (nullable): a clone function for the @value
+ *
+ * Sets a named property @name value @value for the given @info_index
+ * of the @mode validity part. If the @value is %NULL, then the property
+ * is removed. With a non-%NULL @value also @value_free and @value_clone
+ * functions cannot be %NULL.
+ *
+ * Since: 3.22
+ **/
+void
+camel_cipher_validity_set_certinfo_property (CamelCipherValidity *vin,
+ CamelCipherValidityMode mode,
+ gint info_index,
+ const gchar *name,
+ gpointer value,
+ GDestroyNotify value_free,
+ CamelCipherCloneFunc value_clone)
+{
+ GQueue *queue;
+ CamelCipherCertInfo *cert_info;
+
+ g_return_if_fail (vin != NULL);
+ g_return_if_fail (name != NULL);
+
+ if (mode == CAMEL_CIPHER_VALIDITY_SIGN)
+ queue = &vin->sign.signers;
+ else
+ queue = &vin->encrypt.encrypters;
+
+ g_return_if_fail (info_index >= 0 && info_index < g_queue_get_length (queue));
+
+ cert_info = g_queue_peek_nth (queue, info_index);
+
+ g_return_if_fail (cert_info != NULL);
+
+ camel_cipher_certinfo_set_property (cert_info, name, value, value_free, value_clone);
+}
+
+/**
+ * camel_cipher_validity_envelope:
+ * @parent:
+ * @valid:
+ *
+ * Calculate a conglomerate validity based on wrapping one secure part inside
+ * another one.
+ **/
+void
+camel_cipher_validity_envelope (CamelCipherValidity *parent,
+ CamelCipherValidity *valid)
+{
+
+ g_return_if_fail (parent != NULL);
+ g_return_if_fail (valid != NULL);
+
+ if (parent->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE
+ && parent->encrypt.status == CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE
+ && valid->sign.status == CAMEL_CIPHER_VALIDITY_SIGN_NONE
+ && valid->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE) {
+ GList *head, *link;
+
+ /* case 1: only signed inside only encrypted -> merge both */
+ parent->encrypt.status = valid->encrypt.status;
+ parent->encrypt.description = g_strdup (valid->encrypt.description);
+
+ head = g_queue_peek_head_link (&valid->encrypt.encrypters);
+ for (link = head; link != NULL; link = g_list_next (link)) {
+ CamelCipherCertInfo *info = link->data;
+ camel_cipher_validity_add_certinfo (
+ parent, CAMEL_CIPHER_VALIDITY_ENCRYPT,
+ info->name, info->email);
+ }
+ } else if (parent->sign.status == CAMEL_CIPHER_VALIDITY_SIGN_NONE
+ && parent->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE
+ && valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE
+ && valid->encrypt.status == CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE) {
+ GList *head, *link;
+
+ /* case 2: only encrypted inside only signed */
+ parent->sign.status = valid->sign.status;
+ parent->sign.description = g_strdup (valid->sign.description);
+
+ head = g_queue_peek_head_link (&valid->sign.signers);
+ for (link = head; link != NULL; link = g_list_next (link)) {
+ CamelCipherCertInfo *info = link->data;
+ camel_cipher_validity_add_certinfo (
+ parent, CAMEL_CIPHER_VALIDITY_SIGN,
+ info->name, info->email);
+ }
+ }
+ /* Otherwise, I dunno - what do you do? */
+}
+
+void
+camel_cipher_validity_free (CamelCipherValidity *validity)
+{
+ CamelCipherValidity *child;
+ CamelCipherCertInfo *info;
+ GQueue *queue;
+
+ if (validity == NULL)
+ return;
+
+ queue = &validity->children;
+ while ((child = g_queue_pop_head (queue)) != NULL)
+ camel_cipher_validity_free (child);
+
+ queue = &validity->sign.signers;
+ while ((info = g_queue_pop_head (queue)) != NULL)
+ ccv_certinfo_free (info);
+
+ queue = &validity->encrypt.encrypters;
+ while ((info = g_queue_pop_head (queue)) != NULL)
+ ccv_certinfo_free (info);
+
+ camel_cipher_validity_clear (validity);
+ g_free (validity);
+}
+
+/* ********************************************************************** */
+
+/**
+ * camel_cipher_certinfo_get_property:
+ * @cert_info: a #CamelCipherCertInfo
+ * @name: a property name
+ *
+ * Gets a named property @name value for the given @cert_info.
+ *
+ * Returns: Value of a named property of the @cert_info, or %NULL when no such
+ * property exists. The returned value is owned by the @cert_info
+ * and is valid until the @cert_info is freed.
+ *
+ * Since: 3.22
+ **/
+gpointer
+camel_cipher_certinfo_get_property (CamelCipherCertInfo *cert_info,
+ const gchar *name)
+{
+ GSList *link;
+
+ g_return_val_if_fail (cert_info != NULL, NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ for (link = cert_info->properties; link; link = g_slist_next (link)) {
+ CamelCipherCertInfoProperty *property = link->data;
+
+ if (property && g_ascii_strcasecmp (property->name, name) == 0)
+ return property->value;
+ }
+
+ return NULL;
+}
+
+/**
+ * camel_cipher_certinfo_set_property:
+ * @cert_info: a #CamelCipherCertInfo
+ * @name: a property name
+ * @value: (nullable): a property value, or %NULL
+ * @value_free: (nullable): a free function for the @value
+ * @value_clone: (nullable): a clone function for the @value
+ *
+ * Sets a named property @name value @value for the given @cert_info.
+ * If the @value is %NULL, then the property is removed. With a non-%NULL
+ * @value also @value_free and @value_clone functions cannot be %NULL.
+ *
+ * Since: 3.22
+ **/
+void
+camel_cipher_certinfo_set_property (CamelCipherCertInfo *cert_info,
+ const gchar *name,
+ gpointer value,
+ GDestroyNotify value_free,
+ CamelCipherCloneFunc value_clone)
+{
+ CamelCipherCertInfoProperty *property;
+ GSList *link;
+
+ g_return_if_fail (cert_info != NULL);
+ g_return_if_fail (name != NULL);
+
+ if (value) {
+ g_return_if_fail (value_free != NULL);
+ g_return_if_fail (value_clone != NULL);
+ }
+
+ for (link = cert_info->properties; link; link = g_slist_next (link)) {
+ property = link->data;
+
+ if (property && g_ascii_strcasecmp (property->name, name) == 0) {
+ if (value && property->value != value) {
+ /* Replace current value with the new value. */
+ property->value_free (property->value);
+
+ property->value = value;
+ property->value_free = value_free;
+ property->value_clone = value_clone;
+ } else if (!value) {
+ cert_info->properties = g_slist_remove (cert_info->properties, property);
+ ccv_certinfo_property_free (property);
+ }
+ break;
+ }
+ }
+
+ if (value && !link) {
+ property = g_new0 (CamelCipherCertInfoProperty, 1);
+ property->name = g_strdup (name);
+ property->value = value;
+ property->value_free = value_free;
+ property->value_clone = value_clone;
+
+ cert_info->properties = g_slist_prepend (cert_info->properties, property);
+ }
+}
+
+/* ********************************************************************** */
+
+/**
+ * camel_cipher_context_new:
+ * @session: a #CamelSession
+ *
+ * This creates a new CamelCipherContext object which is used to sign,
+ * verify, encrypt and decrypt streams.
+ *
+ * Returns: the new CamelCipherContext
+ **/
+CamelCipherContext *
+camel_cipher_context_new (CamelSession *session)
+{
+ g_return_val_if_fail (session != NULL, NULL);
+
+ return g_object_new (
+ CAMEL_TYPE_CIPHER_CONTEXT,
+ "session", session, NULL);
+}
+
+/**
+ * camel_cipher_context_get_session:
+ * @context: a #CamelCipherContext
+ *
+ * Returns: (transfer none):
+ *
+ * Since: 2.32
+ **/
+CamelSession *
+camel_cipher_context_get_session (CamelCipherContext *context)
+{
+ g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), NULL);
+
+ return context->priv->session;
+}
+
+/* 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
+cc_prepare_sign (CamelMimePart *part)
+{
+ CamelDataWrapper *dw;
+ CamelTransferEncoding encoding;
+ gint parts, i;
+
+ dw = camel_medium_get_content ((CamelMedium *) part);
+ if (!dw)
+ return;
+
+ /* should not change encoding for these, they have the right encoding set already */
+ if (CAMEL_IS_MULTIPART_SIGNED (dw) || CAMEL_IS_MULTIPART_ENCRYPTED (dw))
+ return;
+
+ if (CAMEL_IS_MULTIPART (dw)) {
+ parts = camel_multipart_get_number ((CamelMultipart *) dw);
+ for (i = 0; i < parts; i++)
+ cc_prepare_sign (camel_multipart_get_part ((CamelMultipart *) dw, i));
+ } else if (CAMEL_IS_MIME_MESSAGE (dw)) {
+ cc_prepare_sign ((CamelMimePart *) dw);
+ } else {
+ encoding = camel_mime_part_get_encoding (part);
+
+ if (encoding != CAMEL_TRANSFER_ENCODING_BASE64
+ && encoding != CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE) {
+ camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE);
+ }
+ }
+}
+
+/**
+ * camel_cipher_canonical_to_stream:
+ * @part: Part to write.
+ * @flags: flags for the canonicalisation filter (CamelMimeFilterCanon)
+ * @ostream: stream to write canonicalised output to.
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Writes a part to a stream in a canonicalised format, suitable for signing/encrypting.
+ *
+ * The transfer encoding paramaters for the part may be changed by this function.
+ *
+ * Returns: -1 on error;
+ **/
+gint
+camel_cipher_canonical_to_stream (CamelMimePart *part,
+ guint32 flags,
+ CamelStream *ostream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStream *filter;
+ CamelMimeFilter *canon;
+ gint res = -1;
+
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (part), -1);
+ g_return_val_if_fail (CAMEL_IS_STREAM (ostream), -1);
+
+ if (flags & (CAMEL_MIME_FILTER_CANON_FROM | CAMEL_MIME_FILTER_CANON_STRIP))
+ cc_prepare_sign (part);
+
+ filter = camel_stream_filter_new (ostream);
+ canon = camel_mime_filter_canon_new (flags);
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (filter), canon);
+ g_object_unref (canon);
+
+ if (camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (part), filter, cancellable, error) != -1
+ && camel_stream_flush (filter, cancellable, error) != -1)
+ res = 0;
+
+ g_object_unref (filter);
+
+ /* Reset stream position to beginning. */
+ if (G_IS_SEEKABLE (ostream))
+ g_seekable_seek (
+ G_SEEKABLE (ostream), 0,
+ G_SEEK_SET, NULL, NULL);
+
+ return res;
+}
+
+/**
+ * camel_cipher_can_load_photos:
+ *
+ * Returns: Whether ciphers can load photos, as being setup by the user.
+ *
+ * Since: 3.22
+ **/
+gboolean
+camel_cipher_can_load_photos (void)
+{
+ GSettings *settings;
+ gboolean load_photos;
+
+ settings = g_settings_new ("org.gnome.evolution-data-server");
+ load_photos = g_settings_get_boolean (settings, "camel-cipher-load-photos");
+ g_clear_object (&settings);
+
+ return load_photos;
+}
diff --git a/src/camel/camel-cipher-context.h b/src/camel/camel-cipher-context.h
new file mode 100644
index 000000000..191a24dd1
--- /dev/null
+++ b/src/camel/camel-cipher-context.h
@@ -0,0 +1,353 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_CIPHER_CONTEXT_H
+#define CAMEL_CIPHER_CONTEXT_H
+
+#include <camel/camel-mime-part.h>
+#include <camel/camel-session.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_CIPHER_CONTEXT \
+ (camel_cipher_context_get_type ())
+#define CAMEL_CIPHER_CONTEXT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_CIPHER_CONTEXT, CamelCipherContext))
+#define CAMEL_CIPHER_CONTEXT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_CIPHER_CONTEXT, CamelCipherContextClass))
+#define CAMEL_IS_CIPHER_CONTEXT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_CIPHER_CONTEXT))
+#define CAMEL_IS_CIPHER_CONTEXT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_CIPHER_CONTEXT))
+#define CAMEL_CIPHER_CONTEXT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_CIPHER_CONTEXT, CamelCipherContextClass))
+
+/**
+ * CAMEL_CIPHER_CERT_INFO_PROPERTY_PHOTO_FILENAME:
+ *
+ * Name of the photo-filename property which can be stored
+ * on a #CamelCipherCertInfo.
+ *
+ * Since: 3.22
+ **/
+#define CAMEL_CIPHER_CERT_INFO_PROPERTY_PHOTO_FILENAME "photo-filename"
+
+G_BEGIN_DECLS
+
+typedef gpointer (* CamelCipherCloneFunc) (gpointer value);
+
+typedef struct _CamelCipherValidity CamelCipherValidity;
+typedef struct _CamelCipherCertInfo CamelCipherCertInfo;
+typedef struct _CamelCipherCertInfoProperty CamelCipherCertInfoProperty;
+
+typedef struct _CamelCipherContext CamelCipherContext;
+typedef struct _CamelCipherContextClass CamelCipherContextClass;
+typedef struct _CamelCipherContextPrivate CamelCipherContextPrivate;
+
+typedef enum {
+ CAMEL_CIPHER_HASH_DEFAULT,
+ CAMEL_CIPHER_HASH_MD2,
+ CAMEL_CIPHER_HASH_MD5,
+ CAMEL_CIPHER_HASH_SHA1,
+ CAMEL_CIPHER_HASH_SHA256,
+ CAMEL_CIPHER_HASH_SHA384,
+ CAMEL_CIPHER_HASH_SHA512,
+ CAMEL_CIPHER_HASH_RIPEMD160,
+ CAMEL_CIPHER_HASH_TIGER192,
+ CAMEL_CIPHER_HASH_HAVAL5160
+} CamelCipherHash;
+
+typedef enum _camel_cipher_validity_sign_t {
+ CAMEL_CIPHER_VALIDITY_SIGN_NONE,
+ CAMEL_CIPHER_VALIDITY_SIGN_GOOD,
+ CAMEL_CIPHER_VALIDITY_SIGN_BAD,
+ CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN,
+ CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY
+} CamelCipherValiditySign;
+
+typedef enum _camel_cipher_validity_encrypt_t {
+ CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE,
+ CAMEL_CIPHER_VALIDITY_ENCRYPT_WEAK,
+ CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED, /* encrypted, unknown strenght */
+ CAMEL_CIPHER_VALIDITY_ENCRYPT_STRONG
+} CamelCipherValidityEncrypt;
+
+typedef enum _camel_cipher_validity_mode_t {
+ CAMEL_CIPHER_VALIDITY_SIGN,
+ CAMEL_CIPHER_VALIDITY_ENCRYPT
+} CamelCipherValidityMode;
+
+struct _CamelCipherCertInfoProperty {
+ gchar *name;
+ gpointer value;
+
+ GDestroyNotify value_free;
+ CamelCipherCloneFunc value_clone;
+};
+
+struct _CamelCipherCertInfo {
+ gchar *name; /* common name */
+ gchar *email;
+
+ gpointer cert_data; /* custom certificate data; can be NULL */
+ GDestroyNotify cert_data_free; /* called to free cert_data; can be NULL only if cert_data is NULL */
+ CamelCipherCloneFunc cert_data_clone; /* called to clone cert_data; can be NULL only if cert_data is NULL */
+
+ GSList *properties; /* CamelCipherCertInfoProperty * */
+};
+
+struct _CamelCipherValidity {
+ GQueue children;
+
+ struct {
+ CamelCipherValiditySign status;
+ gchar *description;
+ GQueue signers; /* CamelCipherCertInfo's */
+ } sign;
+
+ struct {
+ CamelCipherValidityEncrypt status;
+ gchar *description;
+ GQueue encrypters; /* CamelCipherCertInfo's */
+ } encrypt;
+};
+
+struct _CamelCipherContext {
+ GObject parent;
+ CamelCipherContextPrivate *priv;
+};
+
+struct _CamelCipherContextClass {
+ GObjectClass parent_class;
+
+ /* these MUST be set by implementors */
+ const gchar *sign_protocol;
+ const gchar *encrypt_protocol;
+ const gchar *key_protocol;
+
+ /* Non-Blocking Methods */
+ CamelCipherHash (*id_to_hash) (CamelCipherContext *context,
+ const gchar *id);
+ const gchar * (*hash_to_id) (CamelCipherContext *context,
+ CamelCipherHash hash);
+
+ /* Synchronous I/O Methods */
+ gboolean (*sign_sync) (CamelCipherContext *context,
+ const gchar *userid,
+ CamelCipherHash hash,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error);
+ CamelCipherValidity *
+ (*verify_sync) (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*encrypt_sync) (CamelCipherContext *context,
+ const gchar *userid,
+ GPtrArray *recipients,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error);
+ CamelCipherValidity *
+ (*decrypt_sync) (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* Reserved slots. */
+ gpointer reserved[8];
+};
+
+GType camel_cipher_context_get_type (void);
+CamelCipherContext *
+ camel_cipher_context_new (CamelSession *session);
+CamelSession * camel_cipher_context_get_session
+ (CamelCipherContext *context);
+
+/* cipher context util routines */
+CamelCipherHash camel_cipher_context_id_to_hash (CamelCipherContext *context,
+ const gchar *id);
+const gchar * camel_cipher_context_hash_to_id (CamelCipherContext *context,
+ CamelCipherHash hash);
+
+/* FIXME:
+ * There are some inconsistencies here, the api's should probably handle CamelMimePart's as input/outputs,
+ * Something that might generate a multipart/signed should do it as part of that processing, internally
+ * to the cipher, etc etc. */
+
+/* cipher routines */
+gboolean camel_cipher_context_sign_sync (CamelCipherContext *context,
+ const gchar *userid,
+ CamelCipherHash hash,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error);
+void camel_cipher_context_sign (CamelCipherContext *context,
+ const gchar *userid,
+ CamelCipherHash hash,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_cipher_context_sign_finish
+ (CamelCipherContext *context,
+ GAsyncResult *result,
+ GError **error);
+CamelCipherValidity *
+ camel_cipher_context_verify_sync
+ (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ GCancellable *cancellable,
+ GError **error);
+void camel_cipher_context_verify (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelCipherValidity *
+ camel_cipher_context_verify_finish
+ (CamelCipherContext *context,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_cipher_context_encrypt_sync
+ (CamelCipherContext *context,
+ const gchar *userid,
+ GPtrArray *recipients,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error);
+void camel_cipher_context_encrypt (CamelCipherContext *context,
+ const gchar *userid,
+ GPtrArray *recipients,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_cipher_context_encrypt_finish
+ (CamelCipherContext *context,
+ GAsyncResult *result,
+ GError **error);
+CamelCipherValidity *
+ camel_cipher_context_decrypt_sync
+ (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error);
+void camel_cipher_context_decrypt (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelCipherValidity *
+ camel_cipher_context_decrypt_finish
+ (CamelCipherContext *context,
+ GAsyncResult *result,
+ GError **error);
+
+/* CamelCipherValidity utility functions */
+GType camel_cipher_validity_get_type (void);
+CamelCipherValidity *
+ camel_cipher_validity_new (void);
+void camel_cipher_validity_init (CamelCipherValidity *validity);
+gboolean camel_cipher_validity_get_valid (CamelCipherValidity *validity);
+void camel_cipher_validity_set_valid (CamelCipherValidity *validity,
+ gboolean valid);
+gchar * camel_cipher_validity_get_description
+ (CamelCipherValidity *validity);
+void camel_cipher_validity_set_description
+ (CamelCipherValidity *validity,
+ const gchar *description);
+void camel_cipher_validity_clear (CamelCipherValidity *validity);
+CamelCipherValidity *
+ camel_cipher_validity_clone (CamelCipherValidity *vin);
+gint camel_cipher_validity_add_certinfo
+ (CamelCipherValidity *vin,
+ CamelCipherValidityMode mode,
+ const gchar *name,
+ const gchar *email);
+gint camel_cipher_validity_add_certinfo_ex (
+ CamelCipherValidity *vin,
+ CamelCipherValidityMode mode,
+ const gchar *name,
+ const gchar *email,
+ gpointer cert_data,
+ GDestroyNotify cert_data_free,
+ CamelCipherCloneFunc cert_data_clone);
+gpointer camel_cipher_validity_get_certinfo_property
+ (CamelCipherValidity *vin,
+ CamelCipherValidityMode mode,
+ gint info_index,
+ const gchar *name);
+void camel_cipher_validity_set_certinfo_property
+ (CamelCipherValidity *vin,
+ CamelCipherValidityMode mode,
+ gint info_index,
+ const gchar *name,
+ gpointer value,
+ GDestroyNotify value_free,
+ CamelCipherCloneFunc value_clone);
+void camel_cipher_validity_envelope (CamelCipherValidity *parent,
+ CamelCipherValidity *valid);
+void camel_cipher_validity_free (CamelCipherValidity *validity);
+
+/* CamelCipherCertInfo utility functions */
+gpointer camel_cipher_certinfo_get_property
+ (CamelCipherCertInfo *cert_info,
+ const gchar *name);
+void camel_cipher_certinfo_set_property
+ (CamelCipherCertInfo *cert_info,
+ const gchar *name,
+ gpointer value,
+ GDestroyNotify value_free,
+ CamelCipherCloneFunc value_clone);
+
+/* utility functions */
+gint camel_cipher_canonical_to_stream
+ (CamelMimePart *part,
+ guint32 flags,
+ CamelStream *ostream,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_cipher_can_load_photos (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_CIPHER_CONTEXT_H */
diff --git a/src/camel/camel-data-cache.c b/src/camel/camel-data-cache.c
new file mode 100644
index 000000000..13615a031
--- /dev/null
+++ b/src/camel/camel-data-cache.c
@@ -0,0 +1,590 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-message-cache.c: Class for a Camel cache.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-data-cache.h"
+#include "camel-object.h"
+#include "camel-object-bag.h"
+#include "camel-stream-mem.h"
+#include "camel-file-utils.h"
+
+#define d(x)
+
+#define CAMEL_DATA_CACHE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_DATA_CACHE, CamelDataCachePrivate))
+
+/* how many 'bits' of hash are used to key the toplevel directory */
+#define CAMEL_DATA_CACHE_BITS (6)
+#define CAMEL_DATA_CACHE_MASK ((1 << CAMEL_DATA_CACHE_BITS)-1)
+
+/* timeout before a cache dir is checked again for expired entries,
+ * once an hour should be enough */
+#define CAMEL_DATA_CACHE_CYCLE_TIME (60*60)
+
+struct _CamelDataCachePrivate {
+ CamelObjectBag *busy_bag;
+
+ gchar *path;
+
+ time_t expire_age;
+ time_t expire_access;
+
+ time_t expire_last[1 << CAMEL_DATA_CACHE_BITS];
+};
+
+enum {
+ PROP_0,
+ PROP_PATH
+};
+
+G_DEFINE_TYPE (CamelDataCache, camel_data_cache, G_TYPE_OBJECT)
+
+static void
+data_cache_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PATH:
+ camel_data_cache_set_path (
+ CAMEL_DATA_CACHE (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+data_cache_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PATH:
+ g_value_set_string (
+ value, camel_data_cache_get_path (
+ CAMEL_DATA_CACHE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+data_cache_finalize (GObject *object)
+{
+ CamelDataCachePrivate *priv;
+
+ priv = CAMEL_DATA_CACHE_GET_PRIVATE (object);
+
+ camel_object_bag_destroy (priv->busy_bag);
+ g_free (priv->path);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_data_cache_parent_class)->finalize (object);
+}
+
+static void
+camel_data_cache_class_init (CamelDataCacheClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelDataCachePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = data_cache_set_property;
+ object_class->get_property = data_cache_get_property;
+ object_class->finalize = data_cache_finalize;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PATH,
+ g_param_spec_string (
+ "path",
+ "Path",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+camel_data_cache_init (CamelDataCache *data_cache)
+{
+ CamelObjectBag *busy_bag;
+
+ busy_bag = camel_object_bag_new (
+ g_str_hash, g_str_equal,
+ (CamelCopyFunc) g_strdup,
+ (GFreeFunc) g_free);
+
+ data_cache->priv = CAMEL_DATA_CACHE_GET_PRIVATE (data_cache);
+ data_cache->priv->busy_bag = busy_bag;
+ data_cache->priv->expire_age = -1;
+ data_cache->priv->expire_access = -1;
+}
+
+/**
+ * camel_data_cache_new:
+ * @path: Base path of cache, subdirectories will be created here.
+ * @error: return location for a #GError, or %NULL
+ *
+ * Create a new data cache.
+ *
+ * Returns: A new cache object, or NULL if the base path cannot
+ * be written to.
+ **/
+CamelDataCache *
+camel_data_cache_new (const gchar *path,
+ GError **error)
+{
+ g_return_val_if_fail (path != NULL, NULL);
+
+ if (g_mkdir_with_parents (path, 0700) == -1) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Unable to create cache path"));
+ return NULL;
+ }
+
+ return g_object_new (CAMEL_TYPE_DATA_CACHE, "path", path, NULL);
+}
+
+/**
+ * camel_data_cache_get_path:
+ * @cdc: a #CamelDataCache
+ *
+ * Returns the path to the data cache.
+ *
+ * Returns: the path to the data cache
+ *
+ * Since: 2.32
+ **/
+const gchar *
+camel_data_cache_get_path (CamelDataCache *cdc)
+{
+ g_return_val_if_fail (CAMEL_IS_DATA_CACHE (cdc), NULL);
+
+ return cdc->priv->path;
+}
+
+/**
+ * camel_data_cache_set_path:
+ * @cdc: a #CamelDataCache
+ * @path: path to the data cache
+ *
+ * Sets the path to the data cache.
+ *
+ * Since: 2.32
+ **/
+void
+camel_data_cache_set_path (CamelDataCache *cdc,
+ const gchar *path)
+{
+ g_return_if_fail (CAMEL_IS_DATA_CACHE (cdc));
+ g_return_if_fail (path != NULL);
+
+ if (g_strcmp0 (cdc->priv->path, path) == 0)
+ return;
+
+ g_free (cdc->priv->path);
+ cdc->priv->path = g_strdup (path);
+
+ g_object_notify (G_OBJECT (cdc), "path");
+}
+
+/**
+ * camel_data_cache_set_expire_age:
+ * @cdc: A #CamelDataCache
+ * @when: Timeout for age expiry, or -1 to disable.
+ *
+ * Set the cache expiration policy for aged entries.
+ *
+ * Items in the cache older than @when seconds may be
+ * flushed at any time. Items are expired in a lazy
+ * manner, so it is indeterminate when the items will
+ * physically be removed.
+ *
+ * Note you can set both an age and an access limit. The
+ * age acts as a hard limit on cache entries.
+ **/
+void
+camel_data_cache_set_expire_age (CamelDataCache *cdc,
+ time_t when)
+{
+ g_return_if_fail (CAMEL_IS_DATA_CACHE (cdc));
+
+ cdc->priv->expire_age = when;
+}
+
+/**
+ * camel_data_cache_set_expire_access:
+ * @cdc: A #CamelDataCache
+ * @when: Timeout for access, or -1 to disable access expiry.
+ *
+ * Set the cache expiration policy for access times.
+ *
+ * Items in the cache which haven't been accessed for @when
+ * seconds may be expired at any time. Items are expired in a lazy
+ * manner, so it is indeterminate when the items will
+ * physically be removed.
+ *
+ * Note you can set both an age and an access limit. The
+ * age acts as a hard limit on cache entries.
+ **/
+void
+camel_data_cache_set_expire_access (CamelDataCache *cdc,
+ time_t when)
+{
+ g_return_if_fail (CAMEL_IS_DATA_CACHE (cdc));
+
+ cdc->priv->expire_access = when;
+}
+
+static void
+data_cache_expire (CamelDataCache *cdc,
+ const gchar *path,
+ const gchar *keep,
+ time_t now,
+ gboolean expire_all)
+{
+ GDir *dir;
+ const gchar *dname;
+ struct stat st;
+ GIOStream *stream;
+
+ dir = g_dir_open (path, 0, NULL);
+ if (dir == NULL)
+ return;
+
+ while ((dname = g_dir_read_name (dir))) {
+ gchar *dpath;
+
+ if (keep && strcmp (dname, keep) == 0)
+ continue;
+
+ dpath = g_build_filename (path, dname, NULL);
+
+ if (g_stat (dpath, &st) == 0
+ && S_ISREG (st.st_mode)
+ && (expire_all
+ || (cdc->priv->expire_age != -1 && st.st_mtime + cdc->priv->expire_age < now)
+ || (cdc->priv->expire_access != -1 && st.st_atime + cdc->priv->expire_access < now))) {
+ g_unlink (dpath);
+ stream = camel_object_bag_get (cdc->priv->busy_bag, dpath);
+ if (stream) {
+ camel_object_bag_remove (cdc->priv->busy_bag, stream);
+ g_object_unref (stream);
+ }
+ }
+
+ g_free (dpath);
+ }
+ g_dir_close (dir);
+}
+
+/* Since we have to stat the directory anyway, we use this opportunity to
+ * lazily expire old data.
+ * If it is this directories 'turn', and we haven't done it for CYCLE_TIME seconds,
+ * then we perform an expiry run */
+static gchar *
+data_cache_path (CamelDataCache *cdc,
+ gint create,
+ const gchar *path,
+ const gchar *key)
+{
+ gchar *dir, *real, *tmp;
+ gsize dir_len;
+ guint32 hash;
+
+ hash = g_str_hash (key);
+ hash = (hash >> 5) &CAMEL_DATA_CACHE_MASK;
+ dir_len = strlen (cdc->priv->path) + strlen (path) + 8;
+ dir = alloca (dir_len);
+ g_snprintf (dir, dir_len, "%s/%s/%02x", cdc->priv->path, path, hash);
+
+ if (g_access (dir, F_OK) == -1) {
+ if (create)
+ g_mkdir_with_parents (dir, 0700);
+ } else if (cdc->priv->expire_age != -1 || cdc->priv->expire_access != -1) {
+ time_t now;
+
+ /* This has a race, but at worst we re-run an expire cycle which is safe */
+ now = time (NULL);
+ if (cdc->priv->expire_last[hash] + CAMEL_DATA_CACHE_CYCLE_TIME < now) {
+ cdc->priv->expire_last[hash] = now;
+ data_cache_expire (cdc, dir, key, now, FALSE);
+ }
+ }
+
+ tmp = camel_file_util_safe_filename (key);
+ real = g_strdup_printf ("%s/%s", dir, tmp);
+ g_free (tmp);
+
+ return real;
+}
+
+/**
+ * camel_data_cache_add:
+ * @cdc: A #CamelDataCache
+ * @path: Relative path of item to add.
+ * @key: Key of item to add.
+ * @error: return location for a #GError, or %NULL
+ *
+ * Add a new item to the cache, returning a #GIOStream to the new item.
+ *
+ * The key and the path combine to form a unique key used to store the item.
+ *
+ * Potentially, expiry processing will be performed while this call is
+ * executing.
+ *
+ * The returned #GIOStream is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: (transfer full): a #GIOStream for the new cache item, or %NULL
+ **/
+GIOStream *
+camel_data_cache_add (CamelDataCache *cdc,
+ const gchar *path,
+ const gchar *key,
+ GError **error)
+{
+ gchar *real;
+ GFileIOStream *stream;
+ GFile *file;
+
+ g_return_val_if_fail (CAMEL_IS_DATA_CACHE (cdc), NULL);
+
+ real = data_cache_path (cdc, TRUE, path, key);
+ /* need to loop 'cause otherwise we can call bag_add/bag_abort
+ * after bag_reserve returned a pointer, which is an invalid
+ * sequence. */
+ do {
+ stream = camel_object_bag_reserve (cdc->priv->busy_bag, real);
+ if (stream) {
+ g_unlink (real);
+ camel_object_bag_remove (cdc->priv->busy_bag, stream);
+ g_object_unref (stream);
+ }
+ } while (stream != NULL);
+
+ file = g_file_new_for_path (real);
+ stream = g_file_replace_readwrite (
+ file, NULL, FALSE, G_FILE_CREATE_PRIVATE, NULL, error);
+ g_object_unref (file);
+
+ if (stream != NULL)
+ camel_object_bag_add (cdc->priv->busy_bag, real, stream);
+ else
+ camel_object_bag_abort (cdc->priv->busy_bag, real);
+
+ g_free (real);
+
+ return G_IO_STREAM (stream);
+}
+
+/**
+ * camel_data_cache_get:
+ * @cdc: A #CamelDataCache
+ * @path: Path to the (sub) cache the item exists in.
+ * @key: Key for the cache item.
+ * @error: return location for a #GError, or %NULL
+ *
+ * Lookup an item in the cache. If the item exists, a #GIOStream is returned
+ * for the item. The stream may be shared by multiple callers, so ensure the
+ * stream is in a valid state through external locking.
+ *
+ * The returned #GIOStream is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: (transfer full): a #GIOStream for the requested cache item, or %NULL
+ **/
+GIOStream *
+camel_data_cache_get (CamelDataCache *cdc,
+ const gchar *path,
+ const gchar *key,
+ GError **error)
+{
+ GFileIOStream *stream;
+ GFile *file;
+ struct stat st;
+ gchar *real;
+
+ g_return_val_if_fail (CAMEL_IS_DATA_CACHE (cdc), NULL);
+
+ real = data_cache_path (cdc, FALSE, path, key);
+ stream = camel_object_bag_reserve (cdc->priv->busy_bag, real);
+ if (stream != NULL)
+ goto exit;
+
+ /* An empty cache file is useless. Return an error. */
+ if (g_stat (real, &st) == 0 && st.st_size == 0) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ "%s: %s", _("Empty cache file"), real);
+ camel_object_bag_abort (cdc->priv->busy_bag, real);
+ goto exit;
+ }
+
+ file = g_file_new_for_path (real);
+ stream = g_file_open_readwrite (file, NULL, error);
+ g_object_unref (file);
+
+ if (stream != NULL)
+ camel_object_bag_add (cdc->priv->busy_bag, real, stream);
+ else
+ camel_object_bag_abort (cdc->priv->busy_bag, real);
+
+exit:
+ g_free (real);
+
+ return G_IO_STREAM (stream);
+}
+
+/**
+ * camel_data_cache_get_filename:
+ * @cdc: A #CamelDataCache
+ * @path: Path to the (sub) cache the item exists in.
+ * @key: Key for the cache item.
+ *
+ * Lookup the filename for an item in the cache
+ *
+ * Returns: The filename for a cache item
+ *
+ * Since: 2.26
+ **/
+gchar *
+camel_data_cache_get_filename (CamelDataCache *cdc,
+ const gchar *path,
+ const gchar *key)
+{
+ g_return_val_if_fail (CAMEL_IS_DATA_CACHE (cdc), NULL);
+
+ return data_cache_path (cdc, FALSE, path, key);
+}
+
+/**
+ * camel_data_cache_remove:
+ * @cdc: A #CamelDataCache
+ * @path:
+ * @key:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Remove/expire a cache item.
+ *
+ * Returns:
+ **/
+gint
+camel_data_cache_remove (CamelDataCache *cdc,
+ const gchar *path,
+ const gchar *key,
+ GError **error)
+{
+ GIOStream *stream;
+ gchar *real;
+ gint ret;
+
+ g_return_val_if_fail (CAMEL_IS_DATA_CACHE (cdc), -1);
+
+ real = data_cache_path (cdc, FALSE, path, key);
+ stream = camel_object_bag_get (cdc->priv->busy_bag, real);
+ if (stream) {
+ camel_object_bag_remove (cdc->priv->busy_bag, stream);
+ g_object_unref (stream);
+ }
+
+ /* maybe we were a mem stream */
+ if (g_unlink (real) == -1 && errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not remove cache entry: %s: %s"),
+ real, g_strerror (errno));
+ ret = -1;
+ } else {
+ ret = 0;
+ }
+
+ g_free (real);
+
+ return ret;
+}
+
+/**
+ * camel_data_cache_clear:
+ * @cdc: a #CamelDataCache
+ * @path: Path to the (sub) cache the item exists in.
+ *
+ * Clear cache's content in @path.
+ *
+ * Since: 3.2
+ **/
+void
+camel_data_cache_clear (CamelDataCache *cdc,
+ const gchar *path)
+{
+ gchar *base_dir;
+ GDir *dir;
+ const gchar *dname;
+ struct stat st;
+
+ g_return_if_fail (CAMEL_IS_DATA_CACHE (cdc));
+ g_return_if_fail (path != NULL);
+
+ base_dir = g_build_filename (cdc->priv->path, path, NULL);
+
+ dir = g_dir_open (base_dir, 0, NULL);
+ if (dir == NULL) {
+ g_free (base_dir);
+ return;
+ }
+
+ while ((dname = g_dir_read_name (dir))) {
+ gchar *dpath;
+
+ dpath = g_build_filename (base_dir, dname, NULL);
+
+ if (g_stat (dpath, &st) == 0
+ && S_ISDIR (st.st_mode)
+ && !g_str_equal (dname, ".")
+ && !g_str_equal (dname, "..")) {
+ data_cache_expire (cdc, dpath, NULL, -1, TRUE);
+ }
+
+ g_free (dpath);
+ }
+
+ g_dir_close (dir);
+ g_free (base_dir);
+}
diff --git a/src/camel/camel-data-cache.h b/src/camel/camel-data-cache.h
new file mode 100644
index 000000000..97a468e77
--- /dev/null
+++ b/src/camel/camel-data-cache.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-data-cache.h: Class for a Camel filesystem cache
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_DATA_CACHE_H
+#define CAMEL_DATA_CACHE_H
+
+#include <camel/camel-stream.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_DATA_CACHE \
+ (camel_data_cache_get_type ())
+#define CAMEL_DATA_CACHE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_DATA_CACHE, CamelDataCache))
+#define CAMEL_DATA_CACHE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_DATA_CACHE, CamelDataCacheClass))
+#define CAMEL_IS_DATA_CACHE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_DATA_CACHE))
+#define CAMEL_IS_DATA_CACHE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_DATA_CACHE))
+#define CAMEL_DATA_CACHE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_DATA_CACHE, CamelDataCacheClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelDataCache CamelDataCache;
+typedef struct _CamelDataCacheClass CamelDataCacheClass;
+typedef struct _CamelDataCachePrivate CamelDataCachePrivate;
+
+struct _CamelDataCache {
+ GObject parent;
+ CamelDataCachePrivate *priv;
+};
+
+struct _CamelDataCacheClass {
+ GObjectClass parent_class;
+};
+
+GType camel_data_cache_get_type (void);
+CamelDataCache *camel_data_cache_new (const gchar *path,
+ GError **error);
+const gchar * camel_data_cache_get_path (CamelDataCache *cdc);
+void camel_data_cache_set_path (CamelDataCache *cdc,
+ const gchar *path);
+void camel_data_cache_set_expire_age (CamelDataCache *cdc,
+ time_t when);
+void camel_data_cache_set_expire_access
+ (CamelDataCache *cdc,
+ time_t when);
+GIOStream * camel_data_cache_add (CamelDataCache *cdc,
+ const gchar *path,
+ const gchar *key,
+ GError **error);
+GIOStream * camel_data_cache_get (CamelDataCache *cdc,
+ const gchar *path,
+ const gchar *key,
+ GError **error);
+gint camel_data_cache_remove (CamelDataCache *cdc,
+ const gchar *path,
+ const gchar *key,
+ GError **error);
+gchar * camel_data_cache_get_filename (CamelDataCache *cdc,
+ const gchar *path,
+ const gchar *key);
+void camel_data_cache_clear (CamelDataCache *cdc,
+ const gchar *path);
+
+G_END_DECLS
+
+#endif /* CAMEL_DATA_CACHE_H */
diff --git a/src/camel/camel-data-wrapper.c b/src/camel/camel-data-wrapper.c
new file mode 100644
index 000000000..1da502651
--- /dev/null
+++ b/src/camel/camel-data-wrapper.c
@@ -0,0 +1,1461 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-data-wrapper.h"
+#include "camel-debug.h"
+#include "camel-filter-output-stream.h"
+#include "camel-mime-filter-basic.h"
+#include "camel-mime-filter-crlf.h"
+#include "camel-stream-filter.h"
+#include "camel-stream-mem.h"
+
+#define d(x)
+
+#define CAMEL_DATA_WRAPPER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_DATA_WRAPPER, CamelDataWrapperPrivate))
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _CamelDataWrapperPrivate {
+ GMutex stream_lock;
+ GByteArray *byte_array;
+};
+
+struct _AsyncContext {
+ CamelStream *stream;
+ GInputStream *input_stream;
+ GOutputStream *output_stream;
+};
+
+G_DEFINE_TYPE (CamelDataWrapper, camel_data_wrapper, G_TYPE_OBJECT)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+ g_clear_object (&async_context->stream);
+ g_clear_object (&async_context->input_stream);
+ g_clear_object (&async_context->output_stream);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static void
+data_wrapper_dispose (GObject *object)
+{
+ CamelDataWrapper *data_wrapper = CAMEL_DATA_WRAPPER (object);
+
+ if (data_wrapper->mime_type != NULL) {
+ camel_content_type_unref (data_wrapper->mime_type);
+ data_wrapper->mime_type = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_data_wrapper_parent_class)->dispose (object);
+}
+
+static void
+data_wrapper_finalize (GObject *object)
+{
+ CamelDataWrapperPrivate *priv;
+
+ priv = CAMEL_DATA_WRAPPER_GET_PRIVATE (object);
+
+ g_mutex_clear (&priv->stream_lock);
+ g_byte_array_free (priv->byte_array, TRUE);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_data_wrapper_parent_class)->finalize (object);
+}
+
+static void
+data_wrapper_set_mime_type (CamelDataWrapper *data_wrapper,
+ const gchar *mime_type)
+{
+ if (data_wrapper->mime_type)
+ camel_content_type_unref (data_wrapper->mime_type);
+ data_wrapper->mime_type = camel_content_type_decode (mime_type);
+}
+
+static gchar *
+data_wrapper_get_mime_type (CamelDataWrapper *data_wrapper)
+{
+ return camel_content_type_simple (data_wrapper->mime_type);
+}
+
+static CamelContentType *
+data_wrapper_get_mime_type_field (CamelDataWrapper *data_wrapper)
+{
+ return data_wrapper->mime_type;
+}
+
+static void
+data_wrapper_set_mime_type_field (CamelDataWrapper *data_wrapper,
+ CamelContentType *mime_type)
+{
+ if (mime_type)
+ camel_content_type_ref (mime_type);
+ if (data_wrapper->mime_type)
+ camel_content_type_unref (data_wrapper->mime_type);
+ data_wrapper->mime_type = mime_type;
+}
+
+static gboolean
+data_wrapper_is_offline (CamelDataWrapper *data_wrapper)
+{
+ return data_wrapper->offline;
+}
+
+static gssize
+data_wrapper_write_to_stream_sync (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStream *memory_stream;
+ gssize ret;
+
+ g_mutex_lock (&data_wrapper->priv->stream_lock);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ g_mutex_unlock (&data_wrapper->priv->stream_lock);
+ return -1;
+ }
+
+ memory_stream = camel_stream_mem_new ();
+
+ /* We retain ownership of the byte array. */
+ camel_stream_mem_set_byte_array (
+ CAMEL_STREAM_MEM (memory_stream),
+ data_wrapper->priv->byte_array);
+
+ ret = camel_stream_write_to_stream (
+ memory_stream, stream, cancellable, error);
+
+ g_object_unref (memory_stream);
+
+ g_mutex_unlock (&data_wrapper->priv->stream_lock);
+
+ return ret;
+}
+
+static gssize
+data_wrapper_decode_to_stream_sync (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimeFilter *filter;
+ CamelStream *fstream;
+ gssize ret;
+
+ fstream = camel_stream_filter_new (stream);
+
+ switch (data_wrapper->encoding) {
+ case CAMEL_TRANSFER_ENCODING_BASE64:
+ filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_BASE64_DEC);
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (fstream), filter);
+ g_object_unref (filter);
+ break;
+ case CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE:
+ filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_QP_DEC);
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (fstream), filter);
+ g_object_unref (filter);
+ break;
+ case CAMEL_TRANSFER_ENCODING_UUENCODE:
+ filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_UU_DEC);
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (fstream), filter);
+ g_object_unref (filter);
+ break;
+ default:
+ break;
+ }
+
+ ret = camel_data_wrapper_write_to_stream_sync (
+ data_wrapper, fstream, cancellable, error);
+
+ camel_stream_flush (fstream, NULL, NULL);
+ g_object_unref (fstream);
+
+ return ret;
+}
+
+static gboolean
+data_wrapper_construct_from_stream_sync (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStream *memory_stream;
+ gssize bytes_written;
+
+ g_mutex_lock (&data_wrapper->priv->stream_lock);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ g_mutex_unlock (&data_wrapper->priv->stream_lock);
+ return FALSE;
+ }
+
+ if (G_IS_SEEKABLE (stream)) {
+ if (!g_seekable_seek (G_SEEKABLE (stream), 0, G_SEEK_SET, cancellable, error)) {
+ g_mutex_unlock (&data_wrapper->priv->stream_lock);
+ return FALSE;
+ }
+ }
+
+ /* Wipe any previous contents from our byte array. */
+ g_byte_array_set_size (data_wrapper->priv->byte_array, 0);
+
+ memory_stream = camel_stream_mem_new ();
+
+ /* We retain ownership of the byte array. */
+ camel_stream_mem_set_byte_array (
+ CAMEL_STREAM_MEM (memory_stream),
+ data_wrapper->priv->byte_array);
+
+ /* Transfer incoming contents to our byte array. */
+ bytes_written = camel_stream_write_to_stream (
+ stream, memory_stream, cancellable, error);
+
+ g_object_unref (memory_stream);
+
+ g_mutex_unlock (&data_wrapper->priv->stream_lock);
+
+ return (bytes_written >= 0);
+}
+
+static gssize
+data_wrapper_write_to_output_stream_sync (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GInputStream *input_stream;
+ gssize bytes_written;
+
+ /* XXX Should keep the internal data as a reference-counted
+ * GBytes to avoid locking while writing to the stream. */
+
+ g_mutex_lock (&data_wrapper->priv->stream_lock);
+
+ /* We retain ownership of the byte array content. */
+ input_stream = g_memory_input_stream_new_from_data (
+ data_wrapper->priv->byte_array->data,
+ data_wrapper->priv->byte_array->len,
+ (GDestroyNotify) NULL);
+
+ bytes_written = g_output_stream_splice (
+ output_stream, input_stream,
+ G_OUTPUT_STREAM_SPLICE_NONE,
+ cancellable, error);
+
+ g_object_unref (input_stream);
+
+ g_mutex_unlock (&data_wrapper->priv->stream_lock);
+
+ return bytes_written;
+}
+
+static gssize
+data_wrapper_decode_to_output_stream_sync (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimeFilter *filter = NULL;
+ GOutputStream *filter_stream = NULL;
+ gboolean content_type_is_text;
+ gssize bytes_written;
+
+ switch (data_wrapper->encoding) {
+ case CAMEL_TRANSFER_ENCODING_BASE64:
+ filter = camel_mime_filter_basic_new (
+ CAMEL_MIME_FILTER_BASIC_BASE64_DEC);
+ filter_stream = camel_filter_output_stream_new (
+ output_stream, filter);
+ g_filter_output_stream_set_close_base_stream (
+ G_FILTER_OUTPUT_STREAM (filter_stream), FALSE);
+ g_object_unref (filter);
+ break;
+ case CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE:
+ filter = camel_mime_filter_basic_new (
+ CAMEL_MIME_FILTER_BASIC_QP_DEC);
+ filter_stream = camel_filter_output_stream_new (
+ output_stream, filter);
+ g_filter_output_stream_set_close_base_stream (
+ G_FILTER_OUTPUT_STREAM (filter_stream), FALSE);
+ g_object_unref (filter);
+ break;
+ case CAMEL_TRANSFER_ENCODING_UUENCODE:
+ filter = camel_mime_filter_basic_new (
+ CAMEL_MIME_FILTER_BASIC_UU_DEC);
+ filter_stream = camel_filter_output_stream_new (
+ output_stream, filter);
+ g_filter_output_stream_set_close_base_stream (
+ G_FILTER_OUTPUT_STREAM (filter_stream), FALSE);
+ g_object_unref (filter);
+ break;
+ default:
+ /* Write directly to the output stream. */
+ filter_stream = g_object_ref (output_stream);
+ break;
+ }
+
+ content_type_is_text =
+ camel_content_type_is (
+ data_wrapper->mime_type, "text", "*") &&
+ !camel_content_type_is (
+ data_wrapper->mime_type, "text", "pdf");
+
+ if (content_type_is_text) {
+ GOutputStream *temp_stream;
+
+ filter = camel_mime_filter_crlf_new (
+ CAMEL_MIME_FILTER_CRLF_DECODE,
+ CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
+ temp_stream = camel_filter_output_stream_new (
+ filter_stream, filter);
+ g_filter_output_stream_set_close_base_stream (
+ G_FILTER_OUTPUT_STREAM (temp_stream), FALSE);
+ g_object_unref (filter);
+
+ g_object_unref (filter_stream);
+ filter_stream = temp_stream;
+ }
+
+ bytes_written = camel_data_wrapper_write_to_output_stream_sync (
+ data_wrapper, filter_stream, cancellable, error);
+
+ g_object_unref (filter_stream);
+
+ return bytes_written;
+}
+
+static gboolean
+data_wrapper_construct_from_input_stream_sync (CamelDataWrapper *data_wrapper,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GOutputStream *output_stream;
+ gssize bytes_written;
+ gboolean success;
+
+ /* XXX Should keep the internal data as a reference-counted
+ * GBytes to avoid locking while reading from the stream. */
+
+ g_mutex_lock (&data_wrapper->priv->stream_lock);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ g_mutex_unlock (&data_wrapper->priv->stream_lock);
+ return FALSE;
+ }
+
+ if (G_IS_SEEKABLE (input_stream)) {
+ success = g_seekable_seek (
+ G_SEEKABLE (input_stream), 0,
+ G_SEEK_SET, cancellable, error);
+ if (!success) {
+ g_mutex_unlock (&data_wrapper->priv->stream_lock);
+ return FALSE;
+ }
+ }
+
+ output_stream = g_memory_output_stream_new_resizable ();
+
+ bytes_written = g_output_stream_splice (
+ output_stream, input_stream,
+ G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ cancellable, error);
+
+ success = (bytes_written >= 0);
+
+ if (success) {
+ GBytes *bytes;
+
+ bytes = g_memory_output_stream_steal_as_bytes (
+ G_MEMORY_OUTPUT_STREAM (output_stream));
+
+ g_byte_array_free (data_wrapper->priv->byte_array, TRUE);
+ data_wrapper->priv->byte_array = g_bytes_unref_to_array (bytes);
+ }
+
+ g_object_unref (output_stream);
+
+ g_mutex_unlock (&data_wrapper->priv->stream_lock);
+
+ return success;
+}
+
+static void
+camel_data_wrapper_class_init (CamelDataWrapperClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelDataWrapperPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = data_wrapper_dispose;
+ object_class->finalize = data_wrapper_finalize;
+
+ class->set_mime_type = data_wrapper_set_mime_type;
+ class->get_mime_type = data_wrapper_get_mime_type;
+ class->get_mime_type_field = data_wrapper_get_mime_type_field;
+ class->set_mime_type_field = data_wrapper_set_mime_type_field;
+ class->is_offline = data_wrapper_is_offline;
+
+ class->write_to_stream_sync = data_wrapper_write_to_stream_sync;
+ class->decode_to_stream_sync = data_wrapper_decode_to_stream_sync;
+ class->construct_from_stream_sync = data_wrapper_construct_from_stream_sync;
+ class->write_to_output_stream_sync = data_wrapper_write_to_output_stream_sync;
+ class->decode_to_output_stream_sync = data_wrapper_decode_to_output_stream_sync;
+ class->construct_from_input_stream_sync = data_wrapper_construct_from_input_stream_sync;
+}
+
+static void
+camel_data_wrapper_init (CamelDataWrapper *data_wrapper)
+{
+ data_wrapper->priv = CAMEL_DATA_WRAPPER_GET_PRIVATE (data_wrapper);
+
+ g_mutex_init (&data_wrapper->priv->stream_lock);
+ data_wrapper->priv->byte_array = g_byte_array_new ();
+
+ data_wrapper->mime_type = camel_content_type_new (
+ "application", "octet-stream");
+ data_wrapper->encoding = CAMEL_TRANSFER_ENCODING_DEFAULT;
+ data_wrapper->offline = FALSE;
+}
+
+/**
+ * camel_data_wrapper_new:
+ *
+ * Create a new #CamelDataWrapper object.
+ *
+ * Returns: a new #CamelDataWrapper object
+ **/
+CamelDataWrapper *
+camel_data_wrapper_new (void)
+{
+ return g_object_new (CAMEL_TYPE_DATA_WRAPPER, NULL);
+}
+
+/**
+ * camel_data_wrapper_get_byte_array:
+ * @data_wrapper: a #CamelDataWrapper
+ *
+ * Returns the #GByteArray being used to hold the contents of @data_wrapper.
+ *
+ * Note, it's up to the caller to use this in a thread-safe manner.
+ *
+ * Returns: (transfer none): the #GByteArray for @data_wrapper
+ *
+ * Since: 3.2
+ **/
+GByteArray *
+camel_data_wrapper_get_byte_array (CamelDataWrapper *data_wrapper)
+{
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), NULL);
+
+ return data_wrapper->priv->byte_array;
+}
+
+/**
+ * camel_data_wrapper_set_mime_type:
+ * @data_wrapper: a #CamelDataWrapper
+ * @mime_type: a MIME type
+ *
+ * This sets the data wrapper's MIME type.
+ *
+ * It might fail, but you won't know. It will allow you to set
+ * Content-Type parameters on the data wrapper, which are meaningless.
+ * You should not be allowed to change the MIME type of a data wrapper
+ * that contains data, or at least, if you do, it should invalidate the
+ * data.
+ **/
+void
+camel_data_wrapper_set_mime_type (CamelDataWrapper *data_wrapper,
+ const gchar *mime_type)
+{
+ CamelDataWrapperClass *class;
+
+ g_return_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper));
+ g_return_if_fail (mime_type != NULL);
+
+ class = CAMEL_DATA_WRAPPER_GET_CLASS (data_wrapper);
+ g_return_if_fail (class->set_mime_type);
+
+ class->set_mime_type (data_wrapper, mime_type);
+}
+
+/**
+ * camel_data_wrapper_get_mime_type:
+ * @data_wrapper: a #CamelDataWrapper
+ *
+ * Returns: the MIME type which must be freed by the caller
+ **/
+gchar *
+camel_data_wrapper_get_mime_type (CamelDataWrapper *data_wrapper)
+{
+ CamelDataWrapperClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), NULL);
+
+ class = CAMEL_DATA_WRAPPER_GET_CLASS (data_wrapper);
+ g_return_val_if_fail (class->get_mime_type != NULL, NULL);
+
+ return class->get_mime_type (data_wrapper);
+}
+
+/**
+ * camel_data_wrapper_get_mime_type_field:
+ * @data_wrapper: a #CamelDataWrapper
+ *
+ * Returns: (transfer none): the parsed form of the data wrapper's MIME type
+ **/
+CamelContentType *
+camel_data_wrapper_get_mime_type_field (CamelDataWrapper *data_wrapper)
+{
+ CamelDataWrapperClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), NULL);
+
+ class = CAMEL_DATA_WRAPPER_GET_CLASS (data_wrapper);
+ g_return_val_if_fail (class->get_mime_type_field != NULL, NULL);
+
+ return class->get_mime_type_field (data_wrapper);
+}
+
+/**
+ * camel_data_wrapper_set_mime_type_field:
+ * @data_wrapper: a #CamelDataWrapper
+ * @mime_type: a #CamelContentType
+ *
+ * This sets the data wrapper's MIME type. It suffers from the same
+ * flaws as camel_data_wrapper_set_mime_type().
+ **/
+void
+camel_data_wrapper_set_mime_type_field (CamelDataWrapper *data_wrapper,
+ CamelContentType *mime_type)
+{
+ CamelDataWrapperClass *class;
+
+ g_return_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper));
+ g_return_if_fail (mime_type != NULL);
+
+ class = CAMEL_DATA_WRAPPER_GET_CLASS (data_wrapper);
+ g_return_if_fail (class->set_mime_type_field != NULL);
+
+ class->set_mime_type_field (data_wrapper, mime_type);
+}
+
+/**
+ * camel_data_wrapper_is_offline:
+ * @data_wrapper: a #CamelDataWrapper
+ *
+ * Returns: whether @data_wrapper is "offline" (data stored
+ * remotely) or not. Some optional code paths may choose to not
+ * operate on offline data.
+ **/
+gboolean
+camel_data_wrapper_is_offline (CamelDataWrapper *data_wrapper)
+{
+ CamelDataWrapperClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), TRUE);
+
+ class = CAMEL_DATA_WRAPPER_GET_CLASS (data_wrapper);
+ g_return_val_if_fail (class->is_offline != NULL, TRUE);
+
+ return class->is_offline (data_wrapper);
+}
+
+/**
+ * camel_data_wrapper_write_to_stream_sync:
+ * @data_wrapper: a #CamelDataWrapper
+ * @stream: a #CamelStream for output
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Writes the content of @data_wrapper to @stream in a machine-independent
+ * format appropriate for the data. It should be possible to construct an
+ * equivalent data wrapper object later by passing this stream to
+ * camel_data_wrapper_construct_from_stream_sync().
+ *
+ * <note>
+ * <para>
+ * This function may block even if the given output stream does not.
+ * For example, the content may have to be fetched across a network
+ * before it can be written to @stream.
+ * </para>
+ * </note>
+ *
+ * Returns: the number of bytes written, or %-1 on error
+ *
+ * Since: 3.0
+ **/
+gssize
+camel_data_wrapper_write_to_stream_sync (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelDataWrapperClass *class;
+ gssize bytes_written;
+
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), -1);
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
+
+ class = CAMEL_DATA_WRAPPER_GET_CLASS (data_wrapper);
+ g_return_val_if_fail (class->write_to_stream_sync != NULL, -1);
+
+ bytes_written = class->write_to_stream_sync (
+ data_wrapper, stream, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ data_wrapper, write_to_stream_sync,
+ bytes_written >= 0, error);
+
+ return bytes_written;
+}
+
+/* Helper for camel_data_wrapper_write_to_stream() */
+static void
+data_wrapper_write_to_stream_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gssize bytes_written;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ bytes_written = camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (source_object),
+ async_context->stream,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_int (task, bytes_written);
+ }
+}
+
+/**
+ * camel_data_wrapper_write_to_stream:
+ * @data_wrapper: a #CamelDataWrapper
+ * @stream: a #CamelStream for writed data to be written to
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously writes the content of @data_wrapper to @stream in a
+ * machine-independent format appropriate for the data. It should be
+ * possible to construct an equivalent data wrapper object later by
+ * passing this stream to camel_data_wrapper_construct_from_stream().
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_data_wrapper_write_to_stream_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_data_wrapper_write_to_stream (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper));
+ g_return_if_fail (CAMEL_IS_STREAM (stream));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->stream = g_object_ref (stream);
+
+ task = g_task_new (data_wrapper, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_data_wrapper_write_to_stream);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, data_wrapper_write_to_stream_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_data_wrapper_write_to_stream_finish:
+ * @data_wrapper: a #CamelDataWrapper
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_data_wrapper_write_to_stream().
+ *
+ * Returns: the number of bytes written, or %-1 or error
+ *
+ * Since: 3.0
+ **/
+gssize
+camel_data_wrapper_write_to_stream_finish (CamelDataWrapper *data_wrapper,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), -1);
+ g_return_val_if_fail (g_task_is_valid (result, data_wrapper), -1);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_data_wrapper_write_to_stream), -1);
+
+ return g_task_propagate_int (G_TASK (result), error);
+}
+
+/**
+ * camel_data_wrapper_decode_to_stream_sync:
+ * @data_wrapper: a #CamelDataWrapper
+ * @stream: a #CamelStream for decoded data to be written to
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Writes the decoded data content to @stream.
+ *
+ * <note>
+ * <para>
+ * This function may block even if the given output stream does not.
+ * For example, the content may have to be fetched across a network
+ * before it can be written to @stream.
+ * </para>
+ * </note>
+ *
+ * Returns: the number of bytes written, or %-1 on error
+ *
+ * Since: 3.0
+ **/
+gssize
+camel_data_wrapper_decode_to_stream_sync (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelDataWrapperClass *class;
+ gssize bytes_written;
+
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), -1);
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
+
+ class = CAMEL_DATA_WRAPPER_GET_CLASS (data_wrapper);
+ g_return_val_if_fail (class->decode_to_stream_sync != NULL, -1);
+
+ bytes_written = class->decode_to_stream_sync (
+ data_wrapper, stream, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ data_wrapper, decode_to_stream_sync,
+ bytes_written >= 0, error);
+
+ return bytes_written;
+}
+
+/* Helper for camel_data_wrapper_decode_to_stream() */
+static void
+data_wrapper_decode_to_stream_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gssize bytes_written;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ bytes_written = camel_data_wrapper_decode_to_stream_sync (
+ CAMEL_DATA_WRAPPER (source_object),
+ async_context->stream,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_int (task, bytes_written);
+ }
+}
+
+/**
+ * camel_data_wrapper_decode_to_stream:
+ * @data_wrapper: a #CamelDataWrapper
+ * @stream: a #CamelStream for decoded data to be written to
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously writes the decoded data content to @stream.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_data_wrapper_decode_to_stream_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_data_wrapper_decode_to_stream (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper));
+ g_return_if_fail (CAMEL_IS_STREAM (stream));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->stream = g_object_ref (stream);
+
+ task = g_task_new (data_wrapper, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_data_wrapper_decode_to_stream);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, data_wrapper_decode_to_stream_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_data_wrapper_decode_to_stream_finish:
+ * @data_wrapper: a #CamelDataWrapper
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_data_wrapper_decode_to_stream().
+ *
+ * Returns: the number of bytes written, or %-1 on error
+ *
+ * Since: 3.0
+ **/
+gssize
+camel_data_wrapper_decode_to_stream_finish (CamelDataWrapper *data_wrapper,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), -1);
+ g_return_val_if_fail (g_task_is_valid (result, data_wrapper), -1);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_data_wrapper_decode_to_stream), -1);
+
+ return g_task_propagate_int (G_TASK (result), error);
+}
+
+/**
+ * camel_data_wrapper_construct_from_stream_sync:
+ * @data_wrapper: a #CamelDataWrapper
+ * @stream: an input #CamelStream
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Constructs the content of @data_wrapper from the given @stream.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_data_wrapper_construct_from_stream_sync (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelDataWrapperClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), FALSE);
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), FALSE);
+
+ class = CAMEL_DATA_WRAPPER_GET_CLASS (data_wrapper);
+ g_return_val_if_fail (class->construct_from_stream_sync != NULL, FALSE);
+
+ success = class->construct_from_stream_sync (
+ data_wrapper, stream, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ data_wrapper, construct_from_stream_sync, success, error);
+
+ return success;
+}
+
+/* Helper for camel_data_wrapper_construct_from_stream() */
+static void
+data_wrapper_construct_from_stream_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_data_wrapper_construct_from_stream_sync (
+ CAMEL_DATA_WRAPPER (source_object),
+ async_context->stream,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_data_wrapper_construct_from_stream:
+ * @data_wrapper: a #CamelDataWrapper
+ * @stream: an input #CamelStream
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously constructs the content of @data_wrapper from the given
+ * @stream.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_data_wrapper_construct_from_stream_finish() to get the result
+ * of the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_data_wrapper_construct_from_stream (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper));
+ g_return_if_fail (CAMEL_IS_STREAM (stream));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->stream = g_object_ref (stream);
+
+ task = g_task_new (data_wrapper, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_data_wrapper_construct_from_stream);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, data_wrapper_construct_from_stream_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_data_wrapper_construct_from_stream_finish:
+ * @data_wrapper: a #CamelDataWrapper
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with
+ * camel_data_wrapper_construct_from_stream().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_data_wrapper_construct_from_stream_finish (CamelDataWrapper *data_wrapper,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, data_wrapper), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_data_wrapper_construct_from_stream), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_data_wrapper_write_to_output_stream_sync:
+ * @data_wrapper: a #CamelDataWrapper
+ * @output_stream: a #GOutputStream
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Writes the content of @data_wrapper to @output_stream in a
+ * machine-independent format appropriate for the data.
+ *
+ * <note>
+ * <para>
+ * This function may block even if the given output stream does not.
+ * For example, the content may have to be fetched across a network
+ * before it can be written to @output_stream.
+ * </para>
+ * </note>
+ *
+ * Returns: the number of bytes written, or %-1 on error
+ *
+ * Since: 3.12
+ **/
+gssize
+camel_data_wrapper_write_to_output_stream_sync (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelDataWrapperClass *class;
+ gssize bytes_written;
+
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), -1);
+ g_return_val_if_fail (G_IS_OUTPUT_STREAM (output_stream), -1);
+
+ class = CAMEL_DATA_WRAPPER_GET_CLASS (data_wrapper);
+ g_return_val_if_fail (class->write_to_output_stream_sync != NULL, -1);
+
+ bytes_written = class->write_to_output_stream_sync (
+ data_wrapper, output_stream, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ data_wrapper, write_to_output_stream_sync,
+ bytes_written >= 0, error);
+
+ if (bytes_written >= 0) {
+ if (!g_output_stream_flush (output_stream, cancellable, error))
+ bytes_written = -1;
+ }
+
+ return bytes_written;
+}
+
+/* Helper for camel_data_wrapper_write_to_output_stream() */
+static void
+data_wrapper_write_to_output_stream_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gssize bytes_written;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ bytes_written = camel_data_wrapper_write_to_output_stream_sync (
+ CAMEL_DATA_WRAPPER (source_object),
+ async_context->output_stream,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_int (task, bytes_written);
+ }
+}
+
+/**
+ * camel_data_wrapper_write_to_output_stream:
+ * @data_wrapper: a #CamelDataWrapper
+ * @output_stream: a #GOutputStream
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously writes the content of @data_wrapper to @output_stream in
+ * a machine-independent format appropriate for the data.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_data_wrapper_write_to_output_stream_finish() to get the result
+ * of the operation.
+ *
+ * Since: 3.12
+ **/
+void
+camel_data_wrapper_write_to_output_stream (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper));
+ g_return_if_fail (G_IS_OUTPUT_STREAM (output_stream));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->output_stream = g_object_ref (output_stream);
+
+ task = g_task_new (data_wrapper, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_data_wrapper_write_to_output_stream);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (
+ task, data_wrapper_write_to_output_stream_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_data_wrapper_write_to_output_stream_finish:
+ * @data_wrapper: a #CamelDataWrapper
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with
+ * camel_data_wrapper_write_to_output_stream().
+ *
+ * Returns: the number of bytes written, or %-1 on error
+ *
+ * Since: 3.12
+ **/
+gssize
+camel_data_wrapper_write_to_output_stream_finish (CamelDataWrapper *data_wrapper,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), -1);
+ g_return_val_if_fail (g_task_is_valid (result, data_wrapper), -1);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_data_wrapper_write_to_output_stream), -1);
+
+ return g_task_propagate_int (G_TASK (result), error);
+}
+
+/**
+ * camel_data_wrapper_decode_to_output_stream_sync:
+ * @data_wrapper: a #CamelDataWrapper
+ * @output_stream: a #GOutputStream
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Writes the decoded data content to @output_stream.
+ *
+ * <note>
+ * <para>
+ * This function may block even if the given output stream does not.
+ * For example, the content may have to be fetched across a network
+ * before it can be written to @output_stream.
+ * </para>
+ * </note>
+ *
+ * Returns: the number of bytes written, or %-1 on error
+ *
+ * Since: 3.12
+ **/
+gssize
+camel_data_wrapper_decode_to_output_stream_sync (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelDataWrapperClass *class;
+ gssize bytes_written;
+
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), -1);
+ g_return_val_if_fail (G_IS_OUTPUT_STREAM (output_stream), -1);
+
+ class = CAMEL_DATA_WRAPPER_GET_CLASS (data_wrapper);
+ g_return_val_if_fail (class->decode_to_output_stream_sync != NULL, -1);
+
+ bytes_written = class->decode_to_output_stream_sync (
+ data_wrapper, output_stream, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ data_wrapper, decode_to_output_stream_sync,
+ bytes_written >= 0, error);
+
+ return bytes_written;
+}
+
+/* Helper for camel_data_wrapper_decode_to_output_stream() */
+static void
+data_wrapper_decode_to_output_stream_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gssize bytes_written;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ bytes_written = camel_data_wrapper_decode_to_output_stream_sync (
+ CAMEL_DATA_WRAPPER (source_object),
+ async_context->output_stream,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_int (task, bytes_written);
+ }
+}
+
+/**
+ * camel_data_wrapper_decode_to_output_stream:
+ * @data_wrapper: a #CamelDataWrapper
+ * @output_stream: a #GOutputStream
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously writes the decoded data content to @output_stream.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_data_wrapper_decode_to_output_stream_finish() to get the result
+ * of the operation.
+ *
+ * Since: 3.12
+ **/
+void
+camel_data_wrapper_decode_to_output_stream (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper));
+ g_return_if_fail (G_IS_OUTPUT_STREAM (output_stream));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->output_stream = g_object_ref (output_stream);
+
+ task = g_task_new (data_wrapper, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_data_wrapper_decode_to_output_stream);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (
+ task, data_wrapper_decode_to_output_stream_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_data_wrapper_decode_to_output_stream_finish:
+ * @data_wrapper: a #CamelDataWrapper
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with
+ * camel_data_wrapper_decode_to_output_stream().
+ *
+ * Returns: the number of bytes written, or %-1 on error
+ *
+ * Since: 3.12
+ **/
+gssize
+camel_data_wrapper_decode_to_output_stream_finish (CamelDataWrapper *data_wrapper,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), -1);
+ g_return_val_if_fail (g_task_is_valid (result, data_wrapper), -1);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_data_wrapper_decode_to_output_stream), -1);
+
+ return g_task_propagate_int (G_TASK (result), error);
+}
+
+/**
+ * camel_data_wrapper_construct_from_input_stream_sync:
+ * @data_wrapper: a #CamelDataWrapper
+ * @input_stream: a #GInputStream
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Constructs the content of @data_wrapper from @input_stream.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.12
+ **/
+gboolean
+camel_data_wrapper_construct_from_input_stream_sync (CamelDataWrapper *data_wrapper,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelDataWrapperClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), FALSE);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input_stream), FALSE);
+
+ class = CAMEL_DATA_WRAPPER_GET_CLASS (data_wrapper);
+ g_return_val_if_fail (class->construct_from_input_stream_sync != NULL, FALSE);
+
+ success = class->construct_from_input_stream_sync (
+ data_wrapper, input_stream, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ data_wrapper, construct_from_input_stream_sync, success, error);
+
+ return success;
+}
+
+/* Helper for camel_data_wrapper_construct_from_input_stream() */
+static void
+data_wrapper_construct_from_input_stream_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_data_wrapper_construct_from_input_stream_sync (
+ CAMEL_DATA_WRAPPER (source_object),
+ async_context->input_stream,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_data_wrapper_construct_from_input_stream:
+ * @data_wrapper: a #CamelDataWrapper
+ * @input_stream: a #GInputStream
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously constructs the content of @data_wrapper from @input_stream.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_data_wrapper_construct_from_input_stream_finish() to get the
+ * result of the operation.
+ *
+ * Since: 3.12
+ **/
+void
+camel_data_wrapper_construct_from_input_stream (CamelDataWrapper *data_wrapper,
+ GInputStream *input_stream,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper));
+ g_return_if_fail (G_IS_INPUT_STREAM (input_stream));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->input_stream = g_object_ref (input_stream);
+
+ task = g_task_new (data_wrapper, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_data_wrapper_construct_from_input_stream);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, data_wrapper_construct_from_input_stream_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_data_wrapper_construct_from_input_stream_finish:
+ * @data_wrapper: a #CamelDataWrapper
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with
+ * camel_data_wrapper_construct_from_input_stream().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.12
+ **/
+gboolean
+camel_data_wrapper_construct_from_input_stream_finish (CamelDataWrapper *data_wrapper,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, data_wrapper), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_data_wrapper_construct_from_input_stream), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
diff --git a/src/camel/camel-data-wrapper.h b/src/camel/camel-data-wrapper.h
new file mode 100644
index 000000000..0e702249e
--- /dev/null
+++ b/src/camel/camel-data-wrapper.h
@@ -0,0 +1,234 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_DATA_WRAPPER_H
+#define CAMEL_DATA_WRAPPER_H
+
+#include <sys/types.h>
+
+#include <camel/camel-mime-utils.h>
+#include <camel/camel-stream.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_DATA_WRAPPER \
+ (camel_data_wrapper_get_type ())
+#define CAMEL_DATA_WRAPPER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_DATA_WRAPPER, CamelDataWrapper))
+#define CAMEL_DATA_WRAPPER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_DATA_WRAPPER, CamelDataWrapperClass))
+#define CAMEL_IS_DATA_WRAPPER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_DATA_WRAPPER))
+#define CAMEL_IS_DATA_WRAPPER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_DATA_WRAPPER))
+#define CAMEL_DATA_WRAPPER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_DATA_WRAPPER, CamelDataWrapperClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelDataWrapper CamelDataWrapper;
+typedef struct _CamelDataWrapperClass CamelDataWrapperClass;
+typedef struct _CamelDataWrapperPrivate CamelDataWrapperPrivate;
+
+struct _CamelDataWrapper {
+ GObject parent;
+ CamelDataWrapperPrivate *priv;
+
+ CamelTransferEncoding encoding;
+
+ CamelContentType *mime_type;
+
+ guint offline : 1;
+};
+
+struct _CamelDataWrapperClass {
+ GObjectClass parent_class;
+
+ /* Non-Blocking Methods */
+ void (*set_mime_type) (CamelDataWrapper *data_wrapper,
+ const gchar *mime_type);
+ gchar * (*get_mime_type) (CamelDataWrapper *data_wrapper);
+ CamelContentType *
+ (*get_mime_type_field) (CamelDataWrapper *data_wrapper);
+ void (*set_mime_type_field) (CamelDataWrapper *data_wrapper,
+ CamelContentType *mime_type);
+ gboolean (*is_offline) (CamelDataWrapper *data_wrapper);
+
+ /* Synchronous I/O Methods */
+ gssize (*write_to_stream_sync) (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+ gssize (*decode_to_stream_sync)(CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*construct_from_stream_sync)
+ (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+ gssize (*write_to_output_stream_sync)
+ (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error);
+ gssize (*decode_to_output_stream_sync)
+ (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*construct_from_input_stream_sync)
+ (CamelDataWrapper *data_wrapper,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* Reserved slots. */
+ gpointer reserved[3];
+};
+
+GType camel_data_wrapper_get_type (void);
+CamelDataWrapper *
+ camel_data_wrapper_new (void);
+GByteArray * camel_data_wrapper_get_byte_array
+ (CamelDataWrapper *data_wrapper);
+void camel_data_wrapper_set_mime_type
+ (CamelDataWrapper *data_wrapper,
+ const gchar *mime_type);
+gchar * camel_data_wrapper_get_mime_type
+ (CamelDataWrapper *data_wrapper);
+CamelContentType *
+ camel_data_wrapper_get_mime_type_field
+ (CamelDataWrapper *data_wrapper);
+void camel_data_wrapper_set_mime_type_field
+ (CamelDataWrapper *data_wrapper,
+ CamelContentType *mime_type);
+gboolean camel_data_wrapper_is_offline (CamelDataWrapper *data_wrapper);
+
+gssize camel_data_wrapper_write_to_stream_sync
+ (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+void camel_data_wrapper_write_to_stream
+ (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gssize camel_data_wrapper_write_to_stream_finish
+ (CamelDataWrapper *data_wrapper,
+ GAsyncResult *result,
+ GError **error);
+gssize camel_data_wrapper_decode_to_stream_sync
+ (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+void camel_data_wrapper_decode_to_stream
+ (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gssize camel_data_wrapper_decode_to_stream_finish
+ (CamelDataWrapper *data_wrapper,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_data_wrapper_construct_from_stream_sync
+ (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+void camel_data_wrapper_construct_from_stream
+ (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_data_wrapper_construct_from_stream_finish
+ (CamelDataWrapper *data_wrapper,
+ GAsyncResult *result,
+ GError **error);
+gssize camel_data_wrapper_write_to_output_stream_sync
+ (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error);
+void camel_data_wrapper_write_to_output_stream
+ (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gssize camel_data_wrapper_write_to_output_stream_finish
+ (CamelDataWrapper *data_wrapper,
+ GAsyncResult *result,
+ GError **error);
+gssize camel_data_wrapper_decode_to_output_stream_sync
+ (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error);
+void camel_data_wrapper_decode_to_output_stream
+ (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gssize camel_data_wrapper_decode_to_output_stream_finish
+ (CamelDataWrapper *data_wrapper,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_data_wrapper_construct_from_input_stream_sync
+ (CamelDataWrapper *data_wrapper,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+void camel_data_wrapper_construct_from_input_stream
+ (CamelDataWrapper *data_wrapper,
+ GInputStream *input_stream,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_data_wrapper_construct_from_input_stream_finish
+ (CamelDataWrapper *data_wrapper,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_DATA_WRAPPER_H */
diff --git a/src/camel/camel-db.c b/src/camel/camel-db.c
new file mode 100644
index 000000000..dffad3242
--- /dev/null
+++ b/src/camel/camel-db.c
@@ -0,0 +1,2778 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Sankar P <psankar@novell.com>
+ * Srinivasa Ragavan <sragavan@novell.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include "camel-debug.h"
+#include "camel-object.h"
+#include "camel-string-utils.h"
+
+#include "camel-db.h"
+
+/* how long to wait before invoking sync on the file */
+#define SYNC_TIMEOUT_SECONDS 5
+
+static sqlite3_vfs *old_vfs = NULL;
+static GThreadPool *sync_pool = NULL;
+
+typedef struct {
+ sqlite3_file parent;
+ sqlite3_file *old_vfs_file; /* pointer to old_vfs' file */
+ GRecMutex sync_mutex;
+ guint timeout_id;
+ gint flags;
+
+ /* Do know how many syncs are pending, to not close
+ the file before the last sync is over */
+ guint pending_syncs;
+ GMutex pending_syncs_lock;
+ GCond pending_syncs_cond;
+} CamelSqlite3File;
+
+static gint
+call_old_file_Sync (CamelSqlite3File *cFile,
+ gint flags)
+{
+ g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR);
+ g_return_val_if_fail (cFile != NULL, SQLITE_ERROR);
+
+ g_return_val_if_fail (cFile->old_vfs_file->pMethods != NULL, SQLITE_ERROR);
+ return cFile->old_vfs_file->pMethods->xSync (cFile->old_vfs_file, flags);
+}
+
+typedef struct {
+ GCond cond;
+ GMutex mutex;
+ gboolean is_set;
+} SyncDone;
+
+struct SyncRequestData
+{
+ CamelSqlite3File *cFile;
+ guint32 flags;
+ SyncDone *done; /* not NULL when waiting for a finish; will be freed by the caller */
+};
+
+static void
+sync_request_thread_cb (gpointer task_data,
+ gpointer null_data)
+{
+ struct SyncRequestData *sync_data = task_data;
+ SyncDone *done;
+
+ g_return_if_fail (sync_data != NULL);
+ g_return_if_fail (sync_data->cFile != NULL);
+
+ call_old_file_Sync (sync_data->cFile, sync_data->flags);
+
+ g_mutex_lock (&sync_data->cFile->pending_syncs_lock);
+ g_warn_if_fail (sync_data->cFile->pending_syncs > 0);
+ sync_data->cFile->pending_syncs--;
+ if (!sync_data->cFile->pending_syncs)
+ g_cond_signal (&sync_data->cFile->pending_syncs_cond);
+ g_mutex_unlock (&sync_data->cFile->pending_syncs_lock);
+
+ done = sync_data->done;
+ g_free (sync_data);
+
+ if (done != NULL) {
+ g_mutex_lock (&done->mutex);
+ done->is_set = TRUE;
+ g_cond_broadcast (&done->cond);
+ g_mutex_unlock (&done->mutex);
+ }
+}
+
+static void
+sync_push_request (CamelSqlite3File *cFile,
+ gboolean wait_for_finish)
+{
+ struct SyncRequestData *data;
+ SyncDone *done = NULL;
+ GError *error = NULL;
+
+ g_return_if_fail (cFile != NULL);
+ g_return_if_fail (sync_pool != NULL);
+
+ g_rec_mutex_lock (&cFile->sync_mutex);
+
+ if (!cFile->flags) {
+ /* nothing to sync, might be when xClose is called
+ * without any pending xSync request */
+ g_rec_mutex_unlock (&cFile->sync_mutex);
+ return;
+ }
+
+ if (wait_for_finish) {
+ done = g_slice_new (SyncDone);
+ g_cond_init (&done->cond);
+ g_mutex_init (&done->mutex);
+ done->is_set = FALSE;
+ }
+
+ data = g_new0 (struct SyncRequestData, 1);
+ data->cFile = cFile;
+ data->flags = cFile->flags;
+ data->done = done;
+
+ cFile->flags = 0;
+
+ g_mutex_lock (&cFile->pending_syncs_lock);
+ cFile->pending_syncs++;
+ g_mutex_unlock (&cFile->pending_syncs_lock);
+
+ g_rec_mutex_unlock (&cFile->sync_mutex);
+
+ g_thread_pool_push (sync_pool, data, &error);
+
+ if (error) {
+ g_warning ("%s: Failed to push to thread pool: %s\n", G_STRFUNC, error->message);
+ g_error_free (error);
+
+ if (done != NULL) {
+ g_cond_clear (&done->cond);
+ g_mutex_clear (&done->mutex);
+ g_slice_free (SyncDone, done);
+ }
+
+ return;
+ }
+
+ if (done != NULL) {
+ g_mutex_lock (&done->mutex);
+ while (!done->is_set)
+ g_cond_wait (&done->cond, &done->mutex);
+ g_mutex_unlock (&done->mutex);
+
+ g_cond_clear (&done->cond);
+ g_mutex_clear (&done->mutex);
+ g_slice_free (SyncDone, done);
+ }
+}
+
+static gboolean
+sync_push_request_timeout (CamelSqlite3File *cFile)
+{
+ g_rec_mutex_lock (&cFile->sync_mutex);
+
+ if (cFile->timeout_id != 0) {
+ sync_push_request (cFile, FALSE);
+ cFile->timeout_id = 0;
+ }
+
+ g_rec_mutex_unlock (&cFile->sync_mutex);
+
+ return FALSE;
+}
+
+#define def_subclassed(_nm, _params, _call) \
+static gint \
+camel_sqlite3_file_ ## _nm _params \
+{ \
+ CamelSqlite3File *cFile; \
+ \
+ g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR); \
+ g_return_val_if_fail (pFile != NULL, SQLITE_ERROR); \
+ \
+ cFile = (CamelSqlite3File *) pFile; \
+ g_return_val_if_fail (cFile->old_vfs_file->pMethods != NULL, SQLITE_ERROR); \
+ return cFile->old_vfs_file->pMethods->_nm _call; \
+}
+
+#define def_subclassed_void(_nm, _params, _call) \
+static void \
+camel_sqlite3_file_ ## _nm _params \
+{ \
+ CamelSqlite3File *cFile; \
+ \
+ g_return_if_fail (old_vfs != NULL); \
+ g_return_if_fail (pFile != NULL); \
+ \
+ cFile = (CamelSqlite3File *) pFile; \
+ g_return_if_fail (cFile->old_vfs_file->pMethods != NULL); \
+ cFile->old_vfs_file->pMethods->_nm _call; \
+}
+
+def_subclassed (xRead, (sqlite3_file *pFile, gpointer pBuf, gint iAmt, sqlite3_int64 iOfst), (cFile->old_vfs_file, pBuf, iAmt, iOfst))
+def_subclassed (xWrite, (sqlite3_file *pFile, gconstpointer pBuf, gint iAmt, sqlite3_int64 iOfst), (cFile->old_vfs_file, pBuf, iAmt, iOfst))
+def_subclassed (xTruncate, (sqlite3_file *pFile, sqlite3_int64 size), (cFile->old_vfs_file, size))
+def_subclassed (xFileSize, (sqlite3_file *pFile, sqlite3_int64 *pSize), (cFile->old_vfs_file, pSize))
+def_subclassed (xLock, (sqlite3_file *pFile, gint lockType), (cFile->old_vfs_file, lockType))
+def_subclassed (xUnlock, (sqlite3_file *pFile, gint lockType), (cFile->old_vfs_file, lockType))
+def_subclassed (xFileControl, (sqlite3_file *pFile, gint op, gpointer pArg), (cFile->old_vfs_file, op, pArg))
+def_subclassed (xSectorSize, (sqlite3_file *pFile), (cFile->old_vfs_file))
+def_subclassed (xDeviceCharacteristics, (sqlite3_file *pFile), (cFile->old_vfs_file))
+def_subclassed (xShmMap, (sqlite3_file *pFile, gint iPg, gint pgsz, gint n, void volatile **arr), (cFile->old_vfs_file, iPg, pgsz, n, arr))
+def_subclassed (xShmLock, (sqlite3_file *pFile, gint offset, gint n, gint flags), (cFile->old_vfs_file, offset, n, flags))
+def_subclassed_void (xShmBarrier, (sqlite3_file *pFile), (cFile->old_vfs_file))
+def_subclassed (xShmUnmap, (sqlite3_file *pFile, gint deleteFlag), (cFile->old_vfs_file, deleteFlag))
+def_subclassed (xFetch, (sqlite3_file *pFile, sqlite3_int64 iOfst, int iAmt, void **pp), (cFile->old_vfs_file, iOfst, iAmt, pp))
+def_subclassed (xUnfetch, (sqlite3_file *pFile, sqlite3_int64 iOfst, void *p), (cFile->old_vfs_file, iOfst, p))
+
+#undef def_subclassed
+
+static gint
+camel_sqlite3_file_xCheckReservedLock (sqlite3_file *pFile,
+ gint *pResOut)
+{
+ CamelSqlite3File *cFile;
+
+ g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR);
+ g_return_val_if_fail (pFile != NULL, SQLITE_ERROR);
+
+ cFile = (CamelSqlite3File *) pFile;
+ g_return_val_if_fail (cFile->old_vfs_file->pMethods != NULL, SQLITE_ERROR);
+
+ /* check version in runtime */
+ if (sqlite3_libversion_number () < 3006000)
+ return ((gint (*)(sqlite3_file *)) (cFile->old_vfs_file->pMethods->xCheckReservedLock)) (cFile->old_vfs_file);
+ else
+ return ((gint (*)(sqlite3_file *, gint *)) (cFile->old_vfs_file->pMethods->xCheckReservedLock)) (cFile->old_vfs_file, pResOut);
+}
+
+static gint
+camel_sqlite3_file_xClose (sqlite3_file *pFile)
+{
+ CamelSqlite3File *cFile;
+ gint res;
+
+ g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR);
+ g_return_val_if_fail (pFile != NULL, SQLITE_ERROR);
+
+ cFile = (CamelSqlite3File *) pFile;
+
+ g_rec_mutex_lock (&cFile->sync_mutex);
+
+ /* Cancel any pending sync requests. */
+ if (cFile->timeout_id > 0) {
+ g_source_remove (cFile->timeout_id);
+ cFile->timeout_id = 0;
+ }
+
+ g_rec_mutex_unlock (&cFile->sync_mutex);
+
+ /* Make the last sync. */
+ sync_push_request (cFile, TRUE);
+
+ g_mutex_lock (&cFile->pending_syncs_lock);
+ while (cFile->pending_syncs > 0) {
+ g_cond_wait (&cFile->pending_syncs_cond, &cFile->pending_syncs_lock);
+ }
+ g_mutex_unlock (&cFile->pending_syncs_lock);
+
+ if (cFile->old_vfs_file->pMethods)
+ res = cFile->old_vfs_file->pMethods->xClose (cFile->old_vfs_file);
+ else
+ res = SQLITE_OK;
+
+ g_free (cFile->old_vfs_file);
+ cFile->old_vfs_file = NULL;
+
+ g_rec_mutex_clear (&cFile->sync_mutex);
+ g_mutex_clear (&cFile->pending_syncs_lock);
+ g_cond_clear (&cFile->pending_syncs_cond);
+
+ return res;
+}
+
+static gint
+camel_sqlite3_file_xSync (sqlite3_file *pFile,
+ gint flags)
+{
+ CamelSqlite3File *cFile;
+
+ g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR);
+ g_return_val_if_fail (pFile != NULL, SQLITE_ERROR);
+
+ cFile = (CamelSqlite3File *) pFile;
+
+ g_rec_mutex_lock (&cFile->sync_mutex);
+
+ /* If a sync request is already scheduled, accumulate flags. */
+ cFile->flags |= flags;
+
+ /* Cancel any pending sync requests. */
+ if (cFile->timeout_id > 0)
+ g_source_remove (cFile->timeout_id);
+
+ /* Wait SYNC_TIMEOUT_SECONDS before we actually sync. */
+ cFile->timeout_id = g_timeout_add_seconds (
+ SYNC_TIMEOUT_SECONDS, (GSourceFunc)
+ sync_push_request_timeout, cFile);
+ g_source_set_name_by_id (
+ cFile->timeout_id,
+ "[camel] sync_push_request_timeout");
+
+ g_rec_mutex_unlock (&cFile->sync_mutex);
+
+ return SQLITE_OK;
+}
+
+static gint
+camel_sqlite3_vfs_xOpen (sqlite3_vfs *pVfs,
+ const gchar *zPath,
+ sqlite3_file *pFile,
+ gint flags,
+ gint *pOutFlags)
+{
+ static GRecMutex only_once_lock;
+ static sqlite3_io_methods io_methods = {0};
+ CamelSqlite3File *cFile;
+ gint res;
+
+ g_return_val_if_fail (old_vfs != NULL, -1);
+ g_return_val_if_fail (pFile != NULL, -1);
+
+ cFile = (CamelSqlite3File *) pFile;
+ cFile->old_vfs_file = g_malloc0 (old_vfs->szOsFile);
+
+ res = old_vfs->xOpen (old_vfs, zPath, cFile->old_vfs_file, flags, pOutFlags);
+ if (res != SQLITE_OK) {
+ g_free (cFile->old_vfs_file);
+ return res;
+ }
+
+ g_rec_mutex_init (&cFile->sync_mutex);
+ g_mutex_init (&cFile->pending_syncs_lock);
+ g_cond_init (&cFile->pending_syncs_cond);
+
+ cFile->pending_syncs = 0;
+
+ g_rec_mutex_lock (&only_once_lock);
+
+ if (!sync_pool)
+ sync_pool = g_thread_pool_new (sync_request_thread_cb, NULL, 2, FALSE, NULL);
+
+ /* cFile->old_vfs_file->pMethods is NULL when open failed for some reason,
+ * thus do not initialize our structure when do not know the version */
+ if (io_methods.xClose == NULL && cFile->old_vfs_file->pMethods) {
+ /* initialize our subclass function only once */
+ io_methods.iVersion = cFile->old_vfs_file->pMethods->iVersion;
+
+ /* check version in compile time */
+ #if SQLITE_VERSION_NUMBER < 3006000
+ io_methods.xCheckReservedLock = (gint (*)(sqlite3_file *)) camel_sqlite3_file_xCheckReservedLock;
+ #else
+ io_methods.xCheckReservedLock = camel_sqlite3_file_xCheckReservedLock;
+ #endif
+
+ #define use_subclassed(x) io_methods.x = camel_sqlite3_file_ ## x
+ use_subclassed (xClose);
+ use_subclassed (xRead);
+ use_subclassed (xWrite);
+ use_subclassed (xTruncate);
+ use_subclassed (xSync);
+ use_subclassed (xFileSize);
+ use_subclassed (xLock);
+ use_subclassed (xUnlock);
+ use_subclassed (xFileControl);
+ use_subclassed (xSectorSize);
+ use_subclassed (xDeviceCharacteristics);
+
+ if (io_methods.iVersion > 1) {
+ use_subclassed (xShmMap);
+ use_subclassed (xShmLock);
+ use_subclassed (xShmBarrier);
+ use_subclassed (xShmUnmap);
+ }
+
+ if (io_methods.iVersion > 2) {
+ use_subclassed (xFetch);
+ use_subclassed (xUnfetch);
+ }
+
+ if (io_methods.iVersion > 3) {
+ g_warning ("%s: Unchecked IOMethods version %d, downgrading to version 3", G_STRFUNC, io_methods.iVersion);
+ io_methods.iVersion = 3;
+ }
+ #undef use_subclassed
+ }
+
+ g_rec_mutex_unlock (&only_once_lock);
+
+ cFile->parent.pMethods = &io_methods;
+
+ return res;
+}
+
+static gpointer
+init_sqlite_vfs (void)
+{
+ static sqlite3_vfs vfs = { 0 };
+
+ old_vfs = sqlite3_vfs_find (NULL);
+ g_return_val_if_fail (old_vfs != NULL, NULL);
+
+ memcpy (&vfs, old_vfs, sizeof (sqlite3_vfs));
+
+ vfs.szOsFile = sizeof (CamelSqlite3File);
+ vfs.zName = "camel_sqlite3_vfs";
+ vfs.xOpen = camel_sqlite3_vfs_xOpen;
+
+ sqlite3_vfs_register (&vfs, 1);
+
+ return NULL;
+}
+
+#define d(x) if (camel_debug("sqlite")) x
+#define START(stmt) \
+ if (camel_debug ("dbtime")) { \
+ g_print ( \
+ "\n===========\n" \
+ "DB SQL operation [%s] started\n", stmt); \
+ if (!cdb->priv->timer) { \
+ cdb->priv->timer = g_timer_new (); \
+ } else { \
+ g_timer_reset (cdb->priv->timer); \
+ } \
+ }
+#define END \
+ if (camel_debug ("dbtime")) { \
+ g_timer_stop (cdb->priv->timer); \
+ g_print ( \
+ "DB Operation ended. " \
+ "Time Taken : %f\n###########\n", \
+ g_timer_elapsed (cdb->priv->timer, NULL)); \
+ }
+#define STARTTS(stmt) \
+ if (camel_debug ("dbtimets")) { \
+ g_print ( \
+ "\n===========\n" \
+ "DB SQL operation [%s] started\n", stmt); \
+ if (!cdb->priv->timer) { \
+ cdb->priv->timer = g_timer_new (); \
+ } else { \
+ g_timer_reset (cdb->priv->timer); \
+ } \
+ }
+#define ENDTS \
+ if (camel_debug ("dbtimets")) { \
+ g_timer_stop (cdb->priv->timer); \
+ g_print ( \
+ "DB Operation ended. " \
+ "Time Taken : %f\n###########\n", \
+ g_timer_elapsed (cdb->priv->timer, NULL)); \
+ }
+
+struct _CamelDBPrivate {
+ GTimer *timer;
+ GRWLock rwlock;
+ gchar *file_name;
+ GMutex transaction_lock;
+ GThread *transaction_thread;
+ guint32 transaction_level;
+};
+
+/**
+ * cdb_sql_exec
+ * @db:
+ * @stmt:
+ * @error:
+ *
+ * Callers should hold the lock
+ **/
+static gint
+cdb_sql_exec (sqlite3 *db,
+ const gchar *stmt,
+ gint (*callback)(gpointer ,gint,gchar **,gchar **),
+ gpointer data,
+ gint *out_sqlite_error_code,
+ GError **error)
+{
+ gchar *errmsg = NULL;
+ gint ret = -1, retries = 0;
+
+ d (g_print ("Camel SQL Exec:\n%s\n", stmt));
+
+ ret = sqlite3_exec (db, stmt, callback, data, &errmsg);
+ while (ret == SQLITE_BUSY || ret == SQLITE_LOCKED || ret == -1) {
+ /* try for ~15 seconds, then give up */
+ if (retries > 150)
+ break;
+ retries++;
+
+ if (errmsg) {
+ sqlite3_free (errmsg);
+ errmsg = NULL;
+ }
+ g_thread_yield ();
+ g_usleep (100 * 1000); /* Sleep for 100 ms */
+
+ ret = sqlite3_exec (db, stmt, NULL, NULL, &errmsg);
+ }
+
+ if (out_sqlite_error_code)
+ *out_sqlite_error_code = ret;
+
+ if (ret != SQLITE_OK) {
+ d (g_print ("Error in SQL EXEC statement: %s [%s].\n", stmt, errmsg));
+ g_set_error (
+ error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC, "%s", errmsg);
+ sqlite3_free (errmsg);
+ errmsg = NULL;
+ return -1;
+ }
+
+ if (errmsg) {
+ sqlite3_free (errmsg);
+ errmsg = NULL;
+ }
+
+ return 0;
+}
+
+/* checks whether string 'where' contains whole word 'what',
+ * case insensitively (ascii, not utf8, same as 'LIKE' in SQLite3)
+*/
+static void
+cdb_match_func (sqlite3_context *ctx,
+ gint nArgs,
+ sqlite3_value **values)
+{
+ gboolean matches = FALSE;
+ const gchar *what, *where;
+
+ g_return_if_fail (ctx != NULL);
+ g_return_if_fail (nArgs == 2);
+ g_return_if_fail (values != NULL);
+
+ what = (const gchar *) sqlite3_value_text (values[0]);
+ where = (const gchar *) sqlite3_value_text (values[1]);
+
+ if (what && where && !*what) {
+ matches = TRUE;
+ } else if (what && where) {
+ gboolean word = TRUE;
+ gint i, j;
+
+ for (i = 0, j = 0; where[i] && !matches; i++) {
+ gchar c = where[i];
+
+ if (c == ' ') {
+ word = TRUE;
+ j = 0;
+ } else if (word && tolower (c) == tolower (what[j])) {
+ j++;
+ if (what[j] == 0 && (where[i + 1] == 0 || isspace (where[i + 1])))
+ matches = TRUE;
+ } else {
+ word = FALSE;
+ }
+ }
+ }
+
+ sqlite3_result_int (ctx, matches ? 1 : 0);
+}
+
+static void
+cdb_writer_lock (CamelDB *cdb)
+{
+ g_return_if_fail (cdb != NULL);
+
+ g_mutex_lock (&cdb->priv->transaction_lock);
+ if (cdb->priv->transaction_thread != g_thread_self ()) {
+ g_mutex_unlock (&cdb->priv->transaction_lock);
+
+ g_rw_lock_writer_lock (&cdb->priv->rwlock);
+
+ g_mutex_lock (&cdb->priv->transaction_lock);
+
+ g_warn_if_fail (cdb->priv->transaction_thread == NULL);
+ g_warn_if_fail (cdb->priv->transaction_level == 0);
+
+ cdb->priv->transaction_thread = g_thread_self ();
+ }
+
+ cdb->priv->transaction_level++;
+
+ g_mutex_unlock (&cdb->priv->transaction_lock);
+}
+
+static void
+cdb_writer_unlock (CamelDB *cdb)
+{
+ g_return_if_fail (cdb != NULL);
+
+ g_mutex_lock (&cdb->priv->transaction_lock);
+
+ g_warn_if_fail (cdb->priv->transaction_thread == g_thread_self ());
+ g_warn_if_fail (cdb->priv->transaction_level > 0);
+
+ cdb->priv->transaction_level--;
+
+ if (!cdb->priv->transaction_level) {
+ cdb->priv->transaction_thread = NULL;
+ g_mutex_unlock (&cdb->priv->transaction_lock);
+
+ g_rw_lock_writer_unlock (&cdb->priv->rwlock);
+ } else {
+ g_mutex_unlock (&cdb->priv->transaction_lock);
+ }
+}
+
+static void
+cdb_reader_lock (CamelDB *cdb)
+{
+ g_return_if_fail (cdb != NULL);
+
+ g_mutex_lock (&cdb->priv->transaction_lock);
+ if (cdb->priv->transaction_thread == g_thread_self ()) {
+ /* already holding write lock */
+ g_mutex_unlock (&cdb->priv->transaction_lock);
+ } else {
+ g_mutex_unlock (&cdb->priv->transaction_lock);
+
+ g_rw_lock_reader_lock (&cdb->priv->rwlock);
+ }
+}
+
+static void
+cdb_reader_unlock (CamelDB *cdb)
+{
+ g_return_if_fail (cdb != NULL);
+
+ g_mutex_lock (&cdb->priv->transaction_lock);
+ if (cdb->priv->transaction_thread == g_thread_self ()) {
+ /* already holding write lock */
+ g_mutex_unlock (&cdb->priv->transaction_lock);
+ } else {
+ g_mutex_unlock (&cdb->priv->transaction_lock);
+
+ g_rw_lock_reader_unlock (&cdb->priv->rwlock);
+ }
+}
+
+static gboolean
+cdb_is_in_transaction (CamelDB *cdb)
+{
+ gboolean res;
+
+ g_return_val_if_fail (cdb != NULL, FALSE);
+
+ g_mutex_lock (&cdb->priv->transaction_lock);
+ res = cdb->priv->transaction_level > 0 && cdb->priv->transaction_thread == g_thread_self ();
+ g_mutex_unlock (&cdb->priv->transaction_lock);
+
+ return res;
+}
+
+static gchar *
+cdb_construct_transaction_stmt (CamelDB *cdb,
+ const gchar *prefix)
+{
+ gchar *name;
+
+ g_return_val_if_fail (cdb != NULL, NULL);
+
+ g_mutex_lock (&cdb->priv->transaction_lock);
+ g_warn_if_fail (cdb->priv->transaction_thread == g_thread_self ());
+ name = g_strdup_printf ("%sTN%d", prefix ? prefix : "", cdb->priv->transaction_level);
+ g_mutex_unlock (&cdb->priv->transaction_lock);
+
+ return name;
+}
+
+static gint
+camel_db_command_internal (CamelDB *cdb,
+ const gchar *stmt,
+ gint *out_sqlite_error_code,
+ GError **error)
+{
+ gint ret;
+
+ if (!cdb)
+ return TRUE;
+
+ cdb_writer_lock (cdb);
+
+ START (stmt);
+ ret = cdb_sql_exec (cdb->db, stmt, NULL, NULL, out_sqlite_error_code, error);
+ END;
+
+ cdb_writer_unlock (cdb);
+
+ return ret;
+}
+
+/**
+ * camel_db_open:
+ *
+ * Since: 2.24
+ **/
+CamelDB *
+camel_db_open (const gchar *path,
+ GError **error)
+{
+ static GOnce vfs_once = G_ONCE_INIT;
+ CamelDB *cdb;
+ sqlite3 *db;
+ gint ret, cdb_sqlite_error_code = SQLITE_OK;
+ gboolean reopening = FALSE;
+ GError *local_error = NULL;
+
+ g_once (&vfs_once, (GThreadFunc) init_sqlite_vfs, NULL);
+
+ CAMEL_DB_USE_SHARED_CACHE;
+
+ reopen:
+ ret = sqlite3_open (path, &db);
+ if (ret) {
+ if (!db) {
+ g_set_error (
+ error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC,
+ _("Insufficient memory"));
+ } else {
+ const gchar *errmsg;
+ errmsg = sqlite3_errmsg (db);
+ d (g_print ("Can't open database %s: %s\n", path, errmsg));
+ g_set_error (
+ error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC, "%s", errmsg);
+ sqlite3_close (db);
+ }
+ return NULL;
+ }
+
+ cdb = g_new (CamelDB, 1);
+ cdb->db = db;
+ cdb->priv = g_new0 (CamelDBPrivate, 1);
+ cdb->priv->file_name = g_strdup (path);
+ g_rw_lock_init (&cdb->priv->rwlock);
+ g_mutex_init (&cdb->priv->transaction_lock);
+ cdb->priv->transaction_thread = NULL;
+ cdb->priv->transaction_level = 0;
+ cdb->priv->timer = NULL;
+ d (g_print ("\nDatabase succesfully opened \n"));
+
+ sqlite3_create_function (db, "MATCH", 2, SQLITE_UTF8, NULL, cdb_match_func, NULL, NULL);
+
+ /* Which is big / costlier ? A Stack frame or a pointer */
+ if (g_getenv ("CAMEL_SQLITE_DEFAULT_CACHE_SIZE") != NULL) {
+ gchar *cache = NULL;
+
+ cache = g_strdup_printf ("PRAGMA cache_size=%s", g_getenv ("CAMEL_SQLITE_DEFAULT_CACHE_SIZE"));
+ camel_db_command_internal (cdb, cache, &cdb_sqlite_error_code, &local_error);
+ g_free (cache);
+ }
+
+ if (cdb_sqlite_error_code == SQLITE_OK)
+ camel_db_command_internal (cdb, "ATTACH DATABASE ':memory:' AS mem", &cdb_sqlite_error_code, &local_error);
+
+ if (cdb_sqlite_error_code == SQLITE_OK && g_getenv ("CAMEL_SQLITE_IN_MEMORY") != NULL) {
+ /* Optionally turn off Journaling, this gets over fsync issues, but could be risky */
+ camel_db_command_internal (cdb, "PRAGMA main.journal_mode = off", &cdb_sqlite_error_code, &local_error);
+ if (cdb_sqlite_error_code == SQLITE_OK)
+ camel_db_command_internal (cdb, "PRAGMA temp_store = memory", &cdb_sqlite_error_code, &local_error);
+ }
+
+ if (!reopening && (
+ cdb_sqlite_error_code == SQLITE_CANTOPEN ||
+ cdb_sqlite_error_code == SQLITE_CORRUPT ||
+ cdb_sqlite_error_code == SQLITE_NOTADB)) {
+ gchar *second_filename;
+
+ camel_db_close (cdb);
+
+ reopening = TRUE;
+
+ second_filename = g_strconcat (path, ".corrupt", NULL);
+ if (g_rename (path, second_filename) == -1) {
+ if (!local_error) {
+ g_set_error (&local_error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Could not rename '%s' to %s: %s"),
+ path, second_filename, g_strerror (errno));
+ }
+
+ g_propagate_error (error, local_error);
+
+ g_free (second_filename);
+
+ return NULL;
+ }
+
+ g_free (second_filename);
+
+ g_warning ("%s: Failed to open '%s', renamed old file to .corrupt; code:%s (%d) error:%s", G_STRFUNC, path,
+ cdb_sqlite_error_code == SQLITE_CANTOPEN ? "SQLITE_CANTOPEN" :
+ cdb_sqlite_error_code == SQLITE_CORRUPT ? "SQLITE_CORRUPT" :
+ cdb_sqlite_error_code == SQLITE_NOTADB ? "SQLITE_NOTADB" : "???",
+ cdb_sqlite_error_code, local_error ? local_error->message : "Unknown error");
+
+ g_clear_error (&local_error);
+
+ goto reopen;
+ }
+
+ if (local_error) {
+ g_propagate_error (error, local_error);
+ camel_db_close (cdb);
+ return NULL;
+ }
+
+ sqlite3_busy_timeout (cdb->db, CAMEL_DB_SLEEP_INTERVAL);
+
+ return cdb;
+}
+
+/**
+ * camel_db_clone:
+ *
+ * Since: 2.26
+ **/
+CamelDB *
+camel_db_clone (CamelDB *cdb,
+ GError **error)
+{
+ return camel_db_open (cdb->priv->file_name, error);
+}
+
+/**
+ * camel_db_close:
+ *
+ * Since: 2.24
+ **/
+void
+camel_db_close (CamelDB *cdb)
+{
+ if (cdb) {
+ sqlite3_close (cdb->db);
+ g_rw_lock_clear (&cdb->priv->rwlock);
+ g_mutex_clear (&cdb->priv->transaction_lock);
+ g_free (cdb->priv->file_name);
+ g_free (cdb->priv);
+ g_free (cdb);
+ d (g_print ("\nDatabase succesfully closed \n"));
+ }
+}
+
+/**
+ * camel_db_set_collate:
+ * @func: (scope call):
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_set_collate (CamelDB *cdb,
+ const gchar *col,
+ const gchar *collate,
+ CamelDBCollate func)
+{
+ gint ret = 0;
+
+ if (!cdb)
+ return 0;
+
+ cdb_writer_lock (cdb);
+ d (g_print ("Creating Collation %s on %s with %p\n", collate, col, (gpointer) func));
+ if (collate && func)
+ ret = sqlite3_create_collation (cdb->db, collate, SQLITE_UTF8, NULL, func);
+ cdb_writer_unlock (cdb);
+
+ return ret;
+}
+
+/**
+ * camel_db_command:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_command (CamelDB *cdb,
+ const gchar *stmt,
+ GError **error)
+{
+ return camel_db_command_internal (cdb, stmt, NULL, error);
+}
+
+/**
+ * camel_db_begin_transaction:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_begin_transaction (CamelDB *cdb,
+ GError **error)
+{
+ gchar *stmt;
+ gint res;
+
+ if (!cdb)
+ return -1;
+
+ cdb_writer_lock (cdb);
+
+ stmt = cdb_construct_transaction_stmt (cdb, "SAVEPOINT ");
+
+ STARTTS (stmt);
+ res = cdb_sql_exec (cdb->db, stmt, NULL, NULL, NULL, error);
+ g_free (stmt);
+
+ return res;
+}
+
+/**
+ * camel_db_end_transaction:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_end_transaction (CamelDB *cdb,
+ GError **error)
+{
+ gchar *stmt;
+ gint ret;
+
+ if (!cdb)
+ return -1;
+
+ stmt = cdb_construct_transaction_stmt (cdb, "RELEASE SAVEPOINT ");
+ ret = cdb_sql_exec (cdb->db, stmt, NULL, NULL, NULL, error);
+ g_free (stmt);
+
+ ENDTS;
+ cdb_writer_unlock (cdb);
+ CAMEL_DB_RELEASE_SQLITE_MEMORY;
+
+ return ret;
+}
+
+/**
+ * camel_db_abort_transaction:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_abort_transaction (CamelDB *cdb,
+ GError **error)
+{
+ gchar *stmt;
+ gint ret;
+
+ stmt = cdb_construct_transaction_stmt (cdb, "ROLLBACK TO SAVEPOINT ");
+ ret = cdb_sql_exec (cdb->db, stmt, NULL, NULL, NULL, error);
+ g_free (stmt);
+
+ cdb_writer_unlock (cdb);
+ CAMEL_DB_RELEASE_SQLITE_MEMORY;
+
+ return ret;
+}
+
+/**
+ * camel_db_add_to_transaction:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_add_to_transaction (CamelDB *cdb,
+ const gchar *stmt,
+ GError **error)
+{
+ if (!cdb)
+ return -1;
+
+ g_return_val_if_fail (cdb_is_in_transaction (cdb), -1);
+
+ return (cdb_sql_exec (cdb->db, stmt, NULL, NULL, NULL, error));
+}
+
+/**
+ * camel_db_transaction_command:
+ * @cdb: a #CamelDB
+ * @qry_list: (element-type utf8) (transfer none): A #GList of querries
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_transaction_command (CamelDB *cdb,
+ GList *qry_list,
+ GError **error)
+{
+ gboolean in_transaction = FALSE;
+ gint ret;
+ const gchar *query;
+
+ if (!cdb)
+ return -1;
+
+ ret = camel_db_begin_transaction (cdb, error);
+ if (ret)
+ goto end;
+
+ in_transaction = TRUE;
+
+ while (qry_list) {
+ query = qry_list->data;
+ ret = cdb_sql_exec (cdb->db, query, NULL, NULL, NULL, error);
+ if (ret)
+ goto end;
+ qry_list = g_list_next (qry_list);
+ }
+
+ ret = camel_db_end_transaction (cdb, error);
+ in_transaction = FALSE;
+end:
+ if (in_transaction)
+ ret = camel_db_abort_transaction (cdb, error);
+
+ return ret;
+}
+
+static gint
+count_cb (gpointer data,
+ gint argc,
+ gchar **argv,
+ gchar **azColName)
+{
+ gint i;
+
+ for (i = 0; i < argc; i++) {
+ if (strstr (azColName[i], "COUNT")) {
+ *(guint32 *)data = argv [i] ? strtoul (argv [i], NULL, 10) : 0;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * camel_db_count_message_info:
+ *
+ * Since: 2.26
+ **/
+gint
+camel_db_count_message_info (CamelDB *cdb,
+ const gchar *query,
+ guint32 *count,
+ GError **error)
+{
+ gint ret = -1;
+
+ cdb_reader_lock (cdb);
+
+ START (query);
+ ret = cdb_sql_exec (cdb->db, query, count_cb, count, NULL, error);
+ END;
+
+ cdb_reader_unlock (cdb);
+
+ CAMEL_DB_RELEASE_SQLITE_MEMORY;
+
+ return ret;
+}
+
+/**
+ * camel_db_count_junk_message_info:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_count_junk_message_info (CamelDB *cdb,
+ const gchar *table_name,
+ guint32 *count,
+ GError **error)
+{
+ gint ret;
+ gchar *query;
+
+ if (!cdb)
+ return -1;
+
+ query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE junk = 1", table_name);
+
+ ret = camel_db_count_message_info (cdb, query, count, error);
+ sqlite3_free (query);
+
+ return ret;
+}
+
+/**
+ * camel_db_count_unread_message_info:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_count_unread_message_info (CamelDB *cdb,
+ const gchar *table_name,
+ guint32 *count,
+ GError **error)
+{
+ gint ret;
+ gchar *query;
+
+ if (!cdb)
+ return -1;
+
+ query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE read = 0", table_name);
+
+ ret = camel_db_count_message_info (cdb, query, count, error);
+ sqlite3_free (query);
+
+ return ret;
+}
+
+/**
+ * camel_db_count_visible_unread_message_info:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_count_visible_unread_message_info (CamelDB *cdb,
+ const gchar *table_name,
+ guint32 *count,
+ GError **error)
+{
+ gint ret;
+ gchar *query;
+
+ if (!cdb)
+ return -1;
+
+ query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE read = 0 AND junk = 0 AND deleted = 0", table_name);
+
+ ret = camel_db_count_message_info (cdb, query, count, error);
+ sqlite3_free (query);
+
+ return ret;
+}
+
+/**
+ * camel_db_count_visible_message_info:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_count_visible_message_info (CamelDB *cdb,
+ const gchar *table_name,
+ guint32 *count,
+ GError **error)
+{
+ gint ret;
+ gchar *query;
+
+ if (!cdb)
+ return -1;
+
+ query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE junk = 0 AND deleted = 0", table_name);
+
+ ret = camel_db_count_message_info (cdb, query, count, error);
+ sqlite3_free (query);
+
+ return ret;
+}
+
+/**
+ * camel_db_count_junk_not-deleted_message_info:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_count_junk_not_deleted_message_info (CamelDB *cdb,
+ const gchar *table_name,
+ guint32 *count,
+ GError **error)
+{
+ gint ret;
+ gchar *query;
+
+ if (!cdb)
+ return -1;
+
+ query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE junk = 1 AND deleted = 0", table_name);
+
+ ret = camel_db_count_message_info (cdb, query, count, error);
+ sqlite3_free (query);
+
+ return ret;
+}
+
+/**
+ * camel_db_count_deleted_message_info:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_count_deleted_message_info (CamelDB *cdb,
+ const gchar *table_name,
+ guint32 *count,
+ GError **error)
+{
+ gint ret;
+ gchar *query;
+
+ if (!cdb)
+ return -1;
+
+ query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE deleted = 1", table_name);
+
+ ret = camel_db_count_message_info (cdb, query, count, error);
+ sqlite3_free (query);
+
+ return ret;
+}
+
+/**
+ * camel_db_count_total_message_info:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_count_total_message_info (CamelDB *cdb,
+ const gchar *table_name,
+ guint32 *count,
+ GError **error)
+{
+
+ gint ret;
+ gchar *query;
+
+ if (!cdb)
+ return -1;
+
+ query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q where read=0 or read=1", table_name);
+
+ ret = camel_db_count_message_info (cdb, query, count, error);
+ sqlite3_free (query);
+
+ return ret;
+}
+
+/**
+ * camel_db_select:
+ * @callback: (scope async):
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_select (CamelDB *cdb,
+ const gchar *stmt,
+ CamelDBSelectCB callback,
+ gpointer user_data,
+ GError **error)
+{
+ gint ret = -1;
+
+ if (!cdb)
+ return ret;
+
+ d (g_print ("\n%s:\n%s \n", G_STRFUNC, stmt));
+ cdb_reader_lock (cdb);
+
+ START (stmt);
+ ret = cdb_sql_exec (cdb->db, stmt, callback, user_data, NULL, error);
+ END;
+
+ cdb_reader_unlock (cdb);
+ CAMEL_DB_RELEASE_SQLITE_MEMORY;
+
+ return ret;
+}
+
+static gint
+read_uids_callback (gpointer ref_array,
+ gint ncol,
+ gchar **cols,
+ gchar **name)
+{
+ GPtrArray *array = ref_array;
+
+ g_return_val_if_fail (ncol == 1, 0);
+
+ if (cols[0])
+ g_ptr_array_add (array, (gchar *) (camel_pstring_strdup (cols[0])));
+
+ return 0;
+}
+
+static gint
+read_uids_to_hash_callback (gpointer ref_hash,
+ gint ncol,
+ gchar **cols,
+ gchar **name)
+{
+ GHashTable *hash = ref_hash;
+
+ g_return_val_if_fail (ncol == 2, 0);
+
+ if (cols[0])
+ g_hash_table_insert (hash, (gchar *) camel_pstring_strdup (cols[0]), GUINT_TO_POINTER (cols[1] ? strtoul (cols[1], NULL, 10) : 0));
+
+ return 0;
+}
+
+/**
+ * camel_db_get_folder_uids:
+ *
+ * Fills hash with uid->GUINT_TO_POINTER (flag)
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_get_folder_uids (CamelDB *db,
+ const gchar *folder_name,
+ const gchar *sort_by,
+ const gchar *collate,
+ GHashTable *hash,
+ GError **error)
+{
+ gchar *sel_query;
+ gint ret;
+
+ sel_query = sqlite3_mprintf (
+ "SELECT uid,flags FROM %Q%s%s%s%s",
+ folder_name,
+ sort_by ? " order by " : "",
+ sort_by ? sort_by : "",
+ (sort_by && collate) ? " collate " : "",
+ (sort_by && collate) ? collate : "");
+
+ ret = camel_db_select (db, sel_query, read_uids_to_hash_callback, hash, error);
+ sqlite3_free (sel_query);
+
+ return ret;
+}
+
+/**
+ * camel_db_get_folder_junk_uids:
+ *
+ * Returns: (element-type utf8) (transfer full):
+ *
+ * Since: 2.24
+ **/
+GPtrArray *
+camel_db_get_folder_junk_uids (CamelDB *db,
+ gchar *folder_name,
+ GError **error)
+{
+ gchar *sel_query;
+ gint ret;
+ GPtrArray *array = g_ptr_array_new ();
+
+ sel_query = sqlite3_mprintf ("SELECT uid FROM %Q where junk=1", folder_name);
+
+ ret = camel_db_select (db, sel_query, read_uids_callback, array, error);
+
+ sqlite3_free (sel_query);
+
+ if (!array->len || ret != 0) {
+ g_ptr_array_free (array, TRUE);
+ array = NULL;
+ }
+ return array;
+}
+
+/**
+ * camel_db_get_folder_deleted_uids:
+ *
+ * Returns: (element-type utf8) (transfer full):
+ *
+ * Since: 2.24
+ **/
+GPtrArray *
+camel_db_get_folder_deleted_uids (CamelDB *db,
+ const gchar *folder_name,
+ GError **error)
+{
+ gchar *sel_query;
+ gint ret;
+ GPtrArray *array = g_ptr_array_new ();
+
+ sel_query = sqlite3_mprintf ("SELECT uid FROM %Q where deleted=1", folder_name);
+
+ ret = camel_db_select (db, sel_query, read_uids_callback, array, error);
+ sqlite3_free (sel_query);
+
+ if (!array->len || ret != 0) {
+ g_ptr_array_free (array, TRUE);
+ array = NULL;
+ }
+
+ return array;
+}
+
+struct ReadPreviewData
+{
+ GHashTable *columns_hash;
+ GHashTable *hash;
+};
+
+static gint
+read_preview_callback (gpointer ref,
+ gint ncol,
+ gchar **cols,
+ gchar **name)
+{
+ struct ReadPreviewData *rpd = ref;
+ const gchar *uid = NULL;
+ gchar *msg = NULL;
+ gint i;
+
+ for (i = 0; i < ncol; ++i) {
+ if (!name[i] || !cols[i])
+ continue;
+
+ switch (camel_db_get_column_ident (&rpd->columns_hash, i, ncol, name)) {
+ case CAMEL_DB_COLUMN_UID:
+ uid = camel_pstring_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_PREVIEW:
+ msg = g_strdup (cols[i]);
+ break;
+ default:
+ g_warn_if_reached ();
+ break;
+ }
+ }
+
+ g_hash_table_insert (rpd->hash, (gchar *) uid, msg);
+
+ return 0;
+}
+
+/**
+ * camel_db_get_folder_preview:
+ *
+ * Returns: (element-type utf8 utf8) (transfer full):
+ *
+ * Since: 2.28
+ **/
+GHashTable *
+camel_db_get_folder_preview (CamelDB *db,
+ const gchar *folder_name,
+ GError **error)
+{
+ gchar *sel_query;
+ gint ret;
+ struct ReadPreviewData rpd;
+ GHashTable *hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+ sel_query = sqlite3_mprintf ("SELECT uid, preview FROM '%q_preview'", folder_name);
+
+ rpd.columns_hash = NULL;
+ rpd.hash = hash;
+
+ ret = camel_db_select (db, sel_query, read_preview_callback, &rpd, error);
+ sqlite3_free (sel_query);
+
+ if (rpd.columns_hash)
+ g_hash_table_destroy (rpd.columns_hash);
+
+ if (!g_hash_table_size (hash) || ret != 0) {
+ g_hash_table_destroy (hash);
+ hash = NULL;
+ }
+
+ return hash;
+}
+
+/**
+ * camel_db_write_preview_record:
+ *
+ * Since: 2.28
+ **/
+gint
+camel_db_write_preview_record (CamelDB *db,
+ const gchar *folder_name,
+ const gchar *uid,
+ const gchar *msg,
+ GError **error)
+{
+ gchar *query;
+ gint ret;
+
+ query = sqlite3_mprintf ("INSERT OR REPLACE INTO '%q_preview' VALUES(%Q,%Q)", folder_name, uid, msg);
+
+ ret = camel_db_add_to_transaction (db, query, error);
+ sqlite3_free (query);
+
+ return ret;
+}
+
+/**
+ * camel_db_create_folders_table:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_create_folders_table (CamelDB *cdb,
+ GError **error)
+{
+ gint ret;
+ const gchar *query = "CREATE TABLE IF NOT EXISTS folders ( "
+ "folder_name TEXT PRIMARY KEY, "
+ "version REAL, "
+ "flags INTEGER, "
+ "nextuid INTEGER, "
+ "time NUMERIC, "
+ "saved_count INTEGER, "
+ "unread_count INTEGER, "
+ "deleted_count INTEGER, "
+ "junk_count INTEGER, "
+ "visible_count INTEGER, "
+ "jnd_count INTEGER, "
+ "bdata TEXT )";
+ CAMEL_DB_RELEASE_SQLITE_MEMORY;
+
+ ret = camel_db_command (cdb, query, error);
+
+ if (ret != -1) {
+ /* Drop 'Deletes' table, leftover from the previous versions. */
+ ret = camel_db_command (cdb, "DROP TABLE IF EXISTS 'Deletes'", error);
+ }
+
+ return ret;
+}
+
+static gint
+camel_db_create_message_info_table (CamelDB *cdb,
+ const gchar *folder_name,
+ GError **error)
+{
+ gint ret;
+ gchar *table_creation_query, *safe_index;
+
+ /* README: It is possible to compress all system flags into a single
+ * column and use just as userflags but that makes querying for other
+ * applications difficult and bloats the parsing code. Instead, it is
+ * better to bloat the tables. Sqlite should have some optimizations
+ * for sparse columns etc. */
+ table_creation_query = sqlite3_mprintf (
+ "CREATE TABLE IF NOT EXISTS %Q ( "
+ "uid TEXT PRIMARY KEY , "
+ "flags INTEGER , "
+ "msg_type INTEGER , "
+ "read INTEGER , "
+ "deleted INTEGER , "
+ "replied INTEGER , "
+ "important INTEGER , "
+ "junk INTEGER , "
+ "attachment INTEGER , "
+ "dirty INTEGER , "
+ "size INTEGER , "
+ "dsent NUMERIC , "
+ "dreceived NUMERIC , "
+ "subject TEXT , "
+ "mail_from TEXT , "
+ "mail_to TEXT , "
+ "mail_cc TEXT , "
+ "mlist TEXT , "
+ "followup_flag TEXT , "
+ "followup_completed_on TEXT , "
+ "followup_due_by TEXT , "
+ "part TEXT , "
+ "labels TEXT , "
+ "usertags TEXT , "
+ "cinfo TEXT , "
+ "bdata TEXT, "
+ "created TEXT, "
+ "modified TEXT)",
+ folder_name);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
+ sqlite3_free (table_creation_query);
+
+ table_creation_query = sqlite3_mprintf (
+ "CREATE TABLE IF NOT EXISTS '%q_bodystructure' ( "
+ "uid TEXT PRIMARY KEY , "
+ "bodystructure TEXT )",
+ folder_name);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
+ sqlite3_free (table_creation_query);
+
+ /* Create message preview table. */
+ table_creation_query = sqlite3_mprintf ("CREATE TABLE IF NOT EXISTS '%q_preview' ( uid TEXT PRIMARY KEY , preview TEXT)", folder_name);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
+ sqlite3_free (table_creation_query);
+
+ /* FIXME: sqlize folder_name before you create the index */
+ safe_index = g_strdup_printf ("SINDEX-%s", folder_name);
+ table_creation_query = sqlite3_mprintf ("DROP INDEX IF EXISTS %Q", safe_index);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
+ g_free (safe_index);
+ sqlite3_free (table_creation_query);
+
+ /* INDEX on preview */
+ safe_index = g_strdup_printf ("SINDEX-%s-preview", folder_name);
+ table_creation_query = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON '%q_preview' (uid, preview)", safe_index, folder_name);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
+ g_free (safe_index);
+ sqlite3_free (table_creation_query);
+
+ /* Index on deleted*/
+ safe_index = g_strdup_printf ("DELINDEX-%s", folder_name);
+ table_creation_query = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON %Q (deleted)", safe_index, folder_name);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
+ g_free (safe_index);
+ sqlite3_free (table_creation_query);
+
+ /* Index on Junk*/
+ safe_index = g_strdup_printf ("JUNKINDEX-%s", folder_name);
+ table_creation_query = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON %Q (junk)", safe_index, folder_name);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
+ g_free (safe_index);
+ sqlite3_free (table_creation_query);
+
+ /* Index on unread*/
+ safe_index = g_strdup_printf ("READINDEX-%s", folder_name);
+ table_creation_query = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON %Q (read)", safe_index, folder_name);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
+ g_free (safe_index);
+ sqlite3_free (table_creation_query);
+
+ return ret;
+}
+
+static gint
+camel_db_migrate_folder_prepare (CamelDB *cdb,
+ const gchar *folder_name,
+ gint version,
+ GError **error)
+{
+ gint ret = 0;
+ gchar *table_creation_query;
+
+ /* Migration stage one: storing the old data */
+
+ if (version < 0) {
+ ret = camel_db_create_message_info_table (cdb, folder_name, error);
+ g_clear_error (error);
+ } else if (version < 1) {
+
+ /* Between version 0-1 the following things are changed
+ * ADDED: created: time
+ * ADDED: modified: time
+ * RENAMED: msg_security to dirty
+ * */
+
+ table_creation_query = sqlite3_mprintf ("DROP TABLE IF EXISTS 'mem.%q'", folder_name);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
+ sqlite3_free (table_creation_query);
+
+ table_creation_query = sqlite3_mprintf (
+ "CREATE TEMP TABLE IF NOT EXISTS 'mem.%q' ( "
+ "uid TEXT PRIMARY KEY , "
+ "flags INTEGER , "
+ "msg_type INTEGER , "
+ "read INTEGER , "
+ "deleted INTEGER , "
+ "replied INTEGER , "
+ "important INTEGER , "
+ "junk INTEGER , "
+ "attachment INTEGER , "
+ "dirty INTEGER , "
+ "size INTEGER , "
+ "dsent NUMERIC , "
+ "dreceived NUMERIC , "
+ "subject TEXT , "
+ "mail_from TEXT , "
+ "mail_to TEXT , "
+ "mail_cc TEXT , "
+ "mlist TEXT , "
+ "followup_flag TEXT , "
+ "followup_completed_on TEXT , "
+ "followup_due_by TEXT , "
+ "part TEXT , "
+ "labels TEXT , "
+ "usertags TEXT , "
+ "cinfo TEXT , "
+ "bdata TEXT, "
+ "created TEXT, "
+ "modified TEXT )",
+ folder_name);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
+ sqlite3_free (table_creation_query);
+ g_clear_error (error);
+
+ table_creation_query = sqlite3_mprintf (
+ "INSERT INTO 'mem.%q' SELECT "
+ "uid , flags , msg_type , read , deleted , "
+ "replied , important , junk , attachment , dirty , "
+ "size , dsent , dreceived , subject , mail_from , "
+ "mail_to , mail_cc , mlist , followup_flag , "
+ "followup_completed_on , followup_due_by , "
+ "part , labels , usertags , cinfo , bdata , "
+ "strftime(\"%%s\", 'now'), "
+ "strftime(\"%%s\", 'now') FROM %Q",
+ folder_name, folder_name);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
+ sqlite3_free (table_creation_query);
+ g_clear_error (error);
+
+ table_creation_query = sqlite3_mprintf ("DROP TABLE IF EXISTS %Q", folder_name);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
+ sqlite3_free (table_creation_query);
+ g_clear_error (error);
+
+ ret = camel_db_create_message_info_table (cdb, folder_name, error);
+ g_clear_error (error);
+ }
+
+ /* Add later version migrations here */
+
+ return ret;
+}
+
+static gint
+camel_db_migrate_folder_recreate (CamelDB *cdb,
+ const gchar *folder_name,
+ gint version,
+ GError **error)
+{
+ gint ret = 0;
+ gchar *table_creation_query;
+
+ /* Migration stage two: writing back the old data */
+
+ if (version < 2) {
+ GError *local_error = NULL;
+
+ table_creation_query = sqlite3_mprintf (
+ "INSERT INTO %Q SELECT uid , flags , msg_type , "
+ "read , deleted , replied , important , junk , "
+ "attachment , dirty , size , dsent , dreceived , "
+ "subject , mail_from , mail_to , mail_cc , mlist , "
+ "followup_flag , followup_completed_on , "
+ "followup_due_by , part , labels , usertags , "
+ "cinfo , bdata, created, modified FROM 'mem.%q'",
+ folder_name, folder_name);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, &local_error);
+ sqlite3_free (table_creation_query);
+
+ if (!local_error) {
+ table_creation_query = sqlite3_mprintf ("DROP TABLE 'mem.%q'", folder_name);
+ ret = camel_db_add_to_transaction (cdb, table_creation_query, &local_error);
+ sqlite3_free (table_creation_query);
+ }
+
+ if (local_error) {
+ if (local_error->message && strstr (local_error->message, "no such table") != NULL) {
+ /* ignore 'no such table' errors here */
+ g_clear_error (&local_error);
+ ret = 0;
+ } else {
+ g_propagate_error (error, local_error);
+ }
+ }
+ }
+
+ /* Add later version migrations here */
+
+ return ret;
+}
+
+/**
+ * camel_db_reset_folder_version:
+ *
+ * Since: 2.28
+ **/
+gint
+camel_db_reset_folder_version (CamelDB *cdb,
+ const gchar *folder_name,
+ gint reset_version,
+ GError **error)
+{
+ gint ret = 0;
+ gchar *version_creation_query;
+ gchar *version_insert_query;
+ gchar *drop_folder_query;
+
+ drop_folder_query = sqlite3_mprintf ("DROP TABLE IF EXISTS '%q_version'", folder_name);
+ version_creation_query = sqlite3_mprintf ("CREATE TABLE IF NOT EXISTS '%q_version' ( version TEXT )", folder_name);
+
+ version_insert_query = sqlite3_mprintf ("INSERT INTO '%q_version' VALUES ('%d')", folder_name, reset_version);
+
+ ret = camel_db_add_to_transaction (cdb, drop_folder_query, error);
+ ret = camel_db_add_to_transaction (cdb, version_creation_query, error);
+ ret = camel_db_add_to_transaction (cdb, version_insert_query, error);
+
+ sqlite3_free (drop_folder_query);
+ sqlite3_free (version_creation_query);
+ sqlite3_free (version_insert_query);
+
+ return ret;
+}
+
+static gint
+camel_db_write_folder_version (CamelDB *cdb,
+ const gchar *folder_name,
+ gint old_version,
+ GError **error)
+{
+ gint ret = 0;
+ gchar *version_creation_query;
+ gchar *version_insert_query;
+
+ version_creation_query = sqlite3_mprintf ("CREATE TABLE IF NOT EXISTS '%q_version' ( version TEXT )", folder_name);
+
+ if (old_version == -1)
+ version_insert_query = sqlite3_mprintf ("INSERT INTO '%q_version' VALUES ('2')", folder_name);
+ else
+ version_insert_query = sqlite3_mprintf ("UPDATE '%q_version' SET version='2'", folder_name);
+
+ ret = camel_db_add_to_transaction (cdb, version_creation_query, error);
+ ret = camel_db_add_to_transaction (cdb, version_insert_query, error);
+
+ sqlite3_free (version_creation_query);
+ sqlite3_free (version_insert_query);
+
+ return ret;
+}
+
+static gint
+read_version_callback (gpointer ref,
+ gint ncol,
+ gchar **cols,
+ gchar **name)
+{
+ gint *version = (gint *) ref;
+
+ if (cols[0])
+ *version = strtoul (cols [0], NULL, 10);
+
+ return 0;
+}
+
+static gint
+camel_db_get_folder_version (CamelDB *cdb,
+ const gchar *folder_name,
+ GError **error)
+{
+ gint version = -1;
+ gchar *query;
+
+ query = sqlite3_mprintf ("SELECT version FROM '%q_version'", folder_name);
+ camel_db_select (cdb, query, read_version_callback, &version, error);
+ sqlite3_free (query);
+
+ return version;
+}
+
+/**
+ * camel_db_prepare_message_info_table:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_prepare_message_info_table (CamelDB *cdb,
+ const gchar *folder_name,
+ GError **error)
+{
+ gint ret, current_version;
+ gboolean in_transaction = TRUE;
+ GError *err = NULL;
+
+ /* Make sure we have the table already */
+ camel_db_begin_transaction (cdb, &err);
+ ret = camel_db_create_message_info_table (cdb, folder_name, &err);
+ if (err)
+ goto exit;
+
+ camel_db_end_transaction (cdb, &err);
+ in_transaction = FALSE;
+
+ /* Migration stage zero: version fetch */
+ current_version = camel_db_get_folder_version (cdb, folder_name, &err);
+ if (err && err->message && strstr (err->message, "no such table") != NULL) {
+ g_clear_error (&err);
+ current_version = -1;
+ }
+
+ camel_db_begin_transaction (cdb, &err);
+ in_transaction = TRUE;
+
+ /* Migration stage one: storing the old data if necessary */
+ ret = camel_db_migrate_folder_prepare (cdb, folder_name, current_version, &err);
+ if (err)
+ goto exit;
+
+ /* Migration stage two: rewriting the old data if necessary */
+ ret = camel_db_migrate_folder_recreate (cdb, folder_name, current_version, &err);
+ if (err)
+ goto exit;
+
+ /* Final step: (over)write the current version label */
+ ret = camel_db_write_folder_version (cdb, folder_name, current_version, &err);
+ if (err)
+ goto exit;
+
+ camel_db_end_transaction (cdb, &err);
+ in_transaction = FALSE;
+
+exit:
+ if (err && in_transaction)
+ camel_db_abort_transaction (cdb, NULL);
+
+ if (err)
+ g_propagate_error (error, err);
+
+ return ret;
+}
+
+static gint
+write_mir (CamelDB *cdb,
+ const gchar *folder_name,
+ CamelMIRecord *record,
+ GError **error,
+ gboolean delete_old_record)
+{
+ gint ret;
+ /*char *del_query;*/
+ gchar *ins_query;
+
+ if (!record) {
+ g_warn_if_reached ();
+ return -1;
+ }
+
+ /* FIXME: We should migrate from this DELETE followed by INSERT model to an INSERT OR REPLACE model as pointed out by pvanhoof */
+
+ /* NB: UGLIEST Hack. We can't modify the schema now. We are using dirty (an unsed one to notify of FLAGGED/Dirty infos */
+
+ ins_query = sqlite3_mprintf (
+ "INSERT OR REPLACE INTO %Q VALUES ("
+ "%Q, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, "
+ "%lld, %lld, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %Q, "
+ "%Q, %Q, %Q, %Q, %Q, "
+ "strftime(\"%%s\", 'now'), "
+ "strftime(\"%%s\", 'now') )",
+ folder_name,
+ record->uid,
+ record->flags,
+ record->msg_type,
+ record->read,
+ record->deleted,
+ record->replied,
+ record->important,
+ record->junk,
+ record->attachment,
+ record->dirty,
+ record->size,
+ (gint64) record->dsent,
+ (gint64) record->dreceived,
+ record->subject,
+ record->from,
+ record->to,
+ record->cc,
+ record->mlist,
+ record->followup_flag,
+ record->followup_completed_on,
+ record->followup_due_by,
+ record->part,
+ record->labels,
+ record->usertags,
+ record->cinfo,
+ record->bdata);
+
+ ret = camel_db_add_to_transaction (cdb, ins_query, error);
+
+ sqlite3_free (ins_query);
+
+ if (ret == 0) {
+ ins_query = sqlite3_mprintf (
+ "INSERT OR REPLACE INTO "
+ "'%q_bodystructure' VALUES (%Q, %Q )",
+ folder_name, record->uid, record->bodystructure);
+ ret = camel_db_add_to_transaction (cdb, ins_query, error);
+ sqlite3_free (ins_query);
+ }
+
+ return ret;
+}
+
+/**
+ * camel_db_write_fresh_message_info_record:
+ *
+ * Since: 2.26
+ **/
+gint
+camel_db_write_fresh_message_info_record (CamelDB *cdb,
+ const gchar *folder_name,
+ CamelMIRecord *record,
+ GError **error)
+{
+ return write_mir (cdb, folder_name, record, error, FALSE);
+}
+
+/**
+ * camel_db_write_message_info_record:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_write_message_info_record (CamelDB *cdb,
+ const gchar *folder_name,
+ CamelMIRecord *record,
+ GError **error)
+{
+ return write_mir (cdb, folder_name, record, error, TRUE);
+}
+
+/**
+ * camel_db_write_folder_info_record:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_write_folder_info_record (CamelDB *cdb,
+ CamelFIRecord *record,
+ GError **error)
+{
+ gint ret;
+
+ gchar *del_query;
+ gchar *ins_query;
+
+ ins_query = sqlite3_mprintf (
+ "INSERT INTO folders VALUES ("
+ "%Q, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %Q ) ",
+ record->folder_name,
+ record->version,
+ record->flags,
+ record->nextuid,
+ record->time,
+ record->saved_count,
+ record->unread_count,
+ record->deleted_count,
+ record->junk_count,
+ record->visible_count,
+ record->jnd_count,
+ record->bdata);
+
+ del_query = sqlite3_mprintf (
+ "DELETE FROM folders WHERE folder_name = %Q",
+ record->folder_name);
+
+ ret = camel_db_add_to_transaction (cdb, del_query, error);
+ ret = camel_db_add_to_transaction (cdb, ins_query, error);
+
+ sqlite3_free (del_query);
+ sqlite3_free (ins_query);
+
+ return ret;
+}
+
+struct ReadFirData {
+ GHashTable *columns_hash;
+ CamelFIRecord *record;
+};
+
+static gint
+read_fir_callback (gpointer ref,
+ gint ncol,
+ gchar **cols,
+ gchar **name)
+{
+ struct ReadFirData *rfd = ref;
+ gint i;
+
+ d (g_print ("\nread_fir_callback called \n"));
+
+ for (i = 0; i < ncol; ++i) {
+ if (!name[i] || !cols[i])
+ continue;
+
+ switch (camel_db_get_column_ident (&rfd->columns_hash, i, ncol, name)) {
+ case CAMEL_DB_COLUMN_FOLDER_NAME:
+ rfd->record->folder_name = g_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_VERSION:
+ rfd->record->version = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_FLAGS:
+ rfd->record->flags = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_NEXTUID:
+ rfd->record->nextuid = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_TIME:
+ rfd->record->time = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_SAVED_COUNT:
+ rfd->record->saved_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_UNREAD_COUNT:
+ rfd->record->unread_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_DELETED_COUNT:
+ rfd->record->deleted_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_JUNK_COUNT:
+ rfd->record->junk_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_VISIBLE_COUNT:
+ rfd->record->visible_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_JND_COUNT:
+ rfd->record->jnd_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_BDATA:
+ rfd->record->bdata = g_strdup (cols[i]);
+ break;
+ default:
+ g_warn_if_reached ();
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * camel_db_read_folder_info_record:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_read_folder_info_record (CamelDB *cdb,
+ const gchar *folder_name,
+ CamelFIRecord *record,
+ GError **error)
+{
+ struct ReadFirData rfd;
+ gchar *query;
+ gint ret;
+
+ rfd.columns_hash = NULL;
+ rfd.record = record;
+
+ query = sqlite3_mprintf ("SELECT * FROM folders WHERE folder_name = %Q", folder_name);
+ ret = camel_db_select (cdb, query, read_fir_callback, &rfd, error);
+ sqlite3_free (query);
+
+ if (rfd.columns_hash)
+ g_hash_table_destroy (rfd.columns_hash);
+
+ return ret;
+}
+
+/**
+ * camel_db_read_message_info_record_with_uid:
+ * @read_mir_callback: (scope async) (closure user_data):
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_read_message_info_record_with_uid (CamelDB *cdb,
+ const gchar *folder_name,
+ const gchar *uid,
+ gpointer user_data,
+ CamelDBSelectCB read_mir_callback,
+ GError **error)
+{
+ gchar *query;
+ gint ret;
+
+ query = sqlite3_mprintf (
+ "SELECT uid, flags, size, dsent, dreceived, subject, "
+ "mail_from, mail_to, mail_cc, mlist, part, labels, "
+ "usertags, cinfo, bdata FROM %Q WHERE uid = %Q",
+ folder_name, uid);
+ ret = camel_db_select (cdb, query, read_mir_callback, user_data, error);
+ sqlite3_free (query);
+
+ return (ret);
+}
+
+/**
+ * camel_db_read_message_info_records:
+ * @read_mir_callback: (scope async) (closure user_data):
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_read_message_info_records (CamelDB *cdb,
+ const gchar *folder_name,
+ gpointer user_data,
+ CamelDBSelectCB read_mir_callback,
+ GError **error)
+{
+ gchar *query;
+ gint ret;
+
+ query = sqlite3_mprintf (
+ "SELECT uid, flags, size, dsent, dreceived, subject, "
+ "mail_from, mail_to, mail_cc, mlist, part, labels, "
+ "usertags, cinfo, bdata FROM %Q ", folder_name);
+ ret = camel_db_select (cdb, query, read_mir_callback, user_data, error);
+ sqlite3_free (query);
+
+ return (ret);
+}
+
+/**
+ * camel_db_delete_uid:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_delete_uid (CamelDB *cdb,
+ const gchar *folder,
+ const gchar *uid,
+ GError **error)
+{
+ gchar *tab;
+ gint ret;
+
+ camel_db_begin_transaction (cdb, error);
+
+ tab = sqlite3_mprintf ("DELETE FROM '%q_bodystructure' WHERE uid = %Q", folder, uid);
+ ret = camel_db_add_to_transaction (cdb, tab, error);
+ sqlite3_free (tab);
+
+ tab = sqlite3_mprintf ("DELETE FROM %Q WHERE uid = %Q", folder, uid);
+ ret = camel_db_add_to_transaction (cdb, tab, error);
+ sqlite3_free (tab);
+
+ ret = camel_db_end_transaction (cdb, error);
+
+ CAMEL_DB_RELEASE_SQLITE_MEMORY;
+ return ret;
+}
+
+static gint
+cdb_delete_ids (CamelDB *cdb,
+ const gchar *folder_name,
+ GList *uids,
+ const gchar *uid_prefix,
+ const gchar *field,
+ GError **error)
+{
+ gchar *tmp;
+ gint ret = 0;
+ gboolean first = TRUE;
+ GString *str = g_string_new ("DELETE FROM ");
+ GList *iterator;
+
+ camel_db_begin_transaction (cdb, error);
+
+ tmp = sqlite3_mprintf ("%Q WHERE %s IN (", folder_name, field);
+ g_string_append_printf (str, "%s ", tmp);
+ sqlite3_free (tmp);
+
+ iterator = uids;
+
+ while (iterator) {
+ gchar *foo = g_strdup_printf ("%s%s", uid_prefix, (gchar *) iterator->data);
+ tmp = sqlite3_mprintf ("%Q", foo);
+ g_free (foo);
+ iterator = iterator->next;
+
+ if (first == TRUE) {
+ g_string_append_printf (str, " %s ", tmp);
+ first = FALSE;
+ } else {
+ g_string_append_printf (str, ", %s ", tmp);
+ }
+
+ sqlite3_free (tmp);
+ }
+
+ g_string_append (str, ")");
+
+ ret = ret == -1 ? ret : camel_db_add_to_transaction (cdb, str->str, error);
+
+ if (ret == -1)
+ camel_db_abort_transaction (cdb, NULL);
+ else
+ ret = camel_db_end_transaction (cdb, error);
+
+ CAMEL_DB_RELEASE_SQLITE_MEMORY;
+
+ g_string_free (str, TRUE);
+
+ return ret;
+}
+
+/**
+ * camel_db_delete_uids:
+ * @cdb: a #CamelDB
+ * @uids: (element-type utf8) (transfer none): A #GList of uids
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_delete_uids (CamelDB *cdb,
+ const gchar *folder_name,
+ GList *uids,
+ GError **error)
+{
+ if (!uids || !uids->data)
+ return 0;
+
+ return cdb_delete_ids (cdb, folder_name, uids, "", "uid", error);
+}
+
+/**
+ * camel_db_clear_folder_summary:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_clear_folder_summary (CamelDB *cdb,
+ const gchar *folder,
+ GError **error)
+{
+ gint ret;
+ gchar *folders_del;
+ gchar *msginfo_del;
+ gchar *bstruct_del;
+
+ folders_del = sqlite3_mprintf ("DELETE FROM folders WHERE folder_name = %Q", folder);
+ msginfo_del = sqlite3_mprintf ("DELETE FROM %Q ", folder);
+ bstruct_del = sqlite3_mprintf ("DELETE FROM '%q_bodystructure' ", folder);
+
+ camel_db_begin_transaction (cdb, error);
+
+ camel_db_add_to_transaction (cdb, msginfo_del, error);
+ camel_db_add_to_transaction (cdb, folders_del, error);
+ camel_db_add_to_transaction (cdb, bstruct_del, error);
+
+ ret = camel_db_end_transaction (cdb, error);
+
+ sqlite3_free (folders_del);
+ sqlite3_free (msginfo_del);
+ sqlite3_free (bstruct_del);
+
+ return ret;
+}
+
+/**
+ * camel_db_delete_folder:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_delete_folder (CamelDB *cdb,
+ const gchar *folder,
+ GError **error)
+{
+ gint ret;
+ gchar *del;
+
+ camel_db_begin_transaction (cdb, error);
+
+ del = sqlite3_mprintf ("DELETE FROM folders WHERE folder_name = %Q", folder);
+ ret = camel_db_add_to_transaction (cdb, del, error);
+ sqlite3_free (del);
+
+ del = sqlite3_mprintf ("DROP TABLE %Q ", folder);
+ ret = camel_db_add_to_transaction (cdb, del, error);
+ sqlite3_free (del);
+
+ del = sqlite3_mprintf ("DROP TABLE '%q_bodystructure' ", folder);
+ ret = camel_db_add_to_transaction (cdb, del, error);
+ sqlite3_free (del);
+
+ ret = camel_db_end_transaction (cdb, error);
+
+ CAMEL_DB_RELEASE_SQLITE_MEMORY;
+ return ret;
+}
+
+/**
+ * camel_db_rename_folder:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_db_rename_folder (CamelDB *cdb,
+ const gchar *old_folder,
+ const gchar *new_folder,
+ GError **error)
+{
+ gint ret;
+ gchar *cmd;
+
+ camel_db_begin_transaction (cdb, error);
+
+ cmd = sqlite3_mprintf ("ALTER TABLE %Q RENAME TO %Q", old_folder, new_folder);
+ ret = camel_db_add_to_transaction (cdb, cmd, error);
+ sqlite3_free (cmd);
+
+ cmd = sqlite3_mprintf ("ALTER TABLE '%q_version' RENAME TO '%q_version'", old_folder, new_folder);
+ ret = camel_db_add_to_transaction (cdb, cmd, error);
+ sqlite3_free (cmd);
+
+ cmd = sqlite3_mprintf ("UPDATE %Q SET modified=strftime(\"%%s\", 'now'), created=strftime(\"%%s\", 'now')", new_folder);
+ ret = camel_db_add_to_transaction (cdb, cmd, error);
+ sqlite3_free (cmd);
+
+ cmd = sqlite3_mprintf ("UPDATE folders SET folder_name = %Q WHERE folder_name = %Q", new_folder, old_folder);
+ ret = camel_db_add_to_transaction (cdb, cmd, error);
+ sqlite3_free (cmd);
+
+ ret = camel_db_end_transaction (cdb, error);
+
+ CAMEL_DB_RELEASE_SQLITE_MEMORY;
+ return ret;
+}
+
+/**
+ * camel_db_camel_mir_free:
+ *
+ * Since: 2.24
+ **/
+void
+camel_db_camel_mir_free (CamelMIRecord *record)
+{
+ if (record) {
+ camel_pstring_free (record->uid);
+ camel_pstring_free (record->subject);
+ camel_pstring_free (record->from);
+ camel_pstring_free (record->to);
+ camel_pstring_free (record->cc);
+ camel_pstring_free (record->mlist);
+ camel_pstring_free (record->followup_flag);
+ camel_pstring_free (record->followup_completed_on);
+ camel_pstring_free (record->followup_due_by);
+ g_free (record->part);
+ g_free (record->labels);
+ g_free (record->usertags);
+ g_free (record->cinfo);
+ g_free (record->bdata);
+ g_free (record->bodystructure);
+
+ g_free (record);
+ }
+}
+
+/**
+ * camel_db_sqlize_string:
+ *
+ * Since: 2.24
+ **/
+gchar *
+camel_db_sqlize_string (const gchar *string)
+{
+ return sqlite3_mprintf ("%Q", string);
+}
+
+/**
+ * camel_db_free_sqlized_string:
+ *
+ * Since: 2.24
+ **/
+void
+camel_db_free_sqlized_string (gchar *string)
+{
+ sqlite3_free (string);
+ string = NULL;
+}
+
+/*
+"( uid TEXT PRIMARY KEY ,
+flags INTEGER ,
+msg_type INTEGER ,
+replied INTEGER ,
+dirty INTEGER ,
+size INTEGER ,
+dsent NUMERIC ,
+dreceived NUMERIC ,
+mlist TEXT ,
+followup_flag TEXT ,
+followup_completed_on TEXT ,
+followup_due_by TEXT ," */
+
+/**
+ * camel_db_get_column_name:
+ *
+ * Since: 2.24
+ **/
+gchar *
+camel_db_get_column_name (const gchar *raw_name)
+{
+ if (!g_ascii_strcasecmp (raw_name, "Subject"))
+ return g_strdup ("subject");
+ else if (!g_ascii_strcasecmp (raw_name, "from"))
+ return g_strdup ("mail_from");
+ else if (!g_ascii_strcasecmp (raw_name, "Cc"))
+ return g_strdup ("mail_cc");
+ else if (!g_ascii_strcasecmp (raw_name, "To"))
+ return g_strdup ("mail_to");
+ else if (!g_ascii_strcasecmp (raw_name, "Flagged"))
+ return g_strdup ("important");
+ else if (!g_ascii_strcasecmp (raw_name, "deleted"))
+ return g_strdup ("deleted");
+ else if (!g_ascii_strcasecmp (raw_name, "junk"))
+ return g_strdup ("junk");
+ else if (!g_ascii_strcasecmp (raw_name, "Answered"))
+ return g_strdup ("replied");
+ else if (!g_ascii_strcasecmp (raw_name, "Seen"))
+ return g_strdup ("read");
+ else if (!g_ascii_strcasecmp (raw_name, "user-tag"))
+ return g_strdup ("usertags");
+ else if (!g_ascii_strcasecmp (raw_name, "user-flag"))
+ return g_strdup ("labels");
+ else if (!g_ascii_strcasecmp (raw_name, "Attachments"))
+ return g_strdup ("attachment");
+ else if (!g_ascii_strcasecmp (raw_name, "x-camel-mlist"))
+ return g_strdup ("mlist");
+
+ /* indicate the header name is not part of the summary */
+ return NULL;
+}
+
+/**
+ * camel_db_start_in_memory_transactions:
+ *
+ * Since: 2.26
+ **/
+gint
+camel_db_start_in_memory_transactions (CamelDB *cdb,
+ GError **error)
+{
+ gint ret;
+ gchar *cmd = sqlite3_mprintf ("ATTACH DATABASE ':memory:' AS %s", CAMEL_DB_IN_MEMORY_DB);
+
+ ret = camel_db_command (cdb, cmd, error);
+ sqlite3_free (cmd);
+
+ cmd = sqlite3_mprintf (
+ "CREATE TEMPORARY TABLE %Q ( "
+ "uid TEXT PRIMARY KEY , "
+ "flags INTEGER , "
+ "msg_type INTEGER , "
+ "read INTEGER , "
+ "deleted INTEGER , "
+ "replied INTEGER , "
+ "important INTEGER , "
+ "junk INTEGER , "
+ "attachment INTEGER , "
+ "dirty INTEGER , "
+ "size INTEGER , "
+ "dsent NUMERIC , "
+ "dreceived NUMERIC , "
+ "subject TEXT , "
+ "mail_from TEXT , "
+ "mail_to TEXT , "
+ "mail_cc TEXT , "
+ "mlist TEXT , "
+ "followup_flag TEXT , "
+ "followup_completed_on TEXT , "
+ "followup_due_by TEXT , "
+ "part TEXT , "
+ "labels TEXT , "
+ "usertags TEXT , "
+ "cinfo TEXT , "
+ "bdata TEXT )",
+ CAMEL_DB_IN_MEMORY_TABLE);
+ ret = camel_db_command (cdb, cmd, error);
+ if (ret != 0 )
+ abort ();
+ sqlite3_free (cmd);
+
+ return ret;
+}
+
+/**
+ * camel_db_flush_in_memory_transactions:
+ *
+ * Since: 2.26
+ **/
+gint
+camel_db_flush_in_memory_transactions (CamelDB *cdb,
+ const gchar *folder_name,
+ GError **error)
+{
+ gint ret;
+ gchar *cmd = sqlite3_mprintf ("INSERT INTO %Q SELECT * FROM %Q", folder_name, CAMEL_DB_IN_MEMORY_TABLE);
+
+ ret = camel_db_command (cdb, cmd, error);
+ sqlite3_free (cmd);
+
+ cmd = sqlite3_mprintf ("DROP TABLE %Q", CAMEL_DB_IN_MEMORY_TABLE);
+ ret = camel_db_command (cdb, cmd, error);
+ sqlite3_free (cmd);
+
+ cmd = sqlite3_mprintf ("DETACH %Q", CAMEL_DB_IN_MEMORY_DB);
+ ret = camel_db_command (cdb, cmd, error);
+ sqlite3_free (cmd);
+
+ return ret;
+}
+
+static struct _known_column_names {
+ const gchar *name;
+ CamelDBKnownColumnNames ident;
+} known_column_names[] = {
+ { "attachment", CAMEL_DB_COLUMN_ATTACHMENT },
+ { "bdata", CAMEL_DB_COLUMN_BDATA },
+ { "bodystructure", CAMEL_DB_COLUMN_BODYSTRUCTURE },
+ { "cinfo", CAMEL_DB_COLUMN_CINFO },
+ { "deleted", CAMEL_DB_COLUMN_DELETED },
+ { "deleted_count", CAMEL_DB_COLUMN_DELETED_COUNT },
+ { "dreceived", CAMEL_DB_COLUMN_DRECEIVED },
+ { "dsent", CAMEL_DB_COLUMN_DSENT },
+ { "flags", CAMEL_DB_COLUMN_FLAGS },
+ { "folder_name", CAMEL_DB_COLUMN_FOLDER_NAME },
+ { "followup_completed_on", CAMEL_DB_COLUMN_FOLLOWUP_COMPLETED_ON },
+ { "followup_due_by", CAMEL_DB_COLUMN_FOLLOWUP_DUE_BY },
+ { "followup_flag", CAMEL_DB_COLUMN_FOLLOWUP_FLAG },
+ { "important", CAMEL_DB_COLUMN_IMPORTANT },
+ { "jnd_count", CAMEL_DB_COLUMN_JND_COUNT },
+ { "junk", CAMEL_DB_COLUMN_JUNK },
+ { "junk_count", CAMEL_DB_COLUMN_JUNK_COUNT },
+ { "labels", CAMEL_DB_COLUMN_LABELS },
+ { "mail_cc", CAMEL_DB_COLUMN_MAIL_CC },
+ { "mail_from", CAMEL_DB_COLUMN_MAIL_FROM },
+ { "mail_to", CAMEL_DB_COLUMN_MAIL_TO },
+ { "mlist", CAMEL_DB_COLUMN_MLIST },
+ { "nextuid", CAMEL_DB_COLUMN_NEXTUID },
+ { "part", CAMEL_DB_COLUMN_PART },
+ { "preview", CAMEL_DB_COLUMN_PREVIEW },
+ { "read", CAMEL_DB_COLUMN_READ },
+ { "replied", CAMEL_DB_COLUMN_REPLIED },
+ { "saved_count", CAMEL_DB_COLUMN_SAVED_COUNT },
+ { "size", CAMEL_DB_COLUMN_SIZE },
+ { "subject", CAMEL_DB_COLUMN_SUBJECT },
+ { "time", CAMEL_DB_COLUMN_TIME },
+ { "uid", CAMEL_DB_COLUMN_UID },
+ { "unread_count", CAMEL_DB_COLUMN_UNREAD_COUNT },
+ { "usertags", CAMEL_DB_COLUMN_USERTAGS },
+ { "version", CAMEL_DB_COLUMN_VERSION },
+ { "visible_count", CAMEL_DB_COLUMN_VISIBLE_COUNT },
+ { "vuid", CAMEL_DB_COLUMN_VUID }
+};
+
+/**
+ * camel_db_get_column_ident:
+ * @hash: (inout):
+ * @index:
+ * @ncols: number of @col_names
+ * @col_names: (array length=ncols):
+ *
+ * Traverses column name from index @index into an enum
+ * #CamelDBKnownColumnNames value. The @col_names contains @ncols columns.
+ * First time this is called is created the @hash from col_names indexes into
+ * the enum, and this is reused for every other call. The function expects
+ * that column names are returned always in the same order. When all rows
+ * are read the @hash table can be freed with g_hash_table_destroy().
+ *
+ * Since: 3.4
+ **/
+CamelDBKnownColumnNames
+camel_db_get_column_ident (GHashTable **hash,
+ gint index,
+ gint ncols,
+ gchar **col_names)
+{
+ gpointer value = NULL;
+
+ g_return_val_if_fail (hash != NULL, CAMEL_DB_COLUMN_UNKNOWN);
+ g_return_val_if_fail (col_names != NULL, CAMEL_DB_COLUMN_UNKNOWN);
+ g_return_val_if_fail (ncols > 0, CAMEL_DB_COLUMN_UNKNOWN);
+ g_return_val_if_fail (index >= 0, CAMEL_DB_COLUMN_UNKNOWN);
+ g_return_val_if_fail (index < ncols, CAMEL_DB_COLUMN_UNKNOWN);
+
+ if (!*hash) {
+ gint ii, jj, from, max = G_N_ELEMENTS (known_column_names);
+
+ *hash = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ for (ii = 0, jj = 0; ii < ncols; ii++) {
+ const gchar *name = col_names[ii];
+ gboolean first = TRUE;
+
+ if (!name)
+ continue;
+
+ for (from = jj; first || jj != from; jj = (jj + 1) % max, first = FALSE) {
+ if (g_str_equal (name, known_column_names[jj].name)) {
+ g_hash_table_insert (*hash, GINT_TO_POINTER (ii), GINT_TO_POINTER (known_column_names[jj].ident));
+ break;
+ }
+ }
+
+ if (from == jj && !first)
+ g_warning ("%s: missing column name '%s' in a list of known columns", G_STRFUNC, name);
+ }
+ }
+
+ g_return_val_if_fail (g_hash_table_lookup_extended (*hash, GINT_TO_POINTER (index), NULL, &value), CAMEL_DB_COLUMN_UNKNOWN);
+
+ return GPOINTER_TO_INT (value);
+}
+
+static gint
+get_number_cb (gpointer data,
+ gint argc,
+ gchar **argv,
+ gchar **azColName)
+{
+ guint64 *pui64 = data;
+
+ if (argc == 1) {
+ *pui64 = argv[0] ? g_ascii_strtoull (argv[0], NULL, 10) : 0;
+ } else {
+ *pui64 = 0;
+ }
+
+ return 0;
+}
+
+/**
+ * camel_db_maybe_run_maintenance:
+ * @cdb: a #CamelDB instance
+ * @error: (allow-none): a #GError or %NULL
+ *
+ * Runs a @cdb maintenance, which includes vacuum, if necessary.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.16
+ **/
+gboolean
+camel_db_maybe_run_maintenance (CamelDB *cdb,
+ GError **error)
+{
+ GError *local_error = NULL;
+ guint64 page_count = 0, freelist_count = 0;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (cdb != NULL, FALSE);
+
+ cdb_writer_lock (cdb);
+
+ if (cdb_sql_exec (cdb->db, "PRAGMA page_count;", get_number_cb, &page_count, NULL, &local_error) == SQLITE_OK &&
+ cdb_sql_exec (cdb->db, "PRAGMA freelist_count;", get_number_cb, &freelist_count, NULL, &local_error) == SQLITE_OK) {
+ /* Vacuum, if there's more than 5% of the free pages */
+ success = !page_count || !freelist_count || freelist_count * 1000 / page_count <= 50 ||
+ cdb_sql_exec (cdb->db, "vacuum;", NULL, NULL, NULL, &local_error) == SQLITE_OK;
+ }
+
+ cdb_writer_unlock (cdb);
+
+ if (local_error) {
+ g_propagate_error (error, local_error);
+ success = FALSE;
+ }
+
+ return success;
+}
diff --git a/src/camel/camel-db.h b/src/camel/camel-db.h
new file mode 100644
index 000000000..b454af9a9
--- /dev/null
+++ b/src/camel/camel-db.h
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Sankar P <psankar@novell.com>
+ * Srinivasa Ragavan <sragavan@novell.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_DB_H
+#define CAMEL_DB_H
+
+#include <sqlite3.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/**
+ * CAMEL_DB_FILE:
+ *
+ * Since: 2.24
+ **/
+#define CAMEL_DB_FILE "folders.db"
+
+/* Hopefully no one will create a folder named EVO_IN_meM_hAnDlE */
+
+/**
+ * CAMEL_DB_IN_MEMORY_TABLE:
+ *
+ * Since: 2.26
+ **/
+#define CAMEL_DB_IN_MEMORY_TABLE "EVO_IN_meM_hAnDlE.temp"
+
+/**
+ * CAMEL_DB_IN_MEMORY_DB:
+ *
+ * Since: 2.26
+ **/
+#define CAMEL_DB_IN_MEMORY_DB "EVO_IN_meM_hAnDlE"
+
+/**
+ * CAMEL_DB_IN_MEMORY_TABLE_LIMIT:
+ *
+ * Since: 2.26
+ **/
+#define CAMEL_DB_IN_MEMORY_TABLE_LIMIT 100000
+
+typedef struct _CamelDBPrivate CamelDBPrivate;
+
+/**
+ * CamelDBCollate:
+ *
+ * Since: 2.24
+ **/
+typedef gint (*CamelDBCollate)(gpointer enc, gint length1, gconstpointer data1, gint length2, gconstpointer data2);
+
+/**
+ * CamelDB:
+ *
+ * Since: 2.24
+ **/
+struct _CamelDB {
+ sqlite3 *db;
+ /* this lock has been replaced with a rw lock which sits inside priv.
+ * This is currently unused. Keeping it, not to break the ABI */
+ GMutex *lock;
+ CamelDBPrivate *priv;
+};
+
+/**
+ * CAMEL_DB_FREE_CACHE_SIZE:
+ *
+ * Since: 2.24
+ **/
+#define CAMEL_DB_FREE_CACHE_SIZE 2 * 1024 * 1024
+
+/**
+ * CAMEL_DB_SLEEP_INTERVAL:
+ *
+ * Since: 2.24
+ **/
+#define CAMEL_DB_SLEEP_INTERVAL 1*10*10
+
+/**
+ * CAMEL_DB_RELEASE_SQLITE_MEMORY:
+ *
+ * Since: 2.24
+ **/
+#define CAMEL_DB_RELEASE_SQLITE_MEMORY if(!g_getenv("CAMEL_SQLITE_FREE_CACHE")) sqlite3_release_memory(CAMEL_DB_FREE_CACHE_SIZE);
+
+/**
+ * CAMEL_DB_USE_SHARED_CACHE:
+ *
+ * Since: 2.24
+ **/
+#define CAMEL_DB_USE_SHARED_CACHE if(g_getenv("CAMEL_SQLITE_SHARED_CACHE")) sqlite3_enable_shared_cache(TRUE);
+
+/**
+ * CamelMIRecord:
+ * @uid:
+ * Message UID
+ * @flags:
+ * Camel Message info flags
+ * @msg_type:
+ * @dirty:
+ * @read:
+ * boolean read status
+ * @deleted:
+ * boolean deleted status
+ * @replied:
+ * boolean replied status
+ * @important:
+ * boolean important status
+ * @junk:
+ * boolean junk status
+ * @attachment:
+ * boolean attachment status
+ * @size:
+ * size of the mail
+ * @dsent:
+ * date sent
+ * @dreceived:
+ * date received
+ * @subject:
+ * subject of the mail
+ * @from:
+ * sender
+ * @to:
+ * recipient
+ * @cc:
+ * CC members
+ * @mlist:
+ * message list headers
+ * @followup_flag:
+ * followup flag / also can be queried to see for followup or not
+ * @followup_completed_on:
+ * completed date, can be used to see if completed
+ * @followup_due_by:
+ * to see the due by date
+ * @part:
+ * part / references / thread id
+ * @labels:
+ * labels of mails also called as userflags
+ * @usertags:
+ * composite string of user tags
+ * @cinfo:
+ * content info string - composite string
+ * @bdata:
+ * provider specific data
+ * @bodystructure:
+ *
+ * The extensive DB format, supporting basic searching and sorting.
+ *
+ * Since: 2.24
+ **/
+typedef struct _CamelMIRecord {
+ gchar *uid;
+ guint32 flags;
+ guint32 msg_type;
+ guint32 dirty;
+ gboolean read;
+ gboolean deleted;
+ gboolean replied;
+ gboolean important;
+ gboolean junk;
+ gboolean attachment;
+ guint32 size;
+ time_t dsent;
+ time_t dreceived;
+ gchar *subject;
+ gchar *from;
+ gchar *to;
+ gchar *cc;
+ gchar *mlist;
+ gchar *followup_flag;
+ gchar *followup_completed_on;
+ gchar *followup_due_by;
+ gchar *part;
+ gchar *labels;
+ gchar *usertags;
+ gchar *cinfo;
+ gchar *bdata;
+ gchar *bodystructure;
+} CamelMIRecord;
+
+/**
+ * CamelFIRecord:
+ *
+ * Since: 2.24
+ **/
+typedef struct _CamelFIRecord {
+ gchar *folder_name;
+ guint32 version;
+ guint32 flags;
+ guint32 nextuid;
+ time_t time;
+ guint32 saved_count;
+ guint32 unread_count;
+ guint32 deleted_count;
+ guint32 junk_count;
+ guint32 visible_count;
+ guint32 jnd_count; /* Junked not deleted */
+ gchar *bdata;
+} CamelFIRecord;
+
+typedef struct _CamelDB CamelDB;
+
+/**
+ * CamelDBKnownColumnNames:
+ *
+ * Since: 3.4
+ **/
+typedef enum {
+ CAMEL_DB_COLUMN_UNKNOWN = -1,
+ CAMEL_DB_COLUMN_ATTACHMENT,
+ CAMEL_DB_COLUMN_BDATA,
+ CAMEL_DB_COLUMN_BODYSTRUCTURE,
+ CAMEL_DB_COLUMN_CINFO,
+ CAMEL_DB_COLUMN_DELETED,
+ CAMEL_DB_COLUMN_DELETED_COUNT,
+ CAMEL_DB_COLUMN_DRECEIVED,
+ CAMEL_DB_COLUMN_DSENT,
+ CAMEL_DB_COLUMN_FLAGS,
+ CAMEL_DB_COLUMN_FOLDER_NAME,
+ CAMEL_DB_COLUMN_FOLLOWUP_COMPLETED_ON,
+ CAMEL_DB_COLUMN_FOLLOWUP_DUE_BY,
+ CAMEL_DB_COLUMN_FOLLOWUP_FLAG,
+ CAMEL_DB_COLUMN_IMPORTANT,
+ CAMEL_DB_COLUMN_JND_COUNT,
+ CAMEL_DB_COLUMN_JUNK,
+ CAMEL_DB_COLUMN_JUNK_COUNT,
+ CAMEL_DB_COLUMN_LABELS,
+ CAMEL_DB_COLUMN_MAIL_CC,
+ CAMEL_DB_COLUMN_MAIL_FROM,
+ CAMEL_DB_COLUMN_MAIL_TO,
+ CAMEL_DB_COLUMN_MLIST,
+ CAMEL_DB_COLUMN_NEXTUID,
+ CAMEL_DB_COLUMN_PART,
+ CAMEL_DB_COLUMN_PREVIEW,
+ CAMEL_DB_COLUMN_READ,
+ CAMEL_DB_COLUMN_REPLIED,
+ CAMEL_DB_COLUMN_SAVED_COUNT,
+ CAMEL_DB_COLUMN_SIZE,
+ CAMEL_DB_COLUMN_SUBJECT,
+ CAMEL_DB_COLUMN_TIME,
+ CAMEL_DB_COLUMN_UID,
+ CAMEL_DB_COLUMN_UNREAD_COUNT,
+ CAMEL_DB_COLUMN_USERTAGS,
+ CAMEL_DB_COLUMN_VERSION,
+ CAMEL_DB_COLUMN_VISIBLE_COUNT,
+ CAMEL_DB_COLUMN_VUID
+} CamelDBKnownColumnNames;
+
+CamelDBKnownColumnNames camel_db_get_column_ident (GHashTable **hash, gint index, gint ncols, gchar **col_names);
+
+/**
+ * CamelDBSelectCB:
+ *
+ * Since: 2.24
+ **/
+typedef gint (*CamelDBSelectCB) (gpointer data, gint ncol, gchar **colvalues, gchar **colnames);
+
+CamelDB * camel_db_open (const gchar *path, GError **error);
+CamelDB * camel_db_clone (CamelDB *cdb, GError **error);
+void camel_db_close (CamelDB *cdb);
+gint camel_db_command (CamelDB *cdb, const gchar *stmt, GError **error);
+
+gint camel_db_transaction_command (CamelDB *cdb, GList *qry_list, GError **error);
+
+gint camel_db_begin_transaction (CamelDB *cdb, GError **error);
+gint camel_db_add_to_transaction (CamelDB *cdb, const gchar *query, GError **error);
+gint camel_db_end_transaction (CamelDB *cdb, GError **error);
+gint camel_db_abort_transaction (CamelDB *cdb, GError **error);
+gint camel_db_clear_folder_summary (CamelDB *cdb, const gchar *folder, GError **error);
+gint camel_db_rename_folder (CamelDB *cdb, const gchar *old_folder, const gchar *new_folder, GError **error);
+
+gint camel_db_delete_folder (CamelDB *cdb, const gchar *folder, GError **error);
+gint camel_db_delete_uid (CamelDB *cdb, const gchar *folder, const gchar *uid, GError **error);
+/*int camel_db_delete_uids (CamelDB *cdb, GError **error, gint nargs, ... );*/
+gint camel_db_delete_uids (CamelDB *cdb, const gchar * folder_name, GList *uids, GError **error);
+
+gint camel_db_create_folders_table (CamelDB *cdb, GError **error);
+gint camel_db_select (CamelDB *cdb, const gchar * stmt, CamelDBSelectCB callback, gpointer user_data, GError **error);
+
+gint camel_db_write_folder_info_record (CamelDB *cdb, CamelFIRecord *record, GError **error);
+gint camel_db_read_folder_info_record (CamelDB *cdb, const gchar *folder_name, CamelFIRecord *record, GError **error);
+
+gint camel_db_prepare_message_info_table (CamelDB *cdb, const gchar *folder_name, GError **error);
+
+gint camel_db_write_message_info_record (CamelDB *cdb, const gchar *folder_name, CamelMIRecord *record, GError **error);
+gint camel_db_write_fresh_message_info_record (CamelDB *cdb, const gchar *folder_name, CamelMIRecord *record, GError **error);
+gint camel_db_read_message_info_records (CamelDB *cdb, const gchar *folder_name, gpointer user_data, CamelDBSelectCB read_mir_callback, GError **error);
+gint camel_db_read_message_info_record_with_uid (CamelDB *cdb, const gchar *folder_name, const gchar *uid, gpointer user_data, CamelDBSelectCB read_mir_callback, GError **error);
+
+gint camel_db_count_junk_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error);
+gint camel_db_count_unread_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error);
+gint camel_db_count_deleted_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error);
+gint camel_db_count_total_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error);
+
+gint camel_db_count_visible_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error);
+gint camel_db_count_visible_unread_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error);
+
+gint camel_db_count_junk_not_deleted_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error);
+gint camel_db_count_message_info (CamelDB *cdb, const gchar *query, guint32 *count, GError **error);
+void camel_db_camel_mir_free (CamelMIRecord *record);
+
+gint camel_db_get_folder_uids (CamelDB *db, const gchar *folder_name, const gchar *sort_by, const gchar *collate, GHashTable *hash, GError **error);
+
+GPtrArray * camel_db_get_folder_junk_uids (CamelDB *db, gchar *folder_name, GError **error);
+GPtrArray * camel_db_get_folder_deleted_uids (CamelDB *db, const gchar *folder_name, GError **error);
+
+gchar * camel_db_sqlize_string (const gchar *string);
+void camel_db_free_sqlized_string (gchar *string);
+
+gchar * camel_db_get_column_name (const gchar *raw_name);
+gint camel_db_set_collate (CamelDB *cdb, const gchar *col, const gchar *collate, CamelDBCollate func);
+
+gint camel_db_start_in_memory_transactions (CamelDB *cdb, GError **error);
+gint camel_db_flush_in_memory_transactions (CamelDB *cdb, const gchar * folder_name, GError **error);
+
+GHashTable *
+camel_db_get_folder_preview (CamelDB *db, const gchar *folder_name, GError **error);
+gint camel_db_write_preview_record (CamelDB *db, const gchar *folder_name, const gchar *uid, const gchar *msg, GError **error);
+
+gint
+camel_db_reset_folder_version (CamelDB *cdb, const gchar *folder_name, gint reset_version, GError **error);
+
+gboolean camel_db_maybe_run_maintenance (CamelDB *cdb, GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/src/camel/camel-debug.c b/src/camel/camel-debug.c
new file mode 100644
index 000000000..a0677ab65
--- /dev/null
+++ b/src/camel/camel-debug.c
@@ -0,0 +1,1181 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_BACKTRACE_SYMBOLS
+#include <execinfo.h>
+#ifdef HAVE_ELFUTILS_LIBDWFL
+#include <elfutils/libdwfl.h>
+#include <errno.h>
+#include <unistd.h>
+#endif
+#endif
+
+#include <glib-object.h>
+
+#include "camel-debug.h"
+
+gint camel_verbose_debug;
+
+static GHashTable *debug_table = NULL;
+
+/**
+ * camel_debug_init:
+ *
+ * Init camel debug.
+ *
+ * CAMEL_DEBUG is set to a comma separated list of modules to debug.
+ * The modules can contain module-specific specifiers after a ':', or
+ * just act as a wildcard for the module or even specifier. e.g. 'imap'
+ * for imap debug, or 'imap:folder' for imap folder debug. Additionaly,
+ * ':folder' can be used for a wildcard for any folder operations.
+ **/
+void
+camel_debug_init (void)
+{
+ gchar *d;
+
+ d = g_strdup (getenv ("CAMEL_DEBUG"));
+ if (d) {
+ gchar *p;
+
+ debug_table = g_hash_table_new (g_str_hash, g_str_equal);
+ p = d;
+ while (*p) {
+ while (*p && *p != ',')
+ p++;
+ if (*p)
+ *p++ = 0;
+ g_hash_table_insert (debug_table, d, d);
+ d = p;
+ }
+
+ if (g_hash_table_lookup (debug_table, "all"))
+ camel_verbose_debug = 1;
+ }
+}
+
+/**
+ * camel_debug:
+ * @mode:
+ *
+ * Check to see if a debug mode is activated. @mode takes one of two forms,
+ * a fully qualified 'module:target', or a wildcard 'module' name. It
+ * returns a boolean to indicate if the module or module and target is
+ * currently activated for debug output.
+ *
+ * Returns:
+ **/
+gboolean camel_debug (const gchar *mode)
+{
+ if (camel_verbose_debug)
+ return TRUE;
+
+ if (debug_table) {
+ gchar *colon;
+ gchar *fallback;
+ gsize fallback_len;
+
+ if (g_hash_table_lookup (debug_table, mode))
+ return TRUE;
+
+ /* Check for fully qualified debug */
+ colon = strchr (mode, ':');
+ if (colon) {
+ fallback_len = strlen (mode) + 1;
+ fallback = g_alloca (fallback_len);
+ g_strlcpy (fallback, mode, fallback_len);
+ colon = (colon - mode) + fallback;
+ /* Now check 'module[:*]' */
+ *colon = 0;
+ if (g_hash_table_lookup (debug_table, fallback))
+ return TRUE;
+ /* Now check ':subsystem' */
+ *colon = ':';
+ if (g_hash_table_lookup (debug_table, colon))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static GMutex debug_lock;
+/**
+ * camel_debug_start:
+ * @mode:
+ *
+ * Start debug output for a given mode, used to make sure debug output
+ * is output atomically and not interspersed with unrelated stuff.
+ *
+ * Returns: Returns true if mode is set, and in which case, you must
+ * call debug_end when finished any screen output.
+ **/
+gboolean
+camel_debug_start (const gchar *mode)
+{
+ if (camel_debug (mode)) {
+ g_mutex_lock (&debug_lock);
+ printf ("Thread %p >\n", g_thread_self ());
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * camel_debug_end:
+ *
+ * Call this when you're done with your debug output. If and only if
+ * you called camel_debug_start, and if it returns TRUE.
+ **/
+void
+camel_debug_end (void)
+{
+ printf ("< %p >\n", g_thread_self ());
+ g_mutex_unlock (&debug_lock);
+}
+
+#if 0
+#include <sys/debugreg.h>
+
+static unsigned
+i386_length_and_rw_bits (gint len,
+ enum target_hw_bp_type type)
+{
+ unsigned rw;
+
+ switch (type)
+ {
+ case hw_execute:
+ rw = DR_RW_EXECUTE;
+ break;
+ case hw_write:
+ rw = DR_RW_WRITE;
+ break;
+ case hw_read: /* x86 doesn't support data-read watchpoints */
+ case hw_access:
+ rw = DR_RW_READ;
+ break;
+#if 0
+ case hw_io_access: /* not yet supported */
+ rw = DR_RW_IORW;
+ break;
+#endif
+ default:
+ internal_error (__FILE__, __LINE__, "Invalid hw breakpoint type %d in i386_length_and_rw_bits.\n", (gint) type);
+ }
+
+ switch (len)
+ {
+ case 1:
+ return (DR_LEN_1 | rw);
+ case 2:
+ return (DR_LEN_2 | rw);
+ case 4:
+ return (DR_LEN_4 | rw);
+ case 8:
+ if (TARGET_HAS_DR_LEN_8)
+ return (DR_LEN_8 | rw);
+ default:
+ internal_error (__FILE__, __LINE__, "Invalid hw breakpoint length %d in i386_length_and_rw_bits.\n", len);
+ }
+}
+
+#define I386_DR_SET_RW_LEN(i,rwlen) \
+ do { \
+ dr_control_mirror &= ~(0x0f << (DR_CONTROL_SHIFT + DR_CONTROL_SIZE * (i))); \
+ dr_control_mirror |= ((rwlen) << (DR_CONTROL_SHIFT + DR_CONTROL_SIZE * (i))); \
+ } while (0)
+
+#define I386_DR_LOCAL_ENABLE(i) \
+ dr_control_mirror |= (1 << (DR_LOCAL_ENABLE_SHIFT + DR_ENABLE_SIZE * (i)))
+
+#define set_dr(regnum, val) \
+ __asm__("movl %0,%%db" #regnum \
+ : /* no output */ \
+ :"r" (val))
+
+#define get_dr(regnum, val) \
+ __asm__("movl %%db" #regnum ", %0" \
+ :"=r" (val))
+
+/* fine idea, but it doesn't work, crashes in get_dr :-/ */
+void
+camel_debug_hwatch (gint wp,
+ gpointer addr)
+{
+ guint32 control, rw;
+
+ g_return_if_fail (wp <= DR_LASTADDR);
+ g_return_if_fail (sizeof (addr) == 4);
+
+ get_dr (7, control);
+ /* set watch mode + size */
+ rw = DR_RW_WRITE | DR_LEN_4;
+ control &= ~(((1 << DR_CONTROL_SIZE) - 1) << (DR_CONTROL_SHIFT + DR_CONTROL_SIZE * wp));
+ control |= rw << (DR_CONTROL_SHIFT + DR_CONTROL_SIZE * wp);
+ /* set watch enable */
+ control |= ( 1<< (DR_LOCAL_ENABLE_SHIFT + DR_ENABLE_SIZE * wp));
+ control |= DR_LOCAL_SLOWDOWN;
+ control &= ~DR_CONTROL_RESERVED;
+
+ switch (wp) {
+ case 0:
+ set_dr (0, addr);
+ break;
+ case 1:
+ set_dr (1, addr);
+ break;
+ case 2:
+ set_dr (2, addr);
+ break;
+ case 3:
+ set_dr (3, addr);
+ break;
+ }
+ set_dr (7, control);
+}
+
+#endif
+
+G_LOCK_DEFINE_STATIC (ptr_tracker);
+static GHashTable *ptr_tracker = NULL;
+
+struct pt_data {
+ gpointer ptr;
+ gchar *info;
+ GString *backtrace;
+};
+
+static void
+free_pt_data (gpointer ptr)
+{
+ struct pt_data *ptd = ptr;
+
+ if (!ptd)
+ return;
+
+ g_free (ptd->info);
+ if (ptd->backtrace)
+ g_string_free (ptd->backtrace, TRUE);
+ g_free (ptd);
+}
+
+static void
+dump_left_ptrs_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ guint *left = user_data;
+ struct pt_data *ptd = value;
+ gboolean have_info = ptd && ptd->info;
+ gboolean have_bt = ptd && ptd->backtrace && ptd->backtrace->str && *ptd->backtrace->str;
+
+ *left = (*left) - 1;
+ g_print (" %p %s%s%s%s%s%s\n", key, have_info ? "(" : "", have_info ? ptd->info : "", have_info ? ")" : "", have_bt ? "\n" : "", have_bt ? ptd->backtrace->str : "", have_bt && *left > 0 ? "\n" : "");
+}
+
+#ifdef HAVE_BACKTRACE_SYMBOLS
+static guint
+by_backtrace_hash (gconstpointer ptr)
+{
+ const struct pt_data *ptd = ptr;
+
+ if (!ptd || !ptd->backtrace)
+ return 0;
+
+ return g_str_hash (ptd->backtrace->str);
+}
+
+static gboolean
+by_backtrace_equal (gconstpointer ptr1,
+ gconstpointer ptr2)
+{
+ const struct pt_data *ptd1 = ptr1, *ptd2 = ptr2;
+
+ if ((!ptd1 || !ptd1->backtrace) && (!ptd2 || !ptd2->backtrace))
+ return TRUE;
+
+ return ptd1 && ptd1->backtrace && ptd2 && ptd2->backtrace && g_str_equal (ptd1->backtrace->str, ptd2->backtrace->str);
+}
+
+static void
+dump_by_backtrace_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ guint *left = user_data;
+ struct pt_data *ptd = key;
+ guint count = GPOINTER_TO_UINT (value);
+
+ if (count == 1) {
+ dump_left_ptrs_cb (ptd->ptr, ptd, left);
+ } else {
+ gboolean have_info = ptd && ptd->info;
+ gboolean have_bt = ptd && ptd->backtrace && ptd->backtrace->str && *ptd->backtrace->str;
+
+ *left = (*left) - 1;
+
+ g_print (" %d x %s%s%s%s%s%s\n", count, have_info ? "(" : "", have_info ? ptd->info : "", have_info ? ")" : "", have_bt ? "\n" : "", have_bt ? ptd->backtrace->str : "", have_bt && *left > 0 ? "\n" : "");
+ }
+}
+
+static void
+dump_by_backtrace (GHashTable *ptrs)
+{
+ GHashTable *by_bt = g_hash_table_new (by_backtrace_hash, by_backtrace_equal);
+ GHashTableIter iter;
+ gpointer key, value;
+ struct ptr_data *ptd;
+ guint count;
+
+ g_hash_table_iter_init (&iter, ptrs);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ guint cnt;
+
+ ptd = value;
+ if (!ptd)
+ continue;
+
+ cnt = GPOINTER_TO_UINT (g_hash_table_lookup (by_bt, ptd));
+ cnt++;
+
+ g_hash_table_insert (by_bt, ptd, GUINT_TO_POINTER (cnt));
+ }
+
+ count = g_hash_table_size (by_bt);
+ g_hash_table_foreach (by_bt, dump_by_backtrace_cb, &count);
+ g_hash_table_destroy (by_bt);
+}
+#endif /* HAVE_BACKTRACE_SYMBOLS */
+
+static void
+dump_tracked_ptrs (gboolean is_at_exit)
+{
+ G_LOCK (ptr_tracker);
+
+ if (ptr_tracker) {
+ g_print ("\n----------------------------------------------------------\n");
+ if (g_hash_table_size (ptr_tracker) == 0) {
+ g_print (" All tracked pointers were properly removed\n");
+ } else {
+ guint count = g_hash_table_size (ptr_tracker);
+ g_print (" Left %d tracked pointers:\n", count);
+ #ifdef HAVE_BACKTRACE_SYMBOLS
+ dump_by_backtrace (ptr_tracker);
+ #else
+ g_hash_table_foreach (ptr_tracker, dump_left_ptrs_cb, &count);
+ #endif
+ }
+ g_print ("----------------------------------------------------------\n");
+ } else if (!is_at_exit) {
+ g_print ("\n----------------------------------------------------------\n");
+ g_print (" Did not track any pointers yet\n");
+ g_print ("----------------------------------------------------------\n");
+ }
+
+ G_UNLOCK (ptr_tracker);
+}
+
+#ifdef HAVE_BACKTRACE_SYMBOLS
+
+#ifdef HAVE_ELFUTILS_LIBDWFL
+static Dwfl *
+dwfl_get (gboolean reload)
+{
+ static gchar *debuginfo_path = NULL;
+ static Dwfl *dwfl = NULL;
+ static gboolean checked_for_dwfl = FALSE;
+ static GMutex dwfl_mutex;
+ static const Dwfl_Callbacks proc_callbacks = {
+ .find_debuginfo = dwfl_standard_find_debuginfo,
+ .debuginfo_path = &debuginfo_path,
+ .find_elf = dwfl_linux_proc_find_elf
+ };
+
+ g_mutex_lock (&dwfl_mutex);
+
+ if (checked_for_dwfl) {
+ if (!reload) {
+ g_mutex_unlock (&dwfl_mutex);
+ return dwfl;
+ }
+
+ dwfl_end (dwfl);
+ dwfl = NULL;
+ }
+
+ checked_for_dwfl = TRUE;
+
+ dwfl = dwfl_begin (&proc_callbacks);
+ if (!dwfl) {
+ g_mutex_unlock (&dwfl_mutex);
+ return NULL;
+ }
+
+ errno = 0;
+ if (dwfl_linux_proc_report (dwfl, getpid ()) != 0 || dwfl_report_end (dwfl, NULL, NULL) != 0) {
+ dwfl_end (dwfl);
+ dwfl = NULL;
+ }
+
+ g_mutex_unlock (&dwfl_mutex);
+
+ return dwfl;
+}
+
+struct getmodules_callback_arg
+{
+ gpointer addr;
+ const gchar *func_name;
+ const gchar *file_path;
+ gint lineno;
+};
+
+static gint
+getmodules_callback (Dwfl_Module *module,
+ gpointer *module_userdata_pointer,
+ const gchar *module_name,
+ Dwarf_Addr module_low_addr,
+ gpointer arg_voidp)
+{
+ struct getmodules_callback_arg *arg = arg_voidp;
+ Dwfl_Line *line;
+
+ arg->func_name = dwfl_module_addrname (module, (GElf_Addr) arg->addr);
+ line = dwfl_module_getsrc (module, (GElf_Addr) arg->addr);
+ if (line) {
+ arg->file_path = dwfl_lineinfo (line, NULL, &arg->lineno, NULL, NULL, NULL);
+ } else {
+ arg->file_path = NULL;
+ }
+
+ return arg->func_name ? DWARF_CB_ABORT : DWARF_CB_OK;
+}
+#endif /* HAVE_ELFUTILS_LIBDWFL */
+
+static const gchar *
+addr_lookup (gpointer addr,
+ const gchar **file_path,
+ gint *lineno,
+ const gchar *fallback)
+{
+#ifdef HAVE_ELFUTILS_LIBDWFL
+ Dwfl *dwfl = dwfl_get (FALSE);
+ struct getmodules_callback_arg arg;
+ static GMutex mutex;
+
+ if (!dwfl)
+ return NULL;
+
+ arg.addr = addr;
+ arg.func_name = NULL;
+ arg.file_path = NULL;
+ arg.lineno = -1;
+
+ g_mutex_lock (&mutex);
+
+ dwfl_getmodules (dwfl, getmodules_callback, &arg, 0);
+
+ if (!arg.func_name && fallback && strstr (fallback, "/lib") != fallback && strstr (fallback, "/usr/lib") != fallback) {
+ dwfl = dwfl_get (TRUE);
+ if (dwfl)
+ dwfl_getmodules (dwfl, getmodules_callback, &arg, 0);
+ }
+
+ g_mutex_unlock (&mutex);
+
+ *file_path = arg.file_path;
+ *lineno = arg.lineno;
+
+ return arg.func_name;
+#else /* HAVE_ELFUTILS_LIBDWFL */
+ return NULL;
+#endif /* HAVE_ELFUTILS_LIBDWFL */
+}
+
+#endif /* HAVE_BACKTRACE_SYMBOLS */
+
+static GString *
+get_current_backtrace (void)
+{
+#ifdef HAVE_BACKTRACE_SYMBOLS
+ #define MAX_BT_DEPTH 50
+ gint nptrs, ii;
+ gpointer bt[MAX_BT_DEPTH + 1];
+ gchar **bt_syms;
+ GString *bt_str;
+
+ nptrs = backtrace (bt, MAX_BT_DEPTH + 1);
+ if (nptrs <= 2)
+ return NULL;
+
+ bt_syms = backtrace_symbols (bt, nptrs);
+ if (!bt_syms)
+ return NULL;
+
+ bt_str = g_string_new ("");
+ for (ii = 2; ii < nptrs; ii++) {
+ gint lineno = -1;
+ const gchar *file_path = NULL;
+ const gchar *str = addr_lookup (bt[ii], &file_path, &lineno, bt_syms[ii]);
+ if (!str) {
+ str = bt_syms[ii];
+ file_path = NULL;
+ lineno = -1;
+ }
+ if (!str)
+ continue;
+
+ if (bt_str->len)
+ g_string_append (bt_str, "\n\t by ");
+ g_string_append (bt_str, str);
+ if (str != bt_syms[ii])
+ g_string_append (bt_str, "()");
+
+ if (file_path && lineno > 0) {
+ const gchar *lastsep = strrchr (file_path, G_DIR_SEPARATOR);
+ g_string_append_printf (bt_str, " at %s:%d", lastsep ? lastsep + 1 : file_path, lineno);
+ }
+ }
+
+ g_free (bt_syms);
+
+ if (bt_str->len == 0) {
+ g_string_free (bt_str, TRUE);
+ bt_str = NULL;
+ } else {
+ g_string_insert (bt_str, 0, "\t at ");
+ }
+
+ return bt_str;
+
+ #undef MAX_BT_DEPTH
+#else /* HAVE_BACKTRACE_SYMBOLS */
+ return NULL;
+#endif /* HAVE_BACKTRACE_SYMBOLS */
+}
+
+static void
+dump_left_at_exit_cb (void)
+{
+ dump_tracked_ptrs (TRUE);
+
+ G_LOCK (ptr_tracker);
+ if (ptr_tracker) {
+ g_hash_table_destroy (ptr_tracker);
+ ptr_tracker = NULL;
+ }
+ G_UNLOCK (ptr_tracker);
+}
+
+/**
+ * camel_pointer_tracker_track_with_info:
+ * @ptr: pointer to add to the pointer tracker
+ * @info: info to print in tracker summary
+ *
+ * Adds pointer to the pointer tracker, with associated information,
+ * which is printed in summary of pointer tracker printed by
+ * camel_pointer_tracker_dump(). For convenience can be used
+ * camel_pointer_tracker_track(), which adds place of the caller
+ * as @info. Added pointer should be removed with pair function
+ * camel_pointer_tracker_untrack().
+ *
+ * Since: 3.6
+ **/
+void
+camel_pointer_tracker_track_with_info (gpointer ptr,
+ const gchar *info)
+{
+ struct pt_data *ptd;
+
+ g_return_if_fail (ptr != NULL);
+
+ G_LOCK (ptr_tracker);
+ if (!ptr_tracker) {
+ ptr_tracker = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, free_pt_data);
+ atexit (dump_left_at_exit_cb);
+ }
+
+ ptd = g_new0 (struct pt_data, 1);
+ ptd->ptr = ptr;
+ ptd->info = g_strdup (info);
+ ptd->backtrace = get_current_backtrace ();
+
+ g_hash_table_insert (ptr_tracker, ptr, ptd);
+
+ G_UNLOCK (ptr_tracker);
+}
+
+/**
+ * camel_pointer_tracker_untrack:
+ * @ptr: pointer to remove from the tracker
+ *
+ * Removes pointer from the pointer tracker. It's an error to try
+ * to remove pointer which was not added to the tracker by
+ * camel_pointer_tracker_track() or camel_pointer_tracker_track_with_info(),
+ * or a pointer which was already removed.
+ *
+ * Since: 3.6
+ **/
+void
+camel_pointer_tracker_untrack (gpointer ptr)
+{
+ g_return_if_fail (ptr != NULL);
+
+ G_LOCK (ptr_tracker);
+
+ if (!ptr_tracker)
+ g_printerr ("Pointer tracker not initialized, thus cannot remove %p\n", ptr);
+ else if (!g_hash_table_lookup (ptr_tracker, ptr))
+ g_printerr ("Pointer %p is not tracked\n", ptr);
+ else
+ g_hash_table_remove (ptr_tracker, ptr);
+
+ G_UNLOCK (ptr_tracker);
+}
+
+/**
+ * camel_pointer_tracker_dump:
+ *
+ * Prints information about currently stored pointers
+ * in the pointer tracker. This is called automatically
+ * on application exit if camel_pointer_tracker_track() or
+ * camel_pointer_tracker_track_with_info() was called.
+ *
+ * Note: If the library is configured with --enable-backtraces,
+ * then also backtraces where the pointer was added is printed
+ * in the summary.
+ *
+ * Since: 3.6
+ **/
+void
+camel_pointer_tracker_dump (void)
+{
+ dump_tracked_ptrs (FALSE);
+}
+
+/**
+ * camel_debug_get_backtrace:
+ *
+ * Gets current backtrace leading to this function call.
+ *
+ * Returns: Current backtrace, or %NULL, if cannot determine it.
+ *
+ * Note: Getting backtraces only works if the library was
+ * configured with --enable-backtraces.
+ *
+ * Since: 3.12
+ **/
+GString *
+camel_debug_get_backtrace (void)
+{
+ return get_current_backtrace ();
+}
+
+G_LOCK_DEFINE_STATIC (ref_unref_backtraces);
+static GQueue *ref_unref_backtraces = NULL;
+static guint total_ref_unref_backtraces = 0;
+
+typedef struct _BacktraceLine
+{
+ gchar *function;
+ gchar *file;
+ gint lineno;
+} BacktraceLine;
+
+static void
+backtrace_line_free (gpointer ptr)
+{
+ BacktraceLine *btline = ptr;
+
+ if (btline) {
+ g_free (btline->function);
+ g_free (btline->file);
+ g_free (btline);
+ }
+}
+
+typedef enum {
+ BACKTRACE_TYPE_OTHER,
+ BACKTRACE_TYPE_REF,
+ BACKTRACE_TYPE_UNREF
+} BacktraceType;
+
+typedef struct _Backtrace
+{
+ BacktraceType type;
+ guint object_ref_count;
+ GSList *lines; /* BacktraceLine */
+} Backtrace;
+
+static void
+backtrace_free (gpointer ptr)
+{
+ Backtrace *bt = ptr;
+
+ if (bt) {
+ g_slist_free_full (bt->lines, backtrace_line_free);
+ g_free (bt);
+ }
+}
+
+static BacktraceLine *
+parse_backtrace_line (const gchar *line)
+{
+ gchar **parts;
+ gint ii, lineno = 0;
+ gchar *function = NULL, *filename = NULL;
+
+ if (!line)
+ return NULL;
+
+ while (*line == ' ' || *line == '\t')
+ line++;
+
+ parts = g_strsplit (line, " ", -1);
+ if (!parts || !*parts) {
+ g_strfreev (parts);
+ return NULL;
+ }
+
+ for (ii = 0; parts[ii]; ii++) {
+ const gchar *part = parts[ii];
+
+ if (!*part)
+ continue;
+
+ if (ii == 1) {
+ function = g_strdup (part);
+ } else if (ii == 3 && function) {
+ gchar **file;
+
+ file = g_strsplit (part, ":", -1);
+ if (file && file[0] && file[1]) {
+ filename = g_strdup (file[0]);
+ lineno = g_ascii_strtoll (file[1], NULL, 10);
+ } else {
+ filename = g_strdup (part);
+ }
+
+ g_strfreev (file);
+ }
+ }
+
+ g_strfreev (parts);
+
+ if (function) {
+ BacktraceLine *btline;
+
+ btline = g_new0 (BacktraceLine, 1);
+ btline->function = function;
+ btline->file = filename;
+ btline->lineno = lineno;
+
+ return btline;
+ }
+
+ g_free (function);
+ g_free (filename);
+
+ return NULL;
+}
+
+static Backtrace *
+parse_backtrace (const GString *backtrace,
+ guint object_ref_count)
+{
+ Backtrace *bt;
+ gchar **btlines;
+ gint ii;
+
+ if (!backtrace)
+ return NULL;
+
+ btlines = g_strsplit (backtrace->str, "\n", -1);
+ if (!btlines || !*btlines) {
+ g_strfreev (btlines);
+ return NULL;
+ }
+
+ bt = g_new0 (Backtrace, 1);
+ bt->object_ref_count = object_ref_count;
+
+ for (ii = 0; btlines[ii]; ii++) {
+ if (ii >= 1) {
+ BacktraceLine *btline = parse_backtrace_line (btlines[ii]);
+
+ if (!btline)
+ continue;
+
+ bt->lines = g_slist_prepend (bt->lines, btline);
+
+ if (ii == 1) {
+ if (g_strcmp0 (btline->function, "g_object_ref()") == 0) {
+ bt->type = BACKTRACE_TYPE_REF;
+ } else if (g_strcmp0 (btline->function, "g_object_unref()") == 0) {
+ bt->type = BACKTRACE_TYPE_UNREF;
+ } else {
+ bt->type = BACKTRACE_TYPE_OTHER;
+ }
+ }
+ }
+ }
+
+ g_strfreev (btlines);
+
+ bt->lines = g_slist_reverse (bt->lines);
+
+ return bt;
+}
+
+static void
+print_backtrace (Backtrace *bt,
+ gint index)
+{
+ GSList *link;
+
+ if (!bt)
+ return;
+
+ g_print (" Backtrace[%d] %s %d~>%d:\n", index,
+ bt->type == BACKTRACE_TYPE_REF ? "ref" :
+ bt->type == BACKTRACE_TYPE_UNREF ? "unref" : "other",
+ bt->object_ref_count, bt->object_ref_count + (bt->type == BACKTRACE_TYPE_REF ? 1 : bt->type == BACKTRACE_TYPE_UNREF ? -1 : 0));
+
+ for (link = bt->lines; link; link = g_slist_next (link)) {
+ BacktraceLine *btline = link->data;
+
+ g_print (" %s %s", link == bt->lines ? "at" : "by", btline->function);
+
+ if (btline->file) {
+ if (btline->lineno > 0) {
+ g_print (" at %s:%d\n", btline->file, btline->lineno);
+ } else {
+ g_print (" at %s\n", btline->file);
+ }
+ } else {
+ g_print ("\n");
+ }
+ }
+
+ g_print ("\n");
+}
+
+static gboolean
+backtrace_matches (const Backtrace *match_bt,
+ const Backtrace *find_bt,
+ gint lines_tolerance)
+{
+ GSList *mlink, *flink;
+ gint lines_matched = 0;
+ gint lt, ii;
+
+ if (!match_bt || !find_bt || !find_bt->lines || !match_bt->lines)
+ return FALSE;
+
+ flink = find_bt->lines;
+ mlink = NULL;
+ lt = lines_tolerance;
+ do {
+ gboolean found = FALSE;
+ BacktraceLine *fline = flink->data;
+
+ if (!fline)
+ return FALSE;
+
+ for (mlink = match_bt->lines, ii = 0; mlink && ii <= lines_tolerance; mlink = g_slist_next (mlink), ii++) {
+ BacktraceLine *mline = mlink->data;
+
+ if (!mline)
+ return FALSE;
+
+ found = g_strcmp0 (fline->function, mline->function) == 0;
+ if (found)
+ break;
+ }
+
+ if (found)
+ break;
+
+ lt--;
+ if (lt >= 0)
+ flink = g_slist_next (flink);
+ else
+ flink = NULL;
+ } while (flink);
+
+ if (!flink)
+ return FALSE;
+
+ for (mlink = g_slist_next (mlink), flink = g_slist_next (flink);
+ mlink && flink;
+ mlink = g_slist_next (mlink), flink = g_slist_next (flink)) {
+ BacktraceLine *mline, *fline;
+
+ mline = mlink->data;
+ fline = flink->data;
+
+ if (!mline || !fline)
+ break;
+
+ if (g_strcmp0 (mline->function, fline->function) != 0 ||
+ g_strcmp0 (mline->file, fline->file) != 0 ||
+ mline->lineno != fline->lineno) {
+ break;
+ }
+
+ lines_matched++;
+ }
+
+ return (!mlink && !flink) || (lines_matched > 40 && (!mlink || !flink));
+}
+
+static gboolean
+backtrace_matches_ref (const Backtrace *match_bt,
+ const Backtrace *find_bt,
+ gint lines_tolerance)
+{
+ if (!match_bt || match_bt->type != BACKTRACE_TYPE_REF)
+ return FALSE;
+
+ return backtrace_matches (match_bt, find_bt, lines_tolerance);
+}
+
+static gboolean
+remove_matching_ref_backtrace (GQueue *backtraces,
+ const Backtrace *unref_backtrace)
+{
+ GList *link;
+ gint up_bts = 2, up_lines = 1;
+
+ g_return_val_if_fail (backtraces != NULL, FALSE);
+ g_return_val_if_fail (unref_backtrace != NULL, FALSE);
+ g_return_val_if_fail (unref_backtrace->type != BACKTRACE_TYPE_REF, FALSE);
+
+ if (unref_backtrace->lines && unref_backtrace->lines->next && unref_backtrace->lines->next->data) {
+ BacktraceLine *btline = unref_backtrace->lines->next->data;
+
+ if (g_strcmp0 ("g_value_object_free_value()", btline->function) == 0)
+ up_lines = 5;
+ else if (g_strcmp0 ("g_object_notify()", btline->function) == 0)
+ up_bts = 5;
+ }
+
+ for (link = g_queue_peek_tail_link (backtraces); link && up_bts > 0; link = g_list_previous (link), up_bts--) {
+ Backtrace *bt = link->data;
+ gint inc_up_lines = 0;
+
+ if (!bt || bt->type != BACKTRACE_TYPE_REF)
+ continue;
+
+ if (bt->lines && bt->lines->next && bt->lines->next->data) {
+ BacktraceLine *btline = bt->lines->next->data;
+
+ if (g_strcmp0 ("g_weak_ref_get()", btline->function) == 0)
+ inc_up_lines = 1;
+ }
+
+ if (backtrace_matches_ref (bt, unref_backtrace, up_lines + inc_up_lines)) {
+ g_queue_delete_link (backtraces, link);
+ backtrace_free (bt);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+dump_ref_unref_backtraces (gboolean is_at_exit)
+{
+ G_LOCK (ref_unref_backtraces);
+
+ if (ref_unref_backtraces) {
+ g_print ("\n----------------------------------------------------------\n");
+ if (g_queue_get_length (ref_unref_backtraces) == 0) {
+ g_print (" All ref/unref backtraces were properly matched\n");
+ } else {
+ guint count = g_queue_get_length (ref_unref_backtraces), refs = 0, unrefs = 0, others = 0;
+ GList *link;
+
+ for (link = g_queue_peek_head_link (ref_unref_backtraces); link; link = g_list_next (link)) {
+ Backtrace *bt = link->data;
+
+ if (!bt)
+ continue;
+
+ if (bt->type == BACKTRACE_TYPE_REF)
+ refs++;
+ else if (bt->type == BACKTRACE_TYPE_UNREF)
+ unrefs++;
+ else
+ others++;
+ }
+
+ g_print (" Left %u (ref(%u)/unref(%u)/other(%u)) backtraces of %u pushed total:\n", count, refs, unrefs, others, total_ref_unref_backtraces);
+
+ for (count = 0, link = g_queue_peek_head_link (ref_unref_backtraces); link; link = g_list_next (link), count++) {
+ Backtrace *bt = link->data;
+
+ if (!bt)
+ continue;
+
+ print_backtrace (bt, count);
+ }
+ }
+ g_print ("----------------------------------------------------------\n");
+ } else if (!is_at_exit) {
+ g_print ("\n----------------------------------------------------------\n");
+ g_print (" Did not receive any ref/unref backtraces yet\n");
+ g_print ("----------------------------------------------------------\n");
+ }
+
+ G_UNLOCK (ref_unref_backtraces);
+}
+
+static void
+dump_left_ref_unref_backtraces_at_exit_cb (void)
+{
+ dump_ref_unref_backtraces (TRUE);
+
+ G_LOCK (ref_unref_backtraces);
+
+ if (ref_unref_backtraces) {
+ g_queue_free_full (ref_unref_backtraces, backtrace_free);
+ ref_unref_backtraces = NULL;
+ }
+
+ G_UNLOCK (ref_unref_backtraces);
+}
+
+/**
+ * camel_debug_ref_unref_push_backtrace:
+ * @backtrace: a backtrace to push, taken from camel_debug_get_backtrace()
+ * @object_ref_count: the current object reference count when the push is done
+ *
+ * Adds this backtrace into the set of backtraces related to some object
+ * reference counting issues debugging. This is usually called inside g_object_ref()
+ * and g_object_unref(). If the backtrace corresponds to a g_object_unref()
+ * call, and a corresponding g_object_ref() backtrace is found in the current list,
+ * then the previous backtrace is removed and this one is skipped.
+ *
+ * Any left backtraces in the list are printed at the application end.
+ *
+ * A convenient function camel_debug_ref_unref_push_backtrace_for_object()
+ * is provided too.
+ *
+ * Since: 3.20
+ **/
+void
+camel_debug_ref_unref_push_backtrace (const GString *backtrace,
+ guint object_ref_count)
+{
+ Backtrace *bt;
+
+ g_return_if_fail (backtrace != NULL);
+
+ G_LOCK (ref_unref_backtraces);
+
+ total_ref_unref_backtraces++;
+
+ bt = parse_backtrace (backtrace, object_ref_count);
+ if (!bt) {
+ G_UNLOCK (ref_unref_backtraces);
+ g_warn_if_fail (bt != NULL);
+ return;
+ }
+
+ if (!ref_unref_backtraces) {
+ ref_unref_backtraces = g_queue_new ();
+ atexit (dump_left_ref_unref_backtraces_at_exit_cb);
+ }
+
+ if (bt->type != BACKTRACE_TYPE_UNREF || !remove_matching_ref_backtrace (ref_unref_backtraces, bt)) {
+ g_queue_push_tail (ref_unref_backtraces, bt);
+ } else {
+ backtrace_free (bt);
+ }
+
+ G_UNLOCK (ref_unref_backtraces);
+}
+
+/**
+ * camel_debug_ref_unref_push_backtrace_for_object:
+ * @_object: a #GObject, for which add the backtrace
+ *
+ * Gets current backtrace of this call and adds it to the list
+ * of backtraces with camel_debug_ref_unref_push_backtrace().
+ *
+ * Usual usage would be, once GNOME bug 758358 is applied to the GLib sources,
+ * or a patched GLib is used, to call this function in an object init() function,
+ * like this:
+ *
+ * static void
+ * my_object_init (MyObject *obj)
+ * {
+ * camel_debug_ref_unref_push_backtrace_for_object (obj);
+ * g_track_object_ref_unref (obj, (GFunc) camel_debug_ref_unref_push_backtrace_for_object, NULL);
+ * }
+ *
+ * Note that the g_track_object_ref_unref() can track only one pointer, thus make
+ * sure you track the right one (add some logic if multiple objects are created at once).
+ *
+ * Since: 3.20
+ **/
+void
+camel_debug_ref_unref_push_backtrace_for_object (gpointer _object)
+{
+ GString *backtrace;
+ GObject *object;
+
+ g_return_if_fail (G_IS_OBJECT (_object));
+
+ object = G_OBJECT (_object);
+
+ backtrace = camel_debug_get_backtrace ();
+ if (backtrace) {
+ camel_debug_ref_unref_push_backtrace (backtrace, object->ref_count);
+ g_string_free (backtrace, TRUE);
+ }
+}
+
+/**
+ * camel_debug_ref_unref_dump_backtraces:
+ *
+ * Prints current backtraces stored with camel_debug_ref_unref_push_backtrace()
+ * or with camel_debug_ref_unref_push_backtrace_for_object().
+ *
+ * It's usually not needed to use this function, as the left backtraces, if any,
+ * are printed at the end of the application.
+ *
+ * Since: 3.20
+ **/
+void
+camel_debug_ref_unref_dump_backtraces (void)
+{
+ dump_ref_unref_backtraces (FALSE);
+}
diff --git a/src/camel/camel-debug.h b/src/camel/camel-debug.h
new file mode 100644
index 000000000..d39725f25
--- /dev/null
+++ b/src/camel/camel-debug.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_DEBUG_H
+#define CAMEL_DEBUG_H
+
+#include <glib.h>
+
+/* This is how the basic debug checking strings should be done */
+#define CAMEL_DEBUG_IMAP "imap"
+#define CAMEL_DEBUG_IMAP_FOLDER "imap:folder"
+
+G_BEGIN_DECLS
+
+void camel_debug_init (void);
+gboolean camel_debug (const gchar *mode);
+
+gboolean camel_debug_start (const gchar *mode);
+void camel_debug_end (void);
+
+/**
+ * CAMEL_CHECK_GERROR:
+ *
+ * This sanity checks return values and #GErrors. If returning
+ * failure, make sure the #GError is set. If returning success,
+ * make sure the #GError is NOT set.
+ *
+ * Example:
+ *
+ * success = class->foo (object, some_data, error);
+ * CAMEL_CHECK_GERROR (object, foo, success, error);
+ * return success;
+ *
+ * Since: 2.32
+ */
+#define CAMEL_CHECK_GERROR(object, method, expr, error) \
+ G_STMT_START { \
+ if (expr) { \
+ if ((error) != NULL && *(error) != NULL) { \
+ g_warning ( \
+ "%s::%s() set its GError " \
+ "but then reported success", \
+ G_OBJECT_TYPE_NAME (object), \
+ G_STRINGIFY (method)); \
+ g_warning ( \
+ "Error message was: %s", \
+ (*(error))->message); \
+ } \
+ } else { \
+ if ((error) != NULL && *(error) == NULL) { \
+ g_warning ( \
+ "%s::%s() reported failure " \
+ "without setting its GError", \
+ G_OBJECT_TYPE_NAME (object), \
+ G_STRINGIFY (method)); \
+ } \
+ } \
+ } G_STMT_END
+
+/**
+ * CAMEL_CHECK_LOCAL_GERROR:
+ *
+ * Same as CAMEL_CHECK_GERROR, but for direct #GError pointers.
+ *
+ * Example:
+ *
+ * success = class->foo (object, some_data, &local_error);
+ * CAMEL_CHECK_LOCAL_GERROR (object, foo, success, local_error);
+ * return success;
+ *
+ * Since: 3.12
+ */
+#define CAMEL_CHECK_LOCAL_GERROR(object, method, expr, error) \
+ G_STMT_START { \
+ if (expr) { \
+ if ((error) != NULL) { \
+ g_warning ( \
+ "%s::%s() set its GError " \
+ "but then reported success", \
+ G_OBJECT_TYPE_NAME (object), \
+ G_STRINGIFY (method)); \
+ g_warning ( \
+ "Error message was: %s", \
+ ((error))->message); \
+ } \
+ } else { \
+ if ((error) == NULL) { \
+ g_warning ( \
+ "%s::%s() reported failure " \
+ "without setting its GError", \
+ G_OBJECT_TYPE_NAME (object), \
+ G_STRINGIFY (method)); \
+ } \
+ } \
+ } G_STMT_END
+/**
+ * camel_pointer_tracker_track:
+ * @ptr: pointer to add to pointer tracker
+ *
+ * Adds pointer 'ptr' to pointer tracker. Usual use case is to add object
+ * to the tracker in GObject::init and remove it from tracker within
+ * GObject::finalize. Since the tracker's functions are called, the application
+ * prints summary of the pointers on console on exit. If everything gone right
+ * then it prints message about all tracked pointers were removed. Otherwise
+ * it prints summary of left pointers in the tracker. Added pointer should
+ * be removed with pair function camel_pointer_tracker_untrack().
+ *
+ * See camel_pointer_tracker_dump(), camel_pointer_tracker_track_with_info().
+ *
+ * Since: 3.6
+ **/
+#define camel_pointer_tracker_track(ptr) \
+ (camel_pointer_tracker_track_with_info ((ptr), G_STRFUNC))
+
+void camel_pointer_tracker_track_with_info
+ (gpointer ptr,
+ const gchar *info);
+void camel_pointer_tracker_untrack (gpointer ptr);
+void camel_pointer_tracker_dump (void);
+
+GString * camel_debug_get_backtrace (void);
+
+void camel_debug_ref_unref_push_backtrace
+ (const GString *backtrace,
+ guint object_ref_count);
+void camel_debug_ref_unref_push_backtrace_for_object
+ (gpointer _object);
+void camel_debug_ref_unref_dump_backtraces
+ (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_DEBUG_H */
diff --git a/src/camel/camel-enums.h b/src/camel/camel-enums.h
new file mode 100644
index 000000000..13577db42
--- /dev/null
+++ b/src/camel/camel-enums.h
@@ -0,0 +1,477 @@
+/*
+ * camel-enums.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_ENUMS_H
+#define CAMEL_ENUMS_H
+
+/**
+ * CamelAuthenticationResult:
+ * @CAMEL_AUTHENTICATION_ERROR:
+ * An error occurred while authenticating.
+ * @CAMEL_AUTHENTICATION_ACCEPTED:
+ * Server accepted our authentication attempt.
+ * @CAMEL_AUTHENTICATION_REJECTED:
+ * Server rejected our authentication attempt.
+ *
+ * Authentication result codes used by #CamelService.
+ *
+ * Since: 3.4
+ **/
+typedef enum {
+ CAMEL_AUTHENTICATION_ERROR,
+ CAMEL_AUTHENTICATION_ACCEPTED,
+ CAMEL_AUTHENTICATION_REJECTED
+} CamelAuthenticationResult;
+
+typedef enum { /*< flags >*/
+ CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY = 1 << 0,
+ CAMEL_FOLDER_FILTER_RECENT = 1 << 2,
+ CAMEL_FOLDER_HAS_BEEN_DELETED = 1 << 3,
+ CAMEL_FOLDER_IS_TRASH = 1 << 4,
+ CAMEL_FOLDER_IS_JUNK = 1 << 5,
+ CAMEL_FOLDER_FILTER_JUNK = 1 << 6
+} CamelFolderFlags;
+
+#define CAMEL_FOLDER_TYPE_BIT (10)
+
+/**
+ * CamelFolderInfoFlags:
+ * @CAMEL_FOLDER_NOSELECT:
+ * The folder cannot contain messages.
+ * @CAMEL_FOLDER_NOINFERIORS:
+ * The folder cannot have child folders.
+ * @CAMEL_FOLDER_CHILDREN:
+ * The folder has children (not yet fully implemented).
+ * @CAMEL_FOLDER_NOCHILDREN:
+ * The folder does not have children (not yet fully implemented).
+ * @CAMEL_FOLDER_SUBSCRIBED:
+ * The folder is subscribed.
+ * @CAMEL_FOLDER_VIRTUAL:
+ * The folder is virtual. Messages cannot be copied or moved to
+ * virtual folders since they are only queries of other folders.
+ * @CAMEL_FOLDER_SYSTEM:
+ * The folder is a built-in "system" folder. System folders
+ * cannot be renamed or deleted.
+ * @CAMEL_FOLDER_VTRASH:
+ * The folder is a virtual trash folder. It cannot be copied to,
+ * and can only be moved to if in an existing folder.
+ * @CAMEL_FOLDER_SHARED_TO_ME:
+ * A folder being shared by someone else.
+ * @CAMEL_FOLDER_SHARED_BY_ME:
+ * A folder being shared by the user.
+ * @CAMEL_FOLDER_TYPE_NORMAL:
+ * The folder is a normal folder.
+ * @CAMEL_FOLDER_TYPE_INBOX:
+ * The folder is an inbox folder.
+ * @CAMEL_FOLDER_TYPE_OUTBOX:
+ * The folder is an outbox folder.
+ * @CAMEL_FOLDER_TYPE_TRASH:
+ * The folder shows deleted messages.
+ * @CAMEL_FOLDER_TYPE_JUNK:
+ * The folder shows junk messages.
+ * @CAMEL_FOLDER_TYPE_SENT:
+ * The folder shows sent messages.
+ * @CAMEL_FOLDER_TYPE_CONTACTS:
+ * The folder contains contacts, instead of mail messages.
+ * @CAMEL_FOLDER_TYPE_EVENTS:
+ * The folder contains calendar events, instead of mail messages.
+ * @CAMEL_FOLDER_TYPE_MEMOS:
+ * The folder contains memos, instead of mail messages.
+ * @CAMEL_FOLDER_TYPE_TASKS:
+ * The folder contains tasks, instead of mail messages.
+ *
+ * These flags are abstractions. It's up to the CamelProvider to give
+ * them suitable interpretations. Use #CAMEL_FOLDER_TYPE_MASK to isolate
+ * the folder's type.
+ **/
+/* WARNING: This enum and CamelStoreInfoFlags must stay in sync.
+ * FIXME: Eliminate the need for two separate types. */
+typedef enum { /*< flags >*/
+ CAMEL_FOLDER_NOSELECT = 1 << 0,
+ CAMEL_FOLDER_NOINFERIORS = 1 << 1,
+ CAMEL_FOLDER_CHILDREN = 1 << 2,
+ CAMEL_FOLDER_NOCHILDREN = 1 << 3,
+ CAMEL_FOLDER_SUBSCRIBED = 1 << 4,
+ CAMEL_FOLDER_VIRTUAL = 1 << 5,
+ CAMEL_FOLDER_SYSTEM = 1 << 6,
+ CAMEL_FOLDER_VTRASH = 1 << 7,
+ CAMEL_FOLDER_SHARED_TO_ME = 1 << 8,
+ CAMEL_FOLDER_SHARED_BY_ME = 1 << 9,
+ CAMEL_FOLDER_TYPE_NORMAL = 0 << CAMEL_FOLDER_TYPE_BIT,
+ CAMEL_FOLDER_TYPE_INBOX = 1 << CAMEL_FOLDER_TYPE_BIT,
+ CAMEL_FOLDER_TYPE_OUTBOX = 2 << CAMEL_FOLDER_TYPE_BIT,
+ CAMEL_FOLDER_TYPE_TRASH = 3 << CAMEL_FOLDER_TYPE_BIT,
+ CAMEL_FOLDER_TYPE_JUNK = 4 << CAMEL_FOLDER_TYPE_BIT,
+ CAMEL_FOLDER_TYPE_SENT = 5 << CAMEL_FOLDER_TYPE_BIT,
+ CAMEL_FOLDER_TYPE_CONTACTS = 6 << CAMEL_FOLDER_TYPE_BIT,
+ CAMEL_FOLDER_TYPE_EVENTS = 7 << CAMEL_FOLDER_TYPE_BIT,
+ CAMEL_FOLDER_TYPE_MEMOS = 8 << CAMEL_FOLDER_TYPE_BIT,
+ CAMEL_FOLDER_TYPE_TASKS = 9 << CAMEL_FOLDER_TYPE_BIT,
+ CAMEL_FOLDER_READONLY = 1 << 16,
+ /* empty gap from unused flag removal */
+ CAMEL_FOLDER_FLAGGED = 1 << 18,
+
+ CAMEL_FOLDER_FLAGS_LAST = 1 << 24 /*< skip >*/
+} CamelFolderInfoFlags;
+
+#define CAMEL_FOLDER_TYPE_MASK (63 << CAMEL_FOLDER_TYPE_BIT)
+
+/* Note: The HTML elements are escaped in the doc comment intentionally,
+ * to have them shown as expected in generated documentation. */
+/**
+ * CamelMimeFilterToHTMLFlags:
+ * @CAMEL_MIME_FILTER_TOHTML_PRE:
+ * Enclose the content in &lt;pre&gt; ... &lt;/pre&gt; tags.
+ * @CAMEL_MIME_FILTER_TOHTML_CONVERT_NL:
+ * Convert newline characters to &lt;br&gt; tags.
+ * @CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES:
+ * Convert space and tab characters to a non-breaking space (&amp;nbsp;).
+ * @CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS:
+ * Convert recognized URLs to &lt;a href="foo"&gt;foo&lt;/a&gt;.
+ * @CAMEL_MIME_FILTER_TOHTML_MARK_CITATION:
+ * Color quoted lines (lines beginning with '&gt;').
+ * @CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES:
+ * Convert mailto: URLs to &lt;a href="mailto:foo"&gt;mailto:foo&lt;/a&gt;.
+ * @CAMEL_MIME_FILTER_TOHTML_ESCAPE_8BIT:
+ * Convert 8-bit characters to escaped hexdecimal (&amp;#nnn;).
+ * @CAMEL_MIME_FILTER_TOHTML_CITE:
+ * Prefix each line with "&gt; ".
+ * @CAMEL_MIME_FILTER_TOHTML_PRESERVE_8BIT:
+ * This flag is not used by #CamelMimeFilterToHTML.
+ * @CAMEL_MIME_FILTER_TOHTML_FORMAT_FLOWED:
+ * This flag is not used by #CamelMimeFilterToHTML.
+ * @CAMEL_MIME_FILTER_TOHTML_QUOTE_CITATION:
+ * Group lines beginning with one or more '&gt;' characters in
+ * &lt;blockquote type="cite"&gt; ... &lt;/blockquote&gt; tags. The tags
+ * are nested according to the number of '&gt;' characters.
+ *
+ * Flags for converting text/plain content into text/html.
+ **/
+typedef enum { /*< flags >*/
+ CAMEL_MIME_FILTER_TOHTML_PRE = 1 << 0,
+ CAMEL_MIME_FILTER_TOHTML_CONVERT_NL = 1 << 1,
+ CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES = 1 << 2,
+ CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS = 1 << 3,
+ CAMEL_MIME_FILTER_TOHTML_MARK_CITATION = 1 << 4,
+ CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES = 1 << 5,
+ CAMEL_MIME_FILTER_TOHTML_ESCAPE_8BIT = 1 << 6,
+ CAMEL_MIME_FILTER_TOHTML_CITE = 1 << 7,
+ CAMEL_MIME_FILTER_TOHTML_PRESERVE_8BIT = 1 << 8,
+ CAMEL_MIME_FILTER_TOHTML_FORMAT_FLOWED = 1 << 9,
+ CAMEL_MIME_FILTER_TOHTML_QUOTE_CITATION = 1 << 10
+} CamelMimeFilterToHTMLFlags;
+
+/* WARNING: This enum and CamelFolderInfoFlags must stay in sync.
+ * FIXME: Eliminate the need for two separate types. */
+typedef enum { /*< flags >*/
+ CAMEL_STORE_INFO_FOLDER_NOSELECT = 1 << 0,
+ CAMEL_STORE_INFO_FOLDER_NOINFERIORS = 1 << 1,
+ CAMEL_STORE_INFO_FOLDER_CHILDREN = 1 << 2,
+ CAMEL_STORE_INFO_FOLDER_NOCHILDREN = 1 << 3,
+ CAMEL_STORE_INFO_FOLDER_SUBSCRIBED = 1 << 4,
+ CAMEL_STORE_INFO_FOLDER_VIRTUAL = 1 << 5,
+ CAMEL_STORE_INFO_FOLDER_SYSTEM = 1 << 6,
+ CAMEL_STORE_INFO_FOLDER_VTRASH = 1 << 7,
+ CAMEL_STORE_INFO_FOLDER_SHARED_TO_ME = 1 << 8,
+ CAMEL_STORE_INFO_FOLDER_SHARED_BY_ME = 1 << 9,
+ CAMEL_STORE_INFO_FOLDER_READONLY = 1 << 16,
+ /* empty gap from unused flag removal */
+ CAMEL_STORE_INFO_FOLDER_FLAGGED = 1 << 18,
+
+ CAMEL_STORE_INFO_FOLDER_LAST = 1 << 24 /*< skip >*/
+} CamelStoreInfoFlags;
+
+/**
+ * CamelFetchHeadersType:
+ * @CAMEL_FETCH_HEADERS_BASIC:
+ * Fetch only basic headers (Date, From, To, Subject, etc.).
+ * @CAMEL_FETCH_HEADERS_BASIC_AND_MAILING_LIST:
+ * Fetch all basic headers and mailing list headers.
+ * @CAMEL_FETCH_HEADERS_ALL:
+ * Fetch all available message headers.
+ *
+ * Describes what headers to fetch when downloading message summaries.
+ *
+ * Since: 3.2
+ **/
+typedef enum {
+ CAMEL_FETCH_HEADERS_BASIC,
+ CAMEL_FETCH_HEADERS_BASIC_AND_MAILING_LIST,
+ CAMEL_FETCH_HEADERS_ALL
+} CamelFetchHeadersType;
+
+/**
+ * CamelJunkStatus:
+ * @CAMEL_JUNK_STATUS_ERROR:
+ * An error occurred while invoking the junk filter.
+ * @CAMEL_JUNK_STATUS_INCONCLUSIVE:
+ * The junk filter could not determine whether the message is junk.
+ * @CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK:
+ * The junk filter believes the message is junk.
+ * @CAMEL_JUNK_STATUS_MESSAGE_IS_NOT_JUNK:
+ * The junk filter believes the message is not junk.
+ *
+ * These are result codes used when passing messages through a junk filter.
+ **/
+typedef enum {
+ CAMEL_JUNK_STATUS_ERROR,
+ CAMEL_JUNK_STATUS_INCONCLUSIVE,
+ CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK,
+ CAMEL_JUNK_STATUS_MESSAGE_IS_NOT_JUNK
+} CamelJunkStatus;
+
+typedef enum {
+ CAMEL_MIME_FILTER_BASIC_INVALID,
+ CAMEL_MIME_FILTER_BASIC_BASE64_ENC,
+ CAMEL_MIME_FILTER_BASIC_BASE64_DEC,
+ CAMEL_MIME_FILTER_BASIC_QP_ENC,
+ CAMEL_MIME_FILTER_BASIC_QP_DEC,
+ CAMEL_MIME_FILTER_BASIC_UU_ENC,
+ CAMEL_MIME_FILTER_BASIC_UU_DEC
+} CamelMimeFilterBasicType;
+
+typedef enum {
+ CAMEL_MIME_FILTER_CRLF_ENCODE,
+ CAMEL_MIME_FILTER_CRLF_DECODE
+} CamelMimeFilterCRLFDirection;
+
+typedef enum {
+ CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS,
+ CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY
+} CamelMimeFilterCRLFMode;
+
+typedef enum {
+ CAMEL_MIME_FILTER_GZIP_MODE_ZIP,
+ CAMEL_MIME_FILTER_GZIP_MODE_UNZIP
+} CamelMimeFilterGZipMode;
+
+typedef enum {
+ CAMEL_MIME_FILTER_YENC_DIRECTION_ENCODE,
+ CAMEL_MIME_FILTER_YENC_DIRECTION_DECODE
+} CamelMimeFilterYencDirection;
+
+/**
+ * CamelNetworkSecurityMethod:
+ * @CAMEL_NETWORK_SECURITY_METHOD_NONE:
+ * Use an unencrypted network connection.
+ * @CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
+ * Use SSL by connecting to an alternate port number.
+ * @CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT:
+ * Use SSL or TLS by connecting to the standard port and invoking
+ * STARTTLS before authenticating. This is the recommended method.
+ *
+ * Methods for establishing an encrypted (or unencrypted) network connection.
+ *
+ * Since: 3.2
+ **/
+typedef enum {
+ CAMEL_NETWORK_SECURITY_METHOD_NONE,
+ CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT,
+ CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT
+} CamelNetworkSecurityMethod;
+
+typedef enum {
+ CAMEL_PROVIDER_CONF_END,
+ CAMEL_PROVIDER_CONF_SECTION_START,
+ CAMEL_PROVIDER_CONF_SECTION_END,
+ CAMEL_PROVIDER_CONF_CHECKBOX,
+ CAMEL_PROVIDER_CONF_CHECKSPIN,
+ CAMEL_PROVIDER_CONF_ENTRY,
+ CAMEL_PROVIDER_CONF_LABEL,
+ CAMEL_PROVIDER_CONF_HIDDEN,
+ CAMEL_PROVIDER_CONF_OPTIONS
+} CamelProviderConfType;
+
+/**
+ * CamelProviderFlags:
+ * @CAMEL_PROVIDER_IS_REMOTE:
+ * Provider works with remote data.
+ * @CAMEL_PROVIDER_IS_LOCAL:
+ * Provider can be used as a backend for local folder tree folders.
+ * (Not just the opposite of #CAMEL_PROVIDER_IS_REMOTE.)
+ * @CAMEL_PROVIDER_IS_SOURCE:
+ * Mail arrives there, so it should be offered as an option in the
+ * mail config dialog.
+ * @CAMEL_PROVIDER_IS_STORAGE:
+ * Mail is stored there. It will appear in the folder tree.
+ * @CAMEL_PROVIDER_IS_EXTERNAL:
+ * Provider appears in the folder tree but is not created by the
+ * mail component.
+ * @CAMEL_PROVIDER_HAS_LICENSE:
+ * Provider configuration first needs the license to be accepted.
+ * (No longer used.)
+ * @CAMEL_PROVIDER_ALLOW_REAL_TRASH_FOLDER:
+ * Provider may use a real trash folder instead of a virtual folder.
+ * @CAMEL_PROVIDER_ALLOW_REAL_JUNK_FOLDER:
+ * Provider may use a real junk folder instead of a virtual folder.
+ * @CAMEL_PROVIDER_SUPPORTS_MOBILE_DEVICES:
+ * Download limited set of emails instead of operating on full cache.
+ * @CAMEL_PROVIDER_SUPPORTS_BATCH_FETCH:
+ * Support to fetch messages in batch.
+ * @CAMEL_PROVIDER_SUPPORTS_PURGE_MESSAGE_CACHE:
+ * Support to remove oldest downloaded messages to conserve space.
+ *
+ **/
+typedef enum { /*< flags >*/
+ CAMEL_PROVIDER_IS_REMOTE = 1 << 0,
+ CAMEL_PROVIDER_IS_LOCAL = 1 << 1,
+ CAMEL_PROVIDER_IS_EXTERNAL = 1 << 2,
+ CAMEL_PROVIDER_IS_SOURCE = 1 << 3,
+ CAMEL_PROVIDER_IS_STORAGE = 1 << 4,
+ CAMEL_PROVIDER_SUPPORTS_SSL = 1 << 5,
+ CAMEL_PROVIDER_HAS_LICENSE = 1 << 6,
+ CAMEL_PROVIDER_DISABLE_SENT_FOLDER = 1 << 7,
+ CAMEL_PROVIDER_ALLOW_REAL_TRASH_FOLDER = 1 << 8,
+ CAMEL_PROVIDER_ALLOW_REAL_JUNK_FOLDER = 1 << 9,
+ CAMEL_PROVIDER_SUPPORTS_MOBILE_DEVICES = 1 << 10,
+ CAMEL_PROVIDER_SUPPORTS_BATCH_FETCH = 1 << 11,
+ CAMEL_PROVIDER_SUPPORTS_PURGE_MESSAGE_CACHE = 1 << 12
+} CamelProviderFlags;
+
+typedef enum {
+ CAMEL_PROVIDER_STORE,
+ CAMEL_PROVIDER_TRANSPORT,
+ CAMEL_NUM_PROVIDER_TYPES /*< skip >*/
+} CamelProviderType;
+
+typedef enum {
+ CAMEL_SASL_ANON_TRACE_EMAIL,
+ CAMEL_SASL_ANON_TRACE_OPAQUE,
+ CAMEL_SASL_ANON_TRACE_EMPTY
+} CamelSaslAnonTraceType;
+
+/**
+ * CamelServiceConnectionStatus:
+ * @CAMEL_SERVICE_DISCONNECTED:
+ * #CamelService is disconnected from a remote server.
+ * @CAMEL_SERVICE_CONNECTING:
+ * #CamelService is connecting to a remote server.
+ * @CAMEL_SERVICE_CONNECTED:
+ * #CamelService is connected to a remote server.
+ * @CAMEL_SERVICE_DISCONNECTING:
+ * #CamelService is disconnecting from a remote server.
+ *
+ * Connection status returned by camel_service_get_connection_status().
+ *
+ * Since: 3.6
+ **/
+typedef enum {
+ CAMEL_SERVICE_DISCONNECTED,
+ CAMEL_SERVICE_CONNECTING,
+ CAMEL_SERVICE_CONNECTED,
+ CAMEL_SERVICE_DISCONNECTING
+} CamelServiceConnectionStatus;
+
+typedef enum {
+ CAMEL_SESSION_ALERT_INFO,
+ CAMEL_SESSION_ALERT_WARNING,
+ CAMEL_SESSION_ALERT_ERROR
+} CamelSessionAlertType;
+
+/**
+ * CamelSortType:
+ * @CAMEL_SORT_ASCENDING:
+ * Sorting is in ascending order.
+ * @CAMEL_SORT_DESCENDING:
+ * Sorting is in descending order.
+ *
+ * Determines the direction of a sort.
+ *
+ * Since: 3.2
+ **/
+typedef enum {
+ CAMEL_SORT_ASCENDING,
+ CAMEL_SORT_DESCENDING
+} CamelSortType;
+
+typedef enum { /*< flags >*/
+ CAMEL_STORE_VTRASH = 1 << 0,
+ CAMEL_STORE_VJUNK = 1 << 1,
+ CAMEL_STORE_PROXY = 1 << 2,
+ CAMEL_STORE_IS_MIGRATING = 1 << 3,
+ CAMEL_STORE_REAL_JUNK_FOLDER = 1 << 4,
+ CAMEL_STORE_CAN_EDIT_FOLDERS = 1 << 5,
+ CAMEL_STORE_USE_CACHE_DIR = 1 << 6,
+ CAMEL_STORE_CAN_DELETE_FOLDERS_AT_ONCE = 1 << 7,
+ CAMEL_STORE_SUPPORTS_INITIAL_SETUP = 1 << 8
+} CamelStoreFlags;
+
+/**
+ * CamelStoreGetFolderInfoFlags:
+ * @CAMEL_STORE_FOLDER_INFO_FAST:
+ * @CAMEL_STORE_FOLDER_INFO_RECURSIVE:
+ * @CAMEL_STORE_FOLDER_INFO_SUBSCRIBED:
+ * @CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL:
+ * Do not include virtual trash or junk folders.
+ * @CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST:
+ * Fetch only the subscription list. Clients should use this
+ * flag for requesting the list of folders available for
+ * subscription. Used in Exchange / IMAP connectors for public
+ * folder fetching.
+ * @CAMEL_STORE_FOLDER_INFO_REFRESH:
+ * Treat this call as a request to refresh the folder summary;
+ * for remote accounts it can be to re-fetch fresh folder
+ * content from the server and update the local cache.
+ **/
+typedef enum { /*< flags >*/
+ CAMEL_STORE_FOLDER_INFO_FAST = 1 << 0,
+ CAMEL_STORE_FOLDER_INFO_RECURSIVE = 1 << 1,
+ CAMEL_STORE_FOLDER_INFO_SUBSCRIBED = 1 << 2,
+ CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL = 1 << 3,
+ CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST = 1 << 4,
+ CAMEL_STORE_FOLDER_INFO_REFRESH = 1 << 5
+} CamelStoreGetFolderInfoFlags;
+
+typedef enum { /*< flags >*/
+ CAMEL_STORE_READ = 1 << 0,
+ CAMEL_STORE_WRITE = 1 << 1
+} CamelStorePermissionFlags;
+
+/* Note: If you change this, make sure you change the
+ * 'encodings' array in camel-mime-part.c. */
+typedef enum {
+ CAMEL_TRANSFER_ENCODING_DEFAULT,
+ CAMEL_TRANSFER_ENCODING_7BIT,
+ CAMEL_TRANSFER_ENCODING_8BIT,
+ CAMEL_TRANSFER_ENCODING_BASE64,
+ CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE,
+ CAMEL_TRANSFER_ENCODING_BINARY,
+ CAMEL_TRANSFER_ENCODING_UUENCODE,
+ CAMEL_TRANSFER_NUM_ENCODINGS
+} CamelTransferEncoding;
+
+/**
+ * CamelThreeState:
+ * @CAMEL_THREE_STATE_OFF: the three-state value is Off
+ * @CAMEL_THREE_STATE_ON: the three-state value is On
+ * @CAMEL_THREE_STATE_INCONSISTENT: the three-state value is neither On, nor Off
+ *
+ * Describes a three-state value, which can be either Off, On or Inconsistent.
+ *
+ * Since: 3.22
+ **/
+typedef enum {
+ CAMEL_THREE_STATE_OFF = 0,
+ CAMEL_THREE_STATE_ON,
+ CAMEL_THREE_STATE_INCONSISTENT
+} CamelThreeState;
+
+#endif /* CAMEL_ENUMS_H */
diff --git a/src/camel/camel-file-utils.c b/src/camel/camel-file-utils.c
new file mode 100644
index 000000000..93f24ee84
--- /dev/null
+++ b/src/camel/camel-file-utils.c
@@ -0,0 +1,645 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ * Dan Winship <danw@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-file-utils.h"
+#include "camel-object.h"
+#include "camel-operation.h"
+#include "camel-url.h"
+
+#ifdef G_OS_WIN32
+#include <winsock2.h>
+#ifndef EWOULDBLOCK
+#define EWOULDBLOCK EAGAIN
+#endif
+#endif
+
+#define IO_TIMEOUT (60*4)
+
+#define CHECK_CALL(x) G_STMT_START { \
+ if ((x) == -1) { \
+ g_debug ("%s: Call of '" #x "' failed: %s", G_STRFUNC, g_strerror (errno)); \
+ } \
+ } G_STMT_END
+
+/**
+ * camel_file_util_encode_uint32:
+ * @out: file to output to
+ * @value: value to output
+ *
+ * Utility function to save an uint32 to a file.
+ *
+ * Returns: %0 on success, %-1 on error.
+ **/
+gint
+camel_file_util_encode_uint32 (FILE *out,
+ guint32 value)
+{
+ gint i;
+
+ for (i = 28; i > 0; i -= 7) {
+ if (value >= (1 << i)) {
+ guint c = (value >> i) & 0x7f;
+ if (fputc (c, out) == -1)
+ return -1;
+ }
+ }
+ return fputc (value | 0x80, out);
+}
+
+/**
+ * camel_file_util_decode_uint32:
+ * @in: file to read from
+ * @dest: pointer to a variable to store the value in
+ *
+ * Retrieve an encoded uint32 from a file.
+ *
+ * Returns: %0 on success, %-1 on error. @*dest will contain the
+ * decoded value.
+ **/
+gint
+camel_file_util_decode_uint32 (FILE *in,
+ guint32 *dest)
+{
+ guint32 value = 0;
+ gint v;
+
+ /* until we get the last byte, keep decoding 7 bits at a time */
+ while ( ((v = fgetc (in)) & 0x80) == 0 && v != EOF) {
+ value |= v;
+ value <<= 7;
+ }
+ if (v == EOF) {
+ *dest = value >> 7;
+ return -1;
+ }
+ *dest = value | (v & 0x7f);
+
+ return 0;
+}
+
+/**
+ * camel_file_util_encode_fixed_int32:
+ * @out: file to output to
+ * @value: value to output
+ *
+ * Encode a gint32, performing no compression, but converting
+ * to network order.
+ *
+ * Returns: %0 on success, %-1 on error.
+ **/
+gint
+camel_file_util_encode_fixed_int32 (FILE *out,
+ gint32 value)
+{
+ guint32 save;
+
+ save = g_htonl (value);
+ if (fwrite (&save, sizeof (save), 1, out) != 1)
+ return -1;
+ return 0;
+}
+
+/**
+ * camel_file_util_decode_fixed_int32:
+ * @in: file to read from
+ * @dest: pointer to a variable to store the value in
+ *
+ * Retrieve a gint32.
+ *
+ * Returns: %0 on success, %-1 on error.
+ **/
+gint
+camel_file_util_decode_fixed_int32 (FILE *in,
+ gint32 *dest)
+{
+ guint32 save;
+
+ if (fread (&save, sizeof (save), 1, in) == 1) {
+ *dest = g_ntohl (save);
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+#define CFU_ENCODE_T(type) \
+gint \
+camel_file_util_encode_##type (FILE *out, type value) \
+{ \
+ gint i; \
+ \
+ for (i = sizeof (type) - 1; i >= 0; i--) { \
+ if (fputc ((value >> (i * 8)) & 0xff, out) == -1) \
+ return -1; \
+ } \
+ return 0; \
+}
+
+#define CFU_DECODE_T(type) \
+gint \
+camel_file_util_decode_##type (FILE *in, type *dest) \
+{ \
+ type save = 0; \
+ gint i = sizeof (type) - 1; \
+ gint v = EOF; \
+ \
+ while (i >= 0 && (v = fgetc (in)) != EOF) { \
+ save |= ((type) v) << (i * 8); \
+ i--; \
+ } \
+ *dest = save; \
+ if (v == EOF) \
+ return -1; \
+ return 0; \
+}
+
+/**
+ * camel_file_util_encode_time_t:
+ * @out: file to output to
+ * @value: value to output
+ *
+ * Encode a time_t value to the file.
+ *
+ * Returns: %0 on success, %-1 on error.
+ **/
+CFU_ENCODE_T (time_t)
+
+/**
+ * camel_file_util_decode_time_t:
+ * @in: file to read from
+ * @dest: pointer to a variable to store the value in
+ *
+ * Decode a time_t value.
+ *
+ * Returns: %0 on success, %-1 on error.
+ **/
+CFU_DECODE_T (time_t)
+
+/**
+ * camel_file_util_encode_off_t:
+ * @out: file to output to
+ * @value: value to output
+ *
+ * Encode an off_t type.
+ *
+ * Returns: %0 on success, %-1 on error.
+ **/
+CFU_ENCODE_T (off_t)
+
+/**
+ * camel_file_util_decode_off_t:
+ * @in: file to read from
+ * @dest: pointer to a variable to put the value in
+ *
+ * Decode an off_t type.
+ *
+ * Returns: %0 on success, %-1 on failure.
+ **/
+CFU_DECODE_T (off_t)
+
+/**
+ * camel_file_util_encode_gsize:
+ * @out: file to output to
+ * @value: value to output
+ *
+ * Encode an gsize type.
+ *
+ * Returns: %0 on success, %-1 on error.
+ **/
+CFU_ENCODE_T (gsize)
+
+/**
+ * camel_file_util_decode_gsize:
+ * @in: file to read from
+ * @dest: pointer to a variable to put the value in
+ *
+ * Decode an gsize type.
+ *
+ * Returns: %0 on success, %-1 on failure.
+ **/
+CFU_DECODE_T (gsize)
+
+/**
+ * camel_file_util_encode_string:
+ * @out: file to output to
+ * @str: value to output
+ *
+ * Encode a normal string and save it in the output file.
+ *
+ * Returns: %0 on success, %-1 on error.
+ **/
+gint
+camel_file_util_encode_string (FILE *out,
+ const gchar *str)
+{
+ register gint len;
+
+ if (str == NULL)
+ return camel_file_util_encode_uint32 (out, 1);
+
+ if ((len = strlen (str)) > 65536)
+ len = 65536;
+
+ if (camel_file_util_encode_uint32 (out, len + 1) == -1)
+ return -1;
+ if (len == 0 || fwrite (str, sizeof (gchar), len, out) == len)
+ return 0;
+ return -1;
+}
+
+/**
+ * camel_file_util_decode_string:
+ * @in: file to read from
+ * @str: pointer to a variable to store the value in
+ *
+ * Decode a normal string from the input file.
+ *
+ * Returns: %0 on success, %-1 on error.
+ **/
+gint
+camel_file_util_decode_string (FILE *in,
+ gchar **str)
+{
+ guint32 len;
+ register gchar *ret;
+
+ if (camel_file_util_decode_uint32 (in, &len) == -1) {
+ *str = NULL;
+ return -1;
+ }
+
+ len--;
+ if (len > 65536) {
+ *str = NULL;
+ return -1;
+ }
+
+ ret = g_malloc (len + 1);
+ if (len > 0 && fread (ret, sizeof (gchar), len, in) != len) {
+ g_free (ret);
+ *str = NULL;
+ return -1;
+ }
+
+ ret[len] = 0;
+ *str = ret;
+ return 0;
+}
+
+/**
+ * camel_file_util_encode_fixed_string:
+ * @out: file to output to
+ * @str: value to output
+ * @len: total-len of str to store
+ *
+ * Encode a normal string and save it in the output file.
+ * Unlike @camel_file_util_encode_string, it pads the
+ * @str with "NULL" bytes, if @len is > strlen(str)
+ *
+ * Returns: %0 on success, %-1 on error.
+ **/
+gint
+camel_file_util_encode_fixed_string (FILE *out,
+ const gchar *str,
+ gsize len)
+{
+ gint retval = -1;
+
+ /* Max size is 64K */
+ if (len > 65536)
+ len = 65536;
+
+ /* Don't allow empty strings to be written. */
+ if (len > 0) {
+ gchar *buf;
+
+ buf = g_malloc0 (len);
+ g_strlcpy (buf, str, len);
+
+ if (fwrite (buf, sizeof (gchar), len, out) == len)
+ retval = 0;
+
+ g_free (buf);
+ }
+
+ return retval;
+}
+
+/**
+ * camel_file_util_decode_fixed_string:
+ * @in: file to read from
+ * @str: pointer to a variable to store the value in
+ * @len: total-len to decode.
+ *
+ * Decode a normal string from the input file.
+ *
+ * Returns: %0 on success, %-1 on error.
+ **/
+gint
+camel_file_util_decode_fixed_string (FILE *in,
+ gchar **str,
+ gsize len)
+{
+ register gchar *ret;
+
+ if (len > 65536) {
+ *str = NULL;
+ return -1;
+ }
+
+ ret = g_malloc (len + 1);
+ if (len > 0 && fread (ret, sizeof (gchar), len, in) != len) {
+ g_free (ret);
+ *str = NULL;
+ return -1;
+ }
+
+ ret[len] = 0;
+ *str = ret;
+ return 0;
+}
+
+/**
+ * camel_file_util_safe_filename:
+ * @name: string to 'flattened' into a safe filename
+ *
+ * 'Flattens' @name into a safe filename string by hex encoding any
+ * chars that may cause problems on the filesystem.
+ *
+ * Returns: a safe filename string.
+ **/
+gchar *
+camel_file_util_safe_filename (const gchar *name)
+{
+#ifdef G_OS_WIN32
+ const gchar *unsafe_chars = "/?()'*<>:\"\\|";
+#else
+ const gchar *unsafe_chars = "/?()'*";
+#endif
+
+ if (name == NULL)
+ return NULL;
+
+ return camel_url_encode (name, unsafe_chars);
+}
+
+/* FIXME: poll() might be more efficient and more portable? */
+
+/**
+ * camel_read:
+ * @fd: file descriptor
+ * @buf: buffer to fill
+ * @n: number of bytes to read into @buf
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Cancellable libc read() replacement.
+ *
+ * Code that intends to be portable to Win32 should call this function
+ * only on file descriptors returned from open(), not on sockets.
+ *
+ * Returns: number of bytes read or -1 on fail. On failure, errno will
+ * be set appropriately.
+ **/
+gssize
+camel_read (gint fd,
+ gchar *buf,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gssize nread;
+ gint cancel_fd;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ errno = EINTR;
+ return -1;
+ }
+
+ cancel_fd = g_cancellable_get_fd (cancellable);
+
+ if (cancel_fd == -1) {
+ do {
+ nread = read (fd, buf, n);
+ } while (nread == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
+ } else {
+#ifndef G_OS_WIN32
+ gint errnosav, flags, fdmax;
+ fd_set rdset;
+
+ flags = fcntl (fd, F_GETFL);
+ CHECK_CALL (fcntl (fd, F_SETFL, flags | O_NONBLOCK));
+
+ do {
+ struct timeval tv;
+ gint res;
+
+ FD_ZERO (&rdset);
+ FD_SET (fd, &rdset);
+ FD_SET (cancel_fd, &rdset);
+ fdmax = MAX (fd, cancel_fd) + 1;
+ tv.tv_sec = IO_TIMEOUT;
+ tv.tv_usec = 0;
+ nread = -1;
+
+ res = select (fdmax, &rdset, 0, 0, &tv);
+ if (res == -1)
+ ;
+ else if (res == 0)
+ errno = ETIMEDOUT;
+ else if (FD_ISSET (cancel_fd, &rdset)) {
+ errno = EINTR;
+ goto failed;
+ } else {
+ do {
+ nread = read (fd, buf, n);
+ } while (nread == -1 && errno == EINTR);
+ }
+ } while (nread == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
+ failed:
+ errnosav = errno;
+ CHECK_CALL (fcntl (fd, F_SETFL, flags));
+ errno = errnosav;
+#endif
+ }
+
+ g_cancellable_release_fd (cancellable);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return -1;
+
+ if (nread == -1)
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s", g_strerror (errno));
+
+ return nread;
+}
+
+/**
+ * camel_write:
+ * @fd: file descriptor
+ * @buf: buffer to write
+ * @n: number of bytes of @buf to write
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Cancellable libc write() replacement.
+ *
+ * Code that intends to be portable to Win32 should call this function
+ * only on file descriptors returned from open(), not on sockets.
+ *
+ * Returns: number of bytes written or -1 on fail. On failure, errno will
+ * be set appropriately.
+ **/
+gssize
+camel_write (gint fd,
+ const gchar *buf,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gssize w, written = 0;
+ gint cancel_fd;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ errno = EINTR;
+ return -1;
+ }
+
+ cancel_fd = g_cancellable_get_fd (cancellable);
+
+ if (cancel_fd == -1) {
+ do {
+ do {
+ w = write (fd, buf + written, n - written);
+ } while (w == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
+ if (w > 0)
+ written += w;
+ } while (w != -1 && written < n);
+ } else {
+#ifndef G_OS_WIN32
+ gint errnosav, flags, fdmax;
+ fd_set rdset, wrset;
+
+ flags = fcntl (fd, F_GETFL);
+ CHECK_CALL (fcntl (fd, F_SETFL, flags | O_NONBLOCK));
+
+ fdmax = MAX (fd, cancel_fd) + 1;
+ do {
+ struct timeval tv;
+ gint res;
+
+ FD_ZERO (&rdset);
+ FD_ZERO (&wrset);
+ FD_SET (fd, &wrset);
+ FD_SET (cancel_fd, &rdset);
+ tv.tv_sec = IO_TIMEOUT;
+ tv.tv_usec = 0;
+ w = -1;
+
+ res = select (fdmax, &rdset, &wrset, 0, &tv);
+ if (res == -1) {
+ if (errno == EINTR)
+ w = 0;
+ } else if (res == 0)
+ errno = ETIMEDOUT;
+ else if (FD_ISSET (cancel_fd, &rdset))
+ errno = EINTR;
+ else {
+ do {
+ w = write (fd, buf + written, n - written);
+ } while (w == -1 && errno == EINTR);
+
+ if (w == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ w = 0;
+ } else
+ written += w;
+ }
+ } while (w != -1 && written < n);
+
+ errnosav = errno;
+ CHECK_CALL (fcntl (fd, F_SETFL, flags));
+ errno = errnosav;
+#endif
+ }
+
+ g_cancellable_release_fd (cancellable);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return -1;
+
+ if (w == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ return -1;
+ }
+
+ return written;
+}
+
+/**
+ * camel_file_util_savename:
+ * @filename: a pathname
+ *
+ * Builds a pathname where the basename is of the form ".#" + the
+ * basename of @filename, for instance used in a two-stage commit file
+ * write.
+ *
+ * Returns: The new pathname. It must be free'd with g_free().
+ **/
+gchar *
+camel_file_util_savename (const gchar *filename)
+{
+ gchar *dirname, *retval;
+
+ dirname = g_path_get_dirname (filename);
+
+ if (strcmp (dirname, ".") == 0) {
+ retval = g_strconcat (".#", filename, NULL);
+ } else {
+ gchar *basename = g_path_get_basename (filename);
+ gchar *newbasename = g_strconcat (".#", basename, NULL);
+
+ retval = g_build_filename (dirname, newbasename, NULL);
+
+ g_free (newbasename);
+ g_free (basename);
+ }
+ g_free (dirname);
+
+ return retval;
+}
diff --git a/src/camel/camel-file-utils.h b/src/camel/camel-file-utils.h
new file mode 100644
index 000000000..6bc4513ac
--- /dev/null
+++ b/src/camel/camel-file-utils.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ * Dan Winship <danw@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_FILE_UTILS_H
+#define CAMEL_FILE_UTILS_H
+
+#include <gio/gio.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <time.h>
+#include <fcntl.h>
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+G_BEGIN_DECLS
+
+gint camel_file_util_encode_fixed_int32 (FILE *out, gint32 value);
+gint camel_file_util_decode_fixed_int32 (FILE *in, gint32 *dest);
+gint camel_file_util_encode_uint32 (FILE *out, guint32 value);
+gint camel_file_util_decode_uint32 (FILE *in, guint32 *dest);
+gint camel_file_util_encode_time_t (FILE *out, time_t value);
+gint camel_file_util_decode_time_t (FILE *in, time_t *dest);
+gint camel_file_util_encode_off_t (FILE *out, off_t value);
+gint camel_file_util_decode_off_t (FILE *in, off_t *dest);
+gint camel_file_util_encode_gsize (FILE *out, gsize value);
+gint camel_file_util_decode_gsize (FILE *in, gsize *dest);
+gint camel_file_util_encode_string (FILE *out, const gchar *str);
+gint camel_file_util_decode_string (FILE *in, gchar **str);
+gint camel_file_util_encode_fixed_string (FILE *out, const gchar *str, gsize len);
+gint camel_file_util_decode_fixed_string (FILE *in, gchar **str, gsize len);
+
+gchar *camel_file_util_safe_filename (const gchar *name);
+
+/* Code that intends to be portable to Win32 should use camel_read()
+ * and camel_write() only on file descriptors returned from open(),
+ * creat(), pipe() or fileno(). On Win32 camel_read() and
+ * camel_write() calls will not be cancellable.
+ */
+gssize camel_read (gint fd,
+ gchar *buf,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error);
+gssize camel_write (gint fd,
+ const gchar *buf,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error);
+
+gchar * camel_file_util_savename (const gchar *filename);
+
+G_END_DECLS
+
+#endif /* CAMEL_FILE_UTILS_H */
diff --git a/src/camel/camel-filter-driver.c b/src/camel/camel-filter-driver.c
new file mode 100644
index 000000000..b0e73b4e2
--- /dev/null
+++ b/src/camel/camel-filter-driver.c
@@ -0,0 +1,1886 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#ifndef G_OS_WIN32
+#include <sys/wait.h>
+#endif
+
+#include "camel-debug.h"
+#include "camel-file-utils.h"
+#include "camel-filter-driver.h"
+#include "camel-filter-search.h"
+#include "camel-mime-message.h"
+#include "camel-service.h"
+#include "camel-session.h"
+#include "camel-sexp.h"
+#include "camel-store.h"
+#include "camel-stream-fs.h"
+#include "camel-stream-mem.h"
+
+#define d(x)
+
+/* an invalid pointer */
+#define FOLDER_INVALID ((gpointer)~0)
+
+#define CAMEL_FILTER_DRIVER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_FILTER_DRIVER, CamelFilterDriverPrivate))
+
+/* 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 {
+ gchar *match;
+ gchar *action;
+ gchar *name;
+};
+
+struct _CamelFilterDriverPrivate {
+ GHashTable *globals; /* global variables */
+
+ CamelSession *session;
+
+ CamelFolder *defaultfolder; /* defualt folder */
+
+ CamelFilterStatusFunc statusfunc; /* status callback */
+ gpointer statusdata; /* status callback data */
+
+ CamelFilterShellFunc shellfunc; /* execute shell command callback */
+ gpointer shelldata; /* execute shell command callback data */
+
+ CamelFilterPlaySoundFunc playfunc; /* play-sound command callback */
+ gpointer playdata; /* play-sound command callback data */
+
+ CamelFilterSystemBeepFunc beep; /* system beep callback */
+ gpointer beepdata; /* system beep callback data */
+
+ /* for callback */
+ CamelFilterGetFolderFunc get_folder;
+ gpointer data;
+
+ /* run-time data */
+ GHashTable *folders; /* folders that message has been copied to */
+ gint closed; /* close count */
+ 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 */
+ gboolean moved; /* message was moved to some folder or another */
+
+ CamelMimeMessage *message; /* input message */
+ CamelMessageInfo *info; /* message summary info */
+ const gchar *uid; /* message uid */
+ CamelFolder *source; /* message source folder */
+ gboolean modified; /* has the input message been modified? */
+
+ FILE *logfile; /* log file */
+
+ GQueue rules; /* queue of _filter_rule structs */
+
+ GError *error;
+
+ /* evaluator */
+ CamelSExp *eval;
+};
+
+static void camel_filter_driver_log (CamelFilterDriver *driver, enum filter_log_t status, const gchar *desc, ...);
+
+static CamelFolder *open_folder (CamelFilterDriver *d, const gchar *folder_url);
+static gint close_folders (CamelFilterDriver *d);
+
+static CamelSExpResult *do_delete (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *do_forward_to (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *do_copy (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *do_move (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *do_stop (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *do_label (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *do_color (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *do_score (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *do_adjust_score (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *set_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *unset_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *do_shell (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *do_beep (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *play_sound (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *do_only_once (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+static CamelSExpResult *pipe_message (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
+
+/* these are our filter actions - each must have a callback */
+static struct {
+ const gchar *name;
+ CamelSExpFunc func;
+ gint type; /* set to 1 if a function can perform shortcut evaluation, or
+ doesn't execute everything, 0 otherwise */
+} symbols[] = {
+ { "delete", (CamelSExpFunc) do_delete, 0 },
+ { "forward-to", (CamelSExpFunc) do_forward_to, 0 },
+ { "copy-to", (CamelSExpFunc) do_copy, 0 },
+ { "move-to", (CamelSExpFunc) do_move, 0 },
+ { "stop", (CamelSExpFunc) do_stop, 0 },
+ { "set-label", (CamelSExpFunc) do_label, 0 },
+ { "set-color", (CamelSExpFunc) do_color, 0 },
+ { "set-score", (CamelSExpFunc) do_score, 0 },
+ { "adjust-score", (CamelSExpFunc) do_adjust_score, 0 },
+ { "set-system-flag", (CamelSExpFunc) set_flag, 0 },
+ { "unset-system-flag", (CamelSExpFunc) unset_flag, 0 },
+ { "pipe-message", (CamelSExpFunc) pipe_message, 0 },
+ { "shell", (CamelSExpFunc) do_shell, 0 },
+ { "beep", (CamelSExpFunc) do_beep, 0 },
+ { "play-sound", (CamelSExpFunc) play_sound, 0 },
+ { "only-once", (CamelSExpFunc) do_only_once, 0 }
+};
+
+G_DEFINE_TYPE (CamelFilterDriver, camel_filter_driver, G_TYPE_OBJECT)
+
+static void
+free_hash_strings (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ g_free (key);
+ g_free (value);
+}
+
+static gint
+filter_rule_compare_by_name (struct _filter_rule *rule,
+ const gchar *name)
+{
+ return g_strcmp0 (rule->name, name);
+}
+
+static void
+filter_driver_dispose (GObject *object)
+{
+ CamelFilterDriverPrivate *priv;
+
+ priv = CAMEL_FILTER_DRIVER_GET_PRIVATE (object);
+
+ if (priv->defaultfolder != NULL) {
+ camel_folder_thaw (priv->defaultfolder);
+ g_object_unref (priv->defaultfolder);
+ priv->defaultfolder = NULL;
+ }
+
+ if (priv->session != NULL) {
+ g_object_unref (priv->session);
+ priv->session = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_filter_driver_parent_class)->dispose (object);
+}
+
+static void
+filter_driver_finalize (GObject *object)
+{
+ CamelFilterDriverPrivate *priv;
+ struct _filter_rule *node;
+
+ priv = CAMEL_FILTER_DRIVER_GET_PRIVATE (object);
+
+ /* close all folders that were opened for appending */
+ close_folders (CAMEL_FILTER_DRIVER (object));
+ g_hash_table_destroy (priv->folders);
+
+ g_hash_table_foreach (priv->globals, free_hash_strings, object);
+ g_hash_table_destroy (priv->globals);
+
+ g_hash_table_foreach (priv->only_once, free_hash_strings, object);
+ g_hash_table_destroy (priv->only_once);
+
+ g_object_unref (priv->eval);
+
+ while ((node = g_queue_pop_head (&priv->rules)) != NULL) {
+ g_free (node->match);
+ g_free (node->action);
+ g_free (node->name);
+ g_free (node);
+ }
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_filter_driver_parent_class)->finalize (object);
+}
+
+static void
+camel_filter_driver_class_init (CamelFilterDriverClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelFilterDriverPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = filter_driver_dispose;
+ object_class->finalize = filter_driver_finalize;
+}
+
+static void
+camel_filter_driver_init (CamelFilterDriver *filter_driver)
+{
+ gint ii;
+
+ filter_driver->priv = CAMEL_FILTER_DRIVER_GET_PRIVATE (filter_driver);
+
+ g_queue_init (&filter_driver->priv->rules);
+
+ filter_driver->priv->eval = camel_sexp_new ();
+
+ /* Load in builtin symbols */
+ for (ii = 0; ii < G_N_ELEMENTS (symbols); ii++) {
+ if (symbols[ii].type == 1) {
+ camel_sexp_add_ifunction (
+ filter_driver->priv->eval, 0,
+ symbols[ii].name, (CamelSExpIFunc)
+ symbols[ii].func, filter_driver);
+ } else {
+ camel_sexp_add_function (
+ filter_driver->priv->eval, 0,
+ symbols[ii].name, symbols[ii].func,
+ filter_driver);
+ }
+ }
+
+ filter_driver->priv->globals =
+ g_hash_table_new (g_str_hash, g_str_equal);
+
+ filter_driver->priv->folders =
+ g_hash_table_new (g_str_hash, g_str_equal);
+
+ filter_driver->priv->only_once =
+ g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+/**
+ * camel_filter_driver_new:
+ * @session: (type CamelSession):
+ *
+ * Returns: A new CamelFilterDriver object
+ **/
+CamelFilterDriver *
+camel_filter_driver_new (CamelSession *session)
+{
+ CamelFilterDriver *d;
+
+ d = g_object_new (CAMEL_TYPE_FILTER_DRIVER, NULL);
+ d->priv->session = g_object_ref (session);
+
+ return d;
+}
+
+/**
+ * camel_filter_driver_set_folder_func:
+ * @get_folder: (scope call):
+ **/
+void
+camel_filter_driver_set_folder_func (CamelFilterDriver *d,
+ CamelFilterGetFolderFunc get_folder,
+ gpointer user_data)
+{
+ d->priv->get_folder = get_folder;
+ d->priv->data = user_data;
+}
+
+void
+camel_filter_driver_set_logfile (CamelFilterDriver *d,
+ FILE *logfile)
+{
+ d->priv->logfile = logfile;
+}
+
+/**
+ * camel_filter_driver_set_status_func:
+ * @func: (scope call):
+ **/
+void
+camel_filter_driver_set_status_func (CamelFilterDriver *d,
+ CamelFilterStatusFunc func,
+ gpointer user_data)
+{
+ d->priv->statusfunc = func;
+ d->priv->statusdata = user_data;
+}
+
+/**
+ * camel_filter_driver_set_shell_func:
+ * @func: (scope call):
+ **/
+void
+camel_filter_driver_set_shell_func (CamelFilterDriver *d,
+ CamelFilterShellFunc func,
+ gpointer user_data)
+{
+ d->priv->shellfunc = func;
+ d->priv->shelldata = user_data;
+}
+
+/**
+ * camel_filter_driver_set_play_sound_func:
+ * @func: (scope call):
+ **/
+void
+camel_filter_driver_set_play_sound_func (CamelFilterDriver *d,
+ CamelFilterPlaySoundFunc func,
+ gpointer user_data)
+{
+ d->priv->playfunc = func;
+ d->priv->playdata = user_data;
+}
+
+/**
+ * camel_filter_driver_set_system_beep_func:
+ * @func: (scope call):
+ **/
+void
+camel_filter_driver_set_system_beep_func (CamelFilterDriver *d,
+ CamelFilterSystemBeepFunc func,
+ gpointer user_data)
+{
+ d->priv->beep = func;
+ d->priv->beepdata = user_data;
+}
+
+void
+camel_filter_driver_set_default_folder (CamelFilterDriver *d,
+ CamelFolder *def)
+{
+ if (d->priv->defaultfolder) {
+ camel_folder_thaw (d->priv->defaultfolder);
+ g_object_unref (d->priv->defaultfolder);
+ }
+
+ d->priv->defaultfolder = def;
+
+ if (d->priv->defaultfolder) {
+ camel_folder_freeze (d->priv->defaultfolder);
+ g_object_ref (d->priv->defaultfolder);
+ }
+}
+
+void
+camel_filter_driver_add_rule (CamelFilterDriver *d,
+ const gchar *name,
+ const gchar *match,
+ const gchar *action)
+{
+ struct _filter_rule *node;
+
+ node = g_malloc (sizeof (*node));
+ node->match = g_strdup (match);
+ node->action = g_strdup (action);
+ node->name = g_strdup (name);
+
+ g_queue_push_tail (&d->priv->rules, node);
+}
+
+gint
+camel_filter_driver_remove_rule_by_name (CamelFilterDriver *d,
+ const gchar *name)
+{
+ GList *link;
+
+ link = g_queue_find_custom (
+ &d->priv->rules, name,
+ (GCompareFunc) filter_rule_compare_by_name);
+
+ if (link != NULL) {
+ struct _filter_rule *rule = link->data;
+
+ g_queue_delete_link (&d->priv->rules, link);
+
+ g_free (rule->match);
+ g_free (rule->action);
+ g_free (rule->name);
+ g_free (rule);
+
+ return 0;
+ }
+
+ return -1;
+}
+
+static void
+report_status (CamelFilterDriver *driver,
+ enum camel_filter_status_t status,
+ gint pc,
+ const gchar *desc,
+ ...)
+{
+ /* call user-defined status report function */
+ va_list ap;
+ gchar *str;
+
+ if (driver->priv->statusfunc) {
+ va_start (ap, desc);
+ str = g_strdup_vprintf (desc, ap);
+ va_end (ap);
+ driver->priv->statusfunc (driver, status, pc, str, driver->priv->statusdata);
+ g_free (str);
+ }
+}
+
+#if 0
+void
+camel_filter_driver_set_global (CamelFilterDriver *d,
+ const gchar *name,
+ const gchar *value)
+{
+ gchar *oldkey, *oldvalue;
+
+ if (g_hash_table_lookup_extended (d->priv->globals, name, (gpointer) &oldkey, (gpointer) &oldvalue)) {
+ g_free (oldvalue);
+ g_hash_table_insert (d->priv->globals, oldkey, g_strdup (value));
+ } else {
+ g_hash_table_insert (d->priv->globals, g_strdup (name), g_strdup (value));
+ }
+}
+#endif
+
+static CamelSExpResult *
+do_delete (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ d (fprintf (stderr, "doing delete\n"));
+ driver->priv->deleted = TRUE;
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Delete");
+
+ return NULL;
+}
+
+static CamelSExpResult *
+do_forward_to (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ d (fprintf (stderr, "marking message for forwarding\n"));
+
+ /* requires one parameter, string with a destination address */
+ if (argc < 1 || argv[0]->type != CAMEL_SEXP_RES_STRING)
+ return NULL;
+
+ /* make sure we have the message... */
+ if (driver->priv->message == NULL) {
+ /* FIXME Pass a GCancellable */
+ driver->priv->message = camel_folder_get_message_sync (
+ driver->priv->source,
+ driver->priv->uid, NULL,
+ &driver->priv->error);
+ if (driver->priv->message == NULL)
+ return NULL;
+ }
+
+ camel_filter_driver_log (
+ driver, FILTER_LOG_ACTION,
+ "Forward message to '%s'",
+ argv[0]->value.string);
+
+ /* XXX Not cancellable. */
+ camel_session_forward_to_sync (
+ driver->priv->session,
+ driver->priv->source,
+ driver->priv->message,
+ argv[0]->value.string,
+ NULL,
+ &driver->priv->error);
+
+ return NULL;
+}
+
+static CamelSExpResult *
+do_copy (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ gint i;
+
+ d (fprintf (stderr, "copying message...\n"));
+
+ for (i = 0; i < argc; i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING) {
+ /* open folders we intent to copy to */
+ gchar *folder = argv[i]->value.string;
+ CamelFolder *outbox;
+
+ outbox = open_folder (driver, folder);
+ if (!outbox)
+ break;
+
+ if (outbox == driver->priv->source)
+ break;
+
+ if (!driver->priv->modified &&
+ driver->priv->uid != NULL &&
+ driver->priv->source != NULL &&
+ camel_folder_has_summary_capability (
+ driver->priv->source)) {
+ GPtrArray *uids;
+
+ uids = g_ptr_array_new ();
+ g_ptr_array_add (
+ uids, (gchar *) driver->priv->uid);
+ /* FIXME Pass a GCancellable */
+ camel_folder_transfer_messages_to_sync (
+ driver->priv->source,
+ uids, outbox, FALSE, NULL, NULL,
+ &driver->priv->error);
+ g_ptr_array_free (uids, TRUE);
+ } else {
+ if (driver->priv->message == NULL)
+ /* FIXME Pass a GCancellable */
+ driver->priv->message = camel_folder_get_message_sync (
+ driver->priv->source,
+ driver->priv->uid, NULL,
+ &driver->priv->error);
+
+ if (!driver->priv->message)
+ continue;
+
+ /* FIXME Pass a GCancellable */
+ camel_folder_append_message_sync (
+ outbox, driver->priv->message,
+ driver->priv->info, NULL, NULL,
+ &driver->priv->error);
+ }
+
+ if (driver->priv->error == NULL)
+ driver->priv->copied = TRUE;
+
+ camel_filter_driver_log (
+ driver, FILTER_LOG_ACTION,
+ "Copy to folder %s", folder);
+ }
+ }
+
+ return NULL;
+}
+
+static CamelSExpResult *
+do_move (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ gint i;
+
+ d (fprintf (stderr, "moving message...\n"));
+
+ for (i = 0; i < argc; i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING) {
+ /* open folders we intent to move to */
+ gchar *folder = argv[i]->value.string;
+ CamelFolder *outbox;
+ gint last;
+
+ outbox = open_folder (driver, folder);
+ if (!outbox)
+ break;
+
+ if (outbox == driver->priv->source)
+ break;
+
+ /* only delete on last folder (only 1 can ever be supplied by ui currently) */
+ last = (i == argc - 1);
+
+ if (!driver->priv->modified && driver->priv->uid && driver->priv->source && camel_folder_has_summary_capability (driver->priv->source)) {
+ GPtrArray *uids;
+
+ uids = g_ptr_array_new ();
+ g_ptr_array_add (uids, (gchar *) driver->priv->uid);
+ /* FIXME Pass a GCancellable */
+ camel_folder_transfer_messages_to_sync (
+ driver->priv->source, uids, outbox, last,
+ NULL, NULL, &driver->priv->error);
+ g_ptr_array_free (uids, TRUE);
+ } else {
+ if (driver->priv->message == NULL)
+ /* FIXME Pass a GCancellable */
+ driver->priv->message = camel_folder_get_message_sync (
+ driver->priv->source, driver->priv->uid, NULL, &driver->priv->error);
+
+ if (!driver->priv->message)
+ continue;
+
+ /* FIXME Pass a GCancellable */
+ camel_folder_append_message_sync (
+ outbox, driver->priv->message, driver->priv->info,
+ NULL, NULL, &driver->priv->error);
+
+ if (driver->priv->error == NULL && last) {
+ if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source))
+ camel_folder_set_message_flags (
+ driver->priv->source, driver->priv->uid,
+ CAMEL_MESSAGE_DELETED |
+ CAMEL_MESSAGE_SEEN, ~0);
+ else
+ camel_message_info_set_flags (
+ driver->priv->info,
+ CAMEL_MESSAGE_DELETED |
+ CAMEL_MESSAGE_SEEN |
+ CAMEL_MESSAGE_FOLDER_FLAGGED,
+ ~0);
+ }
+ }
+
+ if (driver->priv->error == NULL) {
+ driver->priv->moved = TRUE;
+ camel_filter_driver_log (
+ driver, FILTER_LOG_ACTION,
+ "Move to folder %s", folder);
+ }
+ }
+ }
+
+ /* implicit 'stop' with 'move' */
+ camel_filter_driver_log (
+ driver, FILTER_LOG_ACTION,
+ "Stopped processing");
+ driver->priv->terminated = TRUE;
+
+ return NULL;
+}
+
+static CamelSExpResult *
+do_stop (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ camel_filter_driver_log (
+ driver, FILTER_LOG_ACTION,
+ "Stopped processing");
+ d (fprintf (stderr, "terminating message processing\n"));
+ driver->priv->terminated = TRUE;
+
+ return NULL;
+}
+
+static CamelSExpResult *
+do_label (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ d (fprintf (stderr, "setting label tag\n"));
+ if (argc > 0 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
+ /* This is a list of new labels, we should used these in case of passing in old names.
+ * This all is required only because backward compatibility. */
+ const gchar *new_labels[] = { "$Labelimportant", "$Labelwork", "$Labelpersonal", "$Labeltodo", "$Labellater", NULL};
+ const gchar *label;
+ gint i;
+
+ label = argv[0]->value.string;
+
+ for (i = 0; new_labels[i]; i++) {
+ if (label && strcmp (new_labels[i] + 6, label) == 0) {
+ label = new_labels[i];
+ break;
+ }
+ }
+
+ if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source))
+ camel_folder_set_message_user_flag (driver->priv->source, driver->priv->uid, label, TRUE);
+ else
+ camel_message_info_set_user_flag (driver->priv->info, label, TRUE);
+ camel_filter_driver_log (
+ driver, FILTER_LOG_ACTION,
+ "Set label to %s", label);
+ }
+
+ return NULL;
+}
+
+static CamelSExpResult *
+do_color (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ d (fprintf (stderr, "setting color tag\n"));
+ if (argc > 0 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
+ const gchar *color = argv[0]->value.string;
+
+ if (color && !*color)
+ color = NULL;
+
+ if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source))
+ camel_folder_set_message_user_tag (driver->priv->source, driver->priv->uid, "color", color);
+ else
+ camel_message_info_set_user_tag (driver->priv->info, "color", color);
+ camel_filter_driver_log (
+ driver, FILTER_LOG_ACTION,
+ "Set color to %s", color ? color : "None");
+ }
+
+ return NULL;
+}
+
+static CamelSExpResult *
+do_score (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ d (fprintf (stderr, "setting score tag\n"));
+ if (argc > 0 && argv[0]->type == CAMEL_SEXP_RES_INT) {
+ gchar *value;
+
+ value = g_strdup_printf ("%d", argv[0]->value.number);
+ camel_message_info_set_user_tag (driver->priv->info, "score", value);
+ camel_filter_driver_log (
+ driver, FILTER_LOG_ACTION,
+ "Set score to %d", argv[0]->value.number);
+ g_free (value);
+ }
+
+ return NULL;
+}
+
+static CamelSExpResult *
+do_adjust_score (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ d (fprintf (stderr, "adjusting score tag\n"));
+ if (argc > 0 && argv[0]->type == CAMEL_SEXP_RES_INT) {
+ gchar *value;
+ gint old;
+
+ value = (gchar *) camel_message_info_get_user_tag (driver->priv->info, "score");
+ old = value ? atoi (value) : 0;
+ value = g_strdup_printf ("%d", old + argv[0]->value.number);
+ camel_message_info_set_user_tag (driver->priv->info, "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 CamelSExpResult *
+set_flag (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ guint32 flags;
+
+ d (fprintf (stderr, "setting flag\n"));
+ if (argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
+ flags = camel_system_flag (argv[0]->value.string);
+ if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source))
+ camel_folder_set_message_flags (
+ driver->priv->source, driver->priv->uid, flags, ~0);
+ else
+ camel_message_info_set_flags (
+ driver->priv->info, flags |
+ CAMEL_MESSAGE_FOLDER_FLAGGED, ~0);
+ camel_filter_driver_log (
+ driver, FILTER_LOG_ACTION,
+ "Set %s flag", argv[0]->value.string);
+ }
+
+ return NULL;
+}
+
+static CamelSExpResult *
+unset_flag (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ guint32 flags;
+
+ d (fprintf (stderr, "unsetting flag\n"));
+ if (argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
+ flags = camel_system_flag (argv[0]->value.string);
+ if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source))
+ camel_folder_set_message_flags (
+ driver->priv->source, driver->priv->uid, flags, 0);
+ else
+ camel_message_info_set_flags (
+ driver->priv->info, flags |
+ CAMEL_MESSAGE_FOLDER_FLAGGED, 0);
+ camel_filter_driver_log (
+ driver, FILTER_LOG_ACTION,
+ "Unset %s flag", argv[0]->value.string);
+ }
+
+ return NULL;
+}
+
+#ifndef G_OS_WIN32
+static void
+child_setup_func (gpointer user_data)
+{
+ setsid ();
+}
+#else
+#define child_setup_func NULL
+#endif
+
+typedef struct {
+ gint child_status;
+ GMainLoop *loop;
+} child_watch_data_t;
+
+static void
+child_watch (GPid pid,
+ gint status,
+ gpointer user_data)
+{
+ child_watch_data_t *child_watch_data = user_data;
+
+ g_spawn_close_pid (pid);
+
+ child_watch_data->child_status = status;
+
+ g_main_loop_quit (child_watch_data->loop);
+}
+
+static gint
+pipe_to_system (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ gint i, pipe_to_child, pipe_from_child;
+ CamelMimeMessage *message = NULL;
+ CamelMimeParser *parser;
+ CamelStream *stream, *mem;
+ GPid child_pid;
+ GError *error = NULL;
+ GPtrArray *args;
+ child_watch_data_t child_watch_data;
+ GSource *source;
+ GMainContext *context;
+
+ if (argc < 1 || argv[0]->value.string[0] == '\0')
+ return 0;
+
+ /* make sure we have the message... */
+ if (driver->priv->message == NULL) {
+ /* FIXME Pass a GCancellable */
+ driver->priv->message = camel_folder_get_message_sync (
+ driver->priv->source, driver->priv->uid, NULL, &driver->priv->error);
+ if (driver->priv->message == NULL)
+ return -1;
+ }
+
+ 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);
+
+ if (!g_spawn_async_with_pipes (NULL,
+ (gchar **) args->pdata,
+ NULL,
+ G_SPAWN_DO_NOT_REAP_CHILD |
+ G_SPAWN_SEARCH_PATH |
+ G_SPAWN_STDERR_TO_DEV_NULL,
+ child_setup_func,
+ NULL,
+ &child_pid,
+ &pipe_to_child,
+ &pipe_from_child,
+ NULL,
+ &error)) {
+ g_ptr_array_free (args, TRUE);
+
+ g_set_error (
+ &driver->priv->error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Failed to create child process '%s': %s"),
+ argv[0]->value.string, error->message);
+ g_error_free (error);
+ return -1;
+ }
+
+ g_ptr_array_free (args, TRUE);
+
+ stream = camel_stream_fs_new_with_fd (pipe_to_child);
+ if (camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (driver->priv->message), stream, NULL, NULL) == -1) {
+ g_object_unref (stream);
+ close (pipe_from_child);
+ goto wait;
+ }
+
+ if (camel_stream_flush (stream, NULL, NULL) == -1) {
+ g_object_unref (stream);
+ close (pipe_from_child);
+ goto wait;
+ }
+
+ g_object_unref (stream);
+
+ stream = camel_stream_fs_new_with_fd (pipe_from_child);
+ mem = camel_stream_mem_new ();
+ if (camel_stream_write_to_stream (stream, mem, NULL, NULL) == -1) {
+ g_object_unref (stream);
+ g_object_unref (mem);
+ goto wait;
+ }
+
+ g_object_unref (stream);
+
+ g_seekable_seek (G_SEEKABLE (mem), 0, G_SEEK_SET, NULL, NULL);
+
+ parser = camel_mime_parser_new ();
+ camel_mime_parser_init_with_stream (parser, mem, NULL);
+ camel_mime_parser_scan_from (parser, FALSE);
+ g_object_unref (mem);
+
+ message = camel_mime_message_new ();
+ if (!camel_mime_part_construct_from_parser_sync (
+ (CamelMimePart *) message, parser, NULL, NULL)) {
+ gint err = camel_mime_parser_errno (parser);
+ g_set_error (
+ &driver->priv->error, G_IO_ERROR,
+ g_io_error_from_errno (err),
+ _("Invalid message stream received from %s: %s"),
+ argv[0]->value.string, g_strerror (err));
+ g_object_unref (message);
+ message = NULL;
+ } else {
+ g_object_unref (driver->priv->message);
+ driver->priv->message = message;
+ driver->priv->modified = TRUE;
+ }
+
+ g_object_unref (parser);
+
+ wait:
+ context = g_main_context_new ();
+ child_watch_data.loop = g_main_loop_new (context, FALSE);
+ g_main_context_unref (context);
+
+ source = g_child_watch_source_new (child_pid);
+ g_source_set_callback (source, (GSourceFunc) child_watch, &child_watch_data, NULL);
+ g_source_attach (source, g_main_loop_get_context (child_watch_data.loop));
+ g_source_unref (source);
+
+ g_main_loop_run (child_watch_data.loop);
+ g_main_loop_unref (child_watch_data.loop);
+
+#ifndef G_OS_WIN32
+ if (message && WIFEXITED (child_watch_data.child_status))
+ return WEXITSTATUS (child_watch_data.child_status);
+ else
+ return -1;
+#else
+ return child_watch_data.child_status;
+#endif
+}
+
+static CamelSExpResult *
+pipe_message (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ gint i;
+
+ /* make sure all args are strings */
+ for (i = 0; i < argc; i++) {
+ if (argv[i]->type != CAMEL_SEXP_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 CamelSExpResult *
+do_shell (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ GString *command;
+ GPtrArray *args;
+ gint 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 != CAMEL_SEXP_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 (driver->priv->shellfunc && argc >= 1) {
+ driver->priv->shellfunc (driver, argc, (gchar **) args->pdata, driver->priv->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 CamelSExpResult *
+do_beep (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ d (fprintf (stderr, "beep\n"));
+
+ if (driver->priv->beep) {
+ driver->priv->beep (driver, driver->priv->beepdata);
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Beep");
+ }
+
+ return NULL;
+}
+
+static CamelSExpResult *
+play_sound (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ d (fprintf (stderr, "play sound\n"));
+
+ if (driver->priv->playfunc && argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
+ driver->priv->playfunc (driver, argv[0]->value.string, driver->priv->playdata);
+ camel_filter_driver_log (
+ driver, FILTER_LOG_ACTION, "Play sound");
+ }
+
+ return NULL;
+}
+
+static CamelSExpResult *
+do_only_once (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ CamelFilterDriver *driver)
+{
+ d (fprintf (stderr, "only once\n"));
+
+ if (argc == 2 && !g_hash_table_lookup (driver->priv->only_once, argv[0]->value.string))
+ g_hash_table_insert (
+ driver->priv->only_once,
+ g_strdup (argv[0]->value.string),
+ g_strdup (argv[1]->value.string));
+
+ return NULL;
+}
+
+static CamelFolder *
+open_folder (CamelFilterDriver *driver,
+ const gchar *folder_url)
+{
+ CamelFolder *camelfolder;
+
+ /* we have a lookup table of currently open folders */
+ camelfolder = g_hash_table_lookup (driver->priv->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 (driver->priv->defaultfolder) {
+ camelfolder = driver->priv->get_folder (driver, folder_url, driver->priv->data, NULL);
+ } else {
+ camelfolder = driver->priv->get_folder (driver, folder_url, driver->priv->data, &driver->priv->error);
+ }
+
+ if (camelfolder) {
+ g_hash_table_insert (driver->priv->folders, g_strdup (folder_url), camelfolder);
+ camel_folder_freeze (camelfolder);
+ } else {
+ g_hash_table_insert (driver->priv->folders, g_strdup (folder_url), FOLDER_INVALID);
+ }
+
+ return camelfolder;
+}
+
+static void
+close_folder (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ CamelFolder *folder = value;
+ CamelFilterDriver *driver = data;
+
+ driver->priv->closed++;
+ g_free (key);
+
+ if (folder != FOLDER_INVALID) {
+ /* FIXME Pass a GCancellable */
+ if (camel_folder_synchronize_sync (folder, FALSE, NULL,
+ (driver->priv->error != NULL) ? NULL : &driver->priv->error))
+ camel_folder_refresh_info_sync (
+ folder, NULL,
+ (driver->priv->error != NULL) ? NULL : &driver->priv->error);
+ camel_folder_thaw (folder);
+ g_object_unref (folder);
+ }
+
+ report_status (
+ driver, CAMEL_FILTER_STATUS_PROGRESS,
+ g_hash_table_size (driver->priv->folders) * 100 /
+ driver->priv->closed, _("Syncing folders"));
+}
+
+/* flush/close all folders */
+static gint
+close_folders (CamelFilterDriver *driver)
+{
+ report_status (
+ driver, CAMEL_FILTER_STATUS_PROGRESS,
+ 0, _("Syncing folders"));
+
+ driver->priv->closed = 0;
+ g_hash_table_foreach (driver->priv->folders, close_folder, driver);
+ g_hash_table_destroy (driver->priv->folders);
+ driver->priv->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 gchar *desc,
+ ...)
+{
+ if (driver->priv->logfile) {
+ gchar *str = NULL;
+
+ if (desc) {
+ va_list ap;
+
+ va_start (ap, desc);
+ str = g_strdup_vprintf (desc, ap);
+ va_end (ap);
+ }
+
+ switch (status) {
+ case FILTER_LOG_START: {
+ /* write log header */
+ const gchar *subject = NULL;
+ const gchar *from = NULL;
+ gchar date[50];
+ time_t t;
+
+ /* FIXME: does this need locking? Probably */
+
+ from = camel_message_info_get_from (driver->priv->info);
+ subject = camel_message_info_get_subject (driver->priv->info);
+
+ time (&t);
+ strftime (date, 49, "%a, %d %b %Y %H:%M:%S", localtime (&t));
+ fprintf (
+ driver->priv->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 (driver->priv->logfile, "Action: %s\n", str);
+ break;
+ case FILTER_LOG_END:
+ fprintf (driver->priv->logfile, "\n");
+ break;
+ default:
+ /* nothing else is loggable */
+ break;
+ }
+
+ g_free (str);
+ }
+}
+
+struct _run_only_once {
+ CamelFilterDriver *driver;
+ GError *error;
+};
+
+static gboolean
+run_only_once (gpointer key,
+ gchar *action,
+ struct _run_only_once *user_data)
+{
+ CamelFilterDriver *driver = user_data->driver;
+ CamelSExpResult *r;
+
+ d (printf ("evaluating: %s\n\n", action));
+
+ camel_sexp_input_text (driver->priv->eval, action, strlen (action));
+ if (camel_sexp_parse (driver->priv->eval) == -1) {
+ if (user_data->error == NULL)
+ g_set_error (
+ &user_data->error,
+ CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Error parsing filter: %s: %s"),
+ camel_sexp_error (driver->priv->eval), action);
+ goto done;
+ }
+
+ r = camel_sexp_eval (driver->priv->eval);
+ if (r == NULL) {
+ if (user_data->error == NULL)
+ g_set_error (
+ &user_data->error,
+ CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Error executing filter: %s: %s"),
+ camel_sexp_error (driver->priv->eval), action);
+ goto done;
+ }
+
+ camel_sexp_result_free (driver->priv->eval, r);
+
+ done:
+
+ g_free (key);
+ g_free (action);
+
+ return TRUE;
+}
+
+/**
+ * camel_filter_driver_flush:
+ * @driver:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Flush all of the only-once filter actions.
+ **/
+void
+camel_filter_driver_flush (CamelFilterDriver *driver,
+ GError **error)
+{
+ struct _run_only_once data;
+
+ if (!driver->priv->only_once)
+ return;
+
+ data.driver = driver;
+ data.error = NULL;
+
+ g_hash_table_foreach_remove (driver->priv->only_once, (GHRFunc) run_only_once, &data);
+
+ if (data.error != NULL)
+ g_propagate_error (error, data.error);
+}
+
+static gint
+decode_flags_from_xev (const gchar *xev,
+ CamelMessageInfoBase *mi)
+{
+ guint32 uid, flags = 0;
+ gchar *header;
+
+ /* check for uid/flags */
+ header = camel_header_token_decode (xev);
+ if (!(header && strlen (header) == strlen ("00000000-0000")
+ && sscanf (header, "%08x-%04x", &uid, &flags) == 2)) {
+ g_free (header);
+ return 0;
+ }
+ g_free (header);
+
+ mi->flags = flags;
+ return 0;
+}
+
+/**
+ * camel_filter_driver_filter_mbox:
+ * @driver: CamelFilterDriver
+ * @mbox: mbox filename to be filtered
+ * @original_source_url:
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * 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.
+ *
+ **/
+gint
+camel_filter_driver_filter_mbox (CamelFilterDriver *driver,
+ const gchar *mbox,
+ const gchar *original_source_url,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimeParser *mp = NULL;
+ gchar *source_url = NULL;
+ gint fd = -1;
+ gint i = 0;
+ struct stat st;
+ gint status;
+ goffset last = 0;
+ gint ret = -1;
+
+ fd = g_open (mbox, O_RDONLY | O_BINARY, 0);
+ if (fd == -1) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Unable to open spool folder"));
+ goto fail;
+ }
+ /* to get the filesize */
+ if (fstat (fd, &st) != 0)
+ st.st_size = 0;
+
+ mp = camel_mime_parser_new ();
+ camel_mime_parser_scan_from (mp, TRUE);
+ if (camel_mime_parser_init_with_fd (mp, fd) == -1) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Unable to process spool folder"));
+ goto fail;
+ }
+ fd = -1;
+
+ source_url = g_filename_to_uri (mbox, NULL, NULL);
+
+ while (camel_mime_parser_step (mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM) {
+ CamelMessageInfo *info;
+ CamelMimeMessage *message;
+ CamelMimePart *mime_part;
+ gint pc = 0;
+ const gchar *xev;
+ GError *local_error = NULL;
+
+ if (st.st_size > 0)
+ pc = (gint)(100.0 * ((double) camel_mime_parser_tell (mp) / (double) st.st_size));
+
+ if (pc > 0)
+ camel_operation_progress (cancellable, pc);
+
+ report_status (
+ driver, CAMEL_FILTER_STATUS_START,
+ pc, _("Getting message %d (%d%%)"), i, pc);
+
+ message = camel_mime_message_new ();
+ mime_part = CAMEL_MIME_PART (message);
+
+ if (!camel_mime_part_construct_from_parser_sync (
+ mime_part, mp, cancellable, error)) {
+ report_status (
+ driver, CAMEL_FILTER_STATUS_END,
+ 100, _("Failed on message %d"), i);
+ g_object_unref (message);
+ goto fail;
+ }
+
+ info = camel_message_info_new_from_header (NULL, mime_part->headers);
+ /* Try and see if it has X-Evolution headers */
+ xev = camel_header_raw_find (&mime_part->headers, "X-Evolution", NULL);
+ if (xev)
+ decode_flags_from_xev (xev, (CamelMessageInfoBase *) info);
+
+ ((CamelMessageInfoBase *) info)->size = camel_mime_parser_tell (mp) - last;
+
+ last = camel_mime_parser_tell (mp);
+ status = camel_filter_driver_filter_message (
+ driver, message, info, NULL, NULL, source_url,
+ original_source_url ? original_source_url :
+ source_url, cancellable, &local_error);
+ g_object_unref (message);
+ if (local_error != NULL || status == -1) {
+ report_status (
+ driver, CAMEL_FILTER_STATUS_END,
+ 100, _("Failed on message %d"), i);
+ camel_message_info_unref (info);
+ g_propagate_error (error, local_error);
+ goto fail;
+ }
+
+ i++;
+
+ /* skip over the FROM_END state */
+ camel_mime_parser_step (mp, NULL, NULL);
+
+ camel_message_info_unref (info);
+ }
+
+ camel_operation_progress (cancellable, 100);
+
+ if (driver->priv->defaultfolder) {
+ report_status (
+ driver, CAMEL_FILTER_STATUS_PROGRESS,
+ 100, _("Syncing folder"));
+ camel_folder_synchronize_sync (
+ driver->priv->defaultfolder, FALSE, cancellable, NULL);
+ }
+
+ report_status (driver, CAMEL_FILTER_STATUS_END, 100, _("Complete"));
+
+ ret = 0;
+fail:
+ g_free (source_url);
+ if (fd != -1)
+ close (fd);
+ if (mp)
+ g_object_unref (mp);
+
+ return ret;
+}
+
+/**
+ * camel_filter_driver_filter_folder:
+ * @driver: CamelFilterDriver
+ * @folder: CamelFolder to be filtered
+ * @cache: UID cache (needed for POP folders)
+ * @uids: (element-type utf8): message uids to be filtered or NULL (as a
+ * shortcut to filter all messages)
+ * @remove: TRUE to mark filtered messages as deleted
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Filters a folder based on rules defined in the FilterDriver
+ * object.
+ *
+ * Returns: -1 if errors were encountered during filtering,
+ * otherwise returns 0.
+ *
+ **/
+gint
+camel_filter_driver_filter_folder (CamelFilterDriver *driver,
+ CamelFolder *folder,
+ CamelUIDCache *cache,
+ GPtrArray *uids,
+ gboolean remove,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean freeuids = FALSE;
+ CamelMessageInfo *info;
+ CamelStore *parent_store;
+ const gchar *store_uid;
+ gint status = 0;
+ gint i;
+
+ parent_store = camel_folder_get_parent_store (folder);
+ store_uid = camel_service_get_uid (CAMEL_SERVICE (parent_store));
+
+ if (uids == NULL) {
+ uids = camel_folder_get_uids (folder);
+ freeuids = TRUE;
+ }
+
+ for (i = 0; i < uids->len; i++) {
+ gint pc = (100 * i) / uids->len;
+ GError *local_error = NULL;
+
+ camel_operation_progress (cancellable, pc);
+
+ 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,
+ store_uid, store_uid, cancellable, &local_error);
+
+ if (camel_folder_has_summary_capability (folder))
+ camel_message_info_unref (info);
+
+ if (local_error != NULL || status == -1) {
+ report_status (
+ driver, CAMEL_FILTER_STATUS_END, 100,
+ _("Failed at message %d of %d"),
+ i + 1, uids->len);
+ g_propagate_error (error, local_error);
+ 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 (cache && (i % 10) == 0)
+ camel_uid_cache_save (cache);
+ }
+
+ camel_operation_progress (cancellable, 100);
+
+ /* Save the cache of any pending mails. */
+ if (cache)
+ camel_uid_cache_save (cache);
+
+ if (driver->priv->defaultfolder) {
+ report_status (
+ driver, CAMEL_FILTER_STATUS_PROGRESS,
+ 100, _("Syncing folder"));
+ camel_folder_synchronize_sync (
+ driver->priv->defaultfolder, FALSE, cancellable, NULL);
+ }
+
+ if (i == uids->len)
+ report_status (
+ driver, CAMEL_FILTER_STATUS_END,
+ 100, _("Complete"));
+
+ if (freeuids)
+ camel_folder_free_uids (folder, uids);
+
+ return status;
+}
+
+struct _get_message {
+ struct _CamelFilterDriverPrivate *priv;
+ const gchar *store_uid;
+};
+
+static CamelMimeMessage *
+get_message_cb (gpointer data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct _get_message *msgdata = data;
+ CamelMimeMessage *message;
+
+ if (msgdata->priv->message) {
+ message = g_object_ref (msgdata->priv->message);
+ } else {
+ const gchar *uid;
+
+ if (msgdata->priv->uid != NULL)
+ uid = msgdata->priv->uid;
+ else
+ uid = camel_message_info_get_uid (msgdata->priv->info);
+
+ message = camel_folder_get_message_sync (
+ msgdata->priv->source, uid, cancellable, error);
+ }
+
+ if (message != NULL && camel_mime_message_get_source (message) == NULL)
+ camel_mime_message_set_source (message, msgdata->store_uid);
+
+ 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
+ * @store_uid: UID of source store, or %NULL
+ * @original_store_uid: UID of source store (pre-movemail), or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * 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.
+ *
+ **/
+gint
+camel_filter_driver_filter_message (CamelFilterDriver *driver,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ const gchar *uid,
+ CamelFolder *source,
+ const gchar *store_uid,
+ const gchar *original_store_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFilterDriverPrivate *p = driver->priv;
+ gboolean freeinfo = FALSE;
+ gboolean filtered = FALSE;
+ CamelSExpResult *r;
+ GList *list, *link;
+ gint result;
+
+ g_return_val_if_fail (message != NULL || (source != NULL && uid != NULL), -1);
+
+ if (info == NULL) {
+ struct _camel_header_raw *h;
+
+ if (message) {
+ g_object_ref (message);
+ } else {
+ message = camel_folder_get_message_sync (
+ source, uid, cancellable, error);
+ if (!message)
+ return -1;
+ }
+
+ h = CAMEL_MIME_PART (message)->headers;
+ info = camel_message_info_new_from_header (NULL, h);
+ freeinfo = TRUE;
+ } else {
+ if (camel_message_info_get_flags (info) & CAMEL_MESSAGE_DELETED)
+ return 0;
+
+ uid = camel_message_info_get_uid (info);
+
+ if (message)
+ g_object_ref (message);
+ }
+
+ driver->priv->terminated = FALSE;
+ driver->priv->deleted = FALSE;
+ driver->priv->copied = FALSE;
+ driver->priv->moved = FALSE;
+ driver->priv->message = message;
+ driver->priv->info = info;
+ driver->priv->uid = uid;
+ driver->priv->source = source;
+
+ if (message != NULL && camel_mime_message_get_source (message) == NULL)
+ camel_mime_message_set_source (message, original_store_uid);
+
+ if (g_strcmp0 (store_uid, "local") == 0 ||
+ g_strcmp0 (store_uid, "vfolder") == 0) {
+ store_uid = NULL;
+ }
+
+ if (g_strcmp0 (original_store_uid, "local") == 0 ||
+ g_strcmp0 (original_store_uid, "vfolder") == 0) {
+ original_store_uid = NULL;
+ }
+
+ list = g_queue_peek_head_link (&driver->priv->rules);
+ result = CAMEL_SEARCH_NOMATCH;
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ struct _filter_rule *rule = link->data;
+ struct _get_message data;
+
+ if (driver->priv->terminated)
+ break;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, &driver->priv->error))
+ goto error;
+
+ d (printf ("applying rule %s\naction %s\n", rule->match, rule->action));
+
+ data.priv = p;
+ data.store_uid = original_store_uid;
+
+ if (original_store_uid == NULL)
+ original_store_uid = store_uid;
+
+ result = camel_filter_search_match (
+ driver->priv->session, get_message_cb, &data, driver->priv->info,
+ original_store_uid, source, rule->match, cancellable, &driver->priv->error);
+
+ switch (result) {
+ case CAMEL_SEARCH_ERROR:
+ g_prefix_error (
+ &driver->priv->error,
+ _("Execution of filter '%s' failed: "),
+ rule->name);
+ goto error;
+ case CAMEL_SEARCH_MATCHED:
+ filtered = TRUE;
+ camel_filter_driver_log (
+ driver, FILTER_LOG_START,
+ "%s", rule->name);
+
+ /* perform necessary filtering actions */
+ camel_sexp_input_text (
+ driver->priv->eval,
+ rule->action, strlen (rule->action));
+ if (camel_sexp_parse (driver->priv->eval) == -1) {
+ g_set_error (
+ error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC,
+ _("Error parsing filter '%s': %s: %s"),
+ rule->name,
+ camel_sexp_error (driver->priv->eval),
+ rule->action);
+ goto error;
+ }
+ r = camel_sexp_eval (driver->priv->eval);
+ if (driver->priv->error != NULL) {
+ g_prefix_error (
+ &driver->priv->error,
+ _("Execution of filter '%s' failed: "),
+ rule->name);
+ goto error;
+ }
+
+ if (r == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC,
+ _("Error executing filter '%s': %s: %s"),
+ rule->name,
+ camel_sexp_error (driver->priv->eval),
+ rule->action);
+ goto error;
+ }
+ camel_sexp_result_free (driver->priv->eval, r);
+ default:
+ break;
+ }
+ }
+
+ /* *Now* we can set the DELETED flag... */
+ if (driver->priv->deleted) {
+ if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source))
+ camel_folder_set_message_flags (
+ driver->priv->source, driver->priv->uid,
+ CAMEL_MESSAGE_DELETED |
+ CAMEL_MESSAGE_SEEN, ~0);
+ else
+ camel_message_info_set_flags (
+ info, CAMEL_MESSAGE_DELETED |
+ CAMEL_MESSAGE_SEEN |
+ CAMEL_MESSAGE_FOLDER_FLAGGED, ~0);
+ }
+
+ /* Logic: if !Moved and there exists a default folder... */
+ if (!(driver->priv->copied && driver->priv->deleted) && !driver->priv->moved && driver->priv->defaultfolder) {
+ /* copy it to the default inbox */
+ filtered = TRUE;
+ camel_filter_driver_log (
+ driver, FILTER_LOG_ACTION,
+ "Copy to default folder");
+
+ if (!driver->priv->modified && driver->priv->uid && driver->priv->source && camel_folder_has_summary_capability (driver->priv->source)) {
+ GPtrArray *uids;
+
+ uids = g_ptr_array_new ();
+ g_ptr_array_add (uids, (gchar *) driver->priv->uid);
+ camel_folder_transfer_messages_to_sync (
+ driver->priv->source, uids, driver->priv->defaultfolder,
+ FALSE, NULL, cancellable, &driver->priv->error);
+ g_ptr_array_free (uids, TRUE);
+ } else {
+ if (driver->priv->message == NULL) {
+ driver->priv->message = camel_folder_get_message_sync (
+ source, uid, cancellable, error);
+ if (!driver->priv->message)
+ goto error;
+ }
+
+ camel_folder_append_message_sync (
+ driver->priv->defaultfolder,
+ driver->priv->message,
+ driver->priv->info, NULL,
+ cancellable,
+ &driver->priv->error);
+ }
+ }
+
+ if (driver->priv->message)
+ g_object_unref (driver->priv->message);
+
+ if (freeinfo)
+ camel_message_info_unref (info);
+
+ return 0;
+
+ error:
+ if (filtered)
+ camel_filter_driver_log (driver, FILTER_LOG_END, NULL);
+
+ if (driver->priv->message)
+ g_object_unref (driver->priv->message);
+
+ if (freeinfo)
+ camel_message_info_unref (info);
+
+ g_propagate_error (error, driver->priv->error);
+ driver->priv->error = NULL;
+
+ return -1;
+}
diff --git a/src/camel/camel-filter-driver.h b/src/camel/camel-filter-driver.h
new file mode 100644
index 000000000..d0b7eda18
--- /dev/null
+++ b/src/camel/camel-filter-driver.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_FILTER_DRIVER_H
+#define CAMEL_FILTER_DRIVER_H
+
+#include <camel/camel-folder.h>
+#include <camel/camel-uid-cache.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_FILTER_DRIVER \
+ (camel_filter_driver_get_type ())
+#define CAMEL_FILTER_DRIVER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_FILTER_DRIVER, CamelFilterDriver))
+#define CAMEL_FILTER_DRIVER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_FILTER_DRIVER, CamelFilterDriverClass)
+#define CAMEL_IS_FILTER_DRIVER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_FILTER_DRIVER))
+#define CAMEL_IS_FILTER_DRIVER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_FILTER_DRIVER))
+#define CAMEL_FILTER_DRIVER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_FILTER_DRIVER, CamelFilterDriverClass))
+
+G_BEGIN_DECLS
+
+struct _CamelSession;
+
+typedef struct _CamelFilterDriver CamelFilterDriver;
+typedef struct _CamelFilterDriverClass CamelFilterDriverClass;
+typedef struct _CamelFilterDriverPrivate CamelFilterDriverPrivate;
+
+struct _CamelFilterDriver {
+ GObject parent;
+ CamelFilterDriverPrivate *priv;
+};
+
+struct _CamelFilterDriverClass {
+ GObjectClass parent_class;
+};
+
+/* FIXME: this maybe should change... */
+/* type of status for a status report */
+enum camel_filter_status_t {
+ CAMEL_FILTER_STATUS_NONE,
+ CAMEL_FILTER_STATUS_START, /* start of new message processed */
+ CAMEL_FILTER_STATUS_ACTION, /* an action performed */
+ CAMEL_FILTER_STATUS_PROGRESS, /* (an) extra update (s), if its taking longer to process */
+ CAMEL_FILTER_STATUS_END /* end of message */
+};
+
+typedef CamelFolder * (*CamelFilterGetFolderFunc) (CamelFilterDriver *driver, const gchar *uri,
+ gpointer data, GError **error);
+/* report status */
+typedef void (*CamelFilterStatusFunc) (CamelFilterDriver *driver, enum camel_filter_status_t status,
+ gint pc, const gchar *desc, gpointer user_data);
+
+typedef void (*CamelFilterShellFunc) (CamelFilterDriver *driver, gint argc, gchar **argv, gpointer user_data);
+typedef void (*CamelFilterPlaySoundFunc) (CamelFilterDriver *driver, const gchar *filename, gpointer user_data);
+typedef void (*CamelFilterSystemBeepFunc) (CamelFilterDriver *driver, gpointer user_data);
+
+GType camel_filter_driver_get_type (void);
+CamelFilterDriver *camel_filter_driver_new (struct _CamelSession *session);
+
+/* modifiers */
+void camel_filter_driver_set_logfile (CamelFilterDriver *d, FILE *logfile);
+
+void camel_filter_driver_set_status_func (CamelFilterDriver *d, CamelFilterStatusFunc func, gpointer user_data);
+void camel_filter_driver_set_shell_func (CamelFilterDriver *d, CamelFilterShellFunc func, gpointer user_data);
+void camel_filter_driver_set_play_sound_func (CamelFilterDriver *d, CamelFilterPlaySoundFunc func, gpointer user_data);
+void camel_filter_driver_set_system_beep_func (CamelFilterDriver *d, CamelFilterSystemBeepFunc func, gpointer user_data);
+void camel_filter_driver_set_folder_func (CamelFilterDriver *d, CamelFilterGetFolderFunc get_folder, gpointer user_data);
+
+void camel_filter_driver_set_default_folder (CamelFilterDriver *d, CamelFolder *def);
+
+void camel_filter_driver_add_rule (CamelFilterDriver *d, const gchar *name, const gchar *match,
+ const gchar *action);
+gint camel_filter_driver_remove_rule_by_name (CamelFilterDriver *d, const gchar *name);
+
+/*void camel_filter_driver_set_global(CamelFilterDriver *, const gchar *name, const gchar *value);*/
+
+void camel_filter_driver_flush (CamelFilterDriver *driver, GError **error);
+
+gint camel_filter_driver_filter_message
+ (CamelFilterDriver *driver,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ const gchar *uid,
+ CamelFolder *source,
+ const gchar *store_uid,
+ const gchar *original_store_uid,
+ GCancellable *cancellable,
+ GError **error);
+gint camel_filter_driver_filter_mbox (CamelFilterDriver *driver,
+ const gchar *mbox,
+ const gchar *original_source_url,
+ GCancellable *cancellable,
+ GError **error);
+gint camel_filter_driver_filter_folder
+ (CamelFilterDriver *driver,
+ CamelFolder *folder,
+ CamelUIDCache *cache,
+ GPtrArray *uids,
+ gboolean remove,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_FILTER_DRIVER_H */
diff --git a/src/camel/camel-filter-input-stream.c b/src/camel/camel-filter-input-stream.c
new file mode 100644
index 000000000..2563ed029
--- /dev/null
+++ b/src/camel/camel-filter-input-stream.c
@@ -0,0 +1,261 @@
+/*
+ * camel-filter-input-stream.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * SECTION: camel-filter-input-stream
+ * @short_description: Filtered input stream
+ * @include: camel/camel.h
+ * @see_also: #GInputStream, #CamelMimeFilter
+ *
+ * #CamelFilterInputStream is similar to #GConverterInputStream, except it
+ * operates on a #CamelMimeFilter instead of a #GConverter.
+ *
+ * This class is meant to be a temporary solution until all of Camel's MIME
+ * filters are ported to the #GConverter interface.
+ **/
+
+#include "camel-filter-input-stream.h"
+
+#include <string.h>
+
+#define CAMEL_FILTER_INPUT_STREAM_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_FILTER_INPUT_STREAM, CamelFilterInputStreamPrivate))
+
+#define READ_PAD (128) /* bytes padded before buffer */
+#define READ_SIZE (4096)
+
+struct _CamelFilterInputStreamPrivate {
+ CamelMimeFilter *filter;
+
+ gchar real_buffer[READ_SIZE + READ_PAD];
+ gchar *buffer; /* points to real_buffer + READ_PAD */
+
+ gchar *filtered;
+ gsize filtered_length;
+};
+
+enum {
+ PROP_0,
+ PROP_FILTER
+};
+
+G_DEFINE_TYPE (
+ CamelFilterInputStream,
+ camel_filter_input_stream,
+ G_TYPE_FILTER_INPUT_STREAM)
+
+static void
+filter_input_stream_set_filter (CamelFilterInputStream *filter_stream,
+ CamelMimeFilter *filter)
+{
+ g_return_if_fail (CAMEL_IS_MIME_FILTER (filter));
+ g_return_if_fail (filter_stream->priv->filter == NULL);
+
+ filter_stream->priv->filter = g_object_ref (filter);
+}
+
+static void
+filter_input_stream_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_FILTER:
+ filter_input_stream_set_filter (
+ CAMEL_FILTER_INPUT_STREAM (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+filter_input_stream_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_FILTER:
+ g_value_set_object (
+ value,
+ camel_filter_input_stream_get_filter (
+ CAMEL_FILTER_INPUT_STREAM (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+filter_input_stream_dispose (GObject *object)
+{
+ CamelFilterInputStreamPrivate *priv;
+
+ priv = CAMEL_FILTER_INPUT_STREAM_GET_PRIVATE (object);
+
+ g_clear_object (&priv->filter);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_filter_input_stream_parent_class)->
+ dispose (object);
+}
+
+static gssize
+filter_input_stream_read (GInputStream *stream,
+ gpointer buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFilterInputStreamPrivate *priv;
+ CamelMimeFilter *filter;
+ GInputStream *base_stream;
+ gssize n_bytes_read;
+ gsize presize = READ_PAD;
+
+ priv = CAMEL_FILTER_INPUT_STREAM_GET_PRIVATE (stream);
+
+ filter = camel_filter_input_stream_get_filter (
+ CAMEL_FILTER_INPUT_STREAM (stream));
+ base_stream = g_filter_input_stream_get_base_stream (
+ G_FILTER_INPUT_STREAM (stream));
+
+ /* If we already have some filtered data, return it. */
+ if (priv->filtered_length > 0)
+ goto exit;
+
+ n_bytes_read = g_input_stream_read (
+ base_stream, priv->buffer,
+ READ_SIZE, cancellable, error);
+
+ if (n_bytes_read == 0) {
+ camel_mime_filter_complete (
+ filter, priv->filtered, priv->filtered_length, presize,
+ &priv->filtered, &priv->filtered_length, &presize);
+
+ n_bytes_read = priv->filtered_length;
+
+ if (n_bytes_read > 0)
+ goto exit;
+ }
+
+ if (n_bytes_read <= 0)
+ return n_bytes_read;
+
+ priv->filtered = priv->buffer;
+ priv->filtered_length = n_bytes_read;
+
+ camel_mime_filter_filter (
+ filter, priv->filtered, priv->filtered_length, presize,
+ &priv->filtered, &priv->filtered_length, &presize);
+
+exit:
+ n_bytes_read = MIN (count, priv->filtered_length);
+ memcpy (buffer, priv->filtered, n_bytes_read);
+ priv->filtered_length -= n_bytes_read;
+ priv->filtered += n_bytes_read;
+
+ return n_bytes_read;
+}
+
+static void
+camel_filter_input_stream_class_init (CamelFilterInputStreamClass *class)
+{
+ GObjectClass *object_class;
+ GInputStreamClass *stream_class;
+
+ g_type_class_add_private (
+ class, sizeof (CamelFilterInputStreamPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = filter_input_stream_set_property;
+ object_class->get_property = filter_input_stream_get_property;
+ object_class->dispose = filter_input_stream_dispose;
+
+ stream_class = G_INPUT_STREAM_CLASS (class);
+ stream_class->read_fn = filter_input_stream_read;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FILTER,
+ g_param_spec_object (
+ "filter",
+ "Filter",
+ "The MIME filter object",
+ CAMEL_TYPE_MIME_FILTER,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_filter_input_stream_init (CamelFilterInputStream *filter_stream)
+{
+ filter_stream->priv =
+ CAMEL_FILTER_INPUT_STREAM_GET_PRIVATE (filter_stream);
+
+ filter_stream->priv->buffer =
+ filter_stream->priv->real_buffer + READ_PAD;
+}
+
+/**
+ * camel_filter_input_stream_new:
+ * @base_stream: a #GInputStream
+ * @filter: a #CamelMimeFilter
+ *
+ * Creates a new filtered input stream for the @base_stream.
+ *
+ * Returns: a new #GInputStream
+ *
+ * Since: 3.12
+ **/
+GInputStream *
+camel_filter_input_stream_new (GInputStream *base_stream,
+ CamelMimeFilter *filter)
+{
+ g_return_val_if_fail (G_IS_INPUT_STREAM (base_stream), NULL);
+ g_return_val_if_fail (CAMEL_IS_MIME_FILTER (filter), NULL);
+
+ return g_object_new (
+ CAMEL_TYPE_FILTER_INPUT_STREAM,
+ "base-stream", base_stream,
+ "filter", filter, NULL);
+}
+
+/**
+ * camel_filter_input_stream_get_filter:
+ * @filter_stream: a #CamelFilterInputStream
+ *
+ * Gets the #CamelMimeFilter that is used by @filter_stream.
+ *
+ * Returns: (transfer none): a #CamelMimeFilter
+ *
+ * Since: 3.12
+ **/
+CamelMimeFilter *
+camel_filter_input_stream_get_filter (CamelFilterInputStream *filter_stream)
+{
+ g_return_val_if_fail (
+ CAMEL_IS_FILTER_INPUT_STREAM (filter_stream), NULL);
+
+ return filter_stream->priv->filter;
+}
+
diff --git a/src/camel/camel-filter-input-stream.h b/src/camel/camel-filter-input-stream.h
new file mode 100644
index 000000000..abc8f3d30
--- /dev/null
+++ b/src/camel/camel-filter-input-stream.h
@@ -0,0 +1,74 @@
+/*
+ * camel-filter-input-stream.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_FILTER_INPUT_STREAM_H
+#define CAMEL_FILTER_INPUT_STREAM_H
+
+#include <gio/gio.h>
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_FILTER_INPUT_STREAM \
+ (camel_filter_input_stream_get_type ())
+#define CAMEL_FILTER_INPUT_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_FILTER_INPUT_STREAM, CamelFilterInputStream))
+#define CAMEL_FILTER_INPUT_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_FILTER_INPUT_STREAM, CamelFilterInputStreamClass))
+#define CAMEL_IS_FILTER_INPUT_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_FILTER_INPUT_STREAM))
+#define CAMEL_IS_FILTER_INPUT_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_FILTER_INPUT_STREAM))
+#define CAMEL_FILTER_INPUT_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_FILTER_INPUT_STREAM, CamelFilterInputStreamClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelFilterInputStream CamelFilterInputStream;
+typedef struct _CamelFilterInputStreamClass CamelFilterInputStreamClass;
+typedef struct _CamelFilterInputStreamPrivate CamelFilterInputStreamPrivate;
+
+struct _CamelFilterInputStream {
+ GFilterInputStream parent;
+ CamelFilterInputStreamPrivate *priv;
+};
+
+struct _CamelFilterInputStreamClass {
+ GFilterInputStreamClass parent_class;
+};
+
+GType camel_filter_input_stream_get_type
+ (void) G_GNUC_CONST;
+GInputStream * camel_filter_input_stream_new
+ (GInputStream *base_stream,
+ CamelMimeFilter *filter);
+CamelMimeFilter *
+ camel_filter_input_stream_get_filter
+ (CamelFilterInputStream *filter_stream);
+
+G_END_DECLS
+
+#endif /* CAMEL_FILTER_INPUT_STREAM_H */
+
diff --git a/src/camel/camel-filter-output-stream.c b/src/camel/camel-filter-output-stream.c
new file mode 100644
index 000000000..32ac3012f
--- /dev/null
+++ b/src/camel/camel-filter-output-stream.c
@@ -0,0 +1,281 @@
+/*
+ * camel-filter-output-stream.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * SECTION: camel-filter-output-stream
+ * @short_description: Filtered output stream
+ * @include: camel/camel.h
+ * @see_also: #GOutputStream, #CamelMimeFilter
+ *
+ * #CamelFilterOutputStream is similar to #GConverterOutputStream, except it
+ * operates on a #CamelMimeFilter instead of a #GConverter.
+ *
+ * This class is meant to be a temporary solution until all of Camel's MIME
+ * filters are ported to the #GConverter interface.
+ **/
+
+#include "camel-filter-output-stream.h"
+
+#include <string.h>
+
+#define CAMEL_FILTER_OUTPUT_STREAM_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_FILTER_OUTPUT_STREAM, CamelFilterOutputStreamPrivate))
+
+#define READ_PAD (128) /* bytes padded before buffer */
+#define READ_SIZE (4096)
+
+struct _CamelFilterOutputStreamPrivate {
+ CamelMimeFilter *filter;
+};
+
+enum {
+ PROP_0,
+ PROP_FILTER
+};
+
+G_DEFINE_TYPE (
+ CamelFilterOutputStream,
+ camel_filter_output_stream,
+ G_TYPE_FILTER_OUTPUT_STREAM)
+
+static void
+filter_output_stream_set_filter (CamelFilterOutputStream *filter_stream,
+ CamelMimeFilter *filter)
+{
+ g_return_if_fail (CAMEL_IS_MIME_FILTER (filter));
+ g_return_if_fail (filter_stream->priv->filter == NULL);
+
+ filter_stream->priv->filter = g_object_ref (filter);
+}
+
+static void
+filter_output_stream_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_FILTER:
+ filter_output_stream_set_filter (
+ CAMEL_FILTER_OUTPUT_STREAM (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+filter_output_stream_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_FILTER:
+ g_value_set_object (
+ value,
+ camel_filter_output_stream_get_filter (
+ CAMEL_FILTER_OUTPUT_STREAM (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+filter_output_stream_dispose (GObject *object)
+{
+ CamelFilterOutputStreamPrivate *priv;
+
+ priv = CAMEL_FILTER_OUTPUT_STREAM_GET_PRIVATE (object);
+
+ /* XXX GOutputStream calls flush() one last time during
+ * dispose(), so chain up before clearing our filter. */
+ G_OBJECT_CLASS (camel_filter_output_stream_parent_class)->
+ dispose (object);
+
+ g_clear_object (&priv->filter);
+}
+
+static gssize
+filter_output_stream_write (GOutputStream *stream,
+ gconstpointer buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimeFilter *filter;
+ GOutputStream *base_stream;
+ gchar real_buffer[READ_SIZE + READ_PAD];
+ const gchar *input_buffer = buffer;
+ gsize bytes_left = count;
+
+ filter = camel_filter_output_stream_get_filter (
+ CAMEL_FILTER_OUTPUT_STREAM (stream));
+ base_stream = g_filter_output_stream_get_base_stream (
+ G_FILTER_OUTPUT_STREAM (stream));
+
+ while (bytes_left > 0) {
+ gsize length;
+ gsize presize;
+ gchar *bufptr;
+ gboolean success;
+
+ bufptr = real_buffer + READ_PAD;
+ length = MIN (READ_SIZE, bytes_left);
+ memcpy (bufptr, input_buffer, length);
+ input_buffer += length;
+ bytes_left -= length;
+
+ presize = READ_PAD;
+
+ camel_mime_filter_filter (
+ filter, bufptr, length, presize,
+ &bufptr, &length, &presize);
+
+ /* XXX The bytes_written argument can be NULL,
+ * even though the API docs don't say so. */
+ success = g_output_stream_write_all (
+ base_stream, bufptr, length,
+ NULL, cancellable, error);
+ if (!success)
+ return -1;
+ }
+
+ return count;
+}
+
+static gboolean
+filter_output_stream_flush (GOutputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimeFilter *filter;
+ GOutputStream *base_stream;
+ gchar *bufptr = (gchar *) "";
+ gsize length = 0;
+ gsize presize = 0;
+ gboolean success = TRUE;
+
+ filter = camel_filter_output_stream_get_filter (
+ CAMEL_FILTER_OUTPUT_STREAM (stream));
+ base_stream = g_filter_output_stream_get_base_stream (
+ G_FILTER_OUTPUT_STREAM (stream));
+
+ camel_mime_filter_complete (
+ filter, bufptr, length, presize,
+ &bufptr, &length, &presize);
+
+ if (length > 0) {
+ /* XXX The bytes_written argument can be NULL,
+ * even though the API docs don't say so. */
+ success = g_output_stream_write_all (
+ base_stream, bufptr, length,
+ NULL, cancellable, error);
+ }
+
+ if (success) {
+ success = g_output_stream_flush (
+ base_stream, cancellable, error);
+ }
+
+ return success;
+}
+
+static void
+camel_filter_output_stream_class_init (CamelFilterOutputStreamClass *class)
+{
+ GObjectClass *object_class;
+ GOutputStreamClass *stream_class;
+
+ g_type_class_add_private (
+ class, sizeof (CamelFilterOutputStreamPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = filter_output_stream_set_property;
+ object_class->get_property = filter_output_stream_get_property;
+ object_class->dispose = filter_output_stream_dispose;
+
+ stream_class = G_OUTPUT_STREAM_CLASS (class);
+ stream_class->write_fn = filter_output_stream_write;
+ stream_class->flush = filter_output_stream_flush;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FILTER,
+ g_param_spec_object (
+ "filter",
+ "Filter",
+ "The MIME filter object",
+ CAMEL_TYPE_MIME_FILTER,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_filter_output_stream_init (CamelFilterOutputStream *filter_stream)
+{
+ filter_stream->priv =
+ CAMEL_FILTER_OUTPUT_STREAM_GET_PRIVATE (filter_stream);
+}
+
+/**
+ * camel_filter_output_stream_new:
+ * @base_stream: a #GOutputStream
+ * @filter: a #CamelMimeFilter
+ *
+ * Creates a new filtered output stream for the @base_stream.
+ *
+ * Returns: a new #GOutputStream
+ *
+ * Since: 3.12
+ **/
+GOutputStream *
+camel_filter_output_stream_new (GOutputStream *base_stream,
+ CamelMimeFilter *filter)
+{
+ g_return_val_if_fail (G_IS_OUTPUT_STREAM (base_stream), NULL);
+ g_return_val_if_fail (CAMEL_IS_MIME_FILTER (filter), NULL);
+
+ return g_object_new (
+ CAMEL_TYPE_FILTER_OUTPUT_STREAM,
+ "base-stream", base_stream,
+ "filter", filter, NULL);
+}
+
+/**
+ * camel_filter_output_stream_get_filter:
+ * @filter_stream: a #CamelFilterOutputStream
+ *
+ * Gets the #CamelMimeFilter that is used by @filter_stream.
+ *
+ * Returns: (transfer none): a #CamelMimeFilter
+ *
+ * Since: 3.12
+ **/
+CamelMimeFilter *
+camel_filter_output_stream_get_filter (CamelFilterOutputStream *filter_stream)
+{
+ g_return_val_if_fail (
+ CAMEL_IS_FILTER_OUTPUT_STREAM (filter_stream), NULL);
+
+ return filter_stream->priv->filter;
+}
+
diff --git a/src/camel/camel-filter-output-stream.h b/src/camel/camel-filter-output-stream.h
new file mode 100644
index 000000000..064f851e5
--- /dev/null
+++ b/src/camel/camel-filter-output-stream.h
@@ -0,0 +1,74 @@
+/*
+ * camel-filter-output-stream.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_FILTER_OUTPUT_STREAM_H
+#define CAMEL_FILTER_OUTPUT_STREAM_H
+
+#include <gio/gio.h>
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_FILTER_OUTPUT_STREAM \
+ (camel_filter_output_stream_get_type ())
+#define CAMEL_FILTER_OUTPUT_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_FILTER_OUTPUT_STREAM, CamelFilterOutputStream))
+#define CAMEL_FILTER_OUTPUT_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_FILTER_OUTPUT_STREAM, CamelFilterOutputStreamClass))
+#define CAMEL_IS_FILTER_OUTPUT_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_FILTER_OUTPUT_STREAM))
+#define CAMEL_IS_FILTER_OUTPUT_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_FILTER_OUTPUT_STREAM))
+#define CAMEL_FILTER_OUTPUT_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_FILTER_OUTPUT_STREAM, CamelFilterOutputStreamClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelFilterOutputStream CamelFilterOutputStream;
+typedef struct _CamelFilterOutputStreamClass CamelFilterOutputStreamClass;
+typedef struct _CamelFilterOutputStreamPrivate CamelFilterOutputStreamPrivate;
+
+struct _CamelFilterOutputStream {
+ GFilterOutputStream parent;
+ CamelFilterOutputStreamPrivate *priv;
+};
+
+struct _CamelFilterOutputStreamClass {
+ GFilterOutputStreamClass parent_class;
+};
+
+GType camel_filter_output_stream_get_type
+ (void) G_GNUC_CONST;
+GOutputStream * camel_filter_output_stream_new
+ (GOutputStream *base_stream,
+ CamelMimeFilter *filter);
+CamelMimeFilter *
+ camel_filter_output_stream_get_filter
+ (CamelFilterOutputStream *filter_stream);
+
+G_END_DECLS
+
+#endif /* CAMEL_FILTER_OUTPUT_STREAM_H */
+
diff --git a/src/camel/camel-filter-search.c b/src/camel/camel-filter-search.c
new file mode 100644
index 000000000..68f0b636b
--- /dev/null
+++ b/src/camel/camel-filter-search.c
@@ -0,0 +1,1166 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ * Michael Zucchi <NotZed@Ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+/* POSIX requires <sys/types.h> be included before <regex.h> */
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib/gi18n-lib.h>
+
+#ifndef G_OS_WIN32
+#include <sys/wait.h>
+#endif
+
+#include "camel-debug.h"
+#include "camel-filter-search.h"
+#include "camel-iconv.h"
+#include "camel-mime-message.h"
+#include "camel-multipart.h"
+#include "camel-provider.h"
+#include "camel-search-private.h"
+#include "camel-session.h"
+#include "camel-stream-fs.h"
+#include "camel-stream-mem.h"
+#include "camel-string-utils.h"
+#include "camel-url.h"
+
+#define d(x)
+
+typedef struct {
+ CamelSession *session;
+ CamelFilterSearchGetMessageFunc get_message;
+ gpointer get_message_data;
+ CamelMimeMessage *message;
+ CamelMessageInfo *info;
+ CamelFolder *folder;
+ const gchar *source;
+ GCancellable *cancellable;
+ GError **error;
+} FilterMessageSearch;
+
+/* CamelSExp callbacks */
+static CamelSExpResult *header_contains (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *header_has_words (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *header_matches (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *header_starts_with (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *header_ends_with (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *header_exists (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *header_soundex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *header_regex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *header_full_regex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *match_all (struct _CamelSExp *f, gint argc, struct _CamelSExpTerm **argv, FilterMessageSearch *fms);
+static CamelSExpResult *body_contains (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *body_regex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *user_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *user_tag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *system_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *get_sent_date (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *get_received_date (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *get_current_date (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *get_relative_months (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *header_source (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *get_size (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *pipe_message (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *junk_test (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+static CamelSExpResult *message_location (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
+
+/* builtin functions */
+static struct {
+ const gchar *name;
+ CamelSExpFunc func;
+ gint type; /* set to 1 if a function can perform shortcut evaluation, or
+ doesn't execute everything, 0 otherwise */
+} symbols[] = {
+ { "match-all", (CamelSExpFunc) match_all, 1 },
+ { "body-contains", (CamelSExpFunc) body_contains, 0 },
+ { "body-regex", (CamelSExpFunc) body_regex, 0 },
+ { "header-contains", (CamelSExpFunc) header_contains, 0 },
+ { "header-has-words", (CamelSExpFunc) header_has_words, 0 },
+ { "header-matches", (CamelSExpFunc) header_matches, 0 },
+ { "header-starts-with", (CamelSExpFunc) header_starts_with, 0 },
+ { "header-ends-with", (CamelSExpFunc) header_ends_with, 0 },
+ { "header-exists", (CamelSExpFunc) header_exists, 0 },
+ { "header-soundex", (CamelSExpFunc) header_soundex, 0 },
+ { "header-regex", (CamelSExpFunc) header_regex, 0 },
+ { "header-full-regex", (CamelSExpFunc) header_full_regex, 0 },
+ { "user-tag", (CamelSExpFunc) user_tag, 0 },
+ { "user-flag", (CamelSExpFunc) user_flag, 0 },
+ { "system-flag", (CamelSExpFunc) system_flag, 0 },
+ { "get-sent-date", (CamelSExpFunc) get_sent_date, 0 },
+ { "get-received-date", (CamelSExpFunc) get_received_date, 0 },
+ { "get-current-date", (CamelSExpFunc) get_current_date, 0 },
+ { "get-relative-months",(CamelSExpFunc) get_relative_months,0 },
+ { "header-source", (CamelSExpFunc) header_source, 0 },
+ { "get-size", (CamelSExpFunc) get_size, 0 },
+ { "pipe-message", (CamelSExpFunc) pipe_message, 0 },
+ { "junk-test", (CamelSExpFunc) junk_test, 0 },
+ { "message-location", (CamelSExpFunc) message_location, 0 }
+};
+
+static CamelMimeMessage *
+camel_filter_search_get_message (FilterMessageSearch *fms,
+ struct _CamelSExp *sexp)
+{
+ if (fms->message)
+ return fms->message;
+
+ fms->message = fms->get_message (fms->get_message_data, fms->cancellable, fms->error);
+
+ if (fms->message == NULL)
+ camel_sexp_fatal_error (sexp, _("Failed to retrieve message"));
+
+ return fms->message;
+}
+
+static gboolean
+check_header_in_message_info (CamelMessageInfo *info,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ camel_search_match_t how,
+ gboolean *matched)
+{
+ struct _KnownHeaders {
+ const gchar *header_name;
+ guint info_key;
+ } known_headers[] = {
+ { "Subject", CAMEL_MESSAGE_INFO_SUBJECT },
+ { "From", CAMEL_MESSAGE_INFO_FROM },
+ { "To", CAMEL_MESSAGE_INFO_TO },
+ { "Cc", CAMEL_MESSAGE_INFO_CC }
+ };
+ camel_search_t type = CAMEL_SEARCH_TYPE_ENCODED;
+ const gchar *name, *value;
+ gboolean found = FALSE;
+ gint ii;
+
+ g_return_val_if_fail (argc > 1, FALSE);
+ g_return_val_if_fail (argv != NULL, FALSE);
+ g_return_val_if_fail (matched != NULL, FALSE);
+
+ if (!info)
+ return FALSE;
+
+ name = argv[0]->value.string;
+ g_return_val_if_fail (name != NULL, FALSE);
+
+ /* test against any header */
+ if (!*name) {
+ gint jj;
+
+ for (jj = 0; jj < G_N_ELEMENTS (known_headers); jj++) {
+ value = camel_message_info_get_ptr (info, known_headers[jj].info_key);
+ if (!value)
+ continue;
+
+ if (known_headers[jj].info_key == CAMEL_MESSAGE_INFO_SUBJECT)
+ type = CAMEL_SEARCH_TYPE_ENCODED;
+ else
+ type = CAMEL_SEARCH_TYPE_ADDRESS_ENCODED;
+
+ for (ii = 1; ii < argc && !*matched; ii++) {
+ if (argv[ii]->type == CAMEL_SEXP_RES_STRING)
+ *matched = camel_search_header_match (value, argv[ii]->value.string, how, type, NULL);
+ }
+
+ if (*matched)
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ value = NULL;
+
+ for (ii = 0; ii < G_N_ELEMENTS (known_headers); ii++) {
+ found = g_ascii_strcasecmp (name, known_headers[ii].header_name) == 0;
+ if (found) {
+ value = camel_message_info_get_ptr (info, known_headers[ii].info_key);
+ if (known_headers[ii].info_key != CAMEL_MESSAGE_INFO_SUBJECT)
+ type = CAMEL_SEARCH_TYPE_ADDRESS_ENCODED;
+ break;
+ }
+ }
+
+ if (!found || !value)
+ return FALSE;
+
+ for (ii = 1; ii < argc && !*matched; ii++) {
+ if (argv[ii]->type == CAMEL_SEXP_RES_STRING)
+ *matched = camel_search_header_match (value, argv[ii]->value.string, how, type, NULL);
+ }
+
+ return TRUE;
+}
+
+static CamelSExpResult *
+check_header (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms,
+ camel_search_match_t how)
+{
+ gboolean matched = FALSE;
+ CamelSExpResult *r;
+ gint i;
+
+ if (argc > 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
+ gchar *name = argv[0]->value.string;
+
+ /* shortcut: a match for "" against any header always matches */
+ for (i = 1; i < argc && !matched; i++)
+ matched = argv[i]->type == CAMEL_SEXP_RES_STRING && argv[i]->value.string[0] == 0;
+
+ if (g_ascii_strcasecmp (name, "x-camel-mlist") == 0) {
+ const gchar *list = camel_message_info_get_mlist (fms->info);
+
+ if (list) {
+ for (i = 1; i < argc && !matched; i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING)
+ matched = camel_search_header_match (list, argv[i]->value.string, how, CAMEL_SEARCH_TYPE_MLIST, NULL);
+ }
+ }
+ } else if (fms->message || !check_header_in_message_info (fms->info, argc, argv, how, &matched)) {
+ CamelMimeMessage *message;
+ CamelMimePart *mime_part;
+ struct _camel_header_raw *header;
+ const gchar *charset = NULL;
+ camel_search_t type = CAMEL_SEARCH_TYPE_ENCODED;
+ CamelContentType *ct;
+
+ message = camel_filter_search_get_message (fms, f);
+ mime_part = CAMEL_MIME_PART (message);
+
+ if (camel_search_header_is_address (name))
+ type = CAMEL_SEARCH_TYPE_ADDRESS_ENCODED;
+ else if (message) {
+ ct = camel_mime_part_get_content_type (mime_part);
+ if (ct) {
+ charset = camel_content_type_param (ct, "charset");
+ charset = camel_iconv_charset_name (charset);
+ }
+ }
+
+ for (header = mime_part->headers; header && !matched; header = header->next) {
+ /* empty name means any header */
+ if (!name || !*name || !g_ascii_strcasecmp (header->name, name)) {
+ for (i = 1; i < argc && !matched; i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING)
+ matched = camel_search_header_match (header->value, argv[i]->value.string, how, type, charset);
+ }
+ }
+ }
+ }
+ }
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = matched;
+
+ return r;
+}
+
+static CamelSExpResult *
+header_contains (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_CONTAINS);
+}
+
+static CamelSExpResult *
+header_has_words (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_WORD);
+}
+
+static CamelSExpResult *
+header_matches (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_EXACT);
+}
+
+static CamelSExpResult *
+header_starts_with (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_STARTS);
+}
+
+static CamelSExpResult *
+header_ends_with (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_ENDS);
+}
+
+static CamelSExpResult *
+header_soundex (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_SOUNDEX);
+}
+
+static CamelSExpResult *
+header_exists (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelMimeMessage *message;
+ gboolean matched = FALSE;
+ CamelSExpResult *r;
+ gint i;
+
+ message = camel_filter_search_get_message (fms, f);
+
+ for (i = 0; i < argc && !matched; i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING)
+ matched = camel_medium_get_header (CAMEL_MEDIUM (message), argv[i]->value.string) != NULL;
+ }
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = matched;
+
+ return r;
+}
+
+static CamelSExpResult *
+header_regex (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelSExpResult *r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
+ CamelMimeMessage *message;
+ regex_t pattern;
+ gchar *contents = NULL;
+
+ message = camel_filter_search_get_message (fms, f);
+
+ if (argc > 1 && argv[0]->type == CAMEL_SEXP_RES_STRING
+ && (contents = camel_search_get_header_decoded (argv[0]->value.string,
+ camel_medium_get_header (CAMEL_MEDIUM (message), argv[0]->value.string),
+ camel_search_get_default_charset_from_message (message)))
+ && camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_ICASE, argc - 1, argv + 1, fms->error) == 0) {
+ r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
+ regfree (&pattern);
+ } else
+ r->value.boolean = FALSE;
+
+ g_free (contents);
+
+ return r;
+}
+
+static CamelSExpResult *
+header_full_regex (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelSExpResult *r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
+ CamelMimeMessage *message;
+ regex_t pattern;
+ gchar *contents;
+
+ if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_ICASE | CAMEL_SEARCH_MATCH_NEWLINE,
+ argc, argv, fms->error) == 0) {
+ message = camel_filter_search_get_message (fms, f);
+ contents = camel_search_get_all_headers_decoded (message);
+ r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
+ g_free (contents);
+ regfree (&pattern);
+ } else
+ r->value.boolean = FALSE;
+
+ return r;
+}
+
+static CamelSExpResult *
+match_all (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpTerm **argv,
+ FilterMessageSearch *fms)
+{
+ /* match-all: when dealing with single messages is a no-op */
+ CamelSExpResult *r;
+
+ if (argc > 0)
+ return camel_sexp_term_eval (f, argv[0]);
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = TRUE;
+
+ return r;
+}
+
+static CamelSExpResult *
+body_contains (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelSExpResult *r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
+ CamelMimeMessage *message;
+ regex_t pattern;
+
+ if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE, argc, argv, fms->error) == 0) {
+ message = camel_filter_search_get_message (fms, f);
+ r->value.boolean = camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern);
+ regfree (&pattern);
+ } else
+ r->value.boolean = FALSE;
+
+ return r;
+}
+
+static CamelSExpResult *
+body_regex (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelSExpResult *r = camel_sexp_result_new (f, CAMEL_SEXP_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->error) == 0) {
+ message = camel_filter_search_get_message (fms, f);
+ r->value.boolean = camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern);
+ regfree (&pattern);
+ } else
+ r->value.boolean = FALSE;
+
+ return r;
+}
+
+static CamelSExpResult *
+user_flag (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelSExpResult *r;
+ gboolean truth = FALSE;
+ gint i;
+
+ /* performs an OR of all words */
+ for (i = 0; i < argc && !truth; i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING
+ && camel_message_info_get_user_flag (fms->info, argv[i]->value.string)) {
+ truth = TRUE;
+ break;
+ }
+ }
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = truth;
+
+ return r;
+}
+
+static CamelSExpResult *
+system_flag (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelSExpResult *r;
+
+ if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_STRING)
+ camel_sexp_fatal_error (f, _("Invalid arguments to (system-flag)"));
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = camel_system_flag_get (camel_message_info_get_flags (fms->info), argv[0]->value.string);
+
+ return r;
+}
+
+static CamelSExpResult *
+user_tag (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelSExpResult *r;
+ const gchar *tag;
+
+ if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_STRING)
+ camel_sexp_fatal_error (f, _("Invalid arguments to (user-tag)"));
+
+ tag = camel_message_info_get_user_tag (fms->info, argv[0]->value.string);
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+ r->value.string = g_strdup (tag ? tag : "");
+
+ return r;
+}
+
+static CamelSExpResult *
+get_sent_date (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelMimeMessage *message;
+ CamelSExpResult *r;
+
+ message = camel_filter_search_get_message (fms, f);
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
+ r->value.number = camel_mime_message_get_date (message, NULL);
+
+ return r;
+}
+
+static CamelSExpResult *
+get_received_date (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelMimeMessage *message;
+ CamelSExpResult *r;
+
+ message = camel_filter_search_get_message (fms, f);
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
+ r->value.number = camel_mime_message_get_date_received (message, NULL);
+
+ return r;
+}
+
+static CamelSExpResult *
+get_current_date (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelSExpResult *r;
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
+ r->value.number = time (NULL);
+
+ return r;
+}
+
+static CamelSExpResult *
+get_relative_months (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelSExpResult *r;
+
+ if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_INT) {
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = FALSE;
+
+ g_debug ("%s: Expecting 1 argument, an integer, but got %d arguments", G_STRFUNC, argc);
+ } else {
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
+ r->value.number = camel_folder_search_util_add_months (time (NULL), argv[0]->value.number);
+ }
+
+ return r;
+}
+
+static CamelService *
+ref_service_for_source (CamelSession *session,
+ const gchar *src)
+{
+ CamelService *service = NULL;
+
+ /* Source strings are now CamelService UIDs. */
+ if (src != NULL)
+ service = camel_session_ref_service (session, src);
+
+ /* For backward-compability, also handle CamelService URLs. */
+ if (service == NULL && src != NULL) {
+ CamelURL *url;
+
+ url = camel_url_new (src, NULL);
+
+ if (service == NULL && url != NULL)
+ service = camel_session_ref_service_by_url (
+ session, url, CAMEL_PROVIDER_STORE);
+
+ if (service == NULL && url != NULL)
+ service = camel_session_ref_service_by_url (
+ session, url, CAMEL_PROVIDER_TRANSPORT);
+
+ if (url != NULL)
+ camel_url_free (url);
+ }
+
+ return service;
+}
+
+static CamelSExpResult *
+header_source (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelMimeMessage *message;
+ CamelSExpResult *r;
+ const gchar *src;
+ CamelService *msg_source = NULL;
+ gboolean truth = FALSE;
+
+ if (fms->source) {
+ src = fms->source;
+ } else {
+ message = camel_filter_search_get_message (fms, f);
+ src = camel_mime_message_get_source (message);
+ }
+
+ if (src)
+ msg_source = ref_service_for_source (fms->session, src);
+
+ if (msg_source != NULL) {
+ gint ii;
+
+ for (ii = 0; ii < argc && !truth; ii++) {
+ if (argv[ii]->type == CAMEL_SEXP_RES_STRING) {
+ CamelService *candidate;
+
+ candidate = ref_service_for_source (
+ fms->session,
+ argv[ii]->value.string);
+ if (candidate != NULL) {
+ truth = (msg_source == candidate);
+ g_object_unref (candidate);
+ }
+ }
+ }
+
+ g_object_unref (msg_source);
+ }
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = truth;
+
+ return r;
+}
+
+/* remember, the size comparisons are done at Kbytes */
+static CamelSExpResult *
+get_size (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelSExpResult *r;
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
+ r->value.number = camel_message_info_get_size (fms->info) / 1024;
+
+ return r;
+}
+
+#ifndef G_OS_WIN32
+static void
+child_setup_func (gpointer user_data)
+{
+ setsid ();
+}
+#else
+#define child_setup_func NULL
+#endif
+
+typedef struct {
+ gint child_status;
+ GMainLoop *loop;
+} child_watch_data_t;
+
+static void
+child_watch (GPid pid,
+ gint status,
+ gpointer data)
+{
+ child_watch_data_t *child_watch_data = data;
+
+ g_spawn_close_pid (pid);
+
+ child_watch_data->child_status = status;
+ g_main_loop_quit (child_watch_data->loop);
+}
+
+static gint
+run_command (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelMimeMessage *message;
+ CamelStream *stream;
+ gint i;
+ gint pipe_to_child;
+ GPid child_pid;
+ GError *error = NULL;
+ GPtrArray *args;
+ child_watch_data_t child_watch_data;
+ GSource *source;
+ GMainContext *context;
+
+ if (argc < 1 || argv[0]->value.string[0] == '\0')
+ return 0;
+
+ 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);
+
+ if (!g_spawn_async_with_pipes (NULL,
+ (gchar **) args->pdata,
+ NULL,
+ G_SPAWN_DO_NOT_REAP_CHILD |
+ G_SPAWN_SEARCH_PATH |
+ G_SPAWN_STDOUT_TO_DEV_NULL |
+ G_SPAWN_STDERR_TO_DEV_NULL,
+ child_setup_func,
+ NULL,
+ &child_pid,
+ &pipe_to_child,
+ NULL,
+ NULL,
+ &error)) {
+ g_ptr_array_free (args, TRUE);
+
+ g_set_error (
+ fms->error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Failed to create child process '%s': %s"),
+ argv[0]->value.string, error->message);
+ g_error_free (error);
+ return -1;
+ }
+
+ g_ptr_array_free (args, TRUE);
+
+ message = camel_filter_search_get_message (fms, f);
+
+ stream = camel_stream_fs_new_with_fd (pipe_to_child);
+ camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (message), stream, fms->cancellable, NULL);
+ camel_stream_flush (stream, fms->cancellable, NULL);
+ g_object_unref (stream);
+
+ context = g_main_context_new ();
+ child_watch_data.loop = g_main_loop_new (context, FALSE);
+ g_main_context_unref (context);
+
+ source = g_child_watch_source_new (child_pid);
+ g_source_set_callback (source, (GSourceFunc) child_watch, &child_watch_data, NULL);
+ g_source_attach (source, g_main_loop_get_context (child_watch_data.loop));
+ g_source_unref (source);
+
+ g_main_loop_run (child_watch_data.loop);
+ g_main_loop_unref (child_watch_data.loop);
+
+#ifndef G_OS_WIN32
+ if (WIFEXITED (child_watch_data.child_status))
+ return WEXITSTATUS (child_watch_data.child_status);
+ else
+ return -1;
+#else
+ return child_watch_data.child_status;
+#endif
+}
+
+static CamelSExpResult *
+pipe_message (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelSExpResult *r;
+ gint retval, i;
+
+ /* make sure all args are strings */
+ for (i = 0; i < argc; i++) {
+ if (argv[i]->type != CAMEL_SEXP_RES_STRING) {
+ retval = -1;
+ goto done;
+ }
+ }
+
+ retval = run_command (f, argc, argv, fms);
+
+ done:
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
+ r->value.number = retval;
+
+ return r;
+}
+
+static CamelSExpResult *
+junk_test (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelSExpResult *r;
+ CamelMessageInfo *info = fms->info;
+ CamelJunkFilter *junk_filter;
+ CamelMessageFlags flags;
+ CamelMimeMessage *message;
+ CamelJunkStatus status;
+ const GHashTable *ht;
+ const CamelHeaderParam *node;
+ gboolean sender_is_known;
+ gboolean message_is_junk = FALSE;
+ GError *error = NULL;
+
+ /* Check if the message is already classified. */
+
+ flags = camel_message_info_get_flags (info);
+
+ if (flags & CAMEL_MESSAGE_JUNK) {
+ message_is_junk = TRUE;
+ if (camel_debug ("junk"))
+ printf (
+ "Message has a Junk flag set already, "
+ "skipping junk test...\n");
+ goto done;
+ }
+
+ if (flags & CAMEL_MESSAGE_NOTJUNK) {
+ if (camel_debug ("junk"))
+ printf (
+ "Message has a NotJunk flag set already, "
+ "skipping junk test...\n");
+ goto done;
+ }
+
+ /* If the sender is known, the message is not junk.
+ Do this before header test, to be able to override server-side set headers. */
+
+ sender_is_known = camel_session_lookup_addressbook (
+ fms->session, camel_message_info_get_from (info));
+ if (camel_debug ("junk"))
+ printf (
+ "Sender '%s' in book? %d\n",
+ camel_message_info_get_from (info),
+ sender_is_known);
+ if (sender_is_known)
+ goto done;
+
+ /* Check the headers for a junk designation. */
+
+ ht = camel_session_get_junk_headers (fms->session);
+ node = camel_message_info_get_headers (info);
+
+ while (node != NULL) {
+ const gchar *value = NULL;
+
+ if (node->name != NULL)
+ value = g_hash_table_lookup (
+ (GHashTable *) ht, node->name);
+
+ message_is_junk =
+ (value != NULL) &&
+ (camel_strstrcase (node->value, value) != NULL);
+
+ if (message_is_junk) {
+ if (camel_debug ("junk"))
+ printf (
+ "Message contains \"%s: %s\"",
+ node->name, value);
+ goto done;
+ }
+
+ node = node->next;
+ }
+
+ /* Not every message info has headers available, thus try headers of the message itself */
+ message = camel_filter_search_get_message (fms, f);
+ if (message) {
+ struct _camel_header_raw *h;
+
+ for (h = CAMEL_MIME_PART (message)->headers; h; h = h->next) {
+ const gchar *value;
+
+ if (!h->name)
+ continue;
+
+ value = g_hash_table_lookup ((GHashTable *) ht, h->name);
+ if (!value)
+ continue;
+
+ message_is_junk = camel_strstrcase (h->value, value) != NULL;
+
+ if (message_is_junk) {
+ if (camel_debug ("junk"))
+ printf (
+ "Message contains \"%s: %s\"",
+ h->name, value);
+ goto done;
+ }
+ }
+ } else {
+ goto done;
+ }
+
+ /* Consult 3rd party junk filtering software. */
+
+ junk_filter = camel_session_get_junk_filter (fms->session);
+ if (junk_filter == NULL)
+ goto done;
+
+ status = camel_junk_filter_classify (
+ junk_filter, message, fms->cancellable, &error);
+
+ if (error == NULL) {
+ const gchar *status_desc;
+
+ switch (status) {
+ case CAMEL_JUNK_STATUS_INCONCLUSIVE:
+ status_desc = "inconclusive";
+ message_is_junk = FALSE;
+ break;
+ case CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK:
+ status_desc = "junk";
+ message_is_junk = TRUE;
+ break;
+ case CAMEL_JUNK_STATUS_MESSAGE_IS_NOT_JUNK:
+ status_desc = "not junk";
+ message_is_junk = FALSE;
+ break;
+ default:
+ g_warn_if_reached ();
+ status_desc = "invalid";
+ message_is_junk = FALSE;
+ break;
+ }
+
+ if (camel_debug ("junk"))
+ printf (
+ "Junk filter classification: %s\n",
+ status_desc);
+ } else {
+ g_warn_if_fail (status == CAMEL_JUNK_STATUS_ERROR);
+ if (camel_debug ("junk"))
+ printf ("Junk classify failed with error: %s\n", error->message);
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ message_is_junk = FALSE;
+ }
+
+ done:
+ if (camel_debug ("junk"))
+ printf (
+ "Message is determined to be %s\n",
+ message_is_junk ? "*JUNK*" : "clean");
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
+ r->value.number = message_is_junk;
+
+ return r;
+}
+
+/* this is copied from Evolution's libemail-engine/e-mail-folder-utils.c */
+static gchar *
+mail_folder_uri_build (CamelStore *store,
+ const gchar *folder_name)
+{
+ const gchar *uid;
+ gchar *encoded_name;
+ gchar *encoded_uid;
+ gchar *uri;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+ g_return_val_if_fail (folder_name != NULL, NULL);
+
+ /* Skip the leading slash, if present. */
+ if (*folder_name == '/')
+ folder_name++;
+
+ uid = camel_service_get_uid (CAMEL_SERVICE (store));
+
+ encoded_uid = camel_url_encode (uid, ":;@/");
+ encoded_name = camel_url_encode (folder_name, "#");
+
+ uri = g_strdup_printf ("folder://%s/%s", encoded_uid, encoded_name);
+
+ g_free (encoded_uid);
+ g_free (encoded_name);
+
+ return uri;
+}
+
+static CamelSExpResult *
+message_location (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ FilterMessageSearch *fms)
+{
+ CamelSExpResult *r;
+ gboolean same = FALSE;
+
+ if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_STRING)
+ camel_sexp_fatal_error (f, _("Invalid arguments to (message-location)"));
+
+ if (fms->folder && argv[0]->value.string) {
+ CamelStore *store;
+ const gchar *name;
+ gchar *uri;
+
+ store = camel_folder_get_parent_store (fms->folder);
+ name = camel_folder_get_full_name (fms->folder);
+ uri = mail_folder_uri_build (store, name);
+
+ same = g_str_equal (uri, argv[0]->value.string);
+
+ g_free (uri);
+ }
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = same;
+
+ return r;
+}
+
+/**
+ * camel_filter_search_match:
+ * @session:
+ * @get_message: (scope async): function to retrieve the message if necessary
+ * @user_data: data for above
+ * @info:
+ * @source:
+ * @folder: in which folder the message is stored
+ * @expression:
+ * @cancellable: (allow-none): a #GCancellable, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Returns: one of CAMEL_SEARCH_MATCHED, CAMEL_SEARCH_NOMATCH, or
+ * CAMEL_SEARCH_ERROR.
+ **/
+gint
+camel_filter_search_match (CamelSession *session,
+ CamelFilterSearchGetMessageFunc get_message,
+ gpointer user_data,
+ CamelMessageInfo *info,
+ const gchar *source,
+ CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ FilterMessageSearch fms;
+ CamelSExp *sexp;
+ CamelSExpResult *result;
+ gboolean retval;
+ GError *local_error = NULL;
+ gint i;
+
+ fms.session = session;
+ fms.get_message = get_message;
+ fms.get_message_data = user_data;
+ fms.message = NULL;
+ fms.info = info;
+ fms.source = source;
+ fms.folder = folder;
+ fms.cancellable = cancellable;
+ fms.error = &local_error;
+
+ sexp = camel_sexp_new ();
+
+ for (i = 0; i < G_N_ELEMENTS (symbols); i++) {
+ if (symbols[i].type == 1)
+ camel_sexp_add_ifunction (sexp, 0, symbols[i].name, (CamelSExpIFunc) symbols[i].func, &fms);
+ else
+ camel_sexp_add_function (sexp, 0, symbols[i].name, symbols[i].func, &fms);
+ }
+
+ camel_sexp_input_text (sexp, expression, strlen (expression));
+ if (camel_sexp_parse (sexp) == -1) {
+ if (!local_error) {
+ /* A filter search is a search through your filters,
+ * ie. your filters is the corpus being searched thru. */
+ g_set_error (
+ &local_error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Error executing filter search: %s: %s"),
+ camel_sexp_error (sexp), expression);
+ }
+ goto error;
+ }
+
+ result = camel_sexp_eval (sexp);
+ if (result == NULL) {
+ if (!local_error)
+ g_set_error (
+ &local_error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Error executing filter search: %s: %s"),
+ camel_sexp_error (sexp), expression);
+ goto error;
+ }
+
+ if (local_error) {
+ camel_sexp_result_free (sexp, result);
+ goto error;
+ }
+
+ if (result->type == CAMEL_SEXP_RES_BOOL)
+ retval = result->value.boolean ? CAMEL_SEARCH_MATCHED : CAMEL_SEARCH_NOMATCH;
+ else
+ retval = CAMEL_SEARCH_NOMATCH;
+
+ camel_sexp_result_free (sexp, result);
+ g_object_unref (sexp);
+
+ if (fms.message)
+ g_object_unref (fms.message);
+
+ return retval;
+
+ error:
+ if (fms.message)
+ g_object_unref (fms.message);
+
+ g_object_unref (sexp);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return CAMEL_SEARCH_ERROR;
+}
diff --git a/src/camel/camel-filter-search.h b/src/camel/camel-filter-search.h
new file mode 100644
index 000000000..a1669d6f9
--- /dev/null
+++ b/src/camel/camel-filter-search.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ * Michael Zucchi <NotZed@Ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_FILTER_SEARCH_H
+#define CAMEL_FILTER_SEARCH_H
+
+#include <camel/camel-mime-message.h>
+#include <camel/camel-folder-summary.h>
+
+G_BEGIN_DECLS
+
+struct _CamelSession;
+
+enum {
+ CAMEL_SEARCH_ERROR = -1,
+ CAMEL_SEARCH_NOMATCH = 0,
+ CAMEL_SEARCH_MATCHED = 1
+};
+
+typedef CamelMimeMessage * (*CamelFilterSearchGetMessageFunc) (gpointer data, GCancellable *cancellable, GError **error);
+
+gint camel_filter_search_match (struct _CamelSession *session,
+ CamelFilterSearchGetMessageFunc get_message,
+ gpointer user_data,
+ CamelMessageInfo *info,
+ const gchar *source,
+ struct _CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_FILTER_SEARCH_H */
diff --git a/src/camel/camel-folder-search.c b/src/camel/camel-folder-search.c
new file mode 100644
index 000000000..ffb4add31
--- /dev/null
+++ b/src/camel/camel-folder-search.c
@@ -0,0 +1,2243 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/* 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. */
+
+#include "evolution-data-server-config.h"
+
+/* POSIX requires <sys/types.h> be included before <regex.h> */
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <regex.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-folder-search.h"
+#include "camel-folder-thread.h"
+#include "camel-iconv.h"
+#include "camel-medium.h"
+#include "camel-mime-message.h"
+#include "camel-multipart.h"
+#include "camel-search-private.h"
+#include "camel-stream-mem.h"
+#include "camel-db.h"
+#include "camel-debug.h"
+#include "camel-store.h"
+#include "camel-vee-folder.h"
+#include "camel-string-utils.h"
+#include "camel-search-sql-sexp.h"
+
+#define d(x)
+#define r(x)
+#define dd(x) if (camel_debug("search")) x
+
+#define CAMEL_FOLDER_SEARCH_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_FOLDER_SEARCH, CamelFolderSearchPrivate))
+
+struct _CamelFolderSearchPrivate {
+ GCancellable *cancellable;
+ GError **error;
+
+ CamelFolderThread *threads;
+ GHashTable *threads_hash;
+
+ /* Pointers to strings that were pooled with camel_pstring_strdup()
+ * or camel_pstring_add() during matching, where we own the reference(s).
+ *
+ * A pointer value may occur multiple times, once for each reference held.
+ * These references are released once references have been added to the final
+ * set of matches.
+ *
+ * This saves us having to perform more complex UID life cycle management
+ * and the overhead from the additional refs/unrefs it would require. */
+ GPtrArray *owned_pstrings;
+};
+
+typedef enum {
+ CAMEL_FOLDER_SEARCH_NONE = 0,
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER = 1 << 0,
+ CAMEL_FOLDER_SEARCH_IMMEDIATE = 1 << 1
+} CamelFolderSearchFlags;
+
+static struct {
+ const gchar *name;
+ goffset offset;
+ CamelFolderSearchFlags flags;
+} builtins[] = {
+ /* these have default implementations in CamelSExp */
+
+ { "and",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, and_),
+ CAMEL_FOLDER_SEARCH_IMMEDIATE },
+
+ { "or",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, or_),
+ CAMEL_FOLDER_SEARCH_IMMEDIATE },
+
+ /* we need to override this one though to implement an 'array not' */
+ { "not",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, not_),
+ CAMEL_FOLDER_SEARCH_NONE },
+
+ { "<",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, lt),
+ CAMEL_FOLDER_SEARCH_IMMEDIATE },
+
+ { ">",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, gt),
+ CAMEL_FOLDER_SEARCH_IMMEDIATE },
+
+ { "=",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, eq),
+ CAMEL_FOLDER_SEARCH_IMMEDIATE },
+
+ /* 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",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, match_all),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER |
+ CAMEL_FOLDER_SEARCH_IMMEDIATE },
+
+ { "match-threads",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, match_threads),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER |
+ CAMEL_FOLDER_SEARCH_IMMEDIATE },
+
+ { "body-contains",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, body_contains),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "body-regex",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, body_regex),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "header-contains",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, header_contains),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "header-matches",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, header_matches),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "header-starts-with",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, header_starts_with),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "header-ends-with",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, header_ends_with),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "header-exists",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, header_exists),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "header-soundex",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, header_soundex),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "header-regex",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, header_regex),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "header-full-regex",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, header_full_regex),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "user-tag",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, user_tag),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "user-flag",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, user_flag),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "system-flag",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, system_flag),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "get-sent-date",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, get_sent_date),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "get-received-date",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, get_received_date),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "get-current-date",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, get_current_date),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "get-relative-months",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, get_relative_months),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "get-size",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, get_size),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "uid",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, uid),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+
+ { "message-location",
+ G_STRUCT_OFFSET (CamelFolderSearchClass, message_location),
+ CAMEL_FOLDER_SEARCH_ALWAYS_ENTER },
+};
+
+G_DEFINE_TYPE (CamelFolderSearch, camel_folder_search, G_TYPE_OBJECT)
+
+/* this is just to OR results together */
+struct IterData {
+ gint count;
+ GPtrArray *uids;
+};
+
+/* or, store all unique values */
+static void
+htor (gchar *key,
+ gint value,
+ struct IterData *iter_data)
+{
+ g_ptr_array_add (iter_data->uids, key);
+}
+
+/* and, only store duplicates */
+static void
+htand (gchar *key,
+ gint value,
+ struct IterData *iter_data)
+{
+ if (value == iter_data->count)
+ g_ptr_array_add (iter_data->uids, key);
+}
+
+static void
+add_thread_results (CamelFolderThreadNode *root,
+ GHashTable *result_hash)
+{
+ while (root) {
+ g_hash_table_insert (result_hash, (gchar *) camel_message_info_get_uid (root->message), GINT_TO_POINTER (1));
+ if (root->child)
+ add_thread_results (root->child, result_hash);
+ root = root->next;
+ }
+}
+
+static void
+add_results (gchar *uid,
+ gpointer dummy,
+ GPtrArray *result)
+{
+ g_ptr_array_add (result, uid);
+}
+
+static void
+fill_thread_table (CamelFolderThreadNode *root,
+ GHashTable *id_hash)
+{
+ while (root) {
+ g_hash_table_insert (id_hash, (gchar *) camel_message_info_get_uid (root->message), root);
+ if (root->child)
+ fill_thread_table (root->child, id_hash);
+ root = root->next;
+ }
+}
+
+static CamelMimeMessage *
+get_current_message (CamelFolderSearch *search)
+{
+ if (!search || !search->folder || !search->current)
+ return NULL;
+
+ return camel_folder_get_message_sync (
+ search->folder, search->current->uid, search->priv->cancellable, NULL);
+}
+
+static CamelSExpResult *
+check_header (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search,
+ camel_search_match_t how)
+{
+ CamelSExpResult *r;
+ gint truth = FALSE;
+
+ r (printf ("executing check-header %d\n", how));
+
+ /* are we inside a match-all? */
+ if (search->current && argc > 1
+ && argv[0]->type == CAMEL_SEXP_RES_STRING
+ && !g_cancellable_is_cancelled (search->priv->cancellable)) {
+ gchar *headername;
+ const gchar *header = NULL, *charset = NULL;
+ gchar strbuf[32];
+ gint i, j;
+ camel_search_t type = CAMEL_SEARCH_TYPE_ASIS;
+ struct _camel_search_words *words;
+ CamelMimeMessage *message = NULL;
+ struct _camel_header_raw *raw_header;
+
+ /* only a subset of headers are supported .. */
+ headername = argv[0]->value.string;
+ if (!g_ascii_strcasecmp (headername, "subject")) {
+ header = camel_message_info_get_subject (search->current);
+ } else if (!g_ascii_strcasecmp (headername, "date")) {
+ /* FIXME: not a very useful form of the date */
+ g_snprintf (
+ strbuf, sizeof (strbuf), "%d",
+ (gint) camel_message_info_get_date_sent (search->current));
+ header = strbuf;
+ } else if (!g_ascii_strcasecmp (headername, "from")) {
+ header = camel_message_info_get_from (search->current);
+ type = CAMEL_SEARCH_TYPE_ADDRESS;
+ } else if (!g_ascii_strcasecmp (headername, "to")) {
+ header = camel_message_info_get_to (search->current);
+ type = CAMEL_SEARCH_TYPE_ADDRESS;
+ } else if (!g_ascii_strcasecmp (headername, "cc")) {
+ header = camel_message_info_get_cc (search->current);
+ type = CAMEL_SEARCH_TYPE_ADDRESS;
+ } else if (!g_ascii_strcasecmp (headername, "x-camel-mlist")) {
+ header = camel_message_info_get_mlist (search->current);
+ type = CAMEL_SEARCH_TYPE_MLIST;
+ } else {
+ message = get_current_message (search);
+ if (message) {
+ CamelContentType *ct = camel_mime_part_get_content_type (CAMEL_MIME_PART (message));
+
+ if (ct) {
+ charset = camel_content_type_param (ct, "charset");
+ charset = camel_iconv_charset_name (charset);
+ }
+ }
+ }
+
+ if (header == NULL)
+ header = "";
+
+ /* performs an OR of all words */
+ for (i = 1; i < argc && !truth; i++) {
+ if (argv[i]->type == CAMEL_SEXP_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 ((const guchar *) argv[i]->value.string);
+ truth = TRUE;
+ for (j = 0; j < words->len && truth; j++) {
+ if (message) {
+ for (raw_header = ((CamelMimePart *) message)->headers; raw_header; raw_header = raw_header->next) {
+ /* empty name means any header */
+ if (!headername || !*headername || !g_ascii_strcasecmp (raw_header->name, headername)) {
+ if (camel_search_header_match (raw_header->value, words->words[j]->word, how, type, charset))
+ break;
+ }
+ }
+
+ truth = raw_header != NULL;
+ } else
+ truth = camel_search_header_match (
+ header,
+ words->words[j]->word,
+ how, type, charset);
+ }
+ camel_search_words_free (words);
+ } else {
+ if (message) {
+ for (raw_header = ((CamelMimePart *) message)->headers; raw_header && !truth; raw_header = raw_header->next) {
+ /* empty name means any header */
+ if (!headername || !*headername || !g_ascii_strcasecmp (raw_header->name, headername)) {
+ truth = camel_search_header_match (
+ raw_header->value,
+ argv[i]->value.string,
+ how, type, charset);
+ }
+ }
+ } else
+ truth = camel_search_header_match (
+ header,
+ argv[i]->value.string,
+ how, type, charset);
+ }
+ }
+ }
+
+ if (message)
+ g_object_unref (message);
+ }
+ /* TODO: else, find all matches */
+
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = truth;
+
+ return r;
+}
+
+static gint
+match_message_index (CamelIndex *idx,
+ const gchar *uid,
+ const gchar *match,
+ GError **error)
+{
+ CamelIndexCursor *wc, *nc;
+ const gchar *word, *name;
+ gint 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;
+ g_object_unref (nc);
+ }
+ }
+ }
+ g_object_unref (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,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderSearchPrivate *p;
+ GPtrArray *result = g_ptr_array_new ();
+ struct IterData lambdafoo;
+ CamelIndexCursor *wc, *nc;
+ const gchar *word, *name;
+ gint i;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return result;
+
+ p = search->priv;
+
+ g_return_val_if_fail (p->owned_pstrings != NULL, result);
+
+ /* 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) {
+ GHashTable *ht = g_hash_table_new (g_str_hash, g_str_equal);
+
+ 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))) {
+ gchar *name_owned;
+ gint mask;
+
+ name_owned = (gchar *) camel_pstring_strdup (name);
+ g_ptr_array_add (p->owned_pstrings, name_owned);
+
+ mask = (GPOINTER_TO_INT (g_hash_table_lookup (ht, name_owned))) | (1 << i);
+ g_hash_table_insert (
+ ht,
+ name_owned,
+ GINT_TO_POINTER (mask));
+ }
+ g_object_unref (nc);
+ }
+ }
+ }
+ }
+ g_object_unref (wc);
+
+ lambdafoo.uids = result;
+ lambdafoo.count = (1 << words->len) - 1;
+ g_hash_table_foreach (ht, (GHFunc) htand, &lambdafoo);
+ g_hash_table_destroy (ht);
+ }
+
+ return result;
+}
+
+static gboolean
+match_words_1message (CamelDataWrapper *object,
+ struct _camel_search_words *words,
+ guint32 *mask,
+ GCancellable *cancellable)
+{
+ CamelDataWrapper *containee;
+ gint truth = FALSE;
+ gint parts, i;
+
+ if (g_cancellable_is_cancelled (cancellable))
+ return FALSE;
+
+ containee = camel_medium_get_content (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, cancellable);
+ }
+ } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
+ /* for messages we only look at its contents */
+ truth = match_words_1message ((CamelDataWrapper *) containee, words, mask, cancellable);
+ } 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 */
+ CamelStream *stream;
+ GByteArray *byte_array;
+ const gchar *charset;
+
+ byte_array = g_byte_array_new ();
+ stream = camel_stream_mem_new_with_byte_array (byte_array);
+
+ charset = camel_content_type_param (CAMEL_DATA_WRAPPER (containee)->mime_type, "charset");
+ if (charset && *charset) {
+ CamelMimeFilter *filter = camel_mime_filter_charset_new (charset, "UTF-8");
+ if (filter) {
+ CamelStream *filtered = camel_stream_filter_new (stream);
+
+ if (filtered) {
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (filtered), filter);
+ g_object_unref (stream);
+ stream = filtered;
+ }
+
+ g_object_unref (filter);
+ }
+ }
+
+ /* FIXME The match should be part of a stream op */
+ camel_data_wrapper_decode_to_stream_sync (
+ containee, stream, cancellable, NULL);
+ camel_stream_write (stream, "", 1, NULL, NULL);
+ for (i = 0; i < words->len; i++) {
+ /* FIXME: This is horridly slow, and should use a real search algorithm */
+ if (camel_ustrstrcase ((const gchar *) byte_array->data, words->words[i]->word) != NULL) {
+ *mask |= (1 << i);
+ /* shortcut a match */
+ if (*mask == (1 << (words->len)) - 1)
+ return TRUE;
+ }
+ }
+
+ g_object_unref (stream);
+ }
+
+ return truth;
+}
+
+static gboolean
+match_words_message (CamelFolder *folder,
+ const gchar *uid,
+ struct _camel_search_words *words,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint32 mask;
+ CamelMimeMessage *msg;
+ gint truth = FALSE;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return truth;
+
+ msg = camel_folder_get_message_sync (folder, uid, cancellable, error);
+ if (msg) {
+ mask = 0;
+ truth = match_words_1message ((CamelDataWrapper *) msg, words, &mask, cancellable);
+ g_object_unref (msg);
+ }
+
+ return truth;
+}
+
+static GPtrArray *
+match_words_messages (CamelFolderSearch *search,
+ struct _camel_search_words *words,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint i;
+ GPtrArray *matches = g_ptr_array_new ();
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return matches;
+
+ if (search->body_index) {
+ GPtrArray *indexed;
+ struct _camel_search_words *simple;
+
+ simple = camel_search_words_simple (words);
+ indexed = match_words_index (search, simple, cancellable, error);
+ camel_search_words_free (simple);
+
+ for (i = 0; i < indexed->len && !g_cancellable_is_cancelled (cancellable); i++) {
+ const gchar *uid = g_ptr_array_index (indexed, i);
+
+ if (match_words_message (
+ search->folder, uid, words,
+ cancellable, error))
+ g_ptr_array_add (matches, (gchar *) uid);
+ }
+
+ g_ptr_array_free (indexed, TRUE);
+ } else {
+ GPtrArray *v = search->summary_set ? search->summary_set : search->summary;
+
+ for (i = 0; i < v->len && !g_cancellable_is_cancelled (cancellable); i++) {
+ gchar *uid = g_ptr_array_index (v, i);
+
+ if (match_words_message (
+ search->folder, uid, words,
+ cancellable, error))
+ g_ptr_array_add (matches, (gchar *) uid);
+ }
+ }
+
+ return matches;
+}
+
+static gint
+read_uid_callback (gpointer ref,
+ gint ncol,
+ gchar **cols,
+ gchar **name)
+{
+ GPtrArray *matches;
+
+ matches = (GPtrArray *) ref;
+
+ g_ptr_array_add (matches, (gpointer) camel_pstring_strdup (cols[0]));
+ return 0;
+}
+
+/* dummy function, returns false always, or an empty match array */
+static CamelSExpResult *
+folder_search_dummy (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+
+ if (search->current == NULL) {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = FALSE;
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_header_has_words (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ return check_header (sexp, argc, argv, search, CAMEL_SEARCH_MATCH_WORD);
+}
+
+static void
+folder_search_dispose (GObject *object)
+{
+ CamelFolderSearch *search = CAMEL_FOLDER_SEARCH (object);
+
+ if (search->sexp != NULL) {
+ g_object_unref (search->sexp);
+ search->sexp = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_folder_search_parent_class)->dispose (object);
+}
+
+static void
+folder_search_finalize (GObject *object)
+{
+ CamelFolderSearch *search = CAMEL_FOLDER_SEARCH (object);
+
+ g_free (search->last_search);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_folder_search_parent_class)->finalize (object);
+}
+
+static void
+folder_search_constructed (GObject *object)
+{
+ CamelFolderSearch *search;
+ CamelFolderSearchClass *class;
+ gint ii;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (camel_folder_search_parent_class)->constructed (object);
+
+ search = CAMEL_FOLDER_SEARCH (object);
+ class = CAMEL_FOLDER_SEARCH_GET_CLASS (search);
+
+ /* Register class methods with the CamelSExp. */
+ for (ii = 0; ii < G_N_ELEMENTS (builtins); ii++) {
+ CamelFolderSearchFlags flags;
+ const gchar *name;
+ goffset offset;
+ gpointer func;
+
+ name = builtins[ii].name;
+ flags = builtins[ii].flags;
+ offset = builtins[ii].offset;
+
+ /* c is sure messy sometimes */
+ func = *((gpointer *)(((gchar *) class) + offset));
+
+ if (func == NULL && flags & CAMEL_FOLDER_SEARCH_ALWAYS_ENTER) {
+ g_warning (
+ "%s doesn't implement '%s' method",
+ G_OBJECT_TYPE_NAME (search), name);
+ func = (gpointer) folder_search_dummy;
+ }
+ if (func != NULL) {
+ if (flags & CAMEL_FOLDER_SEARCH_IMMEDIATE) {
+ camel_sexp_add_ifunction (
+ search->sexp, 0, name,
+ (CamelSExpIFunc) func, search);
+ } else {
+ camel_sexp_add_function (
+ search->sexp, 0, name,
+ (CamelSExpFunc) func, search);
+ }
+ }
+ }
+
+ camel_sexp_add_function (
+ search->sexp, 0, "header-has-words",
+ (CamelSExpFunc) folder_search_header_has_words, search);
+}
+
+/* implement an 'array not', i.e. everything in the summary, not in the supplied array */
+static CamelSExpResult *
+folder_search_not (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+ gint i;
+
+ if (argc > 0) {
+ if (argv[0]->type == CAMEL_SEXP_RES_ARRAY_PTR) {
+ GPtrArray *v = argv[0]->value.ptrarray;
+ const gchar *uid;
+
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+
+ /* not against a single message?*/
+ if (search->current) {
+ gint found = FALSE;
+
+ uid = camel_message_info_get_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, (gchar *) 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);
+ gchar **s;
+ gchar **m;
+
+ s = (gchar **) 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 = (gchar **) v->pdata;
+ for (i = 0; i < v->len; i++) {
+ gchar *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 {
+ gint res = TRUE;
+
+ if (argv[0]->type == CAMEL_SEXP_RES_BOOL)
+ res = !argv[0]->value.boolean;
+
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = res;
+ }
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = TRUE;
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_match_all (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ CamelFolderSearch *search)
+{
+ gint i;
+ CamelSExpResult *r, *r1;
+ gchar *error_msg;
+ 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_get_subject (search->current)));
+
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = FALSE;
+
+ if (argc > 0) {
+ r1 = camel_sexp_term_eval (sexp, argv[0]);
+ if (r1->type == CAMEL_SEXP_RES_BOOL) {
+ r->value.boolean = r1->value.boolean;
+ } else {
+ g_warning ("invalid syntax, matches require a single bool result");
+ /* Translators: The '%s' is an element type name, part of an expressing language */
+ error_msg = g_strdup_printf (_("(%s) requires a single bool result"), "match-all");
+ camel_sexp_fatal_error (sexp, error_msg);
+ g_free (error_msg);
+ }
+ camel_sexp_result_free (sexp, r1);
+ } else {
+ r->value.boolean = TRUE;
+ }
+ return r;
+ }
+
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_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");
+ return r;
+ }
+
+ v = search->summary_set ? search->summary_set : search->summary;
+
+ if (!CAMEL_IS_VEE_FOLDER (search->folder)) {
+ camel_folder_summary_prepare_fetch_all (search->folder->summary, search->priv->error);
+ }
+
+ for (i = 0; i < v->len && !g_cancellable_is_cancelled (search->priv->cancellable); i++) {
+ const gchar *uid;
+
+ search->current = camel_folder_summary_get (search->folder->summary, v->pdata[i]);
+ if (!search->current)
+ continue;
+ uid = camel_message_info_get_uid (search->current);
+
+ if (argc > 0) {
+ r1 = camel_sexp_term_eval (sexp, argv[0]);
+ if (r1->type == CAMEL_SEXP_RES_BOOL) {
+ if (r1->value.boolean)
+ g_ptr_array_add (r->value.ptrarray, (gchar *) uid);
+ } else {
+ g_warning ("invalid syntax, matches require a single bool result");
+ /* Translators: The '%s' is an element type name, part of an expressing language */
+ error_msg = g_strdup_printf (_("(%s) requires a single bool result"), "match-all");
+ camel_sexp_fatal_error (sexp, error_msg);
+ g_free (error_msg);
+ }
+ camel_sexp_result_free (sexp, r1);
+ } else {
+ g_ptr_array_add (r->value.ptrarray, (gchar *) uid);
+ }
+ camel_message_info_unref (search->current);
+ }
+ search->current = NULL;
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_match_threads (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+ CamelFolderSearchPrivate *p = search->priv;
+ gint i, type;
+ GHashTable *results;
+ gchar *error_msg;
+
+ if (g_cancellable_is_cancelled (search->priv->cancellable)) {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ return r;
+ }
+
+ /* not supported in match-all */
+ if (search->current) {
+ /* Translators: Each '%s' is an element type name, part of an expressing language */
+ error_msg = g_strdup_printf (_("(%s) not allowed inside %s"), "match-threads", "match-all");
+ camel_sexp_fatal_error (sexp, error_msg);
+ g_free (error_msg);
+ }
+
+ if (argc == 0) {
+ /* Translators: The '%s' is an element type name, part of an expressing language */
+ error_msg = g_strdup_printf (_("(%s) requires a match type string"), "match-threads");
+ camel_sexp_fatal_error (sexp, error_msg);
+ g_free (error_msg);
+ }
+
+ r = camel_sexp_term_eval (sexp, argv[0]);
+ if (r->type != CAMEL_SEXP_RES_STRING) {
+ /* Translators: The '%s' is an element type name, part of an expressing language */
+ error_msg = g_strdup_printf (_("(%s) requires a match type string"), "match-threads");
+ camel_sexp_fatal_error (sexp, error_msg);
+ g_free (error_msg);
+ }
+
+ 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;
+ else if (!strcmp (r->value.string, "single"))
+ type = 4;
+ camel_sexp_result_free (sexp, r);
+
+ /* behave as (begin does */
+ r = NULL;
+ for (i = 1; i < argc; i++) {
+ if (r)
+ camel_sexp_result_free (sexp, r);
+ r = camel_sexp_term_eval (sexp, argv[i]);
+ }
+
+ if (r == NULL || r->type != CAMEL_SEXP_RES_ARRAY_PTR) {
+ /* Translators: The '%s' is an element type name, part of an expressing language */
+ error_msg = g_strdup_printf (_("(%s) expects an array result"), "match-threads");
+ camel_sexp_fatal_error (sexp, error_msg);
+ g_free (error_msg);
+ }
+
+ if (type == 0)
+ return r;
+
+ if (search->folder == NULL) {
+ /* Translators: The '%s' is an element type name, part of an expressing language */
+ error_msg = g_strdup_printf (_("(%s) requires the folder set"), "match-threads");
+ camel_sexp_fatal_error (sexp, error_msg);
+ g_free (error_msg);
+ }
+
+ /* 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 && !g_cancellable_is_cancelled (search->priv->cancellable); i++) {
+ CamelFolderThreadNode *node, *scan;
+
+ if (type != 4)
+ g_hash_table_insert (results, g_ptr_array_index (r->value.ptrarray, i), GINT_TO_POINTER (1));
+
+ node = g_hash_table_lookup (p->threads_hash, (gchar *) 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 == 4) {
+ if (node->child == NULL && node->parent == NULL)
+ g_hash_table_insert (results, (gchar *) camel_message_info_get_uid (node->message), GINT_TO_POINTER (1));
+ } else {
+ if (type == 3) {
+ scan = node;
+ /* coverity[check_after_deref] */
+ while (scan && scan->parent) {
+ scan = scan->parent;
+ g_hash_table_insert (results, (gchar *) camel_message_info_get_uid (scan->message), GINT_TO_POINTER (1));
+ }
+ } else if (type == 1) {
+ while (node != NULL && node->parent)
+ node = node->parent;
+ }
+ g_hash_table_insert (results, (gchar *) camel_message_info_get_uid (node->message), GINT_TO_POINTER (1));
+ if (node->child)
+ add_thread_results (node->child, results);
+ }
+ }
+ camel_sexp_result_free (sexp, r);
+
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_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 CamelSExpResult *
+folder_search_body_contains (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ gint i, j;
+ GError **error = search->priv->error;
+ struct _camel_search_words *words;
+ CamelSExpResult *r;
+ struct IterData lambdafoo;
+
+ if (search->current) {
+ gint truth = FALSE;
+
+ if (argc == 1 && argv[0]->value.string[0] == 0) {
+ truth = TRUE;
+ } else {
+ for (i = 0; i < argc && !truth && !g_cancellable_is_cancelled (search->priv->cancellable); i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING) {
+ words = camel_search_words_split ((const guchar *) 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_get_uid (search->current),
+ words->words[j]->word,
+ error);
+ } else {
+ /* TODO: cache current message incase of multiple body search terms */
+ truth = match_words_message (
+ search->folder,
+ camel_message_info_get_uid (search->current),
+ words,
+ search->priv->cancellable,
+ error);
+ }
+ camel_search_words_free (words);
+ }
+ }
+ }
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = truth;
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_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 && !g_cancellable_is_cancelled (search->priv->cancellable); i++) {
+ gchar *uid = g_ptr_array_index (v, i);
+
+ g_ptr_array_add (r->value.ptrarray, uid);
+ }
+ } else {
+ GHashTable *ht = g_hash_table_new (g_str_hash, g_str_equal);
+ GPtrArray *matches;
+
+ for (i = 0; i < argc && !g_cancellable_is_cancelled (search->priv->cancellable); i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING) {
+ words = camel_search_words_split ((const guchar *) argv[i]->value.string);
+ if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) {
+ matches = match_words_index (search, words, search->priv->cancellable, error);
+ } else {
+ matches = match_words_messages (search, words, search->priv->cancellable, error);
+ }
+ 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) htor, &lambdafoo);
+ g_hash_table_destroy (ht);
+ }
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_body_regex (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+ CamelMimeMessage *msg = get_current_message (search);
+
+ if (msg) {
+ regex_t pattern;
+
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+
+ if (!g_cancellable_is_cancelled (search->priv->cancellable) &&
+ camel_search_build_match_regex (
+ &pattern,
+ CAMEL_SEARCH_MATCH_ICASE |
+ CAMEL_SEARCH_MATCH_REGEX |
+ CAMEL_SEARCH_MATCH_NEWLINE,
+ argc, argv,
+ search->priv->error) == 0) {
+ r->value.boolean = camel_search_message_body_contains ((CamelDataWrapper *) msg, &pattern);
+ regfree (&pattern);
+ } else
+ r->value.boolean = FALSE;
+
+ g_object_unref (msg);
+ } else {
+ regex_t pattern;
+
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+
+ if (!g_cancellable_is_cancelled (search->priv->cancellable) &&
+ camel_search_build_match_regex (
+ &pattern,
+ CAMEL_SEARCH_MATCH_ICASE |
+ CAMEL_SEARCH_MATCH_REGEX |
+ CAMEL_SEARCH_MATCH_NEWLINE,
+ argc, argv,
+ search->priv->error) == 0) {
+ gint i;
+ GPtrArray *v = search->summary_set ? search->summary_set : search->summary;
+ CamelMimeMessage *message;
+
+ for (i = 0; i < v->len && !g_cancellable_is_cancelled (search->priv->cancellable); i++) {
+ gchar *uid = g_ptr_array_index (v, i);
+
+ message = camel_folder_get_message_sync (
+ search->folder, uid, search->priv->cancellable, NULL);
+ if (message) {
+ if (camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern)) {
+ g_ptr_array_add (r->value.ptrarray, uid);
+ }
+
+ g_object_unref (message);
+ }
+ }
+
+ regfree (&pattern);
+ }
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_header_contains (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ return check_header (sexp, argc, argv, search, CAMEL_SEARCH_MATCH_CONTAINS);
+}
+
+static CamelSExpResult *
+folder_search_header_matches (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ return check_header (sexp, argc, argv, search, CAMEL_SEARCH_MATCH_EXACT);
+}
+
+static CamelSExpResult *
+folder_search_header_starts_with (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ return check_header (sexp, argc, argv, search, CAMEL_SEARCH_MATCH_STARTS);
+}
+
+static CamelSExpResult *
+folder_search_header_ends_with (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ return check_header (sexp, argc, argv, search, CAMEL_SEARCH_MATCH_ENDS);
+}
+
+static CamelSExpResult *
+folder_search_header_exists (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+
+ r (printf ("executing header-exists\n"));
+
+ if (search->current) {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ if (argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING)
+ r->value.boolean = camel_medium_get_header (CAMEL_MEDIUM (search->current), argv[0]->value.string) != NULL;
+
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_header_soundex (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ return check_header (sexp, argc, argv, search, CAMEL_SEARCH_MATCH_SOUNDEX);
+}
+
+static CamelSExpResult *
+folder_search_header_regex (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+ CamelMimeMessage *msg;
+
+ msg = get_current_message (search);
+
+ if (msg) {
+ regex_t pattern;
+ const gchar *contents;
+
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+
+ if (argc > 1 && argv[0]->type == CAMEL_SEXP_RES_STRING
+ && (contents = camel_medium_get_header (CAMEL_MEDIUM (msg), argv[0]->value.string))
+ && camel_search_build_match_regex (
+ &pattern,
+ CAMEL_SEARCH_MATCH_REGEX |
+ CAMEL_SEARCH_MATCH_ICASE,
+ argc - 1, argv + 1,
+ search->priv->error) == 0) {
+ gchar *decoded;
+ const gchar *header_name = argv[0]->value.string;
+
+ decoded = camel_search_get_header_decoded (header_name, contents, camel_search_get_default_charset_from_message (msg));
+ if (decoded)
+ contents = decoded;
+
+ r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
+ regfree (&pattern);
+
+ g_free (decoded);
+ } else
+ r->value.boolean = FALSE;
+
+ g_object_unref (msg);
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_header_full_regex (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+ CamelMimeMessage *msg;
+
+ msg = get_current_message (search);
+
+ if (msg) {
+ regex_t pattern;
+
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+
+ if (camel_search_build_match_regex (
+ &pattern,
+ CAMEL_SEARCH_MATCH_REGEX |
+ CAMEL_SEARCH_MATCH_ICASE |
+ CAMEL_SEARCH_MATCH_NEWLINE,
+ argc, argv,
+ search->priv->error) == 0) {
+ gchar *contents;
+
+ contents = camel_search_get_all_headers_decoded (msg);
+ r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
+
+ g_free (contents);
+ regfree (&pattern);
+ } else
+ r->value.boolean = FALSE;
+
+ g_object_unref (msg);
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_user_tag (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ const gchar *value = NULL;
+ CamelSExpResult *r;
+
+ r (printf ("executing user-tag\n"));
+
+ if (search->current && argc == 1)
+ value = camel_message_info_get_user_tag (search->current, argv[0]->value.string);
+
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_STRING);
+ r->value.string = g_strdup (value ? value : "");
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_user_flag (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+ gint i;
+
+ r (printf ("executing user-flag\n"));
+
+ /* are we inside a match-all? */
+ if (search->current) {
+ gint truth = FALSE;
+ /* performs an OR of all words */
+ for (i = 0; i < argc && !truth; i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING
+ && camel_message_info_get_user_flag (search->current, argv[i]->value.string)) {
+ truth = TRUE;
+ break;
+ }
+ }
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = truth;
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_system_flag (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+
+ r (printf ("executing system-flag\n"));
+
+ if (search->current) {
+ gboolean truth = FALSE;
+
+ if (argc == 1)
+ truth = camel_system_flag_get (camel_message_info_get_flags (search->current), argv[0]->value.string);
+
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = truth;
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_get_sent_date (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+
+ r (printf ("executing get-sent-date\n"));
+
+ /* are we inside a match-all? */
+ if (search->current) {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT);
+
+ r->value.number = camel_message_info_get_date_sent (search->current);
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_get_received_date (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+
+ r (printf ("executing get-received-date\n"));
+
+ /* are we inside a match-all? */
+ if (search->current) {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT);
+
+ r->value.number = camel_message_info_get_date_received (search->current);
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_get_current_date (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+
+ r (printf ("executing get-current-date\n"));
+
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT);
+ r->value.number = time (NULL);
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_get_relative_months (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+
+ r (printf ("executing get-relative-months\n"));
+
+ if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_INT) {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = FALSE;
+
+ g_debug ("%s: Expecting 1 argument, an integer, but got %d arguments", G_STRFUNC, argc);
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT);
+ r->value.number = camel_folder_search_util_add_months (time (NULL), argv[0]->value.number);
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_get_size (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+
+ r (printf ("executing get-size\n"));
+
+ /* are we inside a match-all? */
+ if (search->current) {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT);
+ r->value.number = camel_message_info_get_size (search->current) / 1024;
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+folder_search_uid (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+ gint i;
+
+ r (printf ("executing uid\n"));
+
+ /* are we inside a match-all? */
+ if (search->current) {
+ gint truth = FALSE;
+ const gchar *uid = camel_message_info_get_uid (search->current);
+
+ /* performs an OR of all words */
+ for (i = 0; i < argc && !truth; i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING
+ && !strcmp (uid, argv[i]->value.string)) {
+ truth = TRUE;
+ break;
+ }
+ }
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = truth;
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ for (i = 0; i < argc; i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING)
+ g_ptr_array_add (r->value.ptrarray, argv[i]->value.string);
+ }
+ }
+
+ return r;
+}
+
+/* this is copied from Evolution's libemail-engine/e-mail-folder-utils.c */
+static gchar *
+mail_folder_uri_build (CamelStore *store,
+ const gchar *folder_name)
+{
+ const gchar *uid;
+ gchar *encoded_name;
+ gchar *encoded_uid;
+ gchar *uri;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+ g_return_val_if_fail (folder_name != NULL, NULL);
+
+ /* Skip the leading slash, if present. */
+ if (*folder_name == '/')
+ folder_name++;
+
+ uid = camel_service_get_uid (CAMEL_SERVICE (store));
+
+ encoded_uid = camel_url_encode (uid, ":;@/");
+ encoded_name = camel_url_encode (folder_name, "#");
+
+ uri = g_strdup_printf ("folder://%s/%s", encoded_uid, encoded_name);
+
+ g_free (encoded_uid);
+ g_free (encoded_name);
+
+ return uri;
+}
+
+static CamelSExpResult *
+folder_search_message_location (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *r;
+ gboolean same = FALSE;
+
+ if (argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
+ if (argv[0]->value.string && search->folder) {
+ CamelStore *store;
+ const gchar *name;
+ gchar *uri;
+
+ store = camel_folder_get_parent_store (search->folder);
+ name = camel_folder_get_full_name (search->folder);
+ uri = mail_folder_uri_build (store, name);
+
+ same = g_str_equal (uri, argv[0]->value.string);
+
+ g_free (uri);
+ }
+ }
+
+ if (search->current) {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = same ? TRUE : FALSE;
+ } else {
+ r = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+
+ if (same) {
+ /* all matches */
+ gint i;
+ GPtrArray *v = search->summary_set ? search->summary_set : search->summary;
+
+ for (i = 0; i < v->len; i++) {
+ gchar *uid = g_ptr_array_index (v, i);
+
+ g_ptr_array_add (r->value.ptrarray, uid);
+ }
+ }
+ }
+
+ return r;
+}
+
+static void
+camel_folder_search_class_init (CamelFolderSearchClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelFolderSearchPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = folder_search_dispose;
+ object_class->finalize = folder_search_finalize;
+ object_class->constructed = folder_search_constructed;
+
+ class->not_ = folder_search_not;
+ class->match_all = folder_search_match_all;
+ class->match_threads = folder_search_match_threads;
+ class->body_contains = folder_search_body_contains;
+ class->body_regex = folder_search_body_regex;
+ class->header_contains = folder_search_header_contains;
+ class->header_matches = folder_search_header_matches;
+ class->header_starts_with = folder_search_header_starts_with;
+ class->header_ends_with = folder_search_header_ends_with;
+ class->header_exists = folder_search_header_exists;
+ class->header_soundex = folder_search_header_soundex;
+ class->header_regex = folder_search_header_regex;
+ class->header_full_regex = folder_search_header_full_regex;
+ class->user_tag = folder_search_user_tag;
+ class->user_flag = folder_search_user_flag;
+ class->system_flag = folder_search_system_flag;
+ class->get_sent_date = folder_search_get_sent_date;
+ class->get_received_date = folder_search_get_received_date;
+ class->get_current_date = folder_search_get_current_date;
+ class->get_relative_months = folder_search_get_relative_months;
+ class->get_size = folder_search_get_size;
+ class->uid = folder_search_uid;
+ class->message_location = folder_search_message_location;
+}
+
+static void
+camel_folder_search_init (CamelFolderSearch *search)
+{
+ search->priv = CAMEL_FOLDER_SEARCH_GET_PRIVATE (search);
+ search->sexp = camel_sexp_new ();
+}
+
+/**
+ * camel_folder_search_construct:
+ * @search: a #CamelFolderSearch
+ *
+ * This function used to register callbacks with @search's internal
+ * #CamelSExp, but this now happens during instance initialization.
+ *
+ * Deprecated: 3.8: The function no longer does anything.
+ **/
+void
+camel_folder_search_construct (CamelFolderSearch *search)
+{
+ /* XXX constructed() method handles what used to be here. */
+}
+
+/**
+ * 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.
+ *
+ * Returns: A new CamelFolderSearch widget.
+ **/
+CamelFolderSearch *
+camel_folder_search_new (void)
+{
+ return g_object_new (CAMEL_TYPE_FOLDER_SEARCH, NULL);
+}
+
+/**
+ * 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)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER_SEARCH (search));
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ search->folder = folder;
+}
+
+/**
+ * camel_folder_search_set_summary:
+ * @search:
+ * @summary: (element-type CamelMessageInfo): 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)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER_SEARCH (search));
+
+ search->summary = summary;
+}
+
+/**
+ * camel_folder_search_set_body_index:
+ * @search:
+ * @body_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 *body_index)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER_SEARCH (search));
+
+ if (body_index != NULL) {
+ g_return_if_fail (CAMEL_IS_INDEX (body_index));
+ g_object_ref (body_index);
+ }
+
+ if (search->body_index != NULL)
+ g_object_unref (search->body_index);
+
+ search->body_index = body_index;
+}
+
+static gboolean
+do_search_in_memory (CamelFolder *search_in_folder,
+ const gchar *expr,
+ gchar **psql_query)
+{
+ /* if the expression contains any of these tokens, then perform a memory search, instead of the SQL one */
+ const gchar *in_memory_tokens[] = {
+ "body-contains",
+ "body-regex",
+ "match-threads",
+ "message-location",
+ "header-soundex",
+ "header-regex",
+ "header-full-regex",
+ "header-contains",
+ "header-has-words",
+ "header-ends-with",
+ NULL };
+ gint i;
+
+ if (search_in_folder &&
+ search_in_folder->summary &&
+ (search_in_folder->summary->flags & CAMEL_FOLDER_SUMMARY_IN_MEMORY_ONLY) != 0)
+ return TRUE;
+
+ if (!expr)
+ return FALSE;
+
+ for (i = 0; in_memory_tokens[i]; i++) {
+ if (strstr (expr, in_memory_tokens[i]))
+ return TRUE;
+ }
+
+ *psql_query = camel_sexp_to_sql_sexp (expr);
+
+ /* unknown column can cause NULL sql_query, then an in-memory
+ * search is required */
+ return !*psql_query;
+}
+
+static void
+free_pstring_array (GPtrArray *array)
+{
+ if (!array)
+ return;
+
+ g_ptr_array_foreach (array, (GFunc) camel_pstring_free, NULL);
+ g_ptr_array_free (array, TRUE);
+}
+
+/**
+ * camel_folder_search_count:
+ * @search:
+ * @expr:
+ * @cancellable: a #GCancellable
+ * @error: return location for a #GError, or %NULL
+ *
+ * Run a search. Search must have had Folder already set on it, and
+ * it must implement summaries.
+ *
+ * Returns: Number of messages that match the query.
+ *
+ * Since: 2.26
+ **/
+
+guint32
+camel_folder_search_count (CamelFolderSearch *search,
+ const gchar *expr,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSExpResult *r;
+ GPtrArray *summary_set;
+ gint i;
+ CamelDB *cdb;
+ gchar *sql_query = NULL, *tmp, *tmp1;
+ GHashTable *results;
+ guint32 count = 0;
+
+ CamelFolderSearchPrivate *p;
+
+ g_return_val_if_fail (search != NULL, 0);
+
+ p = search->priv;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ goto fail;
+
+ if (!expr || !*expr)
+ expr = "(match-all)";
+
+ if (!search->folder) {
+ g_warn_if_reached ();
+ goto fail;
+ }
+
+ p->owned_pstrings = g_ptr_array_new ();
+ p->cancellable = cancellable;
+ p->error = error;
+
+ /* We route body-contains search and thread based search through memory and not via db. */
+ if (do_search_in_memory (search->folder, expr, &sql_query)) {
+ /* setup our search list only contains those we're interested in */
+ search->summary = camel_folder_get_summary (search->folder);
+ if (search->folder->summary)
+ camel_folder_summary_prepare_fetch_all (search->folder->summary, NULL);
+
+ summary_set = search->summary;
+
+ /* only re-parse if the search has changed */
+ if (search->last_search == NULL
+ || strcmp (search->last_search, expr)) {
+ camel_sexp_input_text (search->sexp, expr, strlen (expr));
+ if (camel_sexp_parse (search->sexp) == -1) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot parse search expression: %s:\n%s"),
+ camel_sexp_error (search->sexp), expr);
+ goto fail;
+ }
+
+ g_free (search->last_search);
+ search->last_search = g_strdup (expr);
+ }
+ r = camel_sexp_eval (search->sexp);
+ if (r == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Error executing search expression: %s:\n%s"),
+ camel_sexp_error (search->sexp), expr);
+ goto fail;
+ }
+
+ /* now create a folder summary to return?? */
+ if (r->type == CAMEL_SEXP_RES_ARRAY_PTR) {
+ d (printf ("got result\n"));
+
+ /* 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", (gchar *) 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++) {
+ gchar *uid = g_ptr_array_index (summary_set, i);
+ if (g_hash_table_lookup (results, uid))
+ count++;
+ }
+ g_hash_table_destroy (results);
+ }
+
+ camel_sexp_result_free (search->sexp, r);
+
+ } else {
+ CamelStore *parent_store;
+ const gchar *full_name;
+ GError *local_error = NULL;
+
+ full_name = camel_folder_get_full_name (search->folder);
+ parent_store = camel_folder_get_parent_store (search->folder);
+
+ /* Sync the db, so that we search the db for changes */
+ camel_folder_summary_save_to_db (search->folder->summary, error);
+
+ dd (printf ("sexp is : [%s]\n", expr));
+ tmp1 = camel_db_sqlize_string (full_name);
+ tmp = g_strdup_printf ("SELECT COUNT (*) FROM %s %s %s", tmp1, sql_query ? "WHERE" : "", sql_query ? sql_query : "");
+ camel_db_free_sqlized_string (tmp1);
+ g_free (sql_query);
+ dd (printf ("Equivalent sql %s\n", tmp));
+
+ cdb = (CamelDB *) (parent_store->cdb_r);
+ camel_db_count_message_info (cdb, tmp, &count, &local_error);
+ if (local_error != NULL) {
+ const gchar *message = local_error->message;
+ if (strncmp (message, "no such table", 13) == 0) {
+ d (g_warning ("Error during searching %s: %s\n", tmp, message));
+ /* Suppress no such table */
+ g_clear_error (&local_error);
+ }
+ g_propagate_error (error, local_error);
+ }
+ g_free (tmp);
+ }
+
+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);
+ if (search->summary)
+ camel_folder_free_summary (search->folder, search->summary);
+
+ free_pstring_array (p->owned_pstrings);
+ p->owned_pstrings = NULL;
+ p->cancellable = NULL;
+ p->error = NULL;
+ p->threads = NULL;
+ p->threads_hash = NULL;
+ search->folder = NULL;
+ search->summary = NULL;
+ search->summary_set = NULL;
+ search->current = NULL;
+ search->body_index = NULL;
+
+ return count;
+}
+
+/**
+ * camel_folder_search_search:
+ * @search:
+ * @expr:
+ * @uids: (element-type utf8): to search against, NULL for all uid's.
+ * @cancellable: a #GCancellable
+ * @error: return location for a #GError, or %NULL
+ *
+ * Run a search. Search must have had Folder already set on it, and
+ * it must implement summaries.
+ *
+ * Returns: (element-type utf8) (transfer full):
+ **/
+GPtrArray *
+camel_folder_search_search (CamelFolderSearch *search,
+ const gchar *expr,
+ GPtrArray *uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSExpResult *r;
+ GPtrArray *matches = NULL, *summary_set;
+ gint i;
+ CamelDB *cdb;
+ gchar *sql_query = NULL, *tmp, *tmp1;
+ GHashTable *results;
+
+ CamelFolderSearchPrivate *p;
+
+ g_return_val_if_fail (search != NULL, NULL);
+
+ p = search->priv;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ goto fail;
+
+ if (!expr || !*expr)
+ expr = "(match-all)";
+
+ if (!search->folder) {
+ g_warn_if_reached ();
+ goto fail;
+ }
+
+ p->owned_pstrings = g_ptr_array_new ();
+ p->cancellable = cancellable;
+ p->error = error;
+
+ /* We route body-contains / thread based search and uid search through memory and not via db. */
+ if (uids || do_search_in_memory (search->folder, expr, &sql_query)) {
+ /* setup our search list only contains those we're interested in */
+ search->summary = camel_folder_get_summary (search->folder);
+
+ 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, search->summary->pdata[i]))
+ g_ptr_array_add (search->summary_set, search->summary->pdata[i]);
+ g_hash_table_destroy (uids_hash);
+ } else {
+ if (search->folder->summary)
+ camel_folder_summary_prepare_fetch_all (search->folder->summary, NULL);
+ summary_set = search->summary;
+ }
+
+ /* only re-parse if the search has changed */
+ if (search->last_search == NULL
+ || strcmp (search->last_search, expr)) {
+ camel_sexp_input_text (search->sexp, expr, strlen (expr));
+ if (camel_sexp_parse (search->sexp) == -1) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot parse search expression: %s:\n%s"),
+ camel_sexp_error (search->sexp), expr);
+ goto fail;
+ }
+
+ g_free (search->last_search);
+ search->last_search = g_strdup (expr);
+ }
+ r = camel_sexp_eval (search->sexp);
+ if (r == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Error executing search expression: %s:\n%s"),
+ camel_sexp_error (search->sexp), expr);
+ goto fail;
+ }
+
+ matches = g_ptr_array_new ();
+
+ /* now create a folder summary to return?? */
+ if (r->type == CAMEL_SEXP_RES_ARRAY_PTR) {
+ d (printf ("got result\n"));
+
+ /* 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", (gchar *) 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++) {
+ gchar *uid = g_ptr_array_index (summary_set, i);
+ if (g_hash_table_lookup (results, uid))
+ g_ptr_array_add (matches, (gpointer) camel_pstring_strdup (uid));
+ }
+ g_hash_table_destroy (results);
+ }
+
+ camel_sexp_result_free (search->sexp, r);
+
+ } else {
+ CamelStore *parent_store;
+ const gchar *full_name;
+ GError *local_error = NULL;
+
+ full_name = camel_folder_get_full_name (search->folder);
+ parent_store = camel_folder_get_parent_store (search->folder);
+
+ /* Sync the db, so that we search the db for changes */
+ camel_folder_summary_save_to_db (search->folder->summary, error);
+
+ dd (printf ("sexp is : [%s]\n", expr));
+ tmp1 = camel_db_sqlize_string (full_name);
+ tmp = g_strdup_printf ("SELECT uid FROM %s %s %s", tmp1, sql_query ? "WHERE":"", sql_query ? sql_query:"");
+ camel_db_free_sqlized_string (tmp1);
+ g_free (sql_query);
+ dd (printf ("Equivalent sql %s\n", tmp));
+
+ matches = g_ptr_array_new ();
+ cdb = (CamelDB *) (parent_store->cdb_r);
+ camel_db_select (
+ cdb, tmp, (CamelDBSelectCB)
+ read_uid_callback, matches, &local_error);
+ if (local_error != NULL) {
+ const gchar *message = local_error->message;
+ if (strncmp (message, "no such table", 13) == 0) {
+ d (g_warning ("Error during searching %s: %s\n", tmp, message));
+ /* Suppress no such table */
+ g_clear_error (&local_error);
+ } else
+ g_propagate_error (error, local_error);
+ }
+ g_free (tmp);
+
+ }
+
+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);
+ if (search->summary)
+ camel_folder_free_summary (search->folder, search->summary);
+
+ free_pstring_array (p->owned_pstrings);
+ p->owned_pstrings = NULL;
+ p->cancellable = NULL;
+ p->error = NULL;
+ p->threads = NULL;
+ p->threads_hash = NULL;
+ search->folder = NULL;
+ search->summary = NULL;
+ search->summary_set = NULL;
+ search->current = NULL;
+ search->body_index = NULL;
+
+ if (error && *error) {
+ camel_folder_search_free_result (search, matches);
+ matches = NULL;
+ }
+
+ return matches;
+}
+
+/**
+ * camel_folder_search_free_result:
+ * @result: (element-type utf8):
+ **/
+void
+camel_folder_search_free_result (CamelFolderSearch *search,
+ GPtrArray *result)
+{
+ free_pstring_array (result);
+}
+
+/**
+ * camel_folder_search_util_add_months:
+ * @t: Initial time
+ * @months: number of months to add or subtract
+ *
+ * Increases time @t by the given number of months (or decreases, if
+ * @months is negative).
+ *
+ * Returns: a new #time_t value
+ *
+ * Since: 3.2
+ **/
+time_t
+camel_folder_search_util_add_months (time_t t,
+ gint months)
+{
+ GDateTime *dt, *dt2;
+ time_t res;
+
+ if (!months)
+ return t;
+
+ dt = g_date_time_new_from_unix_utc (t);
+
+ /* just for issues, to return something inaccurate, but sane */
+ res = t + (60 * 60 * 24 * 30 * months);
+
+ g_return_val_if_fail (dt != NULL, res);
+
+ dt2 = g_date_time_add_months (dt, months);
+ g_date_time_unref (dt);
+ g_return_val_if_fail (dt2 != NULL, res);
+
+ res = g_date_time_to_unix (dt2);
+ g_date_time_unref (dt2);
+
+ return res;
+}
diff --git a/src/camel/camel-folder-search.h b/src/camel/camel-folder-search.h
new file mode 100644
index 000000000..7c88f68d9
--- /dev/null
+++ b/src/camel/camel-folder-search.h
@@ -0,0 +1,305 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_FOLDER_SEARCH_H
+#define CAMEL_FOLDER_SEARCH_H
+
+#include <camel/camel-folder.h>
+#include <camel/camel-index.h>
+#include <camel/camel-sexp.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_FOLDER_SEARCH \
+ (camel_folder_search_get_type ())
+#define CAMEL_FOLDER_SEARCH(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_FOLDER_SEARCH, CamelFolderSearch))
+#define CAMEL_FOLDER_SEARCH_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_FOLDER_SEARCH, CamelFolderSearchClass))
+#define CAMEL_IS_FOLDER_SEARCH(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_FOLDER_SEARCH))
+#define CAMEL_IS_FOLDER_SEARCH_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_FOLDER_SEARCH))
+#define CAMEL_FOLDER_SEARCH_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_FOLDER_SEARCH, CamelFolderSearchClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelFolderSearch CamelFolderSearch;
+typedef struct _CamelFolderSearchClass CamelFolderSearchClass;
+typedef struct _CamelFolderSearchPrivate CamelFolderSearchPrivate;
+
+struct _CamelFolderSearch {
+ GObject parent;
+ CamelFolderSearchPrivate *priv;
+
+ CamelSExp *sexp; /* s-exp evaluator */
+ gchar *last_search; /* last searched expression */
+
+ /* these are only valid during the search, and are reset afterwards */
+ CamelFolder *folder; /* folder for current search */
+ GPtrArray *summary; /* summary array for current search */
+ GPtrArray *summary_set; /* subset of summary to actually include in search */
+ CamelMessageInfo *current; /* current message info, when searching one by one */
+ CamelMimeMessage *current_message; /* cache of current message, if required */
+ CamelIndex *body_index;
+};
+
+struct _CamelFolderSearchClass {
+ GObjectClass parent_class;
+
+ /* General bool/comparison options. Usually these won't need
+ * to be set, unless it is compiling into another language. */
+ CamelSExpResult * (*and_) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ CamelFolderSearch *search);
+ CamelSExpResult * (*or_) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ CamelFolderSearch *search);
+ CamelSExpResult * (*not_) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+ CamelSExpResult * (*lt) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ CamelFolderSearch *search);
+ CamelSExpResult * (*gt) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ CamelFolderSearch *search);
+ CamelSExpResult * (*eq) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ CamelFolderSearch *search);
+
+ /* Search Options */
+
+ /* (match-all [boolean expression])
+ * Apply match to all messages. */
+ CamelSExpResult * (*match_all) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ CamelFolderSearch *search);
+
+ /* (match-threads "type" [array expression])
+ * Add all related threads. */
+ CamelSExpResult * (*match_threads)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ CamelFolderSearch *search);
+
+ /* (body-contains "string1" "string2" ...)
+ * Returns a list of matches, or true if in single-message mode. */
+ CamelSExpResult * (*body_contains)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (body-regex "regex")
+ * Returns a list of matches, or true if in single-message mode. */
+ CamelSExpResult * (*body_regex) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (header-contains "headername" "string1" ...)
+ * List of matches, or true if in single-message mode. */
+ CamelSExpResult * (*header_contains)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (header-matches "headername" "string") */
+ CamelSExpResult * (*header_matches)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (header-starts-with "headername" "string") */
+ CamelSExpResult * (*header_starts_with)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (header-ends-with "headername" "string") */
+ CamelSExpResult * (*header_ends_with)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (header-exists "headername") */
+ CamelSExpResult * (*header_exists)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (header-soundex "headername" "string") */
+ CamelSExpResult * (*header_soundex)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (header-regex "headername" "regex_string") */
+ CamelSExpResult * (*header_regex) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (header-full-regex "regex") */
+ CamelSExpResult * (*header_full_regex)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (user-flag "flagname" "flagname" ...)
+ * If one of user-flag set. */
+ CamelSExpResult * (*user_flag) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (user-tag "flagname")
+ * Returns the value of a user tag. Can only be used in match-all. */
+ CamelSExpResult * (*user_tag) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (system-flag "flagname")
+ * Returns the value of a system flag.
+ * Can only be used in match-all. */
+ CamelSExpResult * (*system_flag) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (get-sent-date)
+ * Retrieve the date that the message was sent on as a time_t. */
+ CamelSExpResult * (*get_sent_date)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (get-received-date)
+ * Retrieve the date that the message was received on as a time_t. */
+ CamelSExpResult * (*get_received_date)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (get-current-date)
+ * Retrieve 'now' as a time_t. */
+ CamelSExpResult * (*get_current_date)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (get-relative-months)
+ * Retrieve relative seconds from 'now' and
+ * specified number of months as a time_t. */
+ CamelSExpResult * (*get_relative_months)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (get-size)
+ * Retrieve message size as an gint (in kilobytes). */
+ CamelSExpResult * (*get_size) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (uid "uid" ...)
+ * True if the uid is in the list. */
+ CamelSExpResult * (*uid) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+
+ /* (message-location "folder_string")
+ * True if the message is in the folder's full name "folder_string". */
+ CamelSExpResult * (*message_location)
+ (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search);
+};
+
+GType camel_folder_search_get_type (void) G_GNUC_CONST;
+CamelFolderSearch *
+ camel_folder_search_new (void);
+
+/* XXX This stuff currently gets cleared when you run a search.
+ * What on earth was i thinking ... */
+void camel_folder_search_set_folder (CamelFolderSearch *search,
+ CamelFolder *folder);
+void camel_folder_search_set_summary (CamelFolderSearch *search,
+ GPtrArray *summary);
+void camel_folder_search_set_body_index
+ (CamelFolderSearch *search,
+ CamelIndex *body_index);
+
+GPtrArray * camel_folder_search_search (CamelFolderSearch *search,
+ const gchar *expr,
+ GPtrArray *uids,
+ GCancellable *cancellable,
+ GError **error);
+guint32 camel_folder_search_count (CamelFolderSearch *search,
+ const gchar *expr,
+ GCancellable *cancellable,
+ GError **error);
+void camel_folder_search_free_result (CamelFolderSearch *search,
+ GPtrArray *result);
+
+/* XXX This belongs in a general utility file. */
+time_t camel_folder_search_util_add_months
+ (time_t t,
+ gint months);
+
+#ifndef CAMEL_DISABLE_DEPRECATED
+void camel_folder_search_construct (CamelFolderSearch *search);
+#endif /* CAMEL_DISABLE_DEPRECATED */
+
+G_END_DECLS
+
+#endif /* CAMEL_FOLDER_SEARCH_H */
diff --git a/src/camel/camel-folder-summary.c b/src/camel/camel-folder-summary.c
new file mode 100644
index 000000000..aec35e339
--- /dev/null
+++ b/src/camel/camel-folder-summary.c
@@ -0,0 +1,5316 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include "camel-db.h"
+#include "camel-debug.h"
+#include "camel-file-utils.h"
+#include "camel-folder-summary.h"
+#include "camel-folder.h"
+#include "camel-iconv.h"
+#include "camel-mime-filter-basic.h"
+#include "camel-mime-filter-charset.h"
+#include "camel-mime-filter-html.h"
+#include "camel-mime-filter-index.h"
+#include "camel-mime-filter.h"
+#include "camel-mime-message.h"
+#include "camel-multipart.h"
+#include "camel-session.h"
+#include "camel-stream-filter.h"
+#include "camel-stream-mem.h"
+#include "camel-stream-null.h"
+#include "camel-string-utils.h"
+#include "camel-store.h"
+#include "camel-vee-folder.h"
+#include "camel-vtrash-folder.h"
+#include "camel-mime-part-utils.h"
+
+#define CAMEL_FOLDER_SUMMARY_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_FOLDER_SUMMARY, CamelFolderSummaryPrivate))
+
+/* Make 5 minutes as default cache drop */
+#define SUMMARY_CACHE_DROP 300
+#define dd(x) if (camel_debug("sync")) x
+
+struct _CamelFolderSummaryPrivate {
+ GHashTable *filter_charset; /* CamelMimeFilterCharset's indexed by source charset */
+
+ struct _CamelMimeFilter *filter_index;
+ struct _CamelMimeFilter *filter_64;
+ struct _CamelMimeFilter *filter_qp;
+ struct _CamelMimeFilter *filter_uu;
+ struct _CamelMimeFilter *filter_save;
+ struct _CamelMimeFilter *filter_html;
+
+ struct _CamelStream *filter_stream;
+
+ struct _CamelIndex *index;
+
+ GRecMutex summary_lock; /* for the summary hashtable/array */
+ GRecMutex filter_lock; /* for accessing any of the filtering/indexing stuff, since we share them */
+
+ gboolean need_preview;
+ GHashTable *preview_updates;
+
+ guint32 nextuid; /* next uid? */
+ guint32 saved_count; /* how many were saved/loaded */
+ guint32 unread_count; /* handy totals */
+ guint32 deleted_count;
+ guint32 junk_count;
+ guint32 junk_not_deleted_count;
+ guint32 visible_count;
+
+ gboolean build_content; /* do we try and parse/index the content, or not? */
+
+ GHashTable *uids; /* uids of all known message infos; the 'value' are used flags for the message info */
+ GHashTable *loaded_infos; /* uid->CamelMessageInfo *, those currently in memory */
+
+ struct _CamelFolder *folder; /* parent folder, for events */
+ time_t cache_load_time;
+ guint timeout_handle;
+};
+
+/* this should probably be conditional on it existing */
+#define USE_BSEARCH
+
+#define d(x)
+#define io(x) /* io debug */
+#define w(x)
+
+#define CAMEL_FOLDER_SUMMARY_VERSION (14)
+
+/* trivial lists, just because ... */
+struct _node {
+ struct _node *next;
+};
+
+static void cfs_schedule_info_release_timer (CamelFolderSummary *summary);
+
+static struct _node *my_list_append (struct _node **list, struct _node *n);
+static gint my_list_size (struct _node **list);
+
+static CamelMessageInfo * message_info_new_from_header (CamelFolderSummary *, struct _camel_header_raw *);
+static CamelMessageInfo * message_info_new_from_parser (CamelFolderSummary *, CamelMimeParser *);
+static CamelMessageInfo * message_info_new_from_message (CamelFolderSummary *summary, CamelMimeMessage *msg, const gchar *bodystructure);
+static void message_info_free (CamelFolderSummary *, CamelMessageInfo *);
+
+static CamelMessageContentInfo * content_info_new_from_header (CamelFolderSummary *, struct _camel_header_raw *);
+static CamelMessageContentInfo * content_info_new_from_parser (CamelFolderSummary *, CamelMimeParser *);
+static CamelMessageContentInfo * content_info_new_from_message (CamelFolderSummary *summary, CamelMimePart *mp);
+static void content_info_free (CamelFolderSummary *, CamelMessageContentInfo *);
+
+static gint save_message_infos_to_db (CamelFolderSummary *summary, GError **error);
+static gint camel_read_mir_callback (gpointer ref, gint ncol, gchar ** cols, gchar ** name);
+
+static gchar *next_uid_string (CamelFolderSummary *summary);
+
+static CamelMessageContentInfo * summary_build_content_info (CamelFolderSummary *summary, CamelMessageInfo *msginfo, CamelMimeParser *mp);
+static CamelMessageContentInfo * summary_build_content_info_message (CamelFolderSummary *summary, CamelMessageInfo *msginfo, CamelMimePart *object);
+
+static CamelMessageInfo * message_info_from_uid (CamelFolderSummary *summary, const gchar *uid);
+
+enum {
+ PROP_0,
+ PROP_FOLDER,
+ PROP_SAVED_COUNT,
+ PROP_UNREAD_COUNT,
+ PROP_DELETED_COUNT,
+ PROP_JUNK_COUNT,
+ PROP_JUNK_NOT_DELETED_COUNT,
+ PROP_VISIBLE_COUNT,
+ PROP_BUILD_CONTENT,
+ PROP_NEED_PREVIEW
+};
+
+G_DEFINE_TYPE (CamelFolderSummary, camel_folder_summary, G_TYPE_OBJECT)
+
+G_DEFINE_BOXED_TYPE (CamelMessageInfo,
+ camel_message_info,
+ camel_message_info_ref,
+ camel_message_info_unref)
+
+static gboolean
+remove_each_item (gpointer uid,
+ gpointer mi,
+ gpointer user_data)
+{
+ GSList **to_remove_infos = user_data;
+
+ *to_remove_infos = g_slist_prepend (*to_remove_infos, mi);
+
+ return TRUE;
+}
+
+static void
+remove_all_loaded (CamelFolderSummary *summary)
+{
+ GSList *to_remove_infos = NULL;
+
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
+
+ camel_folder_summary_lock (summary);
+
+ g_hash_table_foreach_remove (summary->priv->loaded_infos, remove_each_item, &to_remove_infos);
+
+ g_slist_foreach (to_remove_infos, (GFunc) camel_message_info_unref, NULL);
+ g_slist_free (to_remove_infos);
+
+ camel_folder_summary_unlock (summary);
+}
+
+static void
+free_o_name (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ g_object_unref (value);
+ g_free (key);
+}
+
+static void
+folder_summary_dispose (GObject *object)
+{
+ CamelFolderSummaryPrivate *priv;
+
+ priv = CAMEL_FOLDER_SUMMARY_GET_PRIVATE (object);
+
+ if (priv->timeout_handle) {
+ /* this should not happen, because the release timer
+ * holds a reference on object */
+ g_source_remove (priv->timeout_handle);
+ priv->timeout_handle = 0;
+ }
+
+ g_clear_object (&priv->filter_index);
+ g_clear_object (&priv->filter_64);
+ g_clear_object (&priv->filter_qp);
+ g_clear_object (&priv->filter_uu);
+ g_clear_object (&priv->filter_save);
+ g_clear_object (&priv->filter_html);
+ g_clear_object (&priv->filter_stream);
+ g_clear_object (&priv->filter_index);
+
+ if (priv->folder) {
+ g_object_weak_unref (G_OBJECT (priv->folder), (GWeakNotify) g_nullify_pointer, &priv->folder);
+ priv->folder = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_folder_summary_parent_class)->dispose (object);
+}
+
+static void
+folder_summary_finalize (GObject *object)
+{
+ CamelFolderSummary *summary = CAMEL_FOLDER_SUMMARY (object);
+ CamelFolderSummaryPrivate *priv = summary->priv;
+
+ g_hash_table_destroy (priv->uids);
+ remove_all_loaded (summary);
+ g_hash_table_destroy (priv->loaded_infos);
+
+ g_hash_table_foreach (priv->filter_charset, free_o_name, NULL);
+ g_hash_table_destroy (priv->filter_charset);
+
+ g_hash_table_destroy (priv->preview_updates);
+
+ g_rec_mutex_clear (&priv->summary_lock);
+ g_rec_mutex_clear (&priv->filter_lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_folder_summary_parent_class)->finalize (object);
+}
+
+static void
+folder_summary_set_folder (CamelFolderSummary *summary,
+ CamelFolder *folder)
+{
+ g_return_if_fail (summary->priv->folder == NULL);
+ /* folder can be NULL in certain cases, see maildir-store */
+
+ summary->priv->folder = folder;
+ if (folder)
+ g_object_weak_ref (G_OBJECT (folder), (GWeakNotify) g_nullify_pointer, &summary->priv->folder);
+}
+
+static void
+folder_summary_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_FOLDER:
+ folder_summary_set_folder (
+ CAMEL_FOLDER_SUMMARY (object),
+ CAMEL_FOLDER (g_value_get_object (value)));
+ return;
+
+ case PROP_BUILD_CONTENT:
+ camel_folder_summary_set_build_content (
+ CAMEL_FOLDER_SUMMARY (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_NEED_PREVIEW:
+ camel_folder_summary_set_need_preview (
+ CAMEL_FOLDER_SUMMARY (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+folder_summary_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_FOLDER:
+ g_value_set_object (
+ value,
+ camel_folder_summary_get_folder (
+ CAMEL_FOLDER_SUMMARY (object)));
+ return;
+
+ case PROP_SAVED_COUNT:
+ g_value_set_uint (
+ value,
+ camel_folder_summary_get_saved_count (
+ CAMEL_FOLDER_SUMMARY (object)));
+ return;
+
+ case PROP_UNREAD_COUNT:
+ g_value_set_uint (
+ value,
+ camel_folder_summary_get_unread_count (
+ CAMEL_FOLDER_SUMMARY (object)));
+ return;
+
+ case PROP_DELETED_COUNT:
+ g_value_set_uint (
+ value,
+ camel_folder_summary_get_deleted_count (
+ CAMEL_FOLDER_SUMMARY (object)));
+ return;
+
+ case PROP_JUNK_COUNT:
+ g_value_set_uint (
+ value,
+ camel_folder_summary_get_junk_count (
+ CAMEL_FOLDER_SUMMARY (object)));
+ return;
+
+ case PROP_JUNK_NOT_DELETED_COUNT:
+ g_value_set_uint (
+ value,
+ camel_folder_summary_get_junk_not_deleted_count (
+ CAMEL_FOLDER_SUMMARY (object)));
+ return;
+
+ case PROP_VISIBLE_COUNT:
+ g_value_set_uint (
+ value,
+ camel_folder_summary_get_visible_count (
+ CAMEL_FOLDER_SUMMARY (object)));
+ return;
+
+ case PROP_BUILD_CONTENT:
+ g_value_set_boolean (
+ value,
+ camel_folder_summary_get_build_content (
+ CAMEL_FOLDER_SUMMARY (object)));
+ return;
+
+ case PROP_NEED_PREVIEW:
+ g_value_set_boolean (
+ value,
+ camel_folder_summary_get_need_preview (
+ CAMEL_FOLDER_SUMMARY (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static gboolean
+is_in_memory_summary (CamelFolderSummary *summary)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+
+ return (summary->flags & CAMEL_FOLDER_SUMMARY_IN_MEMORY_ONLY) != 0;
+}
+
+#define UPDATE_COUNTS_ADD (1)
+#define UPDATE_COUNTS_SUB (2)
+#define UPDATE_COUNTS_ADD_WITHOUT_TOTAL (3)
+#define UPDATE_COUNTS_SUB_WITHOUT_TOTAL (4)
+
+static gboolean
+folder_summary_update_counts_by_flags (CamelFolderSummary *summary,
+ guint32 flags,
+ gint op_type)
+{
+ gint unread = 0, deleted = 0, junk = 0;
+ gboolean is_junk_folder = FALSE, is_trash_folder = FALSE;
+ gboolean subtract = op_type == UPDATE_COUNTS_SUB || op_type == UPDATE_COUNTS_SUB_WITHOUT_TOTAL;
+ gboolean without_total = op_type == UPDATE_COUNTS_ADD_WITHOUT_TOTAL || op_type == UPDATE_COUNTS_SUB_WITHOUT_TOTAL;
+ gboolean changed = FALSE;
+ GObject *summary_object;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+
+ summary_object = G_OBJECT (summary);
+
+ if (summary->priv->folder && CAMEL_IS_VTRASH_FOLDER (summary->priv->folder)) {
+ CamelVTrashFolder *vtrash = CAMEL_VTRASH_FOLDER (summary->priv->folder);
+
+ is_junk_folder = vtrash && vtrash->type == CAMEL_VTRASH_FOLDER_JUNK;
+ is_trash_folder = vtrash && vtrash->type == CAMEL_VTRASH_FOLDER_TRASH;
+ }
+
+ if (!(flags & CAMEL_MESSAGE_SEEN))
+ unread = subtract ? -1 : 1;
+
+ if (flags & CAMEL_MESSAGE_DELETED)
+ deleted = subtract ? -1 : 1;
+
+ if (flags & CAMEL_MESSAGE_JUNK)
+ junk = subtract ? -1 : 1;
+
+ dd (printf ("%p: %d %d %d | %d %d %d \n", (gpointer) summary, unread, deleted, junk, summary->priv->unread_count, summary->priv->visible_count, summary->priv->saved_count));
+
+ g_object_freeze_notify (summary_object);
+
+ if (deleted) {
+ summary->priv->deleted_count += deleted;
+ g_object_notify (summary_object, "deleted-count");
+ changed = TRUE;
+ }
+
+ if (junk) {
+ summary->priv->junk_count += junk;
+ g_object_notify (summary_object, "junk-count");
+ changed = TRUE;
+ }
+
+ if (junk && !deleted) {
+ summary->priv->junk_not_deleted_count += junk;
+ g_object_notify (summary_object, "junk-not-deleted-count");
+ changed = TRUE;
+ }
+
+ if (!junk && !deleted) {
+ summary->priv->visible_count += subtract ? -1 : 1;
+ g_object_notify (summary_object, "visible-count");
+ changed = TRUE;
+ }
+
+ if (junk && !is_junk_folder)
+ unread = 0;
+ if (deleted && !is_trash_folder)
+ unread = 0;
+
+ if (unread) {
+ summary->priv->unread_count += unread;
+ g_object_notify (summary_object, "unread-count");
+ changed = TRUE;
+ }
+
+ if (!without_total) {
+ summary->priv->saved_count += subtract ? -1 : 1;
+ g_object_notify (summary_object, "saved-count");
+ changed = TRUE;
+ }
+
+ if (changed)
+ camel_folder_summary_touch (summary);
+
+ g_object_thaw_notify (summary_object);
+
+ dd (printf ("%p: %d %d %d | %d %d %d\n", (gpointer) summary, unread, deleted, junk, summary->priv->unread_count, summary->priv->visible_count, summary->priv->saved_count));
+
+ return changed;
+}
+
+static gboolean
+summary_header_from_db (CamelFolderSummary *summary,
+ CamelFIRecord *record)
+{
+ io (printf ("Loading header from db \n"));
+
+ summary->version = record->version;
+
+ /* We may not worry, as we are setting a new standard here */
+#if 0
+ /* Legacy version check, before version 12 we have no upgrade knowledge */
+ if ((summary->version > 0xff) && (summary->version & 0xff) < 12) {
+ io (printf ("Summary header version mismatch"));
+ errno = EINVAL;
+ return FALSE;
+ }
+
+ if (!(summary->version < 0x100 && summary->version >= 13))
+ io (printf ("Loading legacy summary\n"));
+ else
+ io (printf ("loading new-format summary\n"));
+#endif
+
+ summary->flags = record->flags;
+ summary->priv->nextuid = record->nextuid;
+ summary->time = record->time;
+ summary->priv->saved_count = record->saved_count;
+
+ summary->priv->unread_count = record->unread_count;
+ summary->priv->deleted_count = record->deleted_count;
+ summary->priv->junk_count = record->junk_count;
+ summary->priv->visible_count = record->visible_count;
+ summary->priv->junk_not_deleted_count = record->jnd_count;
+
+ return TRUE;
+}
+
+static CamelFIRecord *
+summary_header_to_db (CamelFolderSummary *summary,
+ GError **error)
+{
+ CamelFIRecord *record = g_new0 (CamelFIRecord, 1);
+ CamelStore *parent_store;
+ CamelDB *db;
+ const gchar *table_name;
+
+ /* Though we are going to read, we do this during write,
+ * so lets use it that way. */
+ table_name = camel_folder_get_full_name (summary->priv->folder);
+ parent_store = camel_folder_get_parent_store (summary->priv->folder);
+ db = parent_store ? parent_store->cdb_w : NULL;
+
+ io (printf ("Savining header to db\n"));
+
+ record->folder_name = g_strdup (table_name);
+
+ /* we always write out the current version */
+ record->version = CAMEL_FOLDER_SUMMARY_VERSION;
+ record->flags = summary->flags;
+ record->nextuid = summary->priv->nextuid;
+ record->time = summary->time;
+
+ if (db && !is_in_memory_summary (summary)) {
+ /* FIXME: Ever heard of Constructors and initializing ? */
+ if (camel_db_count_total_message_info (db, table_name, &(record->saved_count), NULL))
+ record->saved_count = 0;
+ if (camel_db_count_junk_message_info (db, table_name, &(record->junk_count), NULL))
+ record->junk_count = 0;
+ if (camel_db_count_deleted_message_info (db, table_name, &(record->deleted_count), NULL))
+ record->deleted_count = 0;
+ if (camel_db_count_unread_message_info (db, table_name, &(record->unread_count), NULL))
+ record->unread_count = 0;
+ if (camel_db_count_visible_message_info (db, table_name, &(record->visible_count), NULL))
+ record->visible_count = 0;
+ if (camel_db_count_junk_not_deleted_message_info (db, table_name, &(record->jnd_count), NULL))
+ record->jnd_count = 0;
+ }
+
+ summary->priv->unread_count = record->unread_count;
+ summary->priv->deleted_count = record->deleted_count;
+ summary->priv->junk_count = record->junk_count;
+ summary->priv->visible_count = record->visible_count;
+ summary->priv->junk_not_deleted_count = record->jnd_count;
+
+ return record;
+}
+
+static CamelMessageInfo *
+message_info_from_db (CamelFolderSummary *summary,
+ CamelMIRecord *record)
+{
+ CamelMessageInfoBase *mi;
+ gint i;
+ gint count;
+ gchar *part, *label;
+
+ mi = (CamelMessageInfoBase *) camel_message_info_new (summary);
+
+ io (printf ("Loading message info from db\n"));
+
+ mi->flags = record->flags;
+ mi->size = record->size;
+ mi->date_sent = record->dsent;
+ mi->date_received = record->dreceived;
+
+ mi->uid = (gchar *) camel_pstring_strdup (record->uid);
+ mi->subject = (gchar *) camel_pstring_add (record->subject, FALSE);
+ mi->from = (gchar *) camel_pstring_add (record->from, FALSE);
+ mi->to = (gchar *) camel_pstring_add (record->to, FALSE);
+ mi->cc = (gchar *) camel_pstring_add (record->cc, FALSE);
+ mi->mlist = (gchar *) camel_pstring_add (record->mlist, FALSE);
+
+ /* Evolution itself doesn't yet use this, so we ignore it (saving some memory) */
+ mi->bodystructure = NULL;
+
+ /* Extract Message id & References */
+ mi->content = NULL;
+ part = record->part;
+ if (part) {
+ mi->message_id.id.part.hi = bdata_extract_digit (&part);
+ mi->message_id.id.part.lo = bdata_extract_digit (&part);
+ count = bdata_extract_digit (&part);
+
+ 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++) {
+ mi->references->references[i].id.part.hi = bdata_extract_digit (&part);
+ mi->references->references[i].id.part.lo = bdata_extract_digit (&part);
+ }
+ } else
+ mi->references = NULL;
+
+ }
+
+ /* Extract User flags/labels */
+ part = record->labels;
+ if (part) {
+ label = part;
+ for (i = 0; part[i]; i++) {
+
+ if (part[i] == ' ') {
+ part[i] = 0;
+ camel_flag_set (&mi->user_flags, label, TRUE);
+ label = &(part[i + 1]);
+ }
+ }
+ camel_flag_set (&mi->user_flags, label, TRUE);
+ }
+
+ /* Extract User tags */
+ part = record->usertags;
+ count = bdata_extract_digit (&part);
+ for (i = 0; i < count; i++) {
+ gchar *name, *value;
+
+ name = bdata_extract_string (&part);
+ value = bdata_extract_string (&part);
+ camel_tag_set (&mi->user_tags, name, value);
+
+ g_free (name);
+ g_free (value);
+ }
+
+ return (CamelMessageInfo *) mi;
+}
+
+static CamelMIRecord *
+message_info_to_db (CamelFolderSummary *summary,
+ CamelMessageInfo *info)
+{
+ CamelMIRecord *record = g_new0 (CamelMIRecord, 1);
+ CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
+ GString *tmp;
+ CamelFlag *flag;
+ CamelTag *tag;
+ gint count, i;
+
+ /* Assume that we dont have to take care of DB Safeness. It will be done while doing the DB transaction */
+ record->uid = (gchar *) camel_pstring_strdup (camel_message_info_get_uid (info));
+ record->flags = mi->flags;
+
+ record->read = ((mi->flags & (CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_JUNK))) ? 1 : 0;
+ record->deleted = mi->flags & CAMEL_MESSAGE_DELETED ? 1 : 0;
+ record->replied = mi->flags & CAMEL_MESSAGE_ANSWERED ? 1 : 0;
+ record->important = mi->flags & CAMEL_MESSAGE_FLAGGED ? 1 : 0;
+ record->junk = mi->flags & CAMEL_MESSAGE_JUNK ? 1 : 0;
+ record->dirty = mi->flags & CAMEL_MESSAGE_FOLDER_FLAGGED ? 1 : 0;
+ record->attachment = mi->flags & CAMEL_MESSAGE_ATTACHMENTS ? 1 : 0;
+
+ record->size = mi->size;
+ record->dsent = mi->date_sent;
+ record->dreceived = mi->date_received;
+
+ record->subject = (gchar *) camel_pstring_strdup (camel_message_info_get_subject (info));
+ record->from = (gchar *) camel_pstring_strdup (camel_message_info_get_from (info));
+ record->to = (gchar *) camel_pstring_strdup (camel_message_info_get_to (info));
+ record->cc = (gchar *) camel_pstring_strdup (camel_message_info_get_cc (info));
+ record->mlist = (gchar *) camel_pstring_strdup (camel_message_info_get_mlist (info));
+
+ record->followup_flag = (gchar *) camel_pstring_strdup (camel_message_info_get_user_tag (info, "follow-up"));
+ record->followup_completed_on = (gchar *) camel_pstring_strdup (camel_message_info_get_user_tag (info, "completed-on"));
+ record->followup_due_by = (gchar *) camel_pstring_strdup (camel_message_info_get_user_tag (info, "due-by"));
+
+ record->bodystructure = mi->bodystructure ? g_strdup (mi->bodystructure) : NULL;
+
+ tmp = g_string_new (NULL);
+ if (mi->references) {
+ g_string_append_printf (tmp, "%lu %lu %lu", (gulong) mi->message_id.id.part.hi, (gulong) mi->message_id.id.part.lo, (gulong) mi->references->size);
+ for (i = 0; i < mi->references->size; i++)
+ g_string_append_printf (tmp, " %lu %lu", (gulong) mi->references->references[i].id.part.hi, (gulong) mi->references->references[i].id.part.lo);
+ } else {
+ g_string_append_printf (tmp, "%lu %lu %lu", (gulong) mi->message_id.id.part.hi, (gulong) mi->message_id.id.part.lo, (gulong) 0);
+ }
+ record->part = tmp->str;
+ g_string_free (tmp, FALSE);
+
+ tmp = g_string_new (NULL);
+ flag = mi->user_flags;
+ while (flag) {
+ g_string_append_printf (tmp, "%s ", flag->name);
+ flag = flag->next;
+ }
+
+ /* Strip off the last space */
+ if (tmp->len)
+ tmp->len--;
+
+ record->labels = tmp->str;
+ g_string_free (tmp, FALSE);
+
+ tmp = g_string_new (NULL);
+ count = camel_tag_list_size (&mi->user_tags);
+ g_string_append_printf (tmp, "%lu", (gulong) count);
+ tag = mi->user_tags;
+ while (tag) {
+ /* FIXME: Should we handle empty tags? Can it be empty? If it potential crasher ahead*/
+ g_string_append_printf (tmp, " %lu-%s %lu-%s", (gulong) strlen (tag->name), tag->name, (gulong) strlen (tag->value), tag->value);
+ tag = tag->next;
+ }
+ record->usertags = tmp->str;
+ g_string_free (tmp, FALSE);
+
+ return record;
+}
+
+static CamelMessageContentInfo *
+content_info_from_db (CamelFolderSummary *summary,
+ CamelMIRecord *record)
+{
+ CamelMessageContentInfo *ci;
+ gchar *type, *subtype;
+ guint32 count, i;
+ CamelContentType *ct;
+ gchar *part = record->cinfo;
+
+ io (printf ("Loading content info from db\n"));
+
+ if (!part)
+ return NULL;
+
+ ci = camel_folder_summary_content_info_new (summary);
+ if (*part == ' ') part++; /* Move off the space in the record */
+
+ type = bdata_extract_string (&part);
+ subtype = bdata_extract_string (&part);
+ ct = camel_content_type_new (type, subtype);
+ g_free (type); /* can this be removed? */
+ g_free (subtype);
+ count = bdata_extract_digit (&part);
+
+ for (i = 0; i < count; i++) {
+ gchar *name, *value;
+ name = bdata_extract_string (&part);
+ value = bdata_extract_string (&part);
+
+ 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;
+
+ /* FIXME[disk-summary] move all these to camel pstring */
+ ci->id = bdata_extract_string (&part);
+ ci->description = bdata_extract_string (&part);
+ ci->encoding = bdata_extract_string (&part);
+ ci->size = bdata_extract_digit (&part);
+
+ record->cinfo = part; /* Keep moving the cursor in the record */
+
+ ci->childs = NULL;
+
+ return ci;
+}
+
+static gboolean
+content_info_to_db (CamelFolderSummary *summary,
+ CamelMessageContentInfo *ci,
+ CamelMIRecord *record)
+{
+ CamelContentType *ct;
+ struct _camel_header_param *hp;
+ GString *str = g_string_new (NULL);
+ gchar *oldr;
+
+ io (printf ("Saving content info to db\n"));
+
+ ct = ci->type;
+ if (ct) {
+ if (ct->type)
+ g_string_append_printf (str, " %d-%s", (gint) strlen (ct->type), ct->type);
+ else
+ g_string_append_printf (str, " 0-");
+ if (ct->subtype)
+ g_string_append_printf (str, " %d-%s", (gint) strlen (ct->subtype), ct->subtype);
+ else
+ g_string_append_printf (str, " 0-");
+ g_string_append_printf (str, " %d", my_list_size ((struct _node **) &ct->params));
+ hp = ct->params;
+ while (hp) {
+ if (hp->name)
+ g_string_append_printf (str, " %d-%s", (gint) strlen (hp->name), hp->name);
+ else
+ g_string_append_printf (str, " 0-");
+ if (hp->value)
+ g_string_append_printf (str, " %d-%s", (gint) strlen (hp->value), hp->value);
+ else
+ g_string_append_printf (str, " 0-");
+ hp = hp->next;
+ }
+ } else {
+ g_string_append_printf (str, " %d-", 0);
+ g_string_append_printf (str, " %d-", 0);
+ g_string_append_printf (str, " %d", 0);
+ }
+
+ if (ci->id)
+ g_string_append_printf (str, " %d-%s", (gint) strlen (ci->id), ci->id);
+ else
+ g_string_append_printf (str, " 0-");
+ if (ci->description)
+ g_string_append_printf (str, " %d-%s", (gint) strlen (ci->description), ci->description);
+ else
+ g_string_append_printf (str, " 0-");
+ if (ci->encoding)
+ g_string_append_printf (str, " %d-%s", (gint) strlen (ci->encoding), ci->encoding);
+ else
+ g_string_append_printf (str, " 0-");
+ g_string_append_printf (str, " %u", ci->size);
+
+ if (record->cinfo) {
+ oldr = record->cinfo;
+ record->cinfo = g_strconcat (oldr, str->str, NULL);
+ g_free (oldr); g_string_free (str, TRUE);
+ } else {
+ record->cinfo = str->str;
+ g_string_free (str, FALSE);
+ }
+
+ return TRUE;
+}
+
+/**
+ * camel_folder_summary_replace_flags:
+ * @summary: a #CamelFolderSummary
+ * @info: a #CamelMessageInfo
+ *
+ * Updates internal counts based on the flags in @info.
+ *
+ * Returns: Whether any count changed
+ *
+ * Since: 3.6
+ **/
+gboolean
+camel_folder_summary_replace_flags (CamelFolderSummary *summary,
+ CamelMessageInfo *info)
+{
+ guint32 old_flags, new_flags, added_flags, removed_flags;
+ gboolean is_junk_folder = FALSE, is_trash_folder = FALSE;
+ GObject *summary_object;
+ gboolean changed = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+ g_return_val_if_fail (info != NULL, FALSE);
+
+ if (!camel_message_info_get_uid (info) ||
+ !camel_folder_summary_check_uid (summary, camel_message_info_get_uid (info)))
+ return FALSE;
+
+ summary_object = G_OBJECT (summary);
+
+ camel_folder_summary_lock (summary);
+ g_object_freeze_notify (summary_object);
+
+ old_flags = GPOINTER_TO_UINT (g_hash_table_lookup (summary->priv->uids, camel_message_info_get_uid (info)));
+ new_flags = camel_message_info_get_flags (info);
+
+ if ((old_flags & ~CAMEL_MESSAGE_FOLDER_FLAGGED) == (new_flags & ~CAMEL_MESSAGE_FOLDER_FLAGGED)) {
+ g_object_thaw_notify (summary_object);
+ camel_folder_summary_unlock (summary);
+ return FALSE;
+ }
+
+ if (summary->priv->folder && CAMEL_IS_VTRASH_FOLDER (summary->priv->folder)) {
+ CamelVTrashFolder *vtrash = CAMEL_VTRASH_FOLDER (summary->priv->folder);
+
+ is_junk_folder = vtrash && vtrash->type == CAMEL_VTRASH_FOLDER_JUNK;
+ is_trash_folder = vtrash && vtrash->type == CAMEL_VTRASH_FOLDER_TRASH;
+ }
+
+ added_flags = new_flags & (~(old_flags & new_flags));
+ removed_flags = old_flags & (~(old_flags & new_flags));
+
+ if ((old_flags & CAMEL_MESSAGE_SEEN) == (new_flags & CAMEL_MESSAGE_SEEN)) {
+ /* unread count is different from others, it asks for nonexistence
+ * of the flag, thus if it wasn't changed, then simply set it
+ * in added/removed, thus there are no false notifications
+ * on unread counts */
+ added_flags |= CAMEL_MESSAGE_SEEN;
+ removed_flags |= CAMEL_MESSAGE_SEEN;
+ } else if ((!is_junk_folder && (new_flags & CAMEL_MESSAGE_JUNK) != 0 &&
+ (old_flags & CAMEL_MESSAGE_JUNK) == (new_flags & CAMEL_MESSAGE_JUNK)) ||
+ (!is_trash_folder && (new_flags & CAMEL_MESSAGE_DELETED) != 0 &&
+ (old_flags & CAMEL_MESSAGE_DELETED) == (new_flags & CAMEL_MESSAGE_DELETED))) {
+ /* The message was set read or unread, but it is a junk or deleted message,
+ * in a non-Junk/non-Trash folder, thus it doesn't influence an unread count
+ * there, thus pretend unread didn't change */
+ added_flags |= CAMEL_MESSAGE_SEEN;
+ removed_flags |= CAMEL_MESSAGE_SEEN;
+ }
+
+ /* decrement counts with removed flags */
+ changed = folder_summary_update_counts_by_flags (summary, removed_flags, UPDATE_COUNTS_SUB_WITHOUT_TOTAL) || changed;
+ /* increment counts with added flags */
+ changed = folder_summary_update_counts_by_flags (summary, added_flags, UPDATE_COUNTS_ADD_WITHOUT_TOTAL) || changed;
+
+ /* update current flags on the summary */
+ g_hash_table_insert (
+ summary->priv->uids,
+ (gpointer) camel_pstring_strdup (camel_message_info_get_uid (info)),
+ GUINT_TO_POINTER (new_flags));
+
+ g_object_thaw_notify (summary_object);
+ camel_folder_summary_unlock (summary);
+
+ return changed;
+}
+
+static CamelMessageInfo *
+message_info_clone (CamelFolderSummary *summary,
+ const CamelMessageInfo *mi)
+{
+ CamelMessageInfoBase *to, *from = (CamelMessageInfoBase *) mi;
+ CamelFlag *flag;
+ CamelTag *tag;
+
+ to = (CamelMessageInfoBase *) camel_message_info_new (summary);
+
+ to->flags = from->flags;
+ to->size = from->size;
+ to->date_sent = from->date_sent;
+ to->date_received = from->date_received;
+ to->refcount = 1;
+
+ /* NB: We don't clone the uid */
+
+ to->subject = camel_pstring_strdup (from->subject);
+ to->from = camel_pstring_strdup (from->from);
+ to->to = camel_pstring_strdup (from->to);
+ to->cc = camel_pstring_strdup (from->cc);
+ to->mlist = camel_pstring_strdup (from->mlist);
+ memcpy (&to->message_id, &from->message_id, sizeof (to->message_id));
+ to->preview = g_strdup (from->preview);
+ if (from->references) {
+ gint len = sizeof (*from->references) + ((from->references->size - 1) * sizeof (from->references->references[0]));
+
+ to->references = g_malloc (len);
+ memcpy (to->references, from->references, len);
+ }
+
+ 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;
+ }
+
+ if (from->content) {
+ /* FIXME: copy content-infos */
+ }
+
+ return (CamelMessageInfo *) to;
+}
+
+static gconstpointer
+info_ptr (const CamelMessageInfo *mi,
+ gint id)
+{
+ switch (id) {
+ case CAMEL_MESSAGE_INFO_SUBJECT:
+ return ((const CamelMessageInfoBase *) mi)->subject;
+ case CAMEL_MESSAGE_INFO_FROM:
+ return ((const CamelMessageInfoBase *) mi)->from;
+ case CAMEL_MESSAGE_INFO_TO:
+ return ((const CamelMessageInfoBase *) mi)->to;
+ case CAMEL_MESSAGE_INFO_CC:
+ return ((const CamelMessageInfoBase *) mi)->cc;
+ case CAMEL_MESSAGE_INFO_MLIST:
+ return ((const CamelMessageInfoBase *) mi)->mlist;
+ case CAMEL_MESSAGE_INFO_MESSAGE_ID:
+ return &((const CamelMessageInfoBase *) mi)->message_id;
+ case CAMEL_MESSAGE_INFO_REFERENCES:
+ return ((const CamelMessageInfoBase *) mi)->references;
+ case CAMEL_MESSAGE_INFO_USER_FLAGS:
+ return ((const CamelMessageInfoBase *) mi)->user_flags;
+ case CAMEL_MESSAGE_INFO_USER_TAGS:
+ return ((const CamelMessageInfoBase *) mi)->user_tags;
+ case CAMEL_MESSAGE_INFO_HEADERS:
+ return ((const CamelMessageInfoBase *) mi)->headers;
+ case CAMEL_MESSAGE_INFO_CONTENT:
+ return ((const CamelMessageInfoBase *) mi)->content;
+ case CAMEL_MESSAGE_INFO_PREVIEW:
+ return ((const CamelMessageInfoBase *) mi)->preview;
+ default:
+ g_return_val_if_reached (NULL);
+ }
+}
+
+static guint32
+info_uint32 (const CamelMessageInfo *mi,
+ gint id)
+{
+ switch (id) {
+ case CAMEL_MESSAGE_INFO_FLAGS:
+ return ((const CamelMessageInfoBase *) mi)->flags;
+ case CAMEL_MESSAGE_INFO_SIZE:
+ return ((const CamelMessageInfoBase *) mi)->size;
+ default:
+ g_return_val_if_reached (0);
+ }
+}
+
+static time_t
+info_time (const CamelMessageInfo *mi,
+ gint id)
+{
+ switch (id) {
+ case CAMEL_MESSAGE_INFO_DATE_SENT:
+ return ((const CamelMessageInfoBase *) mi)->date_sent;
+ case CAMEL_MESSAGE_INFO_DATE_RECEIVED:
+ return ((const CamelMessageInfoBase *) mi)->date_received;
+ default:
+ g_return_val_if_reached (0);
+ }
+}
+
+static gboolean
+info_user_flag (const CamelMessageInfo *mi,
+ const gchar *id)
+{
+ return camel_flag_get (&((CamelMessageInfoBase *) mi)->user_flags, id);
+}
+
+static const gchar *
+info_user_tag (const CamelMessageInfo *mi,
+ const gchar *id)
+{
+ return camel_tag_get (&((CamelMessageInfoBase *) mi)->user_tags, id);
+}
+
+static gboolean
+info_set_user_flag (CamelMessageInfo *info,
+ const gchar *name,
+ gboolean value)
+{
+ CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
+ gint res;
+
+ res = camel_flag_set (&mi->user_flags, name, value);
+
+ if (mi->summary && res && mi->summary->priv->folder && mi->uid
+ && camel_folder_summary_check_uid (mi->summary, mi->uid)) {
+ CamelFolderChangeInfo *changes = camel_folder_change_info_new ();
+
+ mi->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
+ mi->dirty = TRUE;
+ camel_folder_summary_touch (mi->summary);
+ camel_folder_change_info_change_uid (changes, camel_message_info_get_uid (info));
+ camel_folder_changed (mi->summary->priv->folder, changes);
+ camel_folder_change_info_free (changes);
+ }
+
+ return res;
+}
+
+static gboolean
+info_set_user_tag (CamelMessageInfo *info,
+ const gchar *name,
+ const gchar *value)
+{
+ CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
+ gint res;
+
+ res = camel_tag_set (&mi->user_tags, name, value);
+
+ if (mi->summary && res && mi->summary->priv->folder && mi->uid
+ && camel_folder_summary_check_uid (mi->summary, mi->uid)) {
+ CamelFolderChangeInfo *changes = camel_folder_change_info_new ();
+
+ mi->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
+ mi->dirty = TRUE;
+ camel_folder_summary_touch (mi->summary);
+ camel_folder_change_info_change_uid (changes, camel_message_info_get_uid (info));
+ camel_folder_changed (mi->summary->priv->folder, changes);
+ camel_folder_change_info_free (changes);
+ }
+
+ return res;
+}
+
+static gboolean
+info_set_flags (CamelMessageInfo *info,
+ guint32 flags,
+ guint32 set)
+{
+ guint32 old;
+ CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
+ gboolean counts_changed = FALSE;
+
+ old = camel_message_info_get_flags (info);
+ mi->flags = (old & ~flags) | (set & flags);
+ if (old != mi->flags) {
+ mi->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
+ mi->dirty = TRUE;
+ if (mi->summary)
+ camel_folder_summary_touch (mi->summary);
+ }
+
+ if (mi->summary) {
+ camel_folder_summary_lock (mi->summary);
+ g_object_freeze_notify (G_OBJECT (mi->summary));
+ counts_changed = camel_folder_summary_replace_flags (mi->summary, info);
+ }
+
+ if (!counts_changed && ((old & ~CAMEL_MESSAGE_SYSTEM_MASK) == (mi->flags & ~CAMEL_MESSAGE_SYSTEM_MASK)) && !((set & CAMEL_MESSAGE_JUNK_LEARN) && !(set & CAMEL_MESSAGE_JUNK))) {
+ if (mi->summary) {
+ g_object_thaw_notify (G_OBJECT (mi->summary));
+ camel_folder_summary_unlock (mi->summary);
+ }
+ return FALSE;
+ }
+
+ if (mi->summary) {
+ g_object_thaw_notify (G_OBJECT (mi->summary));
+ camel_folder_summary_unlock (mi->summary);
+ }
+
+ if (mi->summary && mi->summary->priv->folder && mi->uid) {
+ CamelFolderChangeInfo *changes = camel_folder_change_info_new ();
+
+ camel_folder_change_info_change_uid (changes, camel_message_info_get_uid (info));
+ camel_folder_changed (mi->summary->priv->folder, changes);
+ camel_folder_change_info_free (changes);
+ }
+
+ return TRUE;
+}
+
+static void
+camel_folder_summary_class_init (CamelFolderSummaryClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelFolderSummaryPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = folder_summary_set_property;
+ object_class->get_property = folder_summary_get_property;
+ object_class->dispose = folder_summary_dispose;
+ object_class->finalize = folder_summary_finalize;
+
+ class->message_info_size = sizeof (CamelMessageInfoBase);
+ class->content_info_size = sizeof (CamelMessageContentInfo);
+
+ class->summary_header_from_db = summary_header_from_db;
+ class->summary_header_to_db = summary_header_to_db;
+ class->message_info_from_db = message_info_from_db;
+ class->message_info_to_db = message_info_to_db;
+ class->content_info_from_db = content_info_from_db;
+ class->content_info_to_db = content_info_to_db;
+
+ class->message_info_new_from_header = message_info_new_from_header;
+ class->message_info_new_from_parser = message_info_new_from_parser;
+ class->message_info_new_from_message = message_info_new_from_message;
+ class->message_info_free = message_info_free;
+ class->message_info_clone = message_info_clone;
+ class->message_info_from_uid = message_info_from_uid;
+
+ class->content_info_new_from_header = content_info_new_from_header;
+ class->content_info_new_from_parser = content_info_new_from_parser;
+ class->content_info_new_from_message = content_info_new_from_message;
+ class->content_info_free = content_info_free;
+
+ class->next_uid_string = next_uid_string;
+
+ class->info_ptr = info_ptr;
+ class->info_uint32 = info_uint32;
+ class->info_time = info_time;
+ class->info_user_flag = info_user_flag;
+ class->info_user_tag = info_user_tag;
+
+ class->info_set_user_flag = info_set_user_flag;
+ class->info_set_user_tag = info_set_user_tag;
+
+ class->info_set_flags = info_set_flags;
+
+ /**
+ * CamelFolderSummary:folder
+ *
+ * The #CamelFolder to which the folder summary belongs.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_FOLDER,
+ g_param_spec_object (
+ "folder",
+ "Folder",
+ "The folder to which the folder summary belongs",
+ CAMEL_TYPE_FOLDER,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * CamelFolderSummary:saved-count
+ *
+ * How many infos is saved in a summary.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_SAVED_COUNT,
+ g_param_spec_uint (
+ "saved-count",
+ "Saved count",
+ "How many infos is savef in a summary",
+ 0, G_MAXUINT32,
+ 0, G_PARAM_READABLE));
+
+ /**
+ * CamelFolderSummary:unread-count
+ *
+ * How many unread infos is saved in a summary.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_UNREAD_COUNT,
+ g_param_spec_uint (
+ "unread-count",
+ "Unread count",
+ "How many unread infos is saved in a summary",
+ 0, G_MAXUINT32,
+ 0, G_PARAM_READABLE));
+
+ /**
+ * CamelFolderSummary:deleted-count
+ *
+ * How many deleted infos is saved in a summary.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_DELETED_COUNT,
+ g_param_spec_uint (
+ "deleted-count",
+ "Deleted count",
+ "How many deleted infos is saved in a summary",
+ 0, G_MAXUINT32,
+ 0, G_PARAM_READABLE));
+
+ /**
+ * CamelFolderSummary:junk-count
+ *
+ * How many junk infos is saved in a summary.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_JUNK_COUNT,
+ g_param_spec_uint (
+ "junk-count",
+ "Junk count",
+ "How many junk infos is saved in a summary",
+ 0, G_MAXUINT32,
+ 0, G_PARAM_READABLE));
+
+ /**
+ * CamelFolderSummary:junk-not-deleted-count
+ *
+ * How many junk and not deleted infos is saved in a summary.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_JUNK_NOT_DELETED_COUNT,
+ g_param_spec_uint (
+ "junk-not-deleted-count",
+ "Junk not deleted count",
+ "How many junk and not deleted infos is saved in a summary",
+ 0, G_MAXUINT32,
+ 0, G_PARAM_READABLE));
+
+ /**
+ * CamelFolderSummary:visible-count
+ *
+ * How many visible (not deleted and not junk) infos is saved in a summary.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_VISIBLE_COUNT,
+ g_param_spec_uint (
+ "visible-count",
+ "Visible count",
+ "How many visible (not deleted and not junk) infos is saved in a summary",
+ 0, G_MAXUINT32,
+ 0, G_PARAM_READABLE));
+
+ /**
+ * CamelFolderSummary:build-content
+ *
+ * Whether to build CamelMessageInfo.content.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_BUILD_CONTENT,
+ g_param_spec_boolean (
+ "build-content",
+ "Build content",
+ "Whether to build CamelMessageInfo.content",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ /**
+ * CamelFolderSummary:need-preview
+ *
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_NEED_PREVIEW,
+ g_param_spec_boolean (
+ "need-preview",
+ "Need preview",
+ "",
+ FALSE,
+ G_PARAM_READWRITE));
+}
+
+static void
+camel_folder_summary_init (CamelFolderSummary *summary)
+{
+ summary->priv = CAMEL_FOLDER_SUMMARY_GET_PRIVATE (summary);
+
+ summary->version = CAMEL_FOLDER_SUMMARY_VERSION;
+ summary->flags = 0;
+ summary->time = 0;
+
+ summary->priv->filter_charset = g_hash_table_new (
+ camel_strcase_hash, camel_strcase_equal);
+
+ summary->priv->need_preview = FALSE;
+ summary->priv->preview_updates = g_hash_table_new (g_str_hash, g_str_equal);
+
+ summary->priv->nextuid = 1;
+ summary->priv->uids = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) camel_pstring_free, NULL);
+ summary->priv->loaded_infos = g_hash_table_new (g_str_hash, g_str_equal);
+
+ g_rec_mutex_init (&summary->priv->summary_lock);
+ g_rec_mutex_init (&summary->priv->filter_lock);
+
+ summary->priv->cache_load_time = 0;
+ summary->priv->timeout_handle = 0;
+}
+
+/**
+ * camel_folder_summary_new:
+ * @folder: parent #CamelFolder object
+ *
+ * Create a new #CamelFolderSummary object.
+ *
+ * Returns: a new #CamelFolderSummary object
+ **/
+CamelFolderSummary *
+camel_folder_summary_new (CamelFolder *folder)
+{
+ return g_object_new (CAMEL_TYPE_FOLDER_SUMMARY, "folder", folder, NULL);
+}
+
+/**
+ * camel_folder_summary_get_folder:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Returns: (transfer none): a #CamelFolder to which the summary if associated.
+ *
+ * Since: 3.4
+ **/
+CamelFolder *
+camel_folder_summary_get_folder (CamelFolderSummary *summary)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
+
+ return summary->priv->folder;
+}
+
+/**
+ * camel_folder_summary_get_saved_count:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Returns: Count of saved infos.
+ *
+ * Since: 3.4
+ **/
+guint32
+camel_folder_summary_get_saved_count (CamelFolderSummary *summary)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
+
+ return summary->priv->saved_count;
+}
+
+/**
+ * camel_folder_summary_get_unread_count:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Returns: Count of unread infos.
+ *
+ * Since: 3.4
+ **/
+guint32
+camel_folder_summary_get_unread_count (CamelFolderSummary *summary)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
+
+ return summary->priv->unread_count;
+}
+
+/**
+ * camel_folder_summary_get_deleted_count:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Returns: Count of deleted infos.
+ *
+ * Since: 3.4
+ **/
+guint32
+camel_folder_summary_get_deleted_count (CamelFolderSummary *summary)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
+
+ return summary->priv->deleted_count;
+}
+
+/**
+ * camel_folder_summary_get_junk_count:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Returns: Count of junk infos.
+ *
+ * Since: 3.4
+ **/
+guint32
+camel_folder_summary_get_junk_count (CamelFolderSummary *summary)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
+
+ return summary->priv->junk_count;
+}
+
+/**
+ * camel_folder_summary_get_junk_not_deleted_count:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Returns: Count of junk and not deleted infos.
+ *
+ * Since: 3.4
+ **/
+guint32
+camel_folder_summary_get_junk_not_deleted_count (CamelFolderSummary *summary)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
+
+ return summary->priv->junk_not_deleted_count;
+}
+
+/**
+ * camel_folder_summary_get_visible_count:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Returns: Count of visible (not junk and not deleted) infos.
+ *
+ * Since: 3.4
+ **/
+guint32
+camel_folder_summary_get_visible_count (CamelFolderSummary *summary)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
+
+ return summary->priv->visible_count;
+}
+
+/**
+ * camel_folder_summary_set_index:
+ * @summary: a #CamelFolderSummary object
+ * @index: a #CamelIndex
+ *
+ * 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 *summary,
+ CamelIndex *index)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
+
+ if (index != NULL)
+ g_object_ref (index);
+
+ if (summary->priv->index != NULL)
+ g_object_unref (summary->priv->index);
+
+ summary->priv->index = index;
+}
+
+/**
+ * camel_folder_summary_get_index:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Returns: (transfer none): a #CamelIndex used to index body content.
+ *
+ * Since: 3.4
+ **/
+CamelIndex *
+camel_folder_summary_get_index (CamelFolderSummary *summary)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
+
+ return summary->priv->index;
+}
+
+/**
+ * camel_folder_summary_set_build_content:
+ * @summary: a #CamelFolderSummary object
+ * @state: to build or not to build the content
+ *
+ * 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 *summary,
+ gboolean state)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
+
+ if (summary->priv->build_content == state)
+ return;
+
+ summary->priv->build_content = state;
+
+ g_object_notify (G_OBJECT (summary), "build-content");
+}
+
+/**
+ * camel_folder_summary_get_build_content:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Returns: Whether to build #CamelMessageInfo.content.
+ *
+ * Since: 3.4
+ **/
+gboolean
+camel_folder_summary_get_build_content (CamelFolderSummary *summary)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+
+ return summary->priv->build_content;
+}
+
+/**
+ * camel_folder_summary_set_need_preview:
+ *
+ * Since: 2.28
+ **/
+void
+camel_folder_summary_set_need_preview (CamelFolderSummary *summary,
+ gboolean preview)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
+
+ summary->priv->need_preview = preview;
+}
+
+/**
+ * camel_folder_summary_get_need_preview:
+ *
+ * Since: 2.28
+ **/
+gboolean
+camel_folder_summary_get_need_preview (CamelFolderSummary *summary)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+
+ return summary->priv->need_preview;
+}
+
+/**
+ * camel_folder_summary_next_uid:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Generate a new unique uid value as an integer. This
+ * may be used to create a unique sequence of numbers.
+ *
+ * Returns: the next unique uid value
+ **/
+guint32
+camel_folder_summary_next_uid (CamelFolderSummary *summary)
+{
+ guint32 uid;
+
+ camel_folder_summary_lock (summary);
+
+ uid = summary->priv->nextuid++;
+ camel_folder_summary_touch (summary);
+
+ camel_folder_summary_unlock (summary);
+
+ return uid;
+}
+
+/**
+ * camel_folder_summary_set_next_uid:
+ * @summary: a #CamelFolderSummary object
+ * @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_next_uid (CamelFolderSummary *summary,
+ guint32 uid)
+{
+ camel_folder_summary_lock (summary);
+
+ summary->priv->nextuid = MAX (summary->priv->nextuid, uid);
+ camel_folder_summary_touch (summary);
+
+ camel_folder_summary_unlock (summary);
+}
+
+/**
+ * camel_folder_summary_get_next_uid:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Returns: Next uid currently awaiting for assignment. The difference from
+ * camel_folder_summary_next_uid() is that this function returns actual
+ * value and doesn't increment it before returning.
+ *
+ * Since: 3.4
+ **/
+guint32
+camel_folder_summary_get_next_uid (CamelFolderSummary *summary)
+{
+ guint32 res;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
+
+ camel_folder_summary_lock (summary);
+
+ res = summary->priv->nextuid;
+
+ camel_folder_summary_unlock (summary);
+
+ return res;
+}
+
+/**
+ * camel_folder_summary_next_uid_string:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Retrieve the next uid, but as a formatted string.
+ *
+ * Returns: the next uid as an unsigned integer string.
+ * This string must be freed by the caller.
+ **/
+gchar *
+camel_folder_summary_next_uid_string (CamelFolderSummary *summary)
+{
+ CamelFolderSummaryClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
+
+ class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
+ g_return_val_if_fail (class->next_uid_string != NULL, NULL);
+
+ return class->next_uid_string (summary);
+}
+
+/**
+ * camel_folder_summary_count:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Get the number of summary items stored in this summary.
+ *
+ * Returns: the number of items in the summary
+ **/
+guint
+camel_folder_summary_count (CamelFolderSummary *summary)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), 0);
+
+ return g_hash_table_size (summary->priv->uids);
+}
+
+/**
+ * camel_folder_summary_check_uid
+ * @summary: a #CamelFolderSummary object
+ * @uid: a uid
+ *
+ * Check if the uid is valid. This isn't very efficient, so it shouldn't be called iteratively.
+ *
+ *
+ * Returns: if the uid is present in the summary or not (%TRUE or %FALSE)
+ *
+ * Since: 2.24
+ **/
+gboolean
+camel_folder_summary_check_uid (CamelFolderSummary *summary,
+ const gchar *uid)
+{
+ gboolean ret;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+
+ camel_folder_summary_lock (summary);
+
+ ret = g_hash_table_lookup_extended (summary->priv->uids, uid, NULL, NULL);
+
+ camel_folder_summary_unlock (summary);
+
+ return ret;
+}
+
+static void
+folder_summary_dupe_uids_to_array (gpointer key_uid,
+ gpointer value_flags,
+ gpointer user_data)
+{
+ g_ptr_array_add (user_data, (gpointer) camel_pstring_strdup (key_uid));
+}
+
+/**
+ * camel_folder_summary_get_array:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Obtain a copy of the summary array. This is done atomically,
+ * so cannot contain empty entries.
+ *
+ * Free with camel_folder_summary_free_array()
+ *
+ * Returns: (element-type utf8) (transfer full): a #GPtrArray of uids
+ *
+ * Since: 3.4
+ **/
+GPtrArray *
+camel_folder_summary_get_array (CamelFolderSummary *summary)
+{
+ GPtrArray *res;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
+
+ camel_folder_summary_lock (summary);
+
+ res = g_ptr_array_sized_new (g_hash_table_size (summary->priv->uids));
+ g_hash_table_foreach (summary->priv->uids, folder_summary_dupe_uids_to_array, res);
+
+ camel_folder_summary_unlock (summary);
+
+ return res;
+}
+
+/**
+ * camel_folder_summary_free_array:
+ * @array: (element-type utf8): a #GPtrArray returned from camel_folder_summary_get_array()
+ *
+ * Free's array and its elements returned from camel_folder_summary_get_array().
+ *
+ * Since: 3.4
+ **/
+void
+camel_folder_summary_free_array (GPtrArray *array)
+{
+ if (!array)
+ return;
+
+ g_ptr_array_foreach (array, (GFunc) camel_pstring_free, NULL);
+ g_ptr_array_free (array, TRUE);
+}
+
+static void
+cfs_copy_uids_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ const gchar *uid = key;
+ GHashTable *copy_hash = user_data;
+
+ g_hash_table_insert (copy_hash, (gpointer) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
+}
+
+/**
+ * camel_folder_summary_get_hash:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Returns hash of current stored 'uids' in summary, where key is 'uid'
+ * from the string pool, and value is 1. The returned pointer should
+ * be freed with g_hash_table_destroy().
+ *
+ * Note: When searching for values always use uids from the string pool.
+ *
+ * Returns: (element-type utf8 gint) (transfer container):
+ *
+ * Since: 3.6
+ **/
+GHashTable *
+camel_folder_summary_get_hash (CamelFolderSummary *summary)
+{
+ GHashTable *uids;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
+
+ camel_folder_summary_lock (summary);
+
+ /* using direct hash because of strings being from the string pool */
+ uids = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
+ g_hash_table_foreach (summary->priv->uids, cfs_copy_uids_cb, uids);
+
+ camel_folder_summary_unlock (summary);
+
+ return uids;
+}
+
+/**
+ * camel_folder_summary_peek_loaded:
+ *
+ * Since: 2.26
+ **/
+CamelMessageInfo *
+camel_folder_summary_peek_loaded (CamelFolderSummary *summary,
+ const gchar *uid)
+{
+ CamelMessageInfo *info;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
+
+ info = g_hash_table_lookup (summary->priv->loaded_infos, uid);
+
+ if (info)
+ camel_message_info_ref (info);
+
+ return info;
+}
+
+struct _db_pass_data {
+ GHashTable *columns_hash;
+ CamelFolderSummary *summary;
+ gboolean add; /* or just insert to hashtable */
+};
+
+static CamelMessageInfo *
+message_info_from_uid (CamelFolderSummary *summary,
+ const gchar *uid)
+{
+ CamelMessageInfo *info;
+ gint ret;
+
+ camel_folder_summary_lock (summary);
+
+ info = g_hash_table_lookup (summary->priv->loaded_infos, uid);
+
+ if (!info) {
+ CamelDB *cdb;
+ CamelStore *parent_store;
+ const gchar *folder_name;
+ struct _db_pass_data data;
+
+ folder_name = camel_folder_get_full_name (summary->priv->folder);
+
+ if (is_in_memory_summary (summary)) {
+ camel_folder_summary_unlock (summary);
+ g_warning (
+ "%s: Tried to load uid '%s' "
+ "from DB on in-memory summary of '%s'",
+ G_STRFUNC, uid, folder_name);
+ return NULL;
+ }
+
+ parent_store = camel_folder_get_parent_store (summary->priv->folder);
+ if (!parent_store) {
+ camel_folder_summary_unlock (summary);
+ return NULL;
+ }
+
+ cdb = parent_store->cdb_r;
+
+ data.columns_hash = NULL;
+ data.summary = summary;
+ data.add = FALSE;
+
+ ret = camel_db_read_message_info_record_with_uid (
+ cdb, folder_name, uid, &data,
+ camel_read_mir_callback, NULL);
+ if (data.columns_hash)
+ g_hash_table_destroy (data.columns_hash);
+
+ if (ret != 0) {
+ camel_folder_summary_unlock (summary);
+ return NULL;
+ }
+
+ /* We would have double reffed at camel_read_mir_callback */
+ info = g_hash_table_lookup (summary->priv->loaded_infos, uid);
+
+ cfs_schedule_info_release_timer (summary);
+ }
+
+ if (info)
+ camel_message_info_ref (info);
+
+ camel_folder_summary_unlock (summary);
+
+ return info;
+}
+
+/**
+ * camel_folder_summary_get:
+ * @summary: a #CamelFolderSummary object
+ * @uid: a 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.
+ *
+ * Returns: the summary item, or %NULL if the uid @uid is not available
+ *
+ * See camel_folder_summary_get_info_flags().
+ *
+ * Since: 3.4
+ **/
+CamelMessageInfo *
+camel_folder_summary_get (CamelFolderSummary *summary,
+ const gchar *uid)
+{
+ CamelFolderSummaryClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
+ g_return_val_if_fail (uid != NULL, NULL);
+
+ class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
+ g_return_val_if_fail (class->message_info_from_uid != NULL, NULL);
+
+ return class->message_info_from_uid (summary, uid);
+}
+
+/**
+ * camel_folder_summary_get_info_flags:
+ * @summary: a #CamelFolderSummary object
+ * @uid: a uid
+ *
+ * Retrieve CamelMessageInfo::flags for a message info with UID @uid.
+ * This is much quicker than camel_folder_summary_get(), because it
+ * doesn't require reading the message info from a disk.
+ *
+ * Returns: the flags currently stored for message info with UID @uid,
+ * or (~0) on error
+ *
+ * Since: 3.12
+ **/
+guint32
+camel_folder_summary_get_info_flags (CamelFolderSummary *summary,
+ const gchar *uid)
+{
+ gpointer ptr_uid = NULL, ptr_flags = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), (~0));
+ g_return_val_if_fail (uid != NULL, (~0));
+
+ camel_folder_summary_lock (summary);
+ if (!g_hash_table_lookup_extended (summary->priv->uids, uid, &ptr_uid, &ptr_flags)) {
+ camel_folder_summary_unlock (summary);
+ return (~0);
+ }
+
+ camel_folder_summary_unlock (summary);
+
+ return GPOINTER_TO_UINT (ptr_flags);
+}
+
+static CamelMessageContentInfo *
+perform_content_info_load_from_db (CamelFolderSummary *summary,
+ CamelMIRecord *mir)
+{
+ gint i;
+ guint32 count;
+ CamelMessageContentInfo *ci, *pci;
+ gchar *part;
+
+ ci = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_from_db (summary, mir);
+ if (ci == NULL)
+ return NULL;
+ part = mir->cinfo;
+ if (!part)
+ return ci;
+ if (*part == ' ') part++;
+ count = bdata_extract_digit (&part);
+
+ mir->cinfo = part;
+ for (i = 0; i < count; i++) {
+ pci = perform_content_info_load_from_db (summary, mir);
+ if (pci ) {
+ my_list_append ((struct _node **) &ci->childs, (struct _node *) pci);
+ pci->parent = ci;
+ } else {
+ d (fprintf (stderr, "Summary file format messed up?"));
+ camel_folder_summary_content_info_free (summary, ci);
+ return NULL;
+ }
+ }
+ return ci;
+}
+
+static void
+gather_dirty_or_flagged_uids (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ const gchar *uid = key;
+ CamelMessageInfoBase *info = value;
+ GHashTable *hash = user_data;
+
+ if (info->dirty || (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0)
+ g_hash_table_insert (hash, (gpointer) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
+}
+
+static void
+gather_changed_uids (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ const gchar *uid = key;
+ guint32 flags = GPOINTER_TO_UINT (value);
+ GHashTable *hash = user_data;
+
+ if ((flags & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0)
+ g_hash_table_insert (hash, (gpointer) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
+}
+
+/**
+ * camel_folder_summary_get_changed:
+ *
+ * Returns: (element-type utf8) (transfer full):
+ *
+ * Since: 2.24
+ **/
+GPtrArray *
+camel_folder_summary_get_changed (CamelFolderSummary *summary)
+{
+ GPtrArray *res;
+ GHashTable *hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
+
+ camel_folder_summary_lock (summary);
+
+ g_hash_table_foreach (summary->priv->loaded_infos, gather_dirty_or_flagged_uids, hash);
+ g_hash_table_foreach (summary->priv->uids, gather_changed_uids, hash);
+
+ res = g_ptr_array_sized_new (g_hash_table_size (hash));
+ g_hash_table_foreach (hash, folder_summary_dupe_uids_to_array, res);
+
+ camel_folder_summary_unlock (summary);
+
+ g_hash_table_destroy (hash);
+
+ return res;
+}
+
+static void
+count_changed_uids (gchar *key,
+ CamelMessageInfoBase *info,
+ gint *count)
+{
+ if (info->dirty)
+ (*count)++;
+}
+
+static gint
+cfs_count_dirty (CamelFolderSummary *summary)
+{
+ gint count = 0;
+
+ camel_folder_summary_lock (summary);
+ g_hash_table_foreach (summary->priv->loaded_infos, (GHFunc) count_changed_uids, &count);
+ camel_folder_summary_unlock (summary);
+
+ return count;
+}
+
+static gboolean
+remove_item (gchar *uid,
+ CamelMessageInfoBase *info,
+ GSList **to_remove_infos)
+{
+ if (info->refcount == 1 && !info->dirty && (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) == 0) {
+ *to_remove_infos = g_slist_prepend (*to_remove_infos, info);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+remove_cache (CamelSession *session,
+ GCancellable *cancellable,
+ CamelFolderSummary *summary,
+ GError **error)
+{
+ GSList *to_remove_infos = NULL;
+
+ CAMEL_DB_RELEASE_SQLITE_MEMORY;
+
+ if (time (NULL) - summary->priv->cache_load_time < SUMMARY_CACHE_DROP)
+ return;
+
+ camel_folder_summary_lock (summary);
+
+ g_hash_table_foreach_remove (summary->priv->loaded_infos, (GHRFunc) remove_item, &to_remove_infos);
+
+ g_slist_foreach (to_remove_infos, (GFunc) camel_message_info_unref, NULL);
+ g_slist_free (to_remove_infos);
+
+ camel_folder_summary_unlock (summary);
+
+ summary->priv->cache_load_time = time (NULL);
+}
+
+static void
+cfs_free_weakref (gpointer ptr)
+{
+ GWeakRef *weakref = ptr;
+
+ if (weakref) {
+ g_weak_ref_set (weakref, NULL);
+ g_weak_ref_clear (weakref);
+ g_free (weakref);
+ }
+}
+
+static gboolean
+cfs_try_release_memory (gpointer user_data)
+{
+ GWeakRef *weakref = user_data;
+ CamelFolderSummary *summary;
+ CamelStore *parent_store;
+ CamelSession *session;
+ gchar *description;
+
+ g_return_val_if_fail (weakref != NULL, FALSE);
+
+ summary = g_weak_ref_get (weakref);
+
+ if (!summary)
+ return FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+
+ /* If folder is freed or if the cache is nil then clean up */
+ if (!summary->priv->folder ||
+ !g_hash_table_size (summary->priv->loaded_infos) ||
+ is_in_memory_summary (summary)) {
+ summary->priv->cache_load_time = 0;
+ summary->priv->timeout_handle = 0;
+ g_object_unref (summary);
+
+ return FALSE;
+ }
+
+ if (time (NULL) - summary->priv->cache_load_time < SUMMARY_CACHE_DROP) {
+ g_object_unref (summary);
+ return TRUE;
+ }
+
+ parent_store = camel_folder_get_parent_store (summary->priv->folder);
+ if (!parent_store) {
+ summary->priv->cache_load_time = 0;
+ summary->priv->timeout_handle = 0;
+ g_object_unref (summary);
+
+ return FALSE;
+ }
+
+ session = camel_service_ref_session (CAMEL_SERVICE (parent_store));
+ if (!session) {
+ summary->priv->cache_load_time = 0;
+ summary->priv->timeout_handle = 0;
+ g_object_unref (summary);
+
+ return FALSE;
+ }
+
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ description = g_strdup_printf (_("Release unused memory for folder '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (parent_store)),
+ camel_folder_get_full_name (summary->priv->folder));
+
+ camel_session_submit_job (
+ session, description,
+ (CamelSessionCallback) remove_cache,
+ /* Consumes the reference of the 'summary'. */
+ summary, g_object_unref);
+
+ g_object_unref (session);
+ g_free (description);
+
+ return TRUE;
+}
+
+static void
+cfs_schedule_info_release_timer (CamelFolderSummary *summary)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
+
+ if (is_in_memory_summary (summary))
+ return;
+
+ if (!summary->priv->timeout_handle) {
+ static gboolean know_can_do = FALSE, can_do = TRUE;
+
+ if (!know_can_do) {
+ can_do = !g_getenv ("CAMEL_FREE_INFOS");
+ know_can_do = TRUE;
+ }
+
+ /* FIXME[disk-summary] LRU please and not timeouts */
+ if (can_do) {
+ GWeakRef *weakref;
+
+ weakref = g_new0 (GWeakRef, 1);
+ g_weak_ref_init (weakref, summary);
+
+ summary->priv->timeout_handle = g_timeout_add_seconds_full (
+ G_PRIORITY_DEFAULT, SUMMARY_CACHE_DROP,
+ cfs_try_release_memory,
+ weakref, cfs_free_weakref);
+ g_source_set_name_by_id (
+ summary->priv->timeout_handle,
+ "[camel] cfs_try_release_memory");
+ }
+ }
+
+ /* update also cache load time to the actual, to not release something just loaded */
+ summary->priv->cache_load_time = time (NULL);
+}
+
+static gint
+cfs_cache_size (CamelFolderSummary *summary)
+{
+ /* FIXME[disk-summary] this is a timely hack. fix it well */
+ if (!CAMEL_IS_VEE_FOLDER (summary->priv->folder))
+ return g_hash_table_size (summary->priv->loaded_infos);
+ else
+ return g_hash_table_size (summary->priv->uids);
+}
+
+/* Update preview of cached messages */
+
+static void
+msg_update_preview (const gchar *uid,
+ gpointer value,
+ CamelFolder *folder)
+{
+ CamelMessageInfoBase *info = (CamelMessageInfoBase *) camel_folder_summary_get (folder->summary, uid);
+ CamelMimeMessage *msg;
+ CamelStore *parent_store;
+ const gchar *full_name;
+
+ full_name = camel_folder_get_full_name (folder);
+ parent_store = camel_folder_get_parent_store (folder);
+
+ /* FIXME Pass a GCancellable */
+ msg = camel_folder_get_message_sync (folder, uid, NULL, NULL);
+ if (msg != NULL) {
+ if (camel_mime_message_build_preview ((CamelMimePart *) msg, (CamelMessageInfo *) info) && info->preview) {
+ if (parent_store && !is_in_memory_summary (folder->summary))
+ camel_db_write_preview_record (parent_store->cdb_w, full_name, info->uid, info->preview, NULL);
+ }
+ }
+ camel_message_info_unref (info);
+}
+
+static void
+pick_uids (const gchar *uid,
+ CamelMessageInfoBase *mi,
+ GPtrArray *array)
+{
+ if (mi->preview)
+ g_ptr_array_add (array, (gchar *) camel_pstring_strdup (uid));
+}
+
+static void
+copy_all_uids_to_hash (gpointer uid,
+ gpointer hash)
+{
+ g_return_if_fail (uid != NULL);
+
+ g_hash_table_insert (hash, (gchar *) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
+}
+
+static gboolean
+fill_mi (const gchar *uid,
+ const gchar *msg,
+ CamelFolder *folder)
+{
+ CamelMessageInfoBase *info;
+
+ info = g_hash_table_lookup (folder->summary->priv->loaded_infos, uid);
+ if (info) /* We re assign the memory of msg */
+ info->preview = (gchar *) msg;
+ camel_pstring_free (uid); /* unref the uid */
+
+ return TRUE;
+}
+
+static void
+preview_update (CamelSession *session,
+ GCancellable *cancellable,
+ CamelFolder *folder,
+ GError **error)
+{
+ /* FIXME: Either lock & use or copy & use.*/
+ GPtrArray *uids_uncached, *uids_array;
+ GHashTable *preview_data, *uids_hash;
+ CamelStore *parent_store;
+ const gchar *full_name;
+ gboolean is_in_memory = is_in_memory_summary (folder->summary);
+ gint i;
+
+ uids_array = camel_folder_summary_get_array (folder->summary);
+ uids_hash = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) camel_pstring_free, NULL);
+ g_ptr_array_foreach (uids_array, copy_all_uids_to_hash, uids_hash);
+ uids_uncached = camel_folder_get_uncached_uids (folder, uids_array, NULL);
+ camel_folder_summary_free_array (uids_array);
+ uids_array = NULL;
+
+ full_name = camel_folder_get_full_name (folder);
+ parent_store = camel_folder_get_parent_store (folder);
+ preview_data = (!parent_store || is_in_memory) ? NULL : camel_db_get_folder_preview (parent_store->cdb_r, full_name, NULL);
+ if (preview_data) {
+ g_hash_table_foreach_remove (preview_data, (GHRFunc) fill_mi, folder);
+ g_hash_table_destroy (preview_data);
+ }
+
+ camel_folder_summary_lock (folder->summary);
+ g_hash_table_foreach (folder->summary->priv->loaded_infos, (GHFunc) pick_uids, uids_uncached);
+ camel_folder_summary_unlock (folder->summary);
+
+ for (i = 0; i < uids_uncached->len; i++) {
+ g_hash_table_remove (uids_hash, uids_uncached->pdata[i]);
+ }
+
+ camel_folder_lock (folder);
+ if (parent_store && !is_in_memory)
+ camel_db_begin_transaction (parent_store->cdb_w, NULL);
+ g_hash_table_foreach (uids_hash, (GHFunc) msg_update_preview, folder);
+ if (parent_store && !is_in_memory)
+ camel_db_end_transaction (parent_store->cdb_w, NULL);
+ camel_folder_unlock (folder);
+ camel_folder_free_uids (folder, uids_uncached);
+ g_hash_table_destroy (uids_hash);
+}
+
+/* end */
+
+static void
+cfs_reload_from_db (CamelFolderSummary *summary,
+ GError **error)
+{
+ CamelDB *cdb;
+ CamelStore *parent_store;
+ const gchar *folder_name;
+ struct _db_pass_data data;
+
+ /* FIXME[disk-summary] baseclass this, and vfolders we may have to
+ * load better. */
+ d (printf ("\ncamel_folder_summary_reload_from_db called \n"));
+
+ if (is_in_memory_summary (summary))
+ return;
+
+ parent_store = camel_folder_get_parent_store (summary->priv->folder);
+ if (!parent_store)
+ return;
+
+ folder_name = camel_folder_get_full_name (summary->priv->folder);
+ cdb = parent_store->cdb_r;
+
+ data.columns_hash = NULL;
+ data.summary = summary;
+ data.add = FALSE;
+
+ camel_db_read_message_info_records (
+ cdb, folder_name, (gpointer) &data,
+ camel_read_mir_callback, NULL);
+
+ if (data.columns_hash)
+ g_hash_table_destroy (data.columns_hash);
+
+ cfs_schedule_info_release_timer (summary);
+
+ /* FIXME Convert this to a GTask, submitted through
+ * camel_service_queue_task(). Then it won't
+ * have to call camel_folder_lock/unlock(). */
+ if (summary->priv->need_preview) {
+ CamelSession *session;
+
+ /* This may not be available in a case of this being called as part
+ of CamelSession's dispose, because the CamelService uses GWeakRef
+ object which is invalidates its content when it reaches the dispose. */
+ session = camel_service_ref_session (CAMEL_SERVICE (parent_store));
+ if (session) {
+ gchar *description;
+
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ description = g_strdup_printf (_("Update preview data for folder '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (parent_store)),
+ camel_folder_get_full_name (summary->priv->folder));
+
+ camel_session_submit_job (
+ session, description,
+ (CamelSessionCallback) preview_update,
+ g_object_ref (summary->priv->folder),
+ (GDestroyNotify) g_object_unref);
+
+ g_object_unref (session);
+ g_free (description);
+ }
+ }
+
+ return;
+}
+
+/**
+ * camel_folder_summary_add_preview:
+ *
+ * Since: 2.28
+ **/
+void
+camel_folder_summary_add_preview (CamelFolderSummary *summary,
+ CamelMessageInfo *info)
+{
+ camel_folder_summary_lock (summary);
+ g_hash_table_insert (summary->priv->preview_updates, (gchar *) info->uid, ((CamelMessageInfoBase *) info)->preview);
+ camel_folder_summary_touch (summary);
+ camel_folder_summary_unlock (summary);
+}
+
+/**
+ * camel_folder_summary_prepare_fetch_all:
+ * @summary: #CamelFolderSummary object
+ * @error: return location for a #GError, or %NULL
+ *
+ * Loads all infos into memory, if they are not yet and ensures
+ * they will not be freed in next couple minutes. Call this function
+ * before any mass operation or when all message infos will be needed,
+ * for better performance.
+ *
+ * Since: 2.32
+ **/
+void
+camel_folder_summary_prepare_fetch_all (CamelFolderSummary *summary,
+ GError **error)
+{
+ guint loaded, known;
+
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
+
+ loaded = cfs_cache_size (summary);
+ known = camel_folder_summary_count (summary);
+
+ if (known - loaded > 50) {
+ camel_folder_summary_lock (summary);
+ cfs_reload_from_db (summary, error);
+ camel_folder_summary_unlock (summary);
+ }
+
+ /* update also cache load time, even when not loaded anything */
+ summary->priv->cache_load_time = time (NULL);
+}
+
+/**
+ * camel_folder_summary_load_from_db:
+ *
+ * Since: 2.24
+ **/
+gboolean
+camel_folder_summary_load_from_db (CamelFolderSummary *summary,
+ GError **error)
+{
+ CamelDB *cdb;
+ CamelStore *parent_store;
+ const gchar *full_name;
+ gint ret = 0;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+
+ if (is_in_memory_summary (summary))
+ return TRUE;
+
+ camel_folder_summary_lock (summary);
+ camel_folder_summary_save_to_db (summary, NULL);
+
+ /* struct _db_pass_data data; */
+ d (printf ("\ncamel_folder_summary_load_from_db called \n"));
+
+ full_name = camel_folder_get_full_name (summary->priv->folder);
+ parent_store = camel_folder_get_parent_store (summary->priv->folder);
+ if (!camel_folder_summary_header_load_from_db (summary, parent_store, full_name, error)) {
+ camel_folder_summary_unlock (summary);
+ return FALSE;
+ }
+
+ if (!parent_store) {
+ camel_folder_summary_unlock (summary);
+ return FALSE;
+ }
+
+ cdb = parent_store->cdb_r;
+
+ ret = camel_db_get_folder_uids (
+ cdb, full_name, summary->sort_by, summary->collate,
+ summary->priv->uids, &local_error);
+
+ if (local_error != NULL && local_error->message != NULL &&
+ strstr (local_error->message, "no such table") != NULL) {
+ g_clear_error (&local_error);
+
+ /* create table the first time it is accessed and missing */
+ ret = camel_db_prepare_message_info_table (cdb, full_name, error);
+ } else if (local_error != NULL)
+ g_propagate_error (error, local_error);
+
+ camel_folder_summary_unlock (summary);
+
+ return ret == 0;
+}
+
+static void
+mir_from_cols (CamelMIRecord *mir,
+ CamelFolderSummary *summary,
+ GHashTable **columns_hash,
+ gint ncol,
+ gchar **cols,
+ gchar **name)
+{
+ gint i;
+
+ for (i = 0; i < ncol; ++i) {
+ if (!name[i] || !cols[i])
+ continue;
+
+ switch (camel_db_get_column_ident (columns_hash, i, ncol, name)) {
+ case CAMEL_DB_COLUMN_UID:
+ mir->uid = (gchar *) camel_pstring_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_FLAGS:
+ mir->flags = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_READ:
+ mir->read = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
+ break;
+ case CAMEL_DB_COLUMN_DELETED:
+ mir->deleted = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
+ break;
+ case CAMEL_DB_COLUMN_REPLIED:
+ mir->replied = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
+ break;
+ case CAMEL_DB_COLUMN_IMPORTANT:
+ mir->important = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
+ break;
+ case CAMEL_DB_COLUMN_JUNK:
+ mir->junk = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
+ break;
+ case CAMEL_DB_COLUMN_ATTACHMENT:
+ mir->attachment = (cols[i]) ? ( ((strtoul (cols[i], NULL, 10)) ? TRUE : FALSE)) : FALSE;
+ break;
+ case CAMEL_DB_COLUMN_SIZE:
+ mir->size = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_DSENT:
+ mir->dsent = cols[i] ? strtol (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_DRECEIVED:
+ mir->dreceived = cols[i] ? strtol (cols[i], NULL, 10) : 0;
+ break;
+ case CAMEL_DB_COLUMN_SUBJECT:
+ mir->subject = (gchar *) camel_pstring_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_MAIL_FROM:
+ mir->from = (gchar *) camel_pstring_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_MAIL_TO:
+ mir->to = (gchar *) camel_pstring_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_MAIL_CC:
+ mir->cc = (gchar *) camel_pstring_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_MLIST:
+ mir->mlist = (gchar *) camel_pstring_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_FOLLOWUP_FLAG:
+ mir->followup_flag = (gchar *) camel_pstring_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_FOLLOWUP_COMPLETED_ON:
+ mir->followup_completed_on = (gchar *) camel_pstring_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_FOLLOWUP_DUE_BY:
+ mir->followup_due_by = (gchar *) camel_pstring_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_PART:
+ mir->part = g_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_LABELS:
+ mir->labels = g_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_USERTAGS:
+ mir->usertags = g_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_CINFO:
+ mir->cinfo = g_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_BDATA:
+ mir->bdata = g_strdup (cols[i]);
+ break;
+ case CAMEL_DB_COLUMN_BODYSTRUCTURE:
+ /* Evolution itself doesn't yet use this, ignoring */
+ /* mir->bodystructure = g_strdup (cols[i]); */
+ break;
+ default:
+ g_warn_if_reached ();
+ break;
+ }
+ }
+}
+
+static gint
+camel_read_mir_callback (gpointer ref,
+ gint ncol,
+ gchar **cols,
+ gchar **name)
+{
+ struct _db_pass_data *data = (struct _db_pass_data *) ref;
+ CamelFolderSummary *summary = data->summary;
+ CamelMIRecord *mir;
+ CamelMessageInfo *info;
+ gint ret = 0;
+
+ mir = g_new0 (CamelMIRecord , 1);
+ mir_from_cols (mir, summary, &data->columns_hash, ncol, cols, name);
+
+ camel_folder_summary_lock (summary);
+ if (!mir->uid || g_hash_table_lookup (summary->priv->loaded_infos, mir->uid)) {
+ /* Unlock and better return */
+ camel_folder_summary_unlock (summary);
+ camel_db_camel_mir_free (mir);
+ return ret;
+ }
+ camel_folder_summary_unlock (summary);
+
+ info = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_from_db (summary, mir);
+
+ if (info) {
+
+ if (summary->priv->build_content) {
+ gchar *tmp;
+ tmp = mir->cinfo;
+ /* FIXME: this should be done differently, how i don't know */
+ ((CamelMessageInfoBase *) info)->content = perform_content_info_load_from_db (summary, mir);
+ if (((CamelMessageInfoBase *) info)->content == NULL) {
+ camel_message_info_unref (info);
+ info = NULL;
+ }
+ mir->cinfo = tmp;
+
+ if (!info) {
+ camel_db_camel_mir_free (mir);
+ return -1;
+ }
+ }
+
+ /* Just now we are reading from the DB, it can't be dirty. */
+ ((CamelMessageInfoBase *) info)->dirty = FALSE;
+ if (data->add)
+ camel_folder_summary_add (summary, info);
+ else
+ camel_folder_summary_insert (summary, info, TRUE);
+
+ } else {
+ g_warning ("Loading messageinfo from db failed");
+ ret = -1;
+ }
+
+ camel_db_camel_mir_free (mir);
+
+ return ret;
+}
+
+/* saves the content descriptions, recursively */
+static gboolean
+perform_content_info_save_to_db (CamelFolderSummary *summary,
+ CamelMessageContentInfo *ci,
+ CamelMIRecord *record)
+{
+ CamelMessageContentInfo *part;
+ gchar *oldr;
+
+ if (!CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_to_db (summary, ci, record))
+ return FALSE;
+
+ oldr = record->cinfo;
+ record->cinfo = g_strdup_printf ("%s %d", oldr, my_list_size ((struct _node **) &ci->childs));
+ g_free (oldr);
+
+ part = ci->childs;
+ while (part) {
+ if (perform_content_info_save_to_db (summary, part, record) == -1)
+ return FALSE;
+ part = part->next;
+ }
+
+ return TRUE;
+}
+
+static void
+save_to_db_cb (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ CamelMessageInfoBase *mi = (CamelMessageInfoBase *) value;
+ CamelFolderSummary *summary = (CamelFolderSummary *) mi->summary;
+ CamelStore *parent_store;
+ const gchar *full_name;
+ CamelDB *cdb;
+ CamelMIRecord *mir;
+ GError **error = data;
+
+ full_name = camel_folder_get_full_name (summary->priv->folder);
+ parent_store = camel_folder_get_parent_store (summary->priv->folder);
+ if (!parent_store)
+ return;
+
+ cdb = parent_store->cdb_w;
+
+ if (!mi->dirty)
+ return;
+
+ mir = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_to_db (summary, (CamelMessageInfo *) mi);
+
+ if (mir && summary->priv->build_content) {
+ if (!perform_content_info_save_to_db (summary, ((CamelMessageInfoBase *) mi)->content, mir)) {
+ g_warning ("unable to save mir+cinfo for uid: %s\n", mir->uid);
+ camel_db_camel_mir_free (mir);
+ /* FIXME: Add exception here */
+ return;
+ }
+ }
+
+ g_return_if_fail (mir != NULL);
+
+ if (camel_db_write_message_info_record (cdb, full_name, mir, error) != 0) {
+ camel_db_camel_mir_free (mir);
+ return;
+ }
+
+ /* Reset the dirty flag which decides if the changes are synced to the DB or not.
+ The FOLDER_FLAGGED should be used to check if the changes are synced to the server.
+ So, dont unset the FOLDER_FLAGGED flag */
+ mi->dirty = FALSE;
+
+ camel_db_camel_mir_free (mir);
+}
+
+static gint
+save_message_infos_to_db (CamelFolderSummary *summary,
+ GError **error)
+{
+ CamelStore *parent_store;
+ CamelDB *cdb;
+ const gchar *full_name;
+
+ if (is_in_memory_summary (summary))
+ return 0;
+
+ full_name = camel_folder_get_full_name (summary->priv->folder);
+ parent_store = camel_folder_get_parent_store (summary->priv->folder);
+ if (!parent_store)
+ return 0;
+
+ cdb = parent_store->cdb_w;
+
+ if (camel_db_prepare_message_info_table (cdb, full_name, error) != 0)
+ return -1;
+
+ camel_folder_summary_lock (summary);
+
+ /* Push MessageInfo-es */
+ camel_db_begin_transaction (cdb, NULL);
+ g_hash_table_foreach (summary->priv->loaded_infos, save_to_db_cb, error);
+ camel_db_end_transaction (cdb, NULL);
+
+ camel_folder_summary_unlock (summary);
+ cfs_schedule_info_release_timer (summary);
+
+ return 0;
+}
+
+static void
+msg_save_preview (const gchar *uid,
+ gpointer value,
+ CamelFolder *folder)
+{
+ CamelStore *parent_store;
+ const gchar *full_name;
+
+ full_name = camel_folder_get_full_name (folder);
+ parent_store = camel_folder_get_parent_store (folder);
+
+ if (parent_store) {
+ camel_db_write_preview_record (
+ parent_store->cdb_w, full_name, uid, (gchar *) value, NULL);
+ }
+}
+
+/**
+ * camel_folder_summary_save_to_db:
+ *
+ * Since: 2.24
+ **/
+gboolean
+camel_folder_summary_save_to_db (CamelFolderSummary *summary,
+ GError **error)
+{
+ CamelStore *parent_store;
+ CamelDB *cdb;
+ CamelFIRecord *record;
+ gint ret, count;
+
+ g_return_val_if_fail (summary != NULL, FALSE);
+
+ if (!(summary->flags & CAMEL_FOLDER_SUMMARY_DIRTY) ||
+ is_in_memory_summary (summary))
+ return TRUE;
+
+ parent_store = camel_folder_get_parent_store (summary->priv->folder);
+ if (!parent_store)
+ return FALSE;
+
+ cdb = parent_store->cdb_w;
+
+ camel_folder_summary_lock (summary);
+
+ d (printf ("\ncamel_folder_summary_save_to_db called \n"));
+ if (summary->priv->need_preview && g_hash_table_size (summary->priv->preview_updates)) {
+ camel_db_begin_transaction (parent_store->cdb_w, NULL);
+ g_hash_table_foreach (summary->priv->preview_updates, (GHFunc) msg_save_preview, summary->priv->folder);
+ g_hash_table_remove_all (summary->priv->preview_updates);
+ camel_db_end_transaction (parent_store->cdb_w, NULL);
+ }
+
+ summary->flags &= ~CAMEL_FOLDER_SUMMARY_DIRTY;
+
+ count = cfs_count_dirty (summary);
+ if (!count) {
+ gboolean res = camel_folder_summary_header_save_to_db (summary, error);
+ camel_folder_summary_unlock (summary);
+ return res;
+ }
+
+ ret = save_message_infos_to_db (summary, error);
+ if (ret != 0) {
+ /* Failed, so lets reset the flag */
+ summary->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
+ camel_folder_summary_unlock (summary);
+ return FALSE;
+ }
+
+ /* XXX So... if an error is set, how do we even reach this point
+ * given the above error check? Oye vey this logic is nasty. */
+ if (error != NULL && *error != NULL &&
+ strstr ((*error)->message, "26 columns but 28 values") != NULL) {
+ const gchar *full_name;
+
+ full_name = camel_folder_get_full_name (summary->priv->folder);
+ g_warning ("Fixing up a broken summary migration on '%s : %s'\n",
+ camel_service_get_display_name (CAMEL_SERVICE (parent_store)), full_name);
+
+ /* Begin everything again. */
+ camel_db_begin_transaction (cdb, NULL);
+ camel_db_reset_folder_version (cdb, full_name, 0, NULL);
+ camel_db_end_transaction (cdb, NULL);
+
+ ret = save_message_infos_to_db (summary, error);
+ if (ret != 0) {
+ summary->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
+ camel_folder_summary_unlock (summary);
+ return FALSE;
+ }
+ }
+
+ record = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->summary_header_to_db (summary, error);
+ if (!record) {
+ summary->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
+ camel_folder_summary_unlock (summary);
+ return FALSE;
+ }
+
+ camel_db_begin_transaction (cdb, NULL);
+ ret = camel_db_write_folder_info_record (cdb, record, error);
+ g_free (record->folder_name);
+ g_free (record->bdata);
+ g_free (record);
+
+ if (ret != 0) {
+ camel_db_abort_transaction (cdb, NULL);
+ summary->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
+ camel_folder_summary_unlock (summary);
+ return FALSE;
+ }
+
+ camel_db_end_transaction (cdb, NULL);
+ camel_folder_summary_unlock (summary);
+
+ return ret == 0;
+}
+
+/**
+ * camel_folder_summary_header_save_to_db:
+ *
+ * Since: 2.24
+ **/
+gboolean
+camel_folder_summary_header_save_to_db (CamelFolderSummary *summary,
+ GError **error)
+{
+ CamelStore *parent_store;
+ CamelFIRecord *record;
+ CamelDB *cdb;
+ gint ret;
+
+ if (is_in_memory_summary (summary))
+ return TRUE;
+
+ parent_store = camel_folder_get_parent_store (summary->priv->folder);
+ if (!parent_store)
+ return FALSE;
+
+ cdb = parent_store->cdb_w;
+ camel_folder_summary_lock (summary);
+
+ d (printf ("\ncamel_folder_summary_header_save_to_db called \n"));
+
+ record = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->summary_header_to_db (summary, error);
+ if (!record) {
+ camel_folder_summary_unlock (summary);
+ return FALSE;
+ }
+
+ camel_db_begin_transaction (cdb, NULL);
+ ret = camel_db_write_folder_info_record (cdb, record, error);
+ g_free (record->folder_name);
+ g_free (record->bdata);
+ g_free (record);
+
+ if (ret != 0) {
+ camel_db_abort_transaction (cdb, NULL);
+ camel_folder_summary_unlock (summary);
+ return FALSE;
+ }
+
+ camel_db_end_transaction (cdb, NULL);
+ camel_folder_summary_unlock (summary);
+
+ return ret == 0;
+}
+
+/**
+ * camel_folder_summary_header_load_from_db:
+ *
+ * Since: 2.24
+ **/
+gboolean
+camel_folder_summary_header_load_from_db (CamelFolderSummary *summary,
+ CamelStore *store,
+ const gchar *folder_name,
+ GError **error)
+{
+ CamelDB *cdb;
+ CamelFIRecord *record;
+ gboolean ret = FALSE;
+
+ d (printf ("\ncamel_folder_summary_header_load_from_db called \n"));
+
+ if (is_in_memory_summary (summary))
+ return TRUE;
+
+ camel_folder_summary_lock (summary);
+ camel_folder_summary_save_to_db (summary, NULL);
+
+ cdb = store->cdb_r;
+
+ record = g_new0 (CamelFIRecord, 1);
+ camel_db_read_folder_info_record (cdb, folder_name, record, error);
+
+ ret = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->summary_header_from_db (summary, record);
+
+ camel_folder_summary_unlock (summary);
+
+ g_free (record->folder_name);
+ g_free (record->bdata);
+ g_free (record);
+
+ return ret;
+}
+
+static gboolean
+summary_assign_uid (CamelFolderSummary *summary,
+ CamelMessageInfo *info)
+{
+ const gchar *uid;
+ CamelMessageInfo *mi;
+
+ uid = camel_message_info_get_uid (info);
+
+ if (uid == NULL || uid[0] == 0) {
+ camel_pstring_free (info->uid);
+ uid = info->uid = (gchar *) camel_pstring_add (camel_folder_summary_next_uid_string (summary), TRUE);
+ }
+
+ camel_folder_summary_lock (summary);
+
+ while ((mi = g_hash_table_lookup (summary->priv->loaded_infos, uid))) {
+ camel_folder_summary_unlock (summary);
+
+ if (mi == info)
+ return FALSE;
+
+ d (printf ("Trying to insert message with clashing uid (%s). new uid re-assigned", camel_message_info_get_uid (info)));
+
+ camel_pstring_free (info->uid);
+ uid = info->uid = camel_pstring_add (camel_folder_summary_next_uid_string (summary), TRUE);
+ camel_message_info_set_flags (info, CAMEL_MESSAGE_FOLDER_FLAGGED, CAMEL_MESSAGE_FOLDER_FLAGGED);
+
+ camel_folder_summary_lock (summary);
+ }
+
+ camel_folder_summary_unlock (summary);
+
+ return TRUE;
+}
+
+/**
+ * camel_folder_summary_add:
+ * @summary: a #CamelFolderSummary object
+ * @info: a #CamelMessageInfo
+ *
+ * Adds a new @info record to the summary. If @info->uid is %NULL,
+ * then a new uid is automatically re-assigned by calling
+ * camel_folder_summary_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 *summary,
+ CamelMessageInfo *info)
+{
+ CamelMessageInfoBase *base_info;
+
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
+
+ if (info == NULL)
+ return;
+
+ camel_folder_summary_lock (summary);
+ if (!summary_assign_uid (summary, info)) {
+ camel_folder_summary_unlock (summary);
+ return;
+ }
+
+ base_info = (CamelMessageInfoBase *) info;
+ folder_summary_update_counts_by_flags (summary, camel_message_info_get_flags (info), UPDATE_COUNTS_ADD);
+ base_info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
+ base_info->dirty = TRUE;
+
+ g_hash_table_insert (
+ summary->priv->uids,
+ (gpointer) camel_pstring_strdup (camel_message_info_get_uid (info)),
+ GUINT_TO_POINTER (camel_message_info_get_flags (info)));
+
+ /* Summary always holds a ref for the loaded infos */
+ g_hash_table_insert (summary->priv->loaded_infos, (gpointer) camel_message_info_get_uid (info), info);
+
+ camel_folder_summary_touch (summary);
+
+ camel_folder_summary_unlock (summary);
+}
+
+/**
+ * camel_folder_summary_insert:
+ *
+ * Since: 2.24
+ **/
+void
+camel_folder_summary_insert (CamelFolderSummary *summary,
+ CamelMessageInfo *info,
+ gboolean load)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
+
+ if (info == NULL)
+ return;
+
+ camel_folder_summary_lock (summary);
+
+ if (!load) {
+ CamelMessageInfoBase *base_info = (CamelMessageInfoBase *) info;
+
+ folder_summary_update_counts_by_flags (summary, camel_message_info_get_flags (info), UPDATE_COUNTS_ADD);
+ base_info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
+ base_info->dirty = TRUE;
+
+ g_hash_table_insert (
+ summary->priv->uids,
+ (gpointer) camel_pstring_strdup (camel_message_info_get_uid (info)),
+ GUINT_TO_POINTER (camel_message_info_get_flags (info)));
+
+ camel_folder_summary_touch (summary);
+ }
+
+ /* Summary always holds a ref for the loaded infos */
+ g_hash_table_insert (summary->priv->loaded_infos, (gchar *) camel_message_info_get_uid (info), info);
+
+ camel_folder_summary_unlock (summary);
+}
+
+/**
+ * camel_folder_summary_info_new_from_header:
+ * @summary: a #CamelFolderSummary object
+ * @headers: rfc822 headers
+ *
+ * Create a new info record from a header.
+ *
+ * Returns: the newly allocated record which must be unreferenced with
+ * camel_message_info_unref()
+ **/
+CamelMessageInfo *
+camel_folder_summary_info_new_from_header (CamelFolderSummary *summary,
+ struct _camel_header_raw *h)
+{
+ CamelFolderSummaryClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
+
+ class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
+ g_return_val_if_fail (class->message_info_new_from_header != NULL, NULL);
+
+ return class->message_info_new_from_header (summary, h);
+}
+
+/**
+ * camel_folder_summary_info_new_from_parser:
+ * @summary: a #CamelFolderSummary object
+ * @parser: a #CamelMimeParser object
+ *
+ * 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.
+ *
+ * Returns: the newly allocated record which must be unreferenced with
+ * camel_message_info_unref()
+ **/
+CamelMessageInfo *
+camel_folder_summary_info_new_from_parser (CamelFolderSummary *summary,
+ CamelMimeParser *mp)
+{
+ CamelMessageInfo *info = NULL;
+ gchar *buffer;
+ gsize len;
+ CamelFolderSummaryPrivate *p = summary->priv;
+ goffset 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 = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_new_from_parser (summary, 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 (summary, info);
+
+ g_rec_mutex_lock (&summary->priv->filter_lock);
+
+ if (p->index) {
+ if (p->filter_index == NULL)
+ p->filter_index = camel_mime_filter_index_new (p->index);
+ camel_index_delete_name (p->index, camel_message_info_get_uid (info));
+ name = camel_index_add_name (p->index, camel_message_info_get_uid (info));
+ camel_mime_filter_index_set_name (CAMEL_MIME_FILTER_INDEX (p->filter_index), name);
+ }
+
+ /* always scan the content info, even if we dont save it */
+ ((CamelMessageInfoBase *) info)->content = summary_build_content_info (summary, info, mp);
+
+ if (name && p->index) {
+ camel_index_write_name (p->index, name);
+ g_object_unref (name);
+ camel_mime_filter_index_set_name (
+ CAMEL_MIME_FILTER_INDEX (p->filter_index), NULL);
+ }
+
+ g_rec_mutex_unlock (&summary->priv->filter_lock);
+
+ ((CamelMessageInfoBase *) info)->size = camel_mime_parser_tell (mp) - start;
+ }
+ return info;
+}
+
+/**
+ * camel_folder_summary_info_new_from_message:
+ * @summary: a #CamelFolderSummary object
+ * @message: a #CamelMimeMessage object
+ * @bodystructure: a bodystructure or NULL
+ *
+ * Create a summary item from a message.
+ *
+ * Returns: the newly allocated record which must be unreferenced with
+ * camel_message_info_unref()
+ **/
+CamelMessageInfo *
+camel_folder_summary_info_new_from_message (CamelFolderSummary *summary,
+ CamelMimeMessage *msg,
+ const gchar *bodystructure)
+{
+ CamelMessageInfo *info;
+ CamelFolderSummaryPrivate *p = summary->priv;
+ CamelIndexName *name = NULL;
+
+ info = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_new_from_message (summary, msg, bodystructure);
+
+ /* 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 (summary, info);
+
+ g_rec_mutex_lock (&summary->priv->filter_lock);
+
+ if (p->index) {
+ if (p->filter_index == NULL)
+ p->filter_index = camel_mime_filter_index_new (p->index);
+ camel_index_delete_name (p->index, camel_message_info_get_uid (info));
+ name = camel_index_add_name (p->index, camel_message_info_get_uid (info));
+ camel_mime_filter_index_set_name (
+ CAMEL_MIME_FILTER_INDEX (p->filter_index), name);
+
+ if (p->filter_stream == NULL) {
+ CamelStream *null = camel_stream_null_new ();
+
+ p->filter_stream = camel_stream_filter_new (null);
+ g_object_unref (null);
+ }
+ }
+
+ ((CamelMessageInfoBase *) info)->content = summary_build_content_info_message (summary, info, (CamelMimePart *) msg);
+
+ if (name) {
+ camel_index_write_name (p->index, name);
+ g_object_unref (name);
+ camel_mime_filter_index_set_name (
+ CAMEL_MIME_FILTER_INDEX (p->filter_index), NULL);
+ }
+
+ g_rec_mutex_unlock (&summary->priv->filter_lock);
+
+ return info;
+}
+
+/**
+ * camel_folder_summary_content_info_free:
+ * @summary: a #CamelFolderSummary object
+ * @ci: a #CamelMessageContentInfo
+ *
+ * Free the content info @ci, and all associated memory.
+ **/
+void
+camel_folder_summary_content_info_free (CamelFolderSummary *summary,
+ CamelMessageContentInfo *ci)
+{
+ CamelMessageContentInfo *pw, *pn;
+
+ pw = ci->childs;
+ CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_free (summary, ci);
+ while (pw) {
+ pn = pw->next;
+ camel_folder_summary_content_info_free (summary, pw);
+ pw = pn;
+ }
+}
+
+/**
+ * camel_folder_summary_touch:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Mark the summary as changed, so that a save will force it to be
+ * written back to disk.
+ **/
+void
+camel_folder_summary_touch (CamelFolderSummary *summary)
+{
+ camel_folder_summary_lock (summary);
+ summary->flags |= CAMEL_FOLDER_SUMMARY_DIRTY;
+ camel_folder_summary_unlock (summary);
+}
+
+/**
+ * camel_folder_summary_clear:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Empty the summary contents.
+ **/
+gboolean
+camel_folder_summary_clear (CamelFolderSummary *summary,
+ GError **error)
+{
+ GObject *summary_object;
+ CamelStore *parent_store;
+ CamelDB *cdb;
+ const gchar *folder_name;
+ gboolean res;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+
+ camel_folder_summary_lock (summary);
+ if (camel_folder_summary_count (summary) == 0) {
+ camel_folder_summary_unlock (summary);
+ return TRUE;
+ }
+
+ g_hash_table_remove_all (summary->priv->uids);
+ remove_all_loaded (summary);
+ g_hash_table_remove_all (summary->priv->loaded_infos);
+
+ summary->priv->saved_count = 0;
+ summary->priv->unread_count = 0;
+ summary->priv->deleted_count = 0;
+ summary->priv->junk_count = 0;
+ summary->priv->junk_not_deleted_count = 0;
+ summary->priv->visible_count = 0;
+
+ camel_folder_summary_touch (summary);
+
+ folder_name = camel_folder_get_full_name (summary->priv->folder);
+ parent_store = camel_folder_get_parent_store (summary->priv->folder);
+ if (!parent_store) {
+ camel_folder_summary_unlock (summary);
+ return FALSE;
+ }
+
+ cdb = parent_store->cdb_w;
+
+ if (!is_in_memory_summary (summary))
+ res = camel_db_clear_folder_summary (cdb, folder_name, error) == 0;
+ else
+ res = TRUE;
+
+ summary_object = G_OBJECT (summary);
+ g_object_freeze_notify (summary_object);
+ g_object_notify (summary_object, "saved-count");
+ g_object_notify (summary_object, "unread-count");
+ g_object_notify (summary_object, "deleted-count");
+ g_object_notify (summary_object, "junk-count");
+ g_object_notify (summary_object, "junk-not-deleted-count");
+ g_object_notify (summary_object, "visible-count");
+ g_object_thaw_notify (summary_object);
+
+ camel_folder_summary_unlock (summary);
+
+ return res;
+}
+
+/**
+ * camel_folder_summary_remove:
+ * @summary: a #CamelFolderSummary object
+ * @info: a #CamelMessageInfo
+ *
+ * Remove a specific @info record from the summary.
+ *
+ * Returns: Whether the @info was found and removed from the @summary.
+ **/
+gboolean
+camel_folder_summary_remove (CamelFolderSummary *summary,
+ CamelMessageInfo *info)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+ g_return_val_if_fail (info != NULL, FALSE);
+
+ if (camel_folder_summary_remove_uid (summary, camel_message_info_get_uid (info))) {
+ camel_message_info_unref (info);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * camel_folder_summary_remove_uid:
+ * @summary: a #CamelFolderSummary object
+ * @uid: a uid
+ *
+ * Remove a specific info record from the summary, by @uid.
+ *
+ * Returns: Whether the @uid was found and removed from the @summary.
+ **/
+gboolean
+camel_folder_summary_remove_uid (CamelFolderSummary *summary,
+ const gchar *uid)
+{
+ gpointer ptr_uid = NULL, ptr_flags = NULL;
+ CamelStore *parent_store;
+ const gchar *full_name;
+ const gchar *uid_copy;
+ gboolean res = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+
+ camel_folder_summary_lock (summary);
+ if (!g_hash_table_lookup_extended (summary->priv->uids, uid, &ptr_uid, &ptr_flags)) {
+ camel_folder_summary_unlock (summary);
+ return FALSE;
+ }
+
+ folder_summary_update_counts_by_flags (summary, GPOINTER_TO_UINT (ptr_flags), UPDATE_COUNTS_SUB);
+
+ uid_copy = camel_pstring_strdup (uid);
+ g_hash_table_remove (summary->priv->uids, uid_copy);
+ g_hash_table_remove (summary->priv->loaded_infos, uid_copy);
+
+ if (!is_in_memory_summary (summary)) {
+ full_name = camel_folder_get_full_name (summary->priv->folder);
+ parent_store = camel_folder_get_parent_store (summary->priv->folder);
+ if (!parent_store || camel_db_delete_uid (parent_store->cdb_w, full_name, uid_copy, NULL) != 0)
+ res = FALSE;
+ }
+
+ camel_pstring_free (uid_copy);
+
+ camel_folder_summary_touch (summary);
+ camel_folder_summary_unlock (summary);
+
+ return res;
+}
+
+/**
+ * camel_folder_summary_remove_uids:
+ * @summary: a #CamelFolderSummary object
+ * @uids: (element-type utf8): a GList of uids
+ *
+ * Remove a specific info record from the summary, by @uid.
+ *
+ * Returns: Whether the @uid was found and removed from the @summary.
+ *
+ * Since: 3.6
+ **/
+gboolean
+camel_folder_summary_remove_uids (CamelFolderSummary *summary,
+ GList *uids)
+{
+ CamelStore *parent_store;
+ const gchar *full_name;
+ GList *l;
+ gboolean res = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+ g_return_val_if_fail (uids != NULL, FALSE);
+
+ g_object_freeze_notify (G_OBJECT (summary));
+ camel_folder_summary_lock (summary);
+
+ for (l = g_list_first (uids); l; l = g_list_next (l)) {
+ gpointer ptr_uid = NULL, ptr_flags = NULL;
+ if (g_hash_table_lookup_extended (summary->priv->uids, l->data, &ptr_uid, &ptr_flags)) {
+ const gchar *uid_copy = camel_pstring_strdup (l->data);
+ CamelMessageInfo *mi;
+
+ folder_summary_update_counts_by_flags (summary, GPOINTER_TO_UINT (ptr_flags), UPDATE_COUNTS_SUB);
+ g_hash_table_remove (summary->priv->uids, uid_copy);
+
+ mi = g_hash_table_lookup (summary->priv->loaded_infos, uid_copy);
+ g_hash_table_remove (summary->priv->loaded_infos, uid_copy);
+
+ if (mi)
+ camel_message_info_unref (mi);
+ camel_pstring_free (uid_copy);
+ }
+ }
+
+ if (!is_in_memory_summary (summary)) {
+ full_name = camel_folder_get_full_name (summary->priv->folder);
+ parent_store = camel_folder_get_parent_store (summary->priv->folder);
+ if (!parent_store || camel_db_delete_uids (parent_store->cdb_w, full_name, uids, NULL) != 0)
+ res = FALSE;
+ }
+
+ camel_folder_summary_touch (summary);
+ camel_folder_summary_unlock (summary);
+ g_object_thaw_notify (G_OBJECT (summary));
+
+ return res;
+}
+
+static struct _node *
+my_list_append (struct _node **list,
+ struct _node *n)
+{
+ struct _node *ln = *list;
+ n->next = NULL;
+
+ if (!ln) {
+ *list = n;
+ return n;
+ }
+
+ while (ln->next)
+ ln = ln->next;
+ ln->next = n;
+ return n;
+}
+
+static gint
+my_list_size (struct _node **list)
+{
+ gint len = 0;
+ struct _node *ln = (struct _node *) list;
+ while (ln->next) {
+ ln = ln->next;
+ len++;
+ }
+ return len;
+}
+
+/* are these even useful for anything??? */
+static CamelMessageInfo *
+message_info_new_from_parser (CamelFolderSummary *summary,
+ CamelMimeParser *mp)
+{
+ CamelMessageInfo *mi = NULL;
+ gint 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 = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->message_info_new_from_header (summary, camel_mime_parser_headers_raw (mp));
+ break;
+ default:
+ g_error ("Invalid parser state");
+ }
+
+ return mi;
+}
+
+static CamelMessageContentInfo *
+content_info_new_from_parser (CamelFolderSummary *summary,
+ 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 = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_new_from_header (summary, camel_mime_parser_headers_raw (mp));
+ if (ci) {
+ if (ci->type)
+ camel_content_type_unref (ci->type);
+ 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 *summary,
+ CamelMimeMessage *msg,
+ const gchar *bodystructure)
+{
+ CamelMessageInfo *mi;
+
+ mi = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS (summary)))->message_info_new_from_header (summary, ((CamelMimePart *) msg)->headers);
+ ((CamelMessageInfoBase *) mi)->bodystructure = g_strdup (bodystructure);
+
+ return mi;
+}
+
+static CamelMessageContentInfo *
+content_info_new_from_message (CamelFolderSummary *summary,
+ CamelMimePart *mp)
+{
+ CamelMessageContentInfo *ci;
+
+ ci = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS (summary)))->content_info_new_from_header (summary, mp->headers);
+
+ return ci;
+}
+
+static gchar *
+summary_format_address (struct _camel_header_raw *h,
+ const gchar *name,
+ const gchar *charset)
+{
+ CamelHeaderAddress *addr;
+ gchar *text, *str;
+
+ if (!(text = (gchar *) camel_header_raw_find (&h, name, NULL)))
+ return NULL;
+
+ while (isspace ((unsigned) *text))
+ text++;
+
+ text = camel_header_unfold (text);
+
+ if ((addr = camel_header_address_decode (text, charset))) {
+ str = camel_header_address_list_format (addr);
+ camel_header_address_list_clear (&addr);
+ g_free (text);
+ } else {
+ str = text;
+ }
+
+ return str;
+}
+
+static gchar *
+summary_format_string (struct _camel_header_raw *h,
+ const gchar *name,
+ const gchar *charset)
+{
+ gchar *text, *str;
+
+ if (!(text = (gchar *) camel_header_raw_find (&h, name, NULL)))
+ return NULL;
+
+ while (isspace ((unsigned) *text))
+ text++;
+
+ text = camel_header_unfold (text);
+ str = camel_header_decode_string (text, charset);
+ g_free (text);
+
+ return str;
+}
+
+/**
+ * camel_folder_summary_content_info_new:
+ * @summary: a #CamelFolderSummary object
+ *
+ * Allocate a new #CamelMessageContentInfo, suitable for adding
+ * to this summary.
+ *
+ * Returns: a newly allocated #CamelMessageContentInfo
+ **/
+CamelMessageContentInfo *
+camel_folder_summary_content_info_new (CamelFolderSummary *summary)
+{
+ CamelFolderSummaryClass *class;
+
+ class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
+ g_return_val_if_fail (class->content_info_size > 0, NULL);
+
+ return g_slice_alloc0 (class->content_info_size);
+}
+
+static CamelMessageInfo *
+message_info_new_from_header (CamelFolderSummary *summary,
+ struct _camel_header_raw *h)
+{
+ const gchar *received, *date, *content, *charset = NULL;
+ GSList *refs, *irt, *scan;
+ gchar *subject, *from, *to, *cc, *mlist;
+ CamelContentType *ct = NULL;
+ CamelMessageInfoBase *mi;
+ guint8 *digest;
+ gsize length;
+ gchar *msgid;
+ guint count;
+
+ length = g_checksum_type_get_length (G_CHECKSUM_MD5);
+ digest = g_alloca (length);
+
+ mi = (CamelMessageInfoBase *) camel_message_info_new (summary);
+
+ 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 ? camel_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);
+
+ mi->subject = camel_pstring_add (subject, TRUE);
+ mi->from = camel_pstring_add (from, TRUE);
+ mi->to = camel_pstring_add (to, TRUE);
+ mi->cc = camel_pstring_add (cc, TRUE);
+ mi->mlist = camel_pstring_add (mlist, TRUE);
+
+ mi->user_flags = NULL;
+ mi->user_tags = NULL;
+
+ if ((date = camel_header_raw_find (&h, "date", NULL)))
+ mi->date_sent = camel_header_decode_date (date, NULL);
+ else
+ mi->date_sent = 0;
+
+ 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;
+
+ /* Fallback to Received date, when the Date header is missing */
+ if (!mi->date_sent)
+ mi->date_sent = mi->date_received;
+
+ /* If neither Received is available, then use the current time. */
+ if (!mi->date_sent)
+ mi->date_sent = time (NULL);
+
+ msgid = camel_header_msgid_decode (camel_header_raw_find (&h, "message-id", NULL));
+ if (msgid) {
+ GChecksum *checksum;
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (guchar *) msgid, -1);
+ g_checksum_get_digest (checksum, digest, &length);
+ g_checksum_free (checksum);
+
+ 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_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 = g_slist_length (refs);
+ mi->references = g_malloc (sizeof (*mi->references) + ((count - 1) * sizeof (mi->references->references[0])));
+ count = 0;
+ for (scan = refs; scan != NULL; scan = g_slist_next (scan)) {
+ GChecksum *checksum;
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (guchar *) scan->data, -1);
+ g_checksum_get_digest (checksum, digest, &length);
+ g_checksum_free (checksum);
+
+ memcpy (mi->references->references[count].id.hash, digest, sizeof (mi->message_id.id.hash));
+ count++;
+ }
+ mi->references->size = count;
+ g_slist_free_full (refs, g_free);
+ }
+
+ return (CamelMessageInfo *) mi;
+}
+
+static void
+message_info_free (CamelFolderSummary *summary,
+ CamelMessageInfo *info)
+{
+ CamelFolderSummaryClass *class;
+ CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
+
+ if (mi->uid) {
+ if (summary) {
+ camel_folder_summary_lock (summary);
+ if (g_hash_table_lookup (summary->priv->loaded_infos, mi->uid) == mi) {
+ g_hash_table_remove (summary->priv->loaded_infos, mi->uid);
+ }
+ camel_folder_summary_unlock (summary);
+ }
+ camel_pstring_free (mi->uid);
+ }
+ camel_pstring_free (mi->subject);
+ camel_pstring_free (mi->from);
+ camel_pstring_free (mi->to);
+ camel_pstring_free (mi->cc);
+ camel_pstring_free (mi->mlist);
+ g_free (mi->bodystructure);
+ g_free (mi->references);
+ g_free (mi->preview);
+ camel_flag_list_free (&mi->user_flags);
+ camel_tag_list_free (&mi->user_tags);
+ if (mi->headers)
+ camel_header_param_list_free (mi->headers);
+
+ if (summary) {
+ class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
+ g_slice_free1 (class->message_info_size, mi);
+ } else
+ g_slice_free (CamelMessageInfoBase, mi);
+}
+
+static CamelMessageContentInfo *
+content_info_new_from_header (CamelFolderSummary *summary,
+ struct _camel_header_raw *h)
+{
+ CamelMessageContentInfo *ci;
+ const gchar *charset;
+
+ ci = camel_folder_summary_content_info_new (summary);
+
+ charset = camel_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), charset);
+ 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 void
+content_info_free (CamelFolderSummary *summary,
+ CamelMessageContentInfo *ci)
+{
+ CamelFolderSummaryClass *class;
+
+ class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
+
+ camel_content_type_unref (ci->type);
+ g_free (ci->id);
+ g_free (ci->description);
+ g_free (ci->encoding);
+ g_slice_free1 (class->content_info_size, ci);
+}
+
+static gchar *
+next_uid_string (CamelFolderSummary *summary)
+{
+ return g_strdup_printf ("%u", camel_folder_summary_next_uid (summary));
+}
+
+/*
+ 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 *summary,
+ CamelMessageInfo *msginfo,
+ CamelMimeParser *mp)
+{
+ gint state;
+ gsize len;
+ gchar *buffer;
+ CamelMessageContentInfo *info = NULL;
+ CamelContentType *ct;
+ gint enc_id = -1, chr_id = -1, html_id = -1, idx_id = -1;
+ CamelFolderSummaryPrivate *p = summary->priv;
+ CamelMimeFilter *mfc;
+ CamelMessageContentInfo *part;
+ const gchar *calendar_header;
+
+ d (printf ("building content info\n"));
+
+ /* start of this part */
+ state = camel_mime_parser_step (mp, &buffer, &len);
+
+ if (summary->priv->build_content)
+ info = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_new_from_parser (summary, 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
+ )
+ camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
+
+ calendar_header = camel_mime_parser_header (mp, "Content-class", NULL);
+ if (calendar_header && g_ascii_strcasecmp (calendar_header, "urn:content-classes:calendarmessage") != 0)
+ calendar_header = NULL;
+
+ if (!calendar_header)
+ calendar_header = camel_mime_parser_header (mp, "X-Calendar-Attachment", NULL);
+
+ if (calendar_header || camel_content_type_is (ct, "text", "calendar"))
+ camel_message_info_set_user_flag (msginfo, "$has_cal", TRUE);
+
+ if (camel_mime_parser_header (mp, "X-Evolution-Note", NULL))
+ camel_message_info_set_user_flag (msginfo, "$has_note", TRUE);
+
+ if (p->index && camel_content_type_is (ct, "text", "*")) {
+ gchar *encoding;
+ const gchar *charset;
+
+ d (printf ("generating index:\n"));
+
+ encoding = camel_content_transfer_encoding_decode (camel_mime_parser_header (mp, "content-transfer-encoding", NULL));
+ if (encoding) {
+ if (!g_ascii_strcasecmp (encoding, "base64")) {
+ d (printf (" decoding base64\n"));
+ if (p->filter_64 == NULL)
+ p->filter_64 = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_BASE64_DEC);
+ else
+ camel_mime_filter_reset (p->filter_64);
+ enc_id = camel_mime_parser_filter_add (mp, 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 (CAMEL_MIME_FILTER_BASIC_QP_DEC);
+ else
+ camel_mime_filter_reset (p->filter_qp);
+ enc_id = camel_mime_parser_filter_add (mp, p->filter_qp);
+ } else if (!g_ascii_strcasecmp (encoding, "x-uuencode")) {
+ d (printf (" decoding x-uuencode\n"));
+ if (p->filter_uu == NULL)
+ p->filter_uu = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_UU_DEC);
+ else
+ camel_mime_filter_reset (p->filter_uu);
+ enc_id = camel_mime_parser_filter_add (mp, 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
+ || g_ascii_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 (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, mfc);
+ } else {
+ w (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, 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"))
+ camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
+ if (camel_content_type_is (ct, "multipart", "signed")
+ || camel_content_type_is (ct, "multipart", "encrypted"))
+ camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, 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 (summary, 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 */
+ camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
+
+ part = summary_build_content_info (summary, 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 *summary,
+ CamelMessageInfo *msginfo,
+ CamelMimePart *object)
+{
+ CamelDataWrapper *containee;
+ gint parts, i;
+ CamelFolderSummaryPrivate *p = summary->priv;
+ CamelMessageContentInfo *info = NULL, *child;
+ CamelContentType *ct;
+ const struct _camel_header_raw *header;
+ gboolean is_calendar = FALSE, is_note = FALSE;
+
+ if (summary->priv->build_content)
+ info = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->content_info_new_from_message (summary, object);
+
+ containee = camel_medium_get_content (CAMEL_MEDIUM (object));
+
+ if (containee == NULL)
+ return info;
+
+ /* TODO: I find it odd that get_part and get_content 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"))
+ camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_ATTACHMENTS, CAMEL_MESSAGE_ATTACHMENTS);
+ if (camel_content_type_is (ct, "multipart", "signed")
+ || camel_content_type_is (ct, "multipart", "encrypted"))
+ camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, 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
+ ) {
+ camel_message_info_set_flags (msginfo, CAMEL_MESSAGE_SECURE, CAMEL_MESSAGE_SECURE);
+ }
+
+ for (header = object->headers; header; header = header->next) {
+ const gchar *value = header->value;
+
+ /* skip preceding spaces in the value */
+ while (value && *value && isspace (*value))
+ value++;
+
+ if (header->name && value && (
+ (g_ascii_strcasecmp (header->name, "Content-class") == 0 && g_ascii_strcasecmp (value, "urn:content-classes:calendarmessage") == 0) ||
+ (g_ascii_strcasecmp (header->name, "X-Calendar-Attachment") == 0))) {
+ is_calendar = TRUE;
+ if (is_note)
+ break;
+ }
+
+ if (header->name && value && g_ascii_strcasecmp (header->name, "X-Evolution-Note") == 0) {
+ is_note = TRUE;
+ if (is_calendar)
+ break;
+ }
+ }
+
+ if (is_calendar || camel_content_type_is (ct, "text", "calendar"))
+ camel_message_info_set_user_flag (msginfo, "$has_cal", TRUE);
+
+ if (is_note)
+ camel_message_info_set_user_flag (msginfo, "$has_note", TRUE);
+
+ /* 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_return_val_if_fail (part, info);
+ child = summary_build_content_info_message (summary, 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 (summary, 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", "*")) {
+ gint 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 (
+ CAMEL_STREAM_FILTER (p->filter_stream),
+ (CamelMimeFilter *) p->filter_html);
+ }
+ idx_id = camel_stream_filter_add (
+ CAMEL_STREAM_FILTER (p->filter_stream),
+ p->filter_index);
+
+ /* FIXME Pass a GCancellable and GError here. */
+ camel_data_wrapper_decode_to_stream_sync (
+ containee, p->filter_stream, NULL, NULL);
+ camel_stream_flush (p->filter_stream, NULL, NULL);
+
+ camel_stream_filter_remove (
+ CAMEL_STREAM_FILTER (p->filter_stream), idx_id);
+ camel_stream_filter_remove (
+ CAMEL_STREAM_FILTER (p->filter_stream), html_id);
+ }
+
+ return info;
+}
+
+/**
+ * camel_flag_get:
+ * @list: the address of a #CamelFlag list
+ * @name: name of the flag to get
+ *
+ * Find the state of the flag @name in @list.
+ *
+ * Returns: the state of the flag (%TRUE or %FALSE)
+ **/
+gboolean
+camel_flag_get (CamelFlag **list,
+ const gchar *name)
+{
+ CamelFlag *flag;
+ flag = *list;
+ while (flag) {
+ if (!strcmp (flag->name, name))
+ return TRUE;
+ flag = flag->next;
+ }
+ return FALSE;
+}
+
+/**
+ * camel_flag_set:
+ * @list: the address of a #CamelFlag list
+ * @name: name of the flag to set or change
+ * @value: the value to set on the flag
+ *
+ * Set the state of a flag @name in the list @list to @value.
+ *
+ * Returns: %TRUE if the value of the flag has been changed or %FALSE
+ * otherwise
+ **/
+gboolean
+camel_flag_set (CamelFlag **list,
+ const gchar *name,
+ gboolean value)
+{
+ CamelFlag *flag, *tmp;
+ gsize tmp_len = 0;
+
+ if (!name)
+ return TRUE;
+
+ /* 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_len = sizeof (*tmp) + strlen (name);
+ tmp = g_malloc (tmp_len);
+ g_strlcpy (tmp->name, name, strlen (name) + 1);
+ tmp->next = NULL;
+ flag->next = tmp;
+ }
+ return value;
+}
+
+/**
+ * camel_flag_list_size:
+ * @list: the address of a #CamelFlag list
+ *
+ * Get the length of the flag list.
+ *
+ * Returns: the number of flags in the list
+ **/
+gint
+camel_flag_list_size (CamelFlag **list)
+{
+ gint count = 0;
+ CamelFlag *flag;
+
+ flag = *list;
+ while (flag) {
+ count++;
+ flag = flag->next;
+ }
+ return count;
+}
+
+/**
+ * camel_flag_list_free:
+ * @list: the address of a #CamelFlag 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: the address of the #CamelFlag list to copy to
+ * @from: the address of the #CamelFlag list to copy from
+ *
+ * Copy a flag list.
+ *
+ * Returns: %TRUE if @to is changed or %FALSE otherwise
+ **/
+gboolean
+camel_flag_list_copy (CamelFlag **to,
+ CamelFlag **from)
+{
+ CamelFlag *flag, *tmp;
+ gboolean 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)) {
+ if (*tmp->name)
+ changed = TRUE;
+ flag->next = tmp->next;
+ g_free (tmp);
+ } else {
+ flag = tmp;
+ }
+ }
+
+ /* Add any new non-empty flags */
+ flag = *from;
+ while (flag) {
+ if (*flag->name)
+ changed |= camel_flag_set (to, flag->name, TRUE);
+ flag = flag->next;
+ }
+
+ return changed;
+}
+
+/**
+ * camel_tag_get:
+ * @list: the address of a #CamelTag list
+ * @name: name of the tag to get
+ *
+ * Find the flag @name in @list and get the value.
+ *
+ * Returns: the value of the flag or %NULL if unset
+ **/
+const gchar *
+camel_tag_get (CamelTag **list,
+ const gchar *name)
+{
+ CamelTag *tag;
+
+ tag = *list;
+ while (tag) {
+ if (!strcmp (tag->name, name))
+ return (const gchar *) tag->value;
+ tag = tag->next;
+ }
+ return NULL;
+}
+
+/**
+ * camel_tag_set:
+ * @list: the address of a #CamelTag list
+ * @name: name of the tag to set
+ * @value: value to set on the tag
+ *
+ * Set the tag @name in the tag list @list to @value.
+ *
+ * Returns: %TRUE if the value on the tag changed or %FALSE otherwise
+ **/
+gboolean
+camel_tag_set (CamelTag **list,
+ const gchar *name,
+ const gchar *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));
+ g_strlcpy (tmp->name, name, strlen (name) + 1);
+ tmp->value = g_strdup (value);
+ tmp->next = NULL;
+ tag->next = tmp;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * camel_tag_list_size:
+ * @list: the address of a #CamelTag list
+ *
+ * Get the number of tags present in the tag list @list.
+ *
+ * Returns: the number of tags
+ **/
+gint
+camel_tag_list_size (CamelTag **list)
+{
+ gint count = 0;
+ CamelTag *tag;
+
+ tag = *list;
+ while (tag) {
+ count++;
+ tag = tag->next;
+ }
+ return count;
+}
+
+static void
+rem_tag (gchar *key,
+ gchar *value,
+ CamelTag **to)
+{
+ camel_tag_set (to, key, NULL);
+}
+
+/**
+ * camel_tag_list_copy:
+ * @to: the address of the #CamelTag list to copy to
+ * @from: the address of the #CamelTag list to copy from
+ *
+ * Copy a tag list.
+ *
+ * Returns: %TRUE if @to is changed or %FALSE otherwise
+ **/
+gboolean
+camel_tag_list_copy (CamelTag **to,
+ CamelTag **from)
+{
+ gint 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: the address of a #CamelTag 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;
+}
+
+static struct flag_names_t {
+ const gchar *name;
+ guint32 value;
+} flag_names[] = {
+ { "answered", CAMEL_MESSAGE_ANSWERED },
+ { "deleted", CAMEL_MESSAGE_DELETED },
+ { "draft", CAMEL_MESSAGE_DRAFT },
+ { "flagged", CAMEL_MESSAGE_FLAGGED },
+ { "seen", CAMEL_MESSAGE_SEEN },
+ { "attachments", CAMEL_MESSAGE_ATTACHMENTS },
+ { "junk", CAMEL_MESSAGE_JUNK },
+ { "notjunk", CAMEL_MESSAGE_NOTJUNK },
+ { "secure", CAMEL_MESSAGE_SECURE },
+ { NULL, 0 }
+};
+
+/**
+ * camel_system_flag:
+ * @name: name of a system flag
+ *
+ * Returns: the integer value of the system flag string
+ **/
+CamelMessageFlags
+camel_system_flag (const gchar *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: bitwise system flags
+ * @name: name of the flag to check for
+ *
+ * Find the state of the flag @name in @flags.
+ *
+ * Returns: %TRUE if the named flag is set or %FALSE otherwise
+ **/
+gboolean
+camel_system_flag_get (CamelMessageFlags flags,
+ const gchar *name)
+{
+ g_return_val_if_fail (name != NULL, FALSE);
+
+ return flags & camel_system_flag (name);
+}
+
+/**
+ * camel_message_info_new:
+ * @summary: (nullable): a #CamelFolderSummary object or %NULL
+ *
+ * Create a new #CamelMessageInfo.
+ *
+ * Returns: (transfer full) (type CamelMessageInfo): a new #CamelMessageInfo
+ **/
+gpointer
+camel_message_info_new (CamelFolderSummary *summary)
+{
+ CamelFolderSummaryClass *class;
+ CamelMessageInfo *info;
+ gsize message_info_size;
+
+ if (summary != NULL) {
+ class = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
+ g_return_val_if_fail (class->message_info_size > 0, NULL);
+ message_info_size = class->message_info_size;
+ } else {
+ message_info_size = sizeof (CamelMessageInfoBase);
+ }
+
+ info = g_slice_alloc0 (message_info_size);
+ info->refcount = 1;
+ info->summary = summary;
+
+ /* We assume that mi is always dirty unless freshly read or just saved*/
+ ((CamelMessageInfoBase *) info)->dirty = TRUE;
+
+ return info;
+}
+
+/**
+ * camel_message_info_ref:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Reference an info.
+ * Returns: (transfer full) (type CamelMessageInfo):
+ **/
+gpointer
+camel_message_info_ref (gpointer o)
+{
+ CamelMessageInfo *mi = o;
+
+ g_return_val_if_fail (mi != NULL, NULL);
+ g_return_val_if_fail (mi->refcount > 0, NULL);
+
+ g_atomic_int_inc (&mi->refcount);
+
+ return o;
+}
+
+/**
+ * camel_message_info_new_from_header:
+ * @summary: a #CamelFolderSummary object or %NULL
+ * @header: raw header
+ *
+ * Create a new #CamelMessageInfo pre-populated with info from
+ * @header.
+ *
+ * Returns: (transfer full): a new #CamelMessageInfo
+ **/
+CamelMessageInfo *
+camel_message_info_new_from_header (CamelFolderSummary *summary,
+ struct _camel_header_raw *header)
+{
+ if (summary != NULL)
+ return CAMEL_FOLDER_SUMMARY_GET_CLASS (summary)->
+ message_info_new_from_header (summary, header);
+ else
+ return message_info_new_from_header (NULL, header);
+}
+
+/**
+ * camel_message_info_unref:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Unref's and potentially frees a #CamelMessageInfo and its contents.
+ **/
+void
+camel_message_info_unref (gpointer o)
+{
+ CamelMessageInfo *mi = o;
+
+ g_return_if_fail (mi != NULL);
+ g_return_if_fail (mi->refcount > 0);
+
+ if (g_atomic_int_dec_and_test (&mi->refcount)) {
+ if (mi->summary != NULL) {
+ CamelFolderSummaryClass *class;
+
+ /* FIXME This is kinda busted, should really
+ * be handled by message_info_free(). */
+ if (mi->summary->priv->build_content
+ && ((CamelMessageInfoBase *) mi)->content) {
+ camel_folder_summary_content_info_free (
+ mi->summary,
+ ((CamelMessageInfoBase *) mi)->content);
+ ((CamelMessageInfoBase *) mi)->content = NULL;
+ }
+
+ class = CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary);
+ g_return_if_fail (class->message_info_free != NULL);
+
+ class->message_info_free (mi->summary, mi);
+ } else {
+ message_info_free (NULL, mi);
+ }
+ }
+}
+
+/**
+ * camel_message_info_clone:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Duplicate a #CamelMessageInfo.
+ *
+ * Returns: (transfer full) (type CamelMessageInfo): the duplicated #CamelMessageInfo
+ **/
+gpointer
+camel_message_info_clone (gconstpointer o)
+{
+ const CamelMessageInfo *mi = o;
+
+ if (mi->summary)
+ return CAMEL_FOLDER_SUMMARY_GET_CLASS (mi->summary)->message_info_clone (mi->summary, mi);
+ else
+ return message_info_clone (NULL, mi);
+}
+
+/**
+ * camel_message_info_get_ptr:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ * @id: info to get
+ *
+ * Generic accessor method for getting pointer data.
+ *
+ * Returns: (transfer none): the pointer data
+ *
+ * Since: 3.22
+ **/
+gconstpointer
+camel_message_info_get_ptr (gconstpointer info,
+ gint id)
+{
+ const CamelMessageInfo *nfo = info;
+
+ g_return_val_if_fail (info != NULL, NULL);
+
+ if (nfo->summary)
+ return CAMEL_FOLDER_SUMMARY_GET_CLASS (nfo->summary)->info_ptr (nfo, id);
+ else
+ return info_ptr (nfo, id);
+}
+
+/**
+ * camel_message_info_get_uint32:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ * @id: info to get
+ *
+ * Generic accessor method for getting 32bit unsigned integer data.
+ *
+ * Returns: the guint32 data
+ *
+ * Since: 3.22
+ **/
+guint32
+camel_message_info_get_uint32 (gconstpointer info,
+ gint id)
+{
+ const CamelMessageInfo *nfo = info;
+
+ g_return_val_if_fail (info != NULL, 0);
+
+ if (nfo->summary)
+ return CAMEL_FOLDER_SUMMARY_GET_CLASS (nfo->summary)->info_uint32 (nfo, id);
+ else
+ return info_uint32 (nfo, id);
+}
+
+/**
+ * camel_message_info_get_time:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ * @id: info to get
+ *
+ * Generic accessor method for getting time_t data.
+ *
+ * Returns: the time_t data
+ *
+ * Since: 3.22
+ **/
+time_t
+camel_message_info_get_time (gconstpointer info,
+ gint id)
+{
+ const CamelMessageInfo *nfo = info;
+
+ g_return_val_if_fail (info != NULL, (time_t) 0);
+
+ if (nfo->summary)
+ return CAMEL_FOLDER_SUMMARY_GET_CLASS (nfo->summary)->info_time (nfo, id);
+ else
+ return info_time (nfo, id);
+}
+
+/**
+ * camel_message_info_get_uid:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the uid of the #CamelMessageInfo
+ *
+ * Returns: the uid
+ *
+ * Since: 3.22
+ **/
+const gchar *
+camel_message_info_get_uid (gconstpointer info)
+{
+ const CamelMessageInfo *nfo = info;
+
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return nfo->uid;
+}
+
+/**
+ * camel_message_info_get_subject:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the subject of the #CamelMessageInfo
+ *
+ * Returns: the subject
+ *
+ * Since: 3.22
+ **/
+const gchar *
+camel_message_info_get_subject (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return camel_message_info_get_ptr (info, CAMEL_MESSAGE_INFO_SUBJECT);
+}
+
+/**
+ * camel_message_info_get_preview:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the preview of the #CamelMessageInfo
+ *
+ * Returns: the preview
+ *
+ * Since: 3.22
+ **/
+const gchar *
+camel_message_info_get_preview (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return camel_message_info_get_ptr (info, CAMEL_MESSAGE_INFO_PREVIEW);
+}
+
+/**
+ * camel_message_info_get_from:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the from field of the #CamelMessageInfo
+ *
+ * Returns: the from field
+ *
+ * Since: 3.22
+ **/
+const gchar *
+camel_message_info_get_from (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return camel_message_info_get_ptr (info, CAMEL_MESSAGE_INFO_FROM);
+}
+
+/**
+ * camel_message_info_get_to:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the to field of the #CamelMessageInfo
+ *
+ * Returns: the to field
+ *
+ * Since: 3.22
+ **/
+const gchar *
+camel_message_info_get_to (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return camel_message_info_get_ptr (info, CAMEL_MESSAGE_INFO_TO);
+}
+
+/**
+ * camel_message_info_get_cc:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the cc field of the #CamelMessageInfo
+ *
+ * Returns: the cc field
+ *
+ * Since: 3.22
+ **/
+const gchar *
+camel_message_info_get_cc (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return camel_message_info_get_ptr (info, CAMEL_MESSAGE_INFO_CC);
+}
+
+/**
+ * camel_message_info_get_mlist:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the mlist of the #CamelMessageInfo
+ *
+ * Returns: the mlist
+ *
+ * Since: 3.22
+ **/
+const gchar *
+camel_message_info_get_mlist (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return camel_message_info_get_ptr (info, CAMEL_MESSAGE_INFO_MLIST);
+}
+
+/**
+ * camel_message_info_get_flags:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the flags of the #CamelMessageInfo
+ *
+ * Returns: the flags
+ *
+ * Since: 3.22
+ **/
+guint32
+camel_message_info_get_flags (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, 0);
+
+ return camel_message_info_get_uint32 (info, CAMEL_MESSAGE_INFO_FLAGS);
+}
+
+/**
+ * camel_message_info_get_size:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the size of the #CamelMessageInfo
+ *
+ * Returns: the size
+ *
+ * Since: 3.22
+ **/
+guint32
+camel_message_info_get_size (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, 0);
+
+ return camel_message_info_get_uint32 (info, CAMEL_MESSAGE_INFO_SIZE);
+}
+
+/**
+ * camel_message_info_get_date_sent:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the sent date of the #CamelMessageInfo
+ *
+ * Returns: the sent date
+ *
+ * Since: 3.22
+ **/
+time_t
+camel_message_info_get_date_sent (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, (time_t) 0);
+
+ return camel_message_info_get_time (info, CAMEL_MESSAGE_INFO_DATE_SENT);
+}
+
+/**
+ * camel_message_info_get_date_received:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the received date of the #CamelMessageInfo
+ *
+ * Returns: the received date
+ *
+ * Since: 3.22
+ **/
+time_t
+camel_message_info_get_date_received (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, (time_t) 0);
+
+ return camel_message_info_get_time (info, CAMEL_MESSAGE_INFO_DATE_RECEIVED);
+}
+
+/**
+ * camel_message_info_get_user_flag:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ * @id: user flag to get
+ *
+ * Get the state of a user flag named @id.
+ *
+ * Returns: the state of the user flag
+ *
+ * Since: 3.22
+ **/
+gboolean
+camel_message_info_get_user_flag (gconstpointer info,
+ const gchar *id)
+{
+ const CamelMessageInfo *nfo = info;
+
+ g_return_val_if_fail (info != NULL, FALSE);
+
+ if (nfo->summary)
+ return CAMEL_FOLDER_SUMMARY_GET_CLASS (nfo->summary)->info_user_flag (nfo, id);
+ else
+ return info_user_flag (nfo, id);
+}
+
+/**
+ * camel_message_info_get_user_tag:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ * @id: user tag to get
+ *
+ * Get the value of a user tag named @id.
+ *
+ * Returns: the value of the user tag
+ *
+ * Since: 3.22
+ **/
+const gchar *
+camel_message_info_get_user_tag (gconstpointer info,
+ const gchar *id)
+{
+ const CamelMessageInfo *nfo = info;
+
+ g_return_val_if_fail (info != NULL, NULL);
+
+ if (nfo->summary)
+ return CAMEL_FOLDER_SUMMARY_GET_CLASS (nfo->summary)->info_user_tag (nfo, id);
+ else
+ return info_user_tag (nfo, id);
+}
+
+/**
+ * camel_message_info_get_message_id:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the #CamelSummaryMessageID of the #CamelMessageInfo
+ *
+ * Returns: the id of the message
+ *
+ * Since: 3.22
+ **/
+const CamelSummaryMessageID *
+camel_message_info_get_message_id (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return camel_message_info_get_ptr (info, CAMEL_MESSAGE_INFO_MESSAGE_ID);
+}
+
+/**
+ * camel_message_info_get_references:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the #CamelSummaryReferences of the #CamelMessageInfo
+ *
+ * Returns: the references of the message
+ *
+ * Since: 3.22
+ **/
+const CamelSummaryReferences *
+camel_message_info_get_references (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return camel_message_info_get_ptr (info, CAMEL_MESSAGE_INFO_REFERENCES);
+}
+
+/**
+ * camel_message_info_get_user_flags:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the #CamelFlag of the #CamelMessageInfo
+ *
+ * Returns: the flags of the message
+ *
+ * Since: 3.22
+ **/
+const CamelFlag *
+camel_message_info_get_user_flags (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return camel_message_info_get_ptr (info, CAMEL_MESSAGE_INFO_USER_FLAGS);
+}
+
+/**
+ * camel_message_info_get_user_tags:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the #CamelTag of the #CamelMessageInfo
+ *
+ * Returns: the tags of the message
+ *
+ * Since: 3.22
+ **/
+const CamelTag *
+camel_message_info_get_user_tags (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return camel_message_info_get_ptr (info, CAMEL_MESSAGE_INFO_USER_TAGS);
+}
+
+/**
+ * camel_message_info_get_headers:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the #CamelHeaderParam of the #CamelMessageInfo
+ *
+ * Returns: the headers of the message
+ *
+ * Since: 3.22
+ **/
+const CamelHeaderParam *
+camel_message_info_get_headers (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return camel_message_info_get_ptr (info, CAMEL_MESSAGE_INFO_HEADERS);
+}
+
+/**
+ * camel_message_info_get_content:
+ * @info: (type CamelMessageInfo): a #CamelMessageInfo
+ *
+ * Get the #CamelMessageContentInfo of the #CamelMessageInfo
+ *
+ * Returns: the content of the message
+ *
+ * Since: 3.22
+ **/
+const CamelMessageContentInfo *
+camel_message_info_get_content (gconstpointer info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return camel_message_info_get_ptr (info, CAMEL_MESSAGE_INFO_CONTENT);
+}
+
+/**
+ * camel_message_info_set_flags:
+ * @info: a #CamelMessageInfo
+ * @flags: mask of flags to change
+ * @set: state the flags should be changed to
+ *
+ * Change the state of the system flags on the #CamelMessageInfo
+ *
+ * Returns: %TRUE if any of the flags changed or %FALSE otherwise
+ **/
+gboolean
+camel_message_info_set_flags (CamelMessageInfo *info,
+ CamelMessageFlags flags,
+ guint32 set)
+{
+ if (info->summary)
+ return CAMEL_FOLDER_SUMMARY_GET_CLASS (info->summary)->info_set_flags (info, flags, set);
+ else
+ return info_set_flags (info, flags, set);
+}
+
+/**
+ * camel_message_info_set_user_flag:
+ * @info: a #CamelMessageInfo
+ * @id: name of the user flag to set
+ * @state: state to set the flag to
+ *
+ * Set the state of a user flag on a #CamelMessageInfo.
+ *
+ * Returns: %TRUE if the state changed or %FALSE otherwise
+ **/
+gboolean
+camel_message_info_set_user_flag (CamelMessageInfo *info,
+ const gchar *id,
+ gboolean state)
+{
+ if (info->summary)
+ return CAMEL_FOLDER_SUMMARY_GET_CLASS (info->summary)->info_set_user_flag (info, id, state);
+ else
+ return info_set_user_flag (info, id, state);
+}
+
+/**
+ * camel_message_info_set_user_tag:
+ * @info: a #CamelMessageInfo
+ * @id: name of the user tag to set
+ * @val: value to set
+ *
+ * Set the value of a user tag on a #CamelMessageInfo.
+ *
+ * Returns: %TRUE if the value changed or %FALSE otherwise
+ **/
+gboolean
+camel_message_info_set_user_tag (CamelMessageInfo *info,
+ const gchar *id,
+ const gchar *val)
+{
+ if (info->summary)
+ return CAMEL_FOLDER_SUMMARY_GET_CLASS (info->summary)->info_set_user_tag (info, id, val);
+ else
+ return info_set_user_tag (info, id, val);
+}
+
+void
+camel_content_info_dump (CamelMessageContentInfo *ci,
+ gint depth)
+{
+ gchar *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, (gulong) ci->size);
+ ci = ci->childs;
+ while (ci) {
+ camel_content_info_dump (ci, depth + 1);
+ ci = ci->next;
+ }
+}
+
+void
+camel_message_info_dump (CamelMessageInfo *info)
+{
+ if (info == NULL) {
+ printf ("No message?\n");
+ return;
+ }
+
+ printf ("Subject: %s\n", camel_message_info_get_subject (info));
+ printf ("To: %s\n", camel_message_info_get_to (info));
+ printf ("Cc: %s\n", camel_message_info_get_cc (info));
+ printf ("mailing list: %s\n", camel_message_info_get_mlist (info));
+ printf ("From: %s\n", camel_message_info_get_from (info));
+ printf ("UID: %s\n", camel_message_info_get_uid (info));
+ printf ("Flags: %04x\n", camel_message_info_get_flags (info));
+ camel_content_info_dump (((CamelMessageInfoBase *) info)->content, 0);
+}
+
+/**
+ * camel_folder_summary_lock:
+ * @summary: a #CamelFolderSummary
+ *
+ * Locks @summary. Unlock it with camel_folder_summary_unlock().
+ *
+ * Since: 2.32
+ **/
+void
+camel_folder_summary_lock (CamelFolderSummary *summary)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+}
+
+/**
+ * camel_folder_summary_unlock:
+ * @summary: a #CamelFolderSummary
+ *
+ * Unlocks @summary, previously locked with camel_folder_summary_lock().
+ *
+ * Since: 2.32
+ **/
+void
+camel_folder_summary_unlock (CamelFolderSummary *summary)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
+
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+}
+
+gint
+bdata_extract_digit (/* const */ gchar **part)
+{
+ if (!part || !*part || !**part)
+ return 0;
+
+ if (**part == ' ')
+ *part += 1;
+
+ if (!**part)
+ return 0;
+
+ return strtoul (*part, part, 10);
+}
+
+/* expecting "digit-value", where digit is length of the value */
+gchar *
+bdata_extract_string (/* const */ gchar **part)
+{
+ gint len, has_len;
+ gchar *val;
+
+ len = bdata_extract_digit (part);
+
+ /* might be a '-' sign */
+ if (part && *part && **part)
+ *part += 1;
+
+ if (len <= 0 || !part || !*part || !**part)
+ return g_strdup ("");
+
+ if (!**part)
+ return g_strdup ("");
+
+ has_len = strlen (*part);
+ if (has_len < len)
+ len = has_len;
+
+ val = g_strndup (*part, len);
+ *part += len;
+
+ return val;
+}
diff --git a/src/camel/camel-folder-summary.h b/src/camel/camel-folder-summary.h
new file mode 100644
index 000000000..d6dfff0fa
--- /dev/null
+++ b/src/camel/camel-folder-summary.h
@@ -0,0 +1,632 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_FOLDER_SUMMARY_H
+#define CAMEL_FOLDER_SUMMARY_H
+
+#include <stdio.h>
+#include <time.h>
+
+#include <camel/camel-index.h>
+#include <camel/camel-memchunk.h>
+#include <camel/camel-mime-message.h>
+#include <camel/camel-mime-parser.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_FOLDER_SUMMARY \
+ (camel_folder_summary_get_type ())
+#define CAMEL_FOLDER_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_FOLDER_SUMMARY, CamelFolderSummary))
+#define CAMEL_FOLDER_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_FOLDER_SUMMARY, CamelFolderSummaryClass))
+#define CAMEL_IS_FOLDER_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_FOLDER_SUMMARY))
+#define CAMEL_IS_FOLDER_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_FOLDER_SUMMARY))
+#define CAMEL_FOLDER_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_FOLDER_SUMMARY, CamelFolderSummaryClass))
+
+G_BEGIN_DECLS
+
+struct _CamelFolder;
+struct _CamelStore;
+
+typedef struct _CamelFolderSummary CamelFolderSummary;
+typedef struct _CamelFolderSummaryClass CamelFolderSummaryClass;
+typedef struct _CamelFolderSummaryPrivate CamelFolderSummaryPrivate;
+
+typedef struct _CamelMessageInfo CamelMessageInfo;
+typedef struct _CamelMessageInfoBase CamelMessageInfoBase;
+
+typedef struct _CamelMessageContentInfo CamelMessageContentInfo;
+
+/* A tree of message content info structures
+ * describe the content structure of the message (if it has any) */
+struct _CamelMessageContentInfo {
+ CamelMessageContentInfo *next;
+
+ CamelMessageContentInfo *childs;
+ CamelMessageContentInfo *parent;
+
+ CamelContentType *type;
+ gchar *id;
+ gchar *description;
+ gchar *encoding; /* this should be an enum?? */
+ guint32 size;
+};
+
+/* system flag bits */
+typedef enum _CamelMessageFlags {
+ CAMEL_MESSAGE_ANSWERED = 1 << 0,
+ CAMEL_MESSAGE_DELETED = 1 << 1,
+ CAMEL_MESSAGE_DRAFT = 1 << 2,
+ CAMEL_MESSAGE_FLAGGED = 1 << 3,
+ CAMEL_MESSAGE_SEEN = 1 << 4,
+
+ /* these aren't really system flag bits, but are convenience flags */
+ CAMEL_MESSAGE_ATTACHMENTS = 1 << 5,
+ CAMEL_MESSAGE_ANSWERED_ALL = 1 << 6,
+ CAMEL_MESSAGE_JUNK = 1 << 7,
+ CAMEL_MESSAGE_SECURE = 1 << 8,
+ CAMEL_MESSAGE_NOTJUNK = 1 << 9,
+ CAMEL_MESSAGE_FORWARDED = 1 << 10,
+
+ /* following flags are for the folder, and are not really permanent flags */
+ CAMEL_MESSAGE_FOLDER_FLAGGED = 1 << 16, /* for use by the folder implementation */
+ /* flags after 1 << 16 are used by camel providers,
+ * if adding non permanent flags, add them to the end */
+
+ CAMEL_MESSAGE_JUNK_LEARN = 1 << 30, /* used when setting CAMEL_MESSAGE_JUNK flag
+ * to say that we request junk plugin
+ * to learn that message as junk/non junk */
+ CAMEL_MESSAGE_USER = 1 << 31 /* supports user flags */
+} CamelMessageFlags;
+
+/* Changes to system flags will NOT trigger a folder changed event */
+#define CAMEL_MESSAGE_SYSTEM_MASK (0xffff << 16)
+
+typedef struct _CamelFlag {
+ struct _CamelFlag *next;
+ gchar name[1]; /* name allocated as part of the structure */
+} CamelFlag;
+
+typedef struct _CamelTag {
+ struct _CamelTag *next;
+ gchar *value;
+ gchar name[1]; /* name allocated as part of the structure */
+} CamelTag;
+
+/* a summary messageid is a 64 bit identifier (partial md5 hash) */
+typedef struct _CamelSummaryMessageID {
+ union {
+ guint64 id;
+ guchar hash[8];
+ struct {
+ guint32 hi;
+ guint32 lo;
+ } part;
+ } id;
+} CamelSummaryMessageID;
+
+/* summary references is a fixed size array of references */
+typedef struct _CamelSummaryReferences {
+ gint size;
+ CamelSummaryMessageID references[1];
+} CamelSummaryReferences;
+
+/* accessor id's */
+enum {
+ CAMEL_MESSAGE_INFO_SUBJECT,
+ CAMEL_MESSAGE_INFO_FROM,
+ CAMEL_MESSAGE_INFO_TO,
+ CAMEL_MESSAGE_INFO_CC,
+ CAMEL_MESSAGE_INFO_MLIST,
+
+ CAMEL_MESSAGE_INFO_FLAGS,
+ CAMEL_MESSAGE_INFO_SIZE,
+
+ CAMEL_MESSAGE_INFO_DATE_SENT,
+ CAMEL_MESSAGE_INFO_DATE_RECEIVED,
+
+ CAMEL_MESSAGE_INFO_MESSAGE_ID,
+ CAMEL_MESSAGE_INFO_REFERENCES,
+ CAMEL_MESSAGE_INFO_USER_FLAGS,
+ CAMEL_MESSAGE_INFO_USER_TAGS,
+
+ CAMEL_MESSAGE_INFO_HEADERS,
+ CAMEL_MESSAGE_INFO_PREVIEW,
+ CAMEL_MESSAGE_INFO_CONTENT,
+ CAMEL_MESSAGE_INFO_LAST
+};
+
+/* information about a given message, use accessors */
+struct _CamelMessageInfo {
+ CamelFolderSummary *summary;
+
+ volatile gint refcount;
+ const gchar *uid;
+ /*FIXME: Make it work with the CAMEL_MESSADE_DB_DIRTY flag instead of another 4 bytes*/
+ guint dirty : 1;
+};
+
+/* For classes wishing to do the provided i/o, or for anonymous users,
+ * they must subclass or use this messageinfo structure */
+/* Otherwise they can do their own thing entirely */
+struct _CamelMessageInfoBase {
+ CamelFolderSummary *summary;
+
+ volatile gint refcount;
+ const gchar *uid;
+ /*FIXME: Make it work with the CAMEL_MESSADE_DB_DIRTY flag instead of another 4 bytes*/
+ guint dirty : 1;
+
+ const gchar *subject;
+ const gchar *from;
+ const gchar *to;
+ const gchar *cc;
+ const gchar *mlist;
+
+ CamelMessageFlags flags;
+ guint32 size;
+
+ time_t date_sent;
+ time_t date_received;
+
+ CamelSummaryMessageID message_id;
+ CamelSummaryReferences *references;/* from parent to root */
+
+ struct _CamelFlag *user_flags;
+ struct _CamelTag *user_tags;
+
+ /* tree of content description - NULL if it is not available */
+ CamelMessageContentInfo *content;
+ struct _camel_header_param *headers;
+ gchar *preview;
+ gchar *bodystructure;
+};
+
+/**
+ * CamelFolderSummaryFlags:
+ * @CAMEL_FOLDER_SUMMARY_DIRTY:
+ * There are changes in summary, which should be saved.
+ * @CAMEL_FOLDER_SUMMARY_IN_MEMORY_ONLY:
+ * Summary with this flag doesn't use DB for storing its content,
+ * it is always created on the fly.
+ **/
+typedef enum {
+ CAMEL_FOLDER_SUMMARY_DIRTY = 1 << 0,
+ CAMEL_FOLDER_SUMMARY_IN_MEMORY_ONLY = 1 << 1
+} CamelFolderSummaryFlags;
+
+struct _CamelFolderSummary {
+ GObject parent;
+ CamelFolderSummaryPrivate *priv;
+
+ /* header info */
+ guint32 version; /* version of file loaded/loading */
+ time_t time; /* timestamp for this summary (for implementors to use) */
+ CamelFolderSummaryFlags flags;
+
+ const gchar *collate;
+ const gchar *sort_by;
+
+ /* Future ABI expansion */
+ gpointer later[4];
+};
+
+struct _CamelMIRecord;
+struct _CamelFIRecord;
+
+struct _CamelFolderSummaryClass {
+ GObjectClass parent_class;
+
+ /* sizes of memory objects */
+ gsize message_info_size;
+ gsize content_info_size;
+
+ /* Load/Save folder summary from DB*/
+ gboolean (*summary_header_from_db)
+ (CamelFolderSummary *summary,
+ struct _CamelFIRecord *fir);
+ struct _CamelFIRecord *
+ (*summary_header_to_db)
+ (CamelFolderSummary *summary,
+ GError **error);
+ CamelMessageInfo *
+ (*message_info_from_db)
+ (CamelFolderSummary *summary,
+ struct _CamelMIRecord *mir);
+ struct _CamelMIRecord *
+ (*message_info_to_db)
+ (CamelFolderSummary *summary,
+ CamelMessageInfo *info);
+ CamelMessageContentInfo *
+ (*content_info_from_db)
+ (CamelFolderSummary *summary,
+ struct _CamelMIRecord *mir);
+ gboolean (*content_info_to_db)
+ (CamelFolderSummary *summary,
+ CamelMessageContentInfo *info,
+ struct _CamelMIRecord *mir);
+
+ /* create/save/load an individual message info */
+ CamelMessageInfo *
+ (*message_info_new_from_header)
+ (CamelFolderSummary *summary,
+ struct _camel_header_raw *header);
+ CamelMessageInfo *
+ (*message_info_new_from_parser)
+ (CamelFolderSummary *summary,
+ CamelMimeParser *parser);
+ CamelMessageInfo *
+ (*message_info_new_from_message)
+ (CamelFolderSummary *summary,
+ CamelMimeMessage *message,
+ const gchar *bodystructure);
+ void (*message_info_free)
+ (CamelFolderSummary *summary,
+ CamelMessageInfo *ci);
+ CamelMessageInfo *
+ (*message_info_clone)
+ (CamelFolderSummary *summary,
+ const CamelMessageInfo *info);
+
+ /* save/load individual content info's */
+ CamelMessageContentInfo *
+ (*content_info_new_from_header)
+ (CamelFolderSummary *summary,
+ struct _camel_header_raw *header);
+ CamelMessageContentInfo *
+ (*content_info_new_from_parser)
+ (CamelFolderSummary *summary,
+ CamelMimeParser *parser);
+ CamelMessageContentInfo *
+ (*content_info_new_from_message)
+ (CamelFolderSummary *summary,
+ CamelMimePart *mime_part);
+ void (*content_info_free)
+ (CamelFolderSummary *summary,
+ CamelMessageContentInfo *ci);
+ CamelMessageInfo *
+ (*message_info_from_uid)
+ (CamelFolderSummary *summary,
+ const gchar *uid);
+
+ /* get the next uid */
+ gchar * (*next_uid_string)
+ (CamelFolderSummary *summary);
+
+ /* virtual accessors on messageinfo's */
+ gconstpointer (*info_ptr) (const CamelMessageInfo *info,
+ gint id);
+ guint32 (*info_uint32) (const CamelMessageInfo *info,
+ gint id);
+ time_t (*info_time) (const CamelMessageInfo *info,
+ gint id);
+
+ gboolean (*info_user_flag)
+ (const CamelMessageInfo *info,
+ const gchar *id);
+ const gchar * (*info_user_tag)
+ (const CamelMessageInfo *info,
+ const gchar *id);
+
+ /* set accessors for the modifyable bits */
+ gboolean (*info_set_user_flag)
+ (CamelMessageInfo *info,
+ const gchar *id,
+ gboolean state);
+ gboolean (*info_set_user_tag)
+ (CamelMessageInfo *info,
+ const gchar *id,
+ const gchar *val);
+ gboolean (*info_set_flags)
+ (CamelMessageInfo *info,
+ guint32 mask,
+ guint32 set);
+};
+
+GType camel_folder_summary_get_type (void);
+CamelFolderSummary *
+ camel_folder_summary_new (struct _CamelFolder *folder);
+
+struct _CamelFolder *
+ camel_folder_summary_get_folder (CamelFolderSummary *summary);
+
+guint32 camel_folder_summary_get_saved_count
+ (CamelFolderSummary *summary);
+guint32 camel_folder_summary_get_unread_count
+ (CamelFolderSummary *summary);
+guint32 camel_folder_summary_get_deleted_count
+ (CamelFolderSummary *summary);
+guint32 camel_folder_summary_get_junk_count
+ (CamelFolderSummary *summary);
+guint32 camel_folder_summary_get_junk_not_deleted_count
+ (CamelFolderSummary *summary);
+guint32 camel_folder_summary_get_visible_count
+ (CamelFolderSummary *summary);
+
+void camel_folder_summary_set_index (CamelFolderSummary *summary,
+ CamelIndex *index);
+CamelIndex * camel_folder_summary_get_index (CamelFolderSummary *summary);
+void camel_folder_summary_set_build_content
+ (CamelFolderSummary *summary,
+ gboolean state);
+gboolean camel_folder_summary_get_build_content
+ (CamelFolderSummary *summary);
+void camel_folder_summary_set_need_preview
+ (CamelFolderSummary *summary,
+ gboolean preview);
+gboolean camel_folder_summary_get_need_preview
+ (CamelFolderSummary *summary);
+guint32 camel_folder_summary_next_uid (CamelFolderSummary *summary);
+void camel_folder_summary_set_next_uid
+ (CamelFolderSummary *summary,
+ guint32 uid);
+guint32 camel_folder_summary_get_next_uid
+ (CamelFolderSummary *summary);
+gchar * camel_folder_summary_next_uid_string
+ (CamelFolderSummary *summary);
+
+/* load/save the full summary from/to the db */
+gboolean camel_folder_summary_save_to_db (CamelFolderSummary *summary,
+ GError **error);
+gboolean camel_folder_summary_load_from_db
+ (CamelFolderSummary *summary,
+ GError **error);
+
+/* only load the header */
+gboolean camel_folder_summary_header_load_from_db
+ (CamelFolderSummary *summary,
+ struct _CamelStore *store,
+ const gchar *folder_name,
+ GError **error);
+gboolean camel_folder_summary_header_save_to_db
+ (CamelFolderSummary *summary,
+ GError **error);
+
+/* set the dirty bit on the summary */
+void camel_folder_summary_touch (CamelFolderSummary *summary);
+
+/* Just build raw summary items */
+CamelMessageInfo *
+ camel_folder_summary_info_new_from_header
+ (CamelFolderSummary *summary,
+ struct _camel_header_raw *headers);
+CamelMessageInfo *
+ camel_folder_summary_info_new_from_parser
+ (CamelFolderSummary *summary,
+ CamelMimeParser *parser);
+CamelMessageInfo *
+ camel_folder_summary_info_new_from_message
+ (CamelFolderSummary *summary,
+ CamelMimeMessage *message,
+ const gchar *bodystructure);
+
+CamelMessageContentInfo *
+ camel_folder_summary_content_info_new
+ (CamelFolderSummary *summary);
+void camel_folder_summary_content_info_free
+ (CamelFolderSummary *summary,
+ CamelMessageContentInfo *ci);
+
+void camel_folder_summary_add_preview
+ (CamelFolderSummary *summary,
+ CamelMessageInfo *info);
+
+/* add a new raw summary item */
+void camel_folder_summary_add (CamelFolderSummary *summary,
+ CamelMessageInfo *info);
+
+/* insert mi to summary */
+void camel_folder_summary_insert (CamelFolderSummary *summary,
+ CamelMessageInfo *info,
+ gboolean load);
+
+gboolean camel_folder_summary_remove (CamelFolderSummary *summary,
+ CamelMessageInfo *info);
+
+gboolean camel_folder_summary_remove_uid (CamelFolderSummary *summary,
+ const gchar *uid);
+gboolean camel_folder_summary_remove_uids (CamelFolderSummary *summary,
+ GList *uids);
+
+/* remove all items */
+gboolean camel_folder_summary_clear (CamelFolderSummary *summary,
+ GError **error);
+
+/* lookup functions */
+guint camel_folder_summary_count (CamelFolderSummary *summary);
+
+gboolean camel_folder_summary_check_uid (CamelFolderSummary *summary,
+ const gchar *uid);
+CamelMessageInfo *
+ camel_folder_summary_get (CamelFolderSummary *summary,
+ const gchar *uid);
+guint32 camel_folder_summary_get_info_flags
+ (CamelFolderSummary *summary,
+ const gchar *uid);
+GPtrArray * camel_folder_summary_get_array (CamelFolderSummary *summary);
+void camel_folder_summary_free_array (GPtrArray *array);
+
+GHashTable * camel_folder_summary_get_hash (CamelFolderSummary *summary);
+
+gboolean camel_folder_summary_replace_flags
+ (CamelFolderSummary *summary,
+ CamelMessageInfo *info);
+
+/* Peek from mem only */
+CamelMessageInfo *
+ camel_folder_summary_peek_loaded
+ (CamelFolderSummary *summary,
+ const gchar *uid);
+
+/* Get only the uids of dirty/changed things to sync to server/db */
+GPtrArray * camel_folder_summary_get_changed
+ (CamelFolderSummary *summary);
+
+/* reload the summary at any required point if required */
+void camel_folder_summary_prepare_fetch_all
+ (CamelFolderSummary *summary,
+ GError **error);
+
+/* summary locking */
+void camel_folder_summary_lock (CamelFolderSummary *summary);
+void camel_folder_summary_unlock (CamelFolderSummary *summary);
+
+/* message flag operations */
+gboolean camel_flag_get (CamelFlag **list,
+ const gchar *name);
+gboolean camel_flag_set (CamelFlag **list,
+ const gchar *name,
+ gboolean value);
+gboolean camel_flag_list_copy (CamelFlag **to,
+ CamelFlag **from);
+gint camel_flag_list_size (CamelFlag **list);
+void camel_flag_list_free (CamelFlag **list);
+
+CamelMessageFlags
+ camel_system_flag (const gchar *name);
+gboolean camel_system_flag_get (CamelMessageFlags flags,
+ const gchar *name);
+
+/* message tag operations */
+const gchar * camel_tag_get (CamelTag **list,
+ const gchar *name);
+gboolean camel_tag_set (CamelTag **list,
+ const gchar *name,
+ const gchar *value);
+gboolean camel_tag_list_copy (CamelTag **to,
+ CamelTag **from);
+gint camel_tag_list_size (CamelTag **list);
+void camel_tag_list_free (CamelTag **list);
+
+/* Summary may be null */
+/* Use anonymous pointers to avoid tons of cast crap */
+GType camel_message_info_get_type (void) G_GNUC_CONST;
+gpointer camel_message_info_new (CamelFolderSummary *summary);
+gpointer camel_message_info_ref (gpointer info);
+CamelMessageInfo *
+ camel_message_info_new_from_header
+ (CamelFolderSummary *summary,
+ struct _camel_header_raw *header);
+void camel_message_info_unref (gpointer info);
+gpointer camel_message_info_clone (gconstpointer info);
+
+/* These will be fully removed soon, left only for a backward compatibility */
+#define camel_message_info_ptr camel_message_info_get_ptr
+#define camel_message_info_uint32 camel_message_info_get_uint32
+#define camel_message_info_time camel_message_info_get_time
+#define camel_message_info_uid camel_message_info_get_uid
+#define camel_message_info_subject camel_message_info_get_subject
+#define camel_message_info_preview camel_message_info_get_preview
+#define camel_message_info_from camel_message_info_get_from
+#define camel_message_info_to camel_message_info_get_to
+#define camel_message_info_cc camel_message_info_get_cc
+#define camel_message_info_mlist camel_message_info_get_mlist
+#define camel_message_info_flags camel_message_info_get_flags
+#define camel_message_info_size camel_message_info_get_size
+#define camel_message_info_date_sent camel_message_info_get_date_sent
+#define camel_message_info_date_received camel_message_info_get_date_received
+#define camel_message_info_message_id camel_message_info_get_message_id
+#define camel_message_info_references camel_message_info_get_references
+#define camel_message_info_user_flags camel_message_info_get_user_flags
+#define camel_message_info_user_tags camel_message_info_get_user_tags
+#define camel_message_info_headers camel_message_info_get_headers
+#define camel_message_info_content camel_message_info_get_content
+#define camel_message_info_user_flag camel_message_info_get_user_flag
+#define camel_message_info_user_tag camel_message_info_get_user_tag
+
+/* accessors */
+gconstpointer camel_message_info_get_ptr (gconstpointer info,
+ gint id);
+guint32 camel_message_info_get_uint32 (gconstpointer info,
+ gint id);
+time_t camel_message_info_get_time (gconstpointer info,
+ gint id);
+
+const gchar * camel_message_info_get_uid (gconstpointer info);
+const gchar * camel_message_info_get_subject (gconstpointer info);
+const gchar * camel_message_info_get_preview (gconstpointer info);
+const gchar * camel_message_info_get_from (gconstpointer info);
+const gchar * camel_message_info_get_to (gconstpointer info);
+
+const gchar * camel_message_info_get_cc (gconstpointer info);
+const gchar * camel_message_info_get_mlist (gconstpointer info);
+guint32 camel_message_info_get_flags (gconstpointer info);
+guint32 camel_message_info_get_size (gconstpointer info);
+
+time_t camel_message_info_get_date_sent
+ (gconstpointer info);
+time_t camel_message_info_get_date_received
+ (gconstpointer info);
+
+const CamelSummaryMessageID *
+ camel_message_info_get_message_id
+ (gconstpointer info);
+const CamelSummaryReferences *
+ camel_message_info_get_references
+ (gconstpointer info);
+const CamelFlag *
+ camel_message_info_get_user_flags
+ (gconstpointer info);
+const CamelTag *
+ camel_message_info_get_user_tags
+ (gconstpointer info);
+const CamelHeaderParam *
+ camel_message_info_get_headers (gconstpointer info);
+const CamelMessageContentInfo *
+ camel_message_info_get_content (gconstpointer info);
+gboolean camel_message_info_get_user_flag(gconstpointer info,
+ const gchar *id);
+const gchar * camel_message_info_get_user_tag (gconstpointer info,
+ const gchar *id);
+
+gboolean camel_message_info_set_flags (CamelMessageInfo *info,
+ CamelMessageFlags flags,
+ guint32 set);
+gboolean camel_message_info_set_user_flag
+ (CamelMessageInfo *info,
+ const gchar *id,
+ gboolean state);
+gboolean camel_message_info_set_user_tag (CamelMessageInfo *info,
+ const gchar *id,
+ const gchar *val);
+
+/* debugging functions */
+void camel_content_info_dump (CamelMessageContentInfo *ci,
+ gint depth);
+void camel_message_info_dump (CamelMessageInfo *info);
+
+/* utility functions for bdata string decomposition */
+gint bdata_extract_digit (gchar **part);
+gchar * bdata_extract_string (gchar **part);
+
+G_END_DECLS
+
+#endif /* CAMEL_FOLDER_SUMMARY_H */
diff --git a/src/camel/camel-folder-thread.c b/src/camel/camel-folder-thread.c
new file mode 100644
index 000000000..277cceabc
--- /dev/null
+++ b/src/camel/camel-folder-thread.c
@@ -0,0 +1,954 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/* TODO: This could probably be made a camel object, but it isn't really required */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "camel-folder-thread.h"
+
+#define d(x)
+#define m(x)
+
+/*#define TIMEIT*/
+
+#ifdef TIMEIT
+#include <sys/time.h>
+#endif
+
+static void
+container_add_child (CamelFolderThreadNode *node,
+ CamelFolderThreadNode *child)
+{
+ d (printf ("\nAdding child %p to parent %p \n", child, node));
+ child->next = node->child;
+ node->child = child;
+ child->parent = node;
+}
+
+static void
+container_parent_child (CamelFolderThreadNode *parent,
+ CamelFolderThreadNode *child)
+{
+ CamelFolderThreadNode *c, *node;
+
+ /* are we already the right parent? */
+ if (child->parent == parent)
+ return;
+
+ /* would this create a loop? */
+ node = parent->parent;
+ while (node) {
+ if (node == child)
+ return;
+ node = node->parent;
+ }
+
+ /* are we unparented? */
+ if (child->parent == NULL) {
+ container_add_child (parent, child);
+ return;
+ }
+
+ /* else remove child from its existing parent, and reparent */
+ node = child->parent;
+ c = (CamelFolderThreadNode *) &node->child;
+ d (printf ("scanning children:\n"));
+ while (c->next) {
+ d (printf (" %p\n", c));
+ if (c->next == child) {
+ d (printf ("found node %p\n", child));
+ c->next = c->next->next;
+ child->parent = NULL;
+ container_add_child (parent, child);
+ return;
+ }
+ c = c->next;
+ }
+
+ printf ("DAMN, we shouldn't be here!\n");
+}
+
+static void
+prune_empty (CamelFolderThread *thread,
+ CamelFolderThreadNode **cp)
+{
+ CamelFolderThreadNode *child, *next, *c, *lastc;
+
+ /* yes, this is intentional */
+ lastc = (CamelFolderThreadNode *) cp;
+ while (lastc->next) {
+ c = lastc->next;
+ prune_empty (thread, &c->child);
+
+ d (printf (
+ "checking message %p %p (%08x%08x)\n", c,
+ c->message,
+ c->message ? c->message->message_id.id.part.hi : 0,
+ c->message ? c->message->message_id.id.part.lo : 0));
+ if (c->message == NULL) {
+ if (c->child == NULL) {
+ d (printf ("removing empty node\n"));
+ lastc->next = c->next;
+ m (memset (c, 0xfe, sizeof (*c)));
+ camel_memchunk_free (thread->node_chunks, c);
+ continue;
+ }
+ if (c->parent || c->child->next == NULL) {
+ d (printf ("promoting child\n"));
+ lastc->next = c->next; /* remove us */
+ child = c->child;
+ while (child) {
+ next = child->next;
+
+ child->parent = c->parent;
+ child->next = lastc->next;
+ lastc->next = child;
+
+ child = next;
+ }
+ continue;
+ }
+ }
+ lastc = c;
+ }
+}
+
+static void
+hashloop (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ CamelFolderThreadNode *c = value;
+ CamelFolderThreadNode *tail = data;
+
+ if (c->parent == NULL) {
+ c->next = tail->next;
+ tail->next = c;
+ }
+}
+
+static gchar *
+skip_list_ids (gchar *s)
+{
+ gchar *p;
+
+ while (isspace (*s))
+ s++;
+
+ while (*s == '[') {
+ p = s + 1;
+
+ while (*p && *p != ']' && !isspace (*p))
+ p++;
+
+ if (*p != ']')
+ break;
+
+ s = p + 1;
+
+ while (isspace (*s))
+ s++;
+
+ if (*s == '-' && isspace (s[1]))
+ s += 2;
+
+ while (isspace (*s))
+ s++;
+ }
+
+ return s;
+}
+
+static gchar *
+get_root_subject (CamelFolderThreadNode *c)
+{
+ gchar *s, *p;
+ CamelFolderThreadNode *scan;
+
+ s = NULL;
+ c->re = FALSE;
+ if (c->message)
+ s = (gchar *) camel_message_info_get_subject (c->message);
+ else {
+ /* one of the children will always have a message */
+ scan = c->child;
+ while (scan) {
+ if (scan->message) {
+ s = (gchar *) camel_message_info_get_subject (scan->message);
+ break;
+ }
+ scan = scan->next;
+ }
+ }
+ if (s != NULL) {
+ s = skip_list_ids (s);
+
+ while (*s) {
+ while (isspace (*s))
+ s++;
+ if (s[0] == 0)
+ break;
+ if ((s[0] == 'r' || s[0]=='R')
+ && (s[1] == 'e' || s[1]=='E')) {
+ p = s + 2;
+ while (isdigit (*p) || (ispunct (*p) && (*p != ':')))
+ p++;
+ if (*p == ':') {
+ c->re = TRUE;
+ s = skip_list_ids (p + 1);
+ } else
+ break;
+ } else
+ break;
+ }
+ if (*s)
+ return s;
+ }
+ return NULL;
+}
+
+/* this can be pretty slow, but not used often */
+/* clast cannot be null */
+static void
+remove_node (CamelFolderThreadNode **list,
+ CamelFolderThreadNode *node,
+ CamelFolderThreadNode **clast)
+{
+ CamelFolderThreadNode *c;
+
+ /* this is intentional, even if it looks funny */
+ /* if we have a parent, then we should remove it from the parent list,
+ * otherwise we remove it from the root list */
+ if (node->parent) {
+ c = (CamelFolderThreadNode *) &node->parent->child;
+ } else {
+ c = (CamelFolderThreadNode *) list;
+ }
+ while (c->next) {
+ if (c->next == node) {
+ if (*clast == c->next)
+ *clast = c;
+ c->next = c->next->next;
+ return;
+ }
+ c = c->next;
+ }
+
+ printf ("ERROR: removing node %p failed\n", (gpointer) node);
+}
+
+static void
+group_root_set (CamelFolderThread *thread,
+ CamelFolderThreadNode **cp)
+{
+ GHashTable *subject_table = g_hash_table_new (g_str_hash, g_str_equal);
+ CamelFolderThreadNode *c, *clast, *scan, *container;
+
+ /* gather subject lines */
+ d (printf ("gathering subject lines\n"));
+ clast = (CamelFolderThreadNode *) cp;
+ c = clast->next;
+ while (c) {
+ c->root_subject = get_root_subject (c);
+ if (c->root_subject) {
+ container = g_hash_table_lookup (subject_table, c->root_subject);
+ if (container == NULL
+ || (container->message == NULL && c->message)
+ || (container->re == TRUE && !c->re)) {
+ g_hash_table_insert (subject_table, c->root_subject, c);
+ }
+ }
+ c = c->next;
+ }
+
+ /* merge common subjects? */
+ clast = (CamelFolderThreadNode *) cp;
+ while (clast->next) {
+ c = clast->next;
+ d (printf ("checking %p %s\n", c, c->root_subject));
+ if (c->root_subject
+ && (container = g_hash_table_lookup (subject_table, c->root_subject))
+ && (container != c)) {
+ d (printf (" matching %p %s\n", container, container->root_subject));
+ if (c->message == NULL && container->message == NULL) {
+ d (printf ("merge containers children\n"));
+ /* steal the children from c onto container, and unlink c */
+ scan = (CamelFolderThreadNode *) &container->child;
+ while (scan->next)
+ scan = scan->next;
+ scan->next = c->child;
+ clast->next = c->next;
+ m (memset (c, 0xee, sizeof (*c)));
+ camel_memchunk_free (thread->node_chunks, c);
+ continue;
+ } if (c->message == NULL && container->message != NULL) {
+ d (printf ("container is non-empty parent\n"));
+ remove_node (cp, container, &clast);
+ container_add_child (c, container);
+ } else if (c->message != NULL && container->message == NULL) {
+ d (printf ("container is empty child\n"));
+ clast->next = c->next;
+ container_add_child (container, c);
+ continue;
+ } else if (c->re && !container->re) {
+ d (printf ("container is re\n"));
+ clast->next = c->next;
+ container_add_child (container, c);
+ continue;
+ } else if (!c->re && container->re) {
+ d (printf ("container is not re\n"));
+ remove_node (cp, container, &clast);
+ container_add_child (c, container);
+ } else {
+ d (printf ("subjects are common %p and %p\n", c, container));
+
+ /* build a phantom node */
+ remove_node (cp, container, &clast);
+ remove_node (cp, c, &clast);
+
+ scan = camel_memchunk_alloc0 (thread->node_chunks);
+
+ scan->root_subject = c->root_subject;
+ scan->re = c->re && container->re;
+ scan->next = c->next;
+ clast->next = scan;
+ container_add_child (scan, c);
+ container_add_child (scan, container);
+ clast = scan;
+ g_hash_table_insert (subject_table, scan->root_subject, scan);
+ continue;
+ }
+ }
+ clast = c;
+ }
+ g_hash_table_destroy (subject_table);
+}
+
+struct _tree_info {
+ GHashTable *visited;
+};
+
+static gint
+dump_tree_rec (struct _tree_info *info,
+ CamelFolderThreadNode *c,
+ gint depth)
+{
+ gchar *p;
+ gint count = 0;
+
+ p = alloca (depth * 2 + 1);
+ memset (p, ' ', depth * 2);
+ p[depth * 2] = 0;
+
+ while (c) {
+ if (g_hash_table_lookup (info->visited, c)) {
+ printf ("WARNING: NODE REVISITED: %p\n", (gpointer) c);
+ } else {
+ g_hash_table_insert (info->visited, c, c);
+ }
+ if (c->message) {
+ printf (
+ "%s %p Subject: %s <%08x%08x>\n",
+ p, (gpointer) c,
+ camel_message_info_get_subject (c->message),
+ camel_message_info_get_message_id (c->message)->id.part.hi,
+ camel_message_info_get_message_id (c->message)->id.part.lo);
+ count += 1;
+ } else {
+ printf ("%s %p <empty>\n", p, (gpointer) c);
+ }
+ if (c->child)
+ count += dump_tree_rec (info, c->child, depth + 1);
+ c = c->next;
+ }
+ return count;
+}
+
+gint
+camel_folder_threaded_messages_dump (CamelFolderThreadNode *c)
+{
+ gint count;
+ struct _tree_info info;
+
+ info.visited = g_hash_table_new (g_direct_hash, g_direct_equal);
+ count = dump_tree_rec (&info, c, 0);
+ g_hash_table_destroy (info.visited);
+ return count;
+}
+
+static gint
+sort_node (gconstpointer a,
+ gconstpointer b)
+{
+ const CamelFolderThreadNode *a1 = ((CamelFolderThreadNode **) a)[0];
+ const CamelFolderThreadNode *b1 = ((CamelFolderThreadNode **) b)[0];
+
+ /* if we have no message, it must be a dummy node, which
+ * also means it must have a child, just use that as the
+ * sort data (close enough?) */
+ if (a1->message == NULL)
+ a1 = a1->child;
+ if (b1->message == NULL)
+ b1 = b1->child;
+ if (a1->order == b1->order)
+ return 0;
+ if (a1->order < b1->order)
+ return -1;
+ else
+ return 1;
+}
+
+static void
+sort_thread (CamelFolderThreadNode **cp)
+{
+ CamelFolderThreadNode *c, *head, **carray;
+ gint size = 0;
+
+ c = *cp;
+ while (c) {
+ /* sort the children while we're at it */
+ if (c->child)
+ sort_thread (&c->child);
+ size++;
+ c = c->next;
+ }
+ if (size < 2)
+ return;
+ carray = alloca (size * sizeof (CamelFolderThreadNode *));
+ c = *cp;
+ size = 0;
+ while (c) {
+ carray[size] = c;
+ c = c->next;
+ size++;
+ }
+ qsort (carray, size, sizeof (CamelFolderThreadNode *), sort_node);
+ size--;
+ head = carray[size];
+ head->next = NULL;
+ size--;
+ do {
+ c = carray[size];
+ c->next = head;
+ head = c;
+ size--;
+ } while (size >= 0);
+ *cp = head;
+}
+
+static guint
+id_hash (gpointer key)
+{
+ CamelSummaryMessageID *id = (CamelSummaryMessageID *) key;
+
+ return id->id.part.lo;
+}
+
+static gint
+id_equal (gpointer a,
+ gpointer b)
+{
+ return ((CamelSummaryMessageID *) a)->id.id == ((CamelSummaryMessageID *) b)->id.id;
+}
+
+/* perform actual threading */
+static void
+thread_summary (CamelFolderThread *thread,
+ GPtrArray *summary)
+{
+ GHashTable *id_table, *no_id_table;
+ gint i;
+ CamelFolderThreadNode *c, *child, *head;
+#ifdef TIMEIT
+ struct timeval start, end;
+ gulong diff;
+
+ gettimeofday (&start, NULL);
+#endif
+
+ id_table = g_hash_table_new ((GHashFunc) id_hash, (GCompareFunc) id_equal);
+ no_id_table = g_hash_table_new (NULL, NULL);
+ for (i = 0; i < summary->len; i++) {
+ CamelMessageInfo *mi = summary->pdata[i];
+ const CamelSummaryMessageID *mid = camel_message_info_get_message_id (mi);
+ const CamelSummaryReferences *references = camel_message_info_get_references (mi);
+
+ if (mid != NULL && mid->id.id) {
+ c = g_hash_table_lookup (id_table, mid);
+ /* check for duplicate messages */
+ if (c && c->order) {
+ /* if duplicate, just make out it is a no-id message, but try and insert it
+ * into the right spot in the tree */
+ d (printf ("doing: (duplicate message id)\n"));
+ c = camel_memchunk_alloc0 (thread->node_chunks);
+ g_hash_table_insert (no_id_table, (gpointer) mi, c);
+ } else if (!c) {
+ d (printf ("doing : %08x%08x (%s)\n", mid->id.part.hi, mid->id.part.lo, camel_message_info_get_subject (mi)));
+ c = camel_memchunk_alloc0 (thread->node_chunks);
+ g_hash_table_insert (id_table, (gpointer) mid, c);
+ }
+ } else {
+ d (printf ("doing : (no message id)\n"));
+ c = camel_memchunk_alloc0 (thread->node_chunks);
+ g_hash_table_insert (no_id_table, (gpointer) mi, c);
+ }
+
+ c->message = mi;
+ c->order = i + 1;
+ child = c;
+ if (references) {
+ gint j;
+
+ d (printf ("%s (%s) references:\n", G_STRLOC, G_STRFUNC); )
+ for (j = 0; j < references->size; j++) {
+ gboolean found = FALSE;
+
+ /* should never be empty, but just incase */
+ if (references->references[j].id.id == 0)
+ continue;
+
+ c = g_hash_table_lookup (id_table, &references->references[j]);
+ if (c == NULL) {
+ d (printf ("%s (%s) not found\n", G_STRLOC, G_STRFUNC));
+ c = camel_memchunk_alloc0 (thread->node_chunks);
+ g_hash_table_insert (id_table, (gpointer) &references->references[j], c);
+ } else
+ found = TRUE;
+ if (c != child) {
+ container_parent_child (c, child);
+ /* Stop on the first parent found, no need to reparent
+ * it once it's placed in. Also, references are from
+ * parent to root, thus this should do the right thing. */
+ if (found)
+ break;
+ }
+ child = c;
+ }
+ }
+ }
+
+ d (printf ("\n\n"));
+ /* build a list of root messages (no parent) */
+ head = NULL;
+ g_hash_table_foreach (id_table, hashloop, &head);
+ g_hash_table_foreach (no_id_table, hashloop, &head);
+
+ g_hash_table_destroy (id_table);
+ g_hash_table_destroy (no_id_table);
+
+ /* remove empty parent nodes */
+ prune_empty (thread, &head);
+
+ /* find any siblings which missed out - but only if we are allowing threading by subject */
+ if (thread->subject)
+ group_root_set (thread, &head);
+
+#if 0
+ printf ("finished\n");
+ i = camel_folder_threaded_messages_dump (head);
+ printf ("%d count, %d items in tree\n", summary->len, i);
+#endif
+
+ sort_thread (&head);
+
+ /* remove any phantom nodes, this could possibly be put in group_root_set()? */
+ c = (CamelFolderThreadNode *) &head;
+ while (c && c->next) {
+ CamelFolderThreadNode *scan, *newtop;
+
+ child = c->next;
+ if (child->message == NULL) {
+ newtop = child->child;
+ newtop->parent = NULL;
+ /* unlink pseudo node */
+ c->next = newtop;
+
+ /* link its siblings onto the end of its children, fix all parent pointers */
+ scan = (CamelFolderThreadNode *) &newtop->child;
+ while (scan->next) {
+ scan = scan->next;
+ }
+ scan->next = newtop->next;
+ while (scan->next) {
+ scan = scan->next;
+ scan->parent = newtop;
+ }
+
+ /* and link the now 'real' node into the list */
+ newtop->next = child->next;
+ c = newtop;
+ m (memset (child, 0xde, sizeof (*child)));
+ camel_memchunk_free (thread->node_chunks, child);
+ } else {
+ c = child;
+ }
+ }
+
+ /* this is only debug assertion stuff */
+ c = (CamelFolderThreadNode *) &head;
+ while (c->next) {
+ c = c->next;
+ if (c->message == NULL)
+ g_warning ("threading missed removing a pseudo node: %s\n", c->root_subject);
+ if (c->parent != NULL)
+ g_warning ("base node has a non-null parent: %s\n", c->root_subject);
+ }
+
+ thread->tree = head;
+
+#ifdef TIMEIT
+ gettimeofday (&end, NULL);
+ diff = end.tv_sec * 1000 + end.tv_usec / 1000;
+ diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
+ printf (
+ "Message threading %d messages took %ld.%03ld seconds\n",
+ summary->len, diff / 1000, diff % 1000);
+#endif
+}
+
+/**
+ * camel_folder_thread_messages_new:
+ * @folder:
+ * @uids: (element-type utf8): The subset of uid's to thread. If NULL. then thread all
+ * uid's in @folder.
+ * @thread_subject: thread based on subject also
+ *
+ * Thread a (subset) of the messages in a folder. And sort the result
+ * in summary order.
+ *
+ * If @thread_subject is %TRUE, messages with
+ * related subjects will also be threaded. The default behaviour is to
+ * only thread based on message-id.
+ *
+ * This function is probably to be removed soon.
+ *
+ * Returns: A CamelFolderThread contianing a tree of CamelFolderThreadNode's
+ * which represent the threaded structure of the messages.
+ **/
+CamelFolderThread *
+camel_folder_thread_messages_new (CamelFolder *folder,
+ GPtrArray *uids,
+ gboolean thread_subject)
+{
+ CamelFolderThread *thread;
+ GPtrArray *summary;
+ GPtrArray *fsummary = NULL;
+ gint i;
+
+ thread = g_malloc (sizeof (*thread));
+ thread->refcount = 1;
+ thread->subject = thread_subject;
+ thread->tree = NULL;
+ thread->node_chunks = camel_memchunk_new (32, sizeof (CamelFolderThreadNode));
+ thread->folder = g_object_ref (folder);
+
+ camel_folder_summary_prepare_fetch_all (folder->summary, NULL);
+ thread->summary = summary = g_ptr_array_new ();
+
+ /* prefer given order from the summary order */
+ if (!uids) {
+ fsummary = camel_folder_summary_get_array (folder->summary);
+ uids = fsummary;
+ }
+
+ for (i = 0; i < uids->len; i++) {
+ CamelMessageInfo *info;
+ gchar *uid = uids->pdata[i];
+
+ info = camel_folder_get_message_info (folder, uid);
+ if (info)
+ g_ptr_array_add (summary, info);
+ /* FIXME: Check if the info is leaking */
+ }
+
+ if (fsummary)
+ camel_folder_summary_free_array (fsummary);
+
+ thread_summary (thread, summary);
+
+ return thread;
+}
+
+/* add any still there, in the existing order */
+static void
+add_present_rec (CamelFolderThread *thread,
+ GHashTable *have,
+ GPtrArray *summary,
+ CamelFolderThreadNode *node)
+{
+ while (node) {
+ CamelMessageInfo *info;
+ const gchar *uid;
+
+ /* XXX Casting away const. */
+ info = (CamelMessageInfo *) node->message;
+ uid = camel_message_info_get_uid (info);
+
+ if (g_hash_table_lookup (have, uid)) {
+ g_hash_table_remove (have, uid);
+ g_ptr_array_add (summary, info);
+ } else {
+ camel_message_info_unref (info);
+ }
+
+ if (node->child)
+ add_present_rec (thread, have, summary, node->child);
+ node = node->next;
+ }
+}
+
+/**
+ * camel_folder_thread_messages_apply:
+ * @uids:(element-type utf8) (transfer none):
+ **/
+void
+camel_folder_thread_messages_apply (CamelFolderThread *thread,
+ GPtrArray *uids)
+{
+ gint i;
+ GPtrArray *all;
+ GHashTable *table;
+ CamelMessageInfo *info;
+
+ all = g_ptr_array_new ();
+ table = g_hash_table_new (g_str_hash, g_str_equal);
+ for (i = 0; i < uids->len; i++)
+ g_hash_table_insert (table, uids->pdata[i], uids->pdata[i]);
+
+ add_present_rec (thread, table, all, thread->tree);
+
+ /* add any new ones, in supplied order */
+ for (i = 0; i < uids->len; i++)
+ if (g_hash_table_lookup (table, uids->pdata[i]) && (info = camel_folder_get_message_info (thread->folder, uids->pdata[i])))
+ g_ptr_array_add (all, info);
+
+ g_hash_table_destroy (table);
+
+ thread->tree = NULL;
+ camel_memchunk_destroy (thread->node_chunks);
+ thread->node_chunks = camel_memchunk_new (32, sizeof (CamelFolderThreadNode));
+ thread_summary (thread, all);
+
+ g_ptr_array_free (thread->summary, TRUE);
+ thread->summary = all;
+}
+
+void
+camel_folder_thread_messages_ref (CamelFolderThread *thread)
+{
+ thread->refcount++;
+}
+
+/**
+ * camel_folder_thread_messages_unref:
+ * @thread:
+ *
+ * Free all memory associated with the thread descriptor @thread.
+ **/
+void
+camel_folder_thread_messages_unref (CamelFolderThread *thread)
+{
+ if (thread->refcount > 1) {
+ thread->refcount--;
+ return;
+ }
+
+ if (thread->folder) {
+ gint i;
+
+ for (i = 0; i < thread->summary->len; i++)
+ camel_message_info_unref (thread->summary->pdata[i]);
+ g_ptr_array_free (thread->summary, TRUE);
+ g_object_unref (thread->folder);
+ }
+ camel_memchunk_destroy (thread->node_chunks);
+ g_free (thread);
+}
+
+#if 0
+/**
+ * camel_folder_thread_messages_new_summary:
+ * @summary: Array of CamelMessageInfo's to thread.
+ *
+ * Thread a list of MessageInfo's. The summary must remain valid for the
+ * life of the CamelFolderThread created by this function, and it is upto the
+ * caller to ensure this.
+ *
+ * Returns: A CamelFolderThread contianing a tree of CamelFolderThreadNode's
+ * which represent the threaded structure of the messages.
+ **/
+CamelFolderThread *
+camel_folder_thread_messages_new_summary (GPtrArray *summary)
+{
+ CamelFolderThread *thread;
+
+#ifdef TIMEIT
+ struct timeval start, end;
+ gulong diff;
+
+ gettimeofday (&start, NULL);
+#endif
+
+ thread = g_malloc (sizeof (*thread));
+ thread->refcount = 1;
+ thread->tree = NULL;
+ thread->node_chunks = camel_memchunk_new (32, sizeof (CamelFolderThreadNode));
+ thread->folder = NULL;
+ thread->summary = NULL;
+
+ thread_summary (thread, summary);
+
+ return thread;
+}
+
+/* scan the list in depth-first fashion */
+static void
+build_summary_rec (GHashTable *have,
+ GPtrArray *summary,
+ CamelFolderThreadNode *node)
+{
+ while (node) {
+ if (node->message)
+ g_hash_table_insert (have, (gchar *) camel_message_info_get_uid (node->message), node->message);
+ g_ptr_array_add (summary, node);
+ if (node->child)
+ build_summary_rec (have, summary, node->child);
+ node = node->next;
+ }
+}
+
+void
+camel_folder_thread_messages_add (CamelFolderThread *thread,
+ GPtrArray *summary)
+{
+ GPtrArray *all;
+ gint i;
+ GHashTable *table;
+
+ /* Instead of working out all the complex in's and out's of
+ * trying to do an incremental summary generation, just redo the whole
+ * thing with the summary in the current order - so it comes out
+ * in the same order */
+
+ all = g_ptr_array_new ();
+ table = g_hash_table_new (g_str_hash, g_str_equal);
+ build_summary_rec (table, all, thread->tree);
+ for (i = 0; i < summary->len; i++) {
+ CamelMessageInfo *info = summary->pdata[i];
+
+ /* check its not already there, we dont want duplicates */
+ if (g_hash_table_lookup (table, camel_message_info_get_uid (info)) == NULL)
+ g_ptr_array_add (all, info);
+ }
+ g_hash_table_destroy (table);
+
+ /* reset the tree, and rebuild fully */
+ thread->tree = NULL;
+ camel_memchunk_empty (thread->node_chunks);
+ thread_summary (thread, all);
+}
+
+static void
+remove_uid_node_rec (CamelFolderThread *thread,
+ GHashTable *table,
+ CamelFolderThreadNode **list,
+ CamelFolderThreadNode *parent)
+{
+ CamelFolderThreadNode *prev = NULL;
+ CamelFolderThreadNode *node, *next, *child, *rest;
+
+ node = (CamelFolderThreadNode *) list;
+ next = node->next;
+ while (next) {
+
+ if (next->child)
+ remove_uid_node_rec (thread, table, &next->child, next);
+
+ /* do we have a node to remove? */
+ if (next->message && g_hash_table_lookup (table, (gchar *) camel_message_info_get_uid (node->message))) {
+ child = next->child;
+ if (child) {
+ /*
+ * node
+ * next
+ * child
+ * lchild
+ * rest
+ *
+ * becomes:
+ * node
+ * child
+ * lchild
+ * rest
+ */
+
+ rest = next->next;
+ node->next = child;
+ camel_memchunk_free (thread->node_chunks, next);
+ next = child;
+ do {
+ lchild = child;
+ child->parent = parent;
+ child = child->next;
+ } while (child);
+ lchild->next = rest;
+ } else {
+ /*
+ * node
+ * next
+ * rest
+ * becomes:
+ * node
+ * rest */
+ node->next = next->next;
+ camel_memchunk_free (thread->node_chunks, next);
+ next = node->next;
+ }
+ } else {
+ node = next;
+ next = node->next;
+ }
+ }
+}
+
+void
+camel_folder_thread_messages_remove (CamelFolderThread *thread,
+ GPtrArray *uids)
+{
+ GHashTable *table;
+ gint i;
+
+ table = g_hash_table_new (g_str_hash, g_str_equal);
+ for (i = 0; i < uids->len; i++)
+ g_hash_table_insert (table, uids->pdata[i], uids->pdata[i]);
+
+ remove_uid_node_rec (thread, table, &thread->tree, NULL);
+ g_hash_table_destroy (table);
+}
+
+#endif
diff --git a/src/camel/camel-folder-thread.h b/src/camel/camel-folder-thread.h
new file mode 100644
index 000000000..88cfac545
--- /dev/null
+++ b/src/camel/camel-folder-thread.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_FOLDER_THREAD_H
+#define CAMEL_FOLDER_THREAD_H
+
+#include <camel/camel-folder-summary.h>
+#include <camel/camel-folder.h>
+#include <camel/camel-memchunk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _CamelFolderThreadNode {
+ struct _CamelFolderThreadNode *next, *parent, *child;
+ const CamelMessageInfo *message;
+ gchar *root_subject; /* cached root equivalent subject */
+ guint32 order : 31;
+ guint32 re:1; /* re version of subject? */
+} CamelFolderThreadNode;
+
+typedef struct _CamelFolderThread {
+ guint32 refcount : 31;
+ guint32 subject : 1;
+
+ struct _CamelFolderThreadNode *tree;
+ CamelMemChunk *node_chunks;
+ CamelFolder *folder;
+ GPtrArray *summary;
+} CamelFolderThread;
+
+/* interface 1: using uid's */
+CamelFolderThread *camel_folder_thread_messages_new (CamelFolder *folder, GPtrArray *uids, gboolean thread_subject);
+void camel_folder_thread_messages_apply (CamelFolderThread *thread, GPtrArray *uids);
+
+/* interface 2: using messageinfo's. Currently disabled. */
+#if 0
+/* new improved interface */
+CamelFolderThread *camel_folder_thread_messages_new_summary (GPtrArray *summary);
+void camel_folder_thread_messages_add (CamelFolderThread *thread, GPtrArray *summary);
+void camel_folder_thread_messages_remove (CamelFolderThread *thread, GPtrArray *uids);
+#endif
+
+void camel_folder_thread_messages_ref (CamelFolderThread *thread);
+void camel_folder_thread_messages_unref (CamelFolderThread *thread);
+
+/* debugging function only */
+gint camel_folder_threaded_messages_dump (CamelFolderThreadNode *c);
+
+G_END_DECLS
+
+#endif /* CAMEL_FOLDER_THREAD_H */
diff --git a/src/camel/camel-folder.c b/src/camel/camel-folder.c
new file mode 100644
index 000000000..2b08478a2
--- /dev/null
+++ b/src/camel/camel-folder.c
@@ -0,0 +1,4625 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-folder.c: Abstract class for an email folder
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-db.h"
+#include "camel-debug.h"
+#include "camel-filter-driver.h"
+#include "camel-folder.h"
+#include "camel-mempool.h"
+#include "camel-mime-message.h"
+#include "camel-network-service.h"
+#include "camel-offline-store.h"
+#include "camel-operation.h"
+#include "camel-session.h"
+#include "camel-store.h"
+#include "camel-vtrash-folder.h"
+#include "camel-string-utils.h"
+
+#define CAMEL_FOLDER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_FOLDER, CamelFolderPrivate))
+
+#define d(x)
+#define w(x)
+
+typedef struct _AsyncContext AsyncContext;
+typedef struct _SignalClosure SignalClosure;
+typedef struct _FolderFilterData FolderFilterData;
+
+struct _CamelFolderPrivate {
+ GRecMutex lock;
+ GMutex change_lock;
+ /* must require the 'change_lock' to access this */
+ gint frozen;
+ CamelFolderChangeInfo *changed_frozen; /* queues changed events */
+ gboolean skip_folder_lock;
+
+ /* Changes to be emitted from an idle callback. */
+ CamelFolderChangeInfo *pending_changes;
+
+ gpointer parent_store; /* weak pointer */
+
+ GMutex property_lock;
+
+ gchar *full_name;
+ gchar *display_name;
+ gchar *description;
+};
+
+struct _AsyncContext {
+ CamelMimeMessage *message;
+ CamelMessageInfo *info;
+ CamelFolder *destination;
+ GPtrArray *message_uids;
+ gchar *message_uid;
+ gboolean delete_originals;
+ gboolean expunge;
+ gchar *start_uid;
+ gchar *end_uid;
+
+ /* results */
+ GPtrArray *transferred_uids;
+};
+
+struct _CamelFolderChangeInfoPrivate {
+ GHashTable *uid_stored; /* what we have stored, which array they're in */
+ GHashTable *uid_source; /* used to create unique lists */
+ GPtrArray *uid_filter; /* uids to be filtered */
+ CamelMemPool *uid_pool; /* pool used to store copies of uid strings */
+};
+
+struct _SignalClosure {
+ GWeakRef folder;
+ gchar *folder_name;
+};
+
+struct _FolderFilterData {
+ GPtrArray *recents;
+ GPtrArray *junk;
+ GPtrArray *notjunk;
+ CamelFolder *folder;
+ CamelFilterDriver *driver;
+};
+
+enum {
+ PROP_0,
+ PROP_DESCRIPTION,
+ PROP_DISPLAY_NAME,
+ PROP_FULL_NAME,
+ PROP_PARENT_STORE
+};
+
+enum {
+ CHANGED,
+ DELETED,
+ RENAMED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_ABSTRACT_TYPE (CamelFolder, camel_folder, CAMEL_TYPE_OBJECT)
+
+G_DEFINE_BOXED_TYPE (CamelFolderQuotaInfo,
+ camel_folder_quota_info,
+ camel_folder_quota_info_clone,
+ camel_folder_quota_info_free)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+ if (async_context->message != NULL)
+ g_object_unref (async_context->message);
+
+ if (async_context->info != NULL)
+ camel_message_info_unref (async_context->info);
+
+ if (async_context->destination != NULL)
+ g_object_unref (async_context->destination);
+
+ if (async_context->message_uids != NULL) {
+ g_ptr_array_foreach (
+ async_context->message_uids, (GFunc) g_free, NULL);
+ g_ptr_array_free (async_context->message_uids, TRUE);
+ }
+
+ if (async_context->transferred_uids != NULL) {
+ g_ptr_array_foreach (
+ async_context->transferred_uids, (GFunc) g_free, NULL);
+ g_ptr_array_free (async_context->transferred_uids, TRUE);
+ }
+
+ g_free (async_context->message_uid);
+ g_free (async_context->start_uid);
+ g_free (async_context->end_uid);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static void
+signal_closure_free (SignalClosure *signal_closure)
+{
+ g_weak_ref_clear (&signal_closure->folder);
+
+ g_free (signal_closure->folder_name);
+
+ g_slice_free (SignalClosure, signal_closure);
+}
+
+static gboolean
+folder_emit_changed_cb (gpointer user_data)
+{
+ SignalClosure *signal_closure = user_data;
+ CamelFolder *folder;
+
+ folder = g_weak_ref_get (&signal_closure->folder);
+
+ if (folder != NULL) {
+ CamelFolderChangeInfo *changes;
+
+ g_mutex_lock (&folder->priv->change_lock);
+ changes = folder->priv->pending_changes;
+ folder->priv->pending_changes = NULL;
+ g_mutex_unlock (&folder->priv->change_lock);
+
+ g_signal_emit (folder, signals[CHANGED], 0, changes);
+
+ camel_folder_change_info_free (changes);
+
+ g_object_unref (folder);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+folder_emit_deleted_cb (gpointer user_data)
+{
+ SignalClosure *signal_closure = user_data;
+ CamelFolder *folder;
+
+ folder = g_weak_ref_get (&signal_closure->folder);
+
+ if (folder != NULL) {
+ g_signal_emit (folder, signals[DELETED], 0);
+ g_object_unref (folder);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+folder_emit_renamed_cb (gpointer user_data)
+{
+ SignalClosure *signal_closure = user_data;
+ CamelFolder *folder;
+
+ folder = g_weak_ref_get (&signal_closure->folder);
+
+ if (folder != NULL) {
+ g_signal_emit (
+ folder,
+ signals[RENAMED], 0,
+ signal_closure->folder_name);
+ g_object_unref (folder);
+ }
+
+ return FALSE;
+}
+
+static gpointer
+folder_filter_data_free_thread (gpointer user_data)
+{
+ FolderFilterData *data = user_data;
+
+ g_return_val_if_fail (data != NULL, NULL);
+
+ if (data->driver != NULL)
+ g_object_unref (data->driver);
+ if (data->recents != NULL)
+ camel_folder_free_deep (data->folder, data->recents);
+ if (data->junk != NULL)
+ camel_folder_free_deep (data->folder, data->junk);
+ if (data->notjunk != NULL)
+ camel_folder_free_deep (data->folder, data->notjunk);
+
+ /* XXX Too late to pass a GError here. */
+ camel_folder_summary_save_to_db (data->folder->summary, NULL);
+
+ camel_folder_thaw (data->folder);
+ g_object_unref (data->folder);
+
+ g_slice_free (FolderFilterData, data);
+
+ return NULL;
+}
+
+static void
+prepare_folder_filter_data_free (FolderFilterData *data)
+{
+ GThread *thread;
+
+ /* Do the actual free in a dedicated thread, because the driver or
+ * folder unref can do network/blocking I/O operations, but this
+ * function is called in the main (UI) thread.
+ */
+ thread = g_thread_new (NULL, folder_filter_data_free_thread, data);
+ g_thread_unref (thread);
+}
+
+static void
+folder_filter (CamelSession *session,
+ GCancellable *cancellable,
+ FolderFilterData *data,
+ GError **error)
+{
+ CamelMessageInfo *info;
+ CamelStore *parent_store;
+ gint i, status = 0;
+ CamelJunkFilter *junk_filter;
+ gboolean synchronize = FALSE;
+ const gchar *full_name;
+
+ full_name = camel_folder_get_full_name (data->folder);
+ parent_store = camel_folder_get_parent_store (data->folder);
+ junk_filter = camel_session_get_junk_filter (session);
+
+ /* Keep the junk filter alive until we're done. */
+ if (junk_filter != NULL)
+ g_object_ref (junk_filter);
+
+ /* Reset junk learn flag so that we don't process it again */
+ if (data->junk) {
+ for (i = 0; i < data->junk->len; i++) {
+ info = camel_folder_summary_get (data->folder->summary, data->junk->pdata[i]);
+ if (!info)
+ continue;
+
+ camel_message_info_set_flags (info, CAMEL_MESSAGE_JUNK_LEARN, 0);
+ camel_message_info_unref (info);
+ }
+ }
+
+ if (data->notjunk) {
+ for (i = 0; i < data->notjunk->len; i++) {
+ info = camel_folder_summary_get (data->folder->summary, data->notjunk->pdata[i]);
+ if (!info)
+ continue;
+
+ camel_message_info_set_flags (info, CAMEL_MESSAGE_JUNK_LEARN, 0);
+ camel_message_info_unref (info);
+ }
+ }
+
+ if (data->junk) {
+ gboolean success = TRUE;
+
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ camel_operation_push_message (
+ cancellable, dngettext (GETTEXT_PACKAGE,
+ "Learning new spam message in '%s : %s'",
+ "Learning new spam messages in '%s : %s'",
+ data->junk->len),
+ camel_service_get_display_name (CAMEL_SERVICE (parent_store)),
+ full_name);
+
+ for (i = 0; success && i < data->junk->len; i++) {
+ CamelMimeMessage *message;
+ gint pc = 100 * i / data->junk->len;
+
+ if (g_cancellable_set_error_if_cancelled (
+ cancellable, error))
+ break;
+
+ message = camel_folder_get_message_sync (
+ data->folder, data->junk->pdata[i],
+ cancellable, error);
+
+ if (message == NULL)
+ break;
+
+ camel_operation_progress (cancellable, pc);
+ success = camel_junk_filter_learn_junk (
+ junk_filter, message, cancellable, error);
+ g_object_unref (message);
+
+ synchronize |= success;
+ }
+
+ camel_operation_pop_message (cancellable);
+ }
+
+ if (*error != NULL)
+ goto exit;
+
+ if (data->notjunk) {
+ gboolean success = TRUE;
+
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ camel_operation_push_message (
+ cancellable, dngettext (GETTEXT_PACKAGE,
+ "Learning new ham message in '%s : %s'",
+ "Learning new ham messages in '%s : %s'",
+ data->notjunk->len),
+ camel_service_get_display_name (CAMEL_SERVICE (parent_store)),
+ full_name);
+
+ for (i = 0; success && i < data->notjunk->len; i++) {
+ CamelMimeMessage *message;
+ gint pc = 100 * i / data->notjunk->len;
+
+ if (g_cancellable_set_error_if_cancelled (
+ cancellable, error))
+ break;
+
+ message = camel_folder_get_message_sync (
+ data->folder, data->notjunk->pdata[i],
+ cancellable, error);
+
+ if (message == NULL)
+ break;
+
+ camel_operation_progress (cancellable, pc);
+ success = camel_junk_filter_learn_not_junk (
+ junk_filter, message, cancellable, error);
+ g_object_unref (message);
+
+ synchronize |= success;
+ }
+
+ camel_operation_pop_message (cancellable);
+ }
+
+ if (*error != NULL)
+ goto exit;
+
+ if (synchronize)
+ camel_junk_filter_synchronize (
+ junk_filter, cancellable, error);
+
+ if (*error != NULL)
+ goto exit;
+
+ if (data->driver && data->recents) {
+ CamelService *service;
+ const gchar *store_uid;
+
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ camel_operation_push_message (
+ cancellable, dngettext (GETTEXT_PACKAGE,
+ "Filtering new message in '%s : %s'",
+ "Filtering new messages in '%s : %s'",
+ data->recents->len),
+ camel_service_get_display_name (CAMEL_SERVICE (parent_store)),
+ full_name);
+
+ service = CAMEL_SERVICE (parent_store);
+ store_uid = camel_service_get_uid (service);
+
+ for (i = 0; status == 0 && i < data->recents->len; i++) {
+ gchar *uid = data->recents->pdata[i];
+ gint pc = 100 * i / data->recents->len;
+
+ camel_operation_progress (cancellable, pc);
+
+ info = camel_folder_get_message_info (
+ data->folder, uid);
+ if (info == NULL) {
+ g_warning (
+ "uid '%s' vanished from folder '%s : %s'",
+ uid, camel_service_get_display_name (CAMEL_SERVICE (parent_store)), full_name);
+ continue;
+ }
+
+ status = camel_filter_driver_filter_message (
+ data->driver, NULL, info, uid, data->folder,
+ store_uid, store_uid, cancellable, error);
+
+ camel_message_info_unref (info);
+ }
+
+ camel_operation_pop_message (cancellable);
+
+ camel_filter_driver_flush (data->driver, error);
+ }
+
+exit:
+ if (junk_filter != NULL)
+ g_object_unref (junk_filter);
+}
+
+static gint
+cmp_array_uids (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ const gchar *uid1 = *(const gchar **) a;
+ const gchar *uid2 = *(const gchar **) b;
+ CamelFolder *folder = user_data;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), 0);
+
+ return camel_folder_cmp_uids (folder, uid1, uid2);
+}
+
+static void
+folder_transfer_message_to (CamelFolder *source,
+ const gchar *uid,
+ CamelFolder *dest,
+ gchar **transferred_uid,
+ gboolean delete_original,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimeMessage *msg;
+ CamelMessageInfo *minfo, *info;
+ GError *local_error = NULL;
+
+ /* Default implementation. */
+
+ msg = camel_folder_get_message_sync (source, uid, cancellable, error);
+ if (!msg)
+ return;
+
+ /* if its deleted we poke the flags, so we need to copy the messageinfo */
+ if ((source->folder_flags & CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY)
+ && (minfo = camel_folder_get_message_info (source, uid))) {
+ info = camel_message_info_clone (minfo);
+ camel_message_info_unref (minfo);
+ } else
+ info = camel_message_info_new_from_header (NULL, ((CamelMimePart *) msg)->headers);
+
+ /* unset deleted flag when transferring from trash folder */
+ if ((source->folder_flags & CAMEL_FOLDER_IS_TRASH) != 0)
+ camel_message_info_set_flags (info, CAMEL_MESSAGE_DELETED, 0);
+ /* unset junk flag when transferring from junk folder */
+ if ((source->folder_flags & CAMEL_FOLDER_IS_JUNK) != 0)
+ camel_message_info_set_flags (info, CAMEL_MESSAGE_JUNK, 0);
+
+ camel_folder_append_message_sync (
+ dest, msg, info, transferred_uid,
+ cancellable, &local_error);
+ g_object_unref (msg);
+
+ if (local_error != NULL)
+ g_propagate_error (error, local_error);
+ else if (delete_original)
+ camel_folder_set_message_flags (
+ source, uid, CAMEL_MESSAGE_DELETED |
+ CAMEL_MESSAGE_SEEN, ~0);
+
+ camel_message_info_unref (info);
+}
+
+static gboolean
+folder_maybe_connect_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelService *service;
+ CamelStore *parent_store;
+ CamelServiceConnectionStatus status;
+ CamelSession *session;
+ gboolean connect = FALSE;
+ gboolean success = TRUE;
+
+ /* This is meant to recover from dropped connections
+ * when the CamelService is online but disconnected. */
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ service = CAMEL_SERVICE (parent_store);
+ session = camel_service_ref_session (service);
+ status = camel_service_get_connection_status (service);
+ connect = session && camel_session_get_online (session) && (status != CAMEL_SERVICE_CONNECTED);
+ g_clear_object (&session);
+
+ if (connect && CAMEL_IS_NETWORK_SERVICE (parent_store)) {
+ /* Disregard errors here. Just want to
+ * know whether to attempt a connection. */
+ connect = camel_network_service_can_reach_sync (
+ CAMEL_NETWORK_SERVICE (parent_store),
+ cancellable, NULL);
+ }
+
+ if (connect && CAMEL_IS_OFFLINE_STORE (parent_store)) {
+ CamelOfflineStore *offline_store;
+
+ offline_store = CAMEL_OFFLINE_STORE (parent_store);
+ if (!camel_offline_store_get_online (offline_store))
+ connect = FALSE;
+ }
+
+ if (connect) {
+ success = camel_service_connect_sync (
+ service, cancellable, error);
+ }
+
+ return success;
+}
+
+static void
+folder_set_parent_store (CamelFolder *folder,
+ CamelStore *parent_store)
+{
+ g_return_if_fail (CAMEL_IS_STORE (parent_store));
+ g_return_if_fail (folder->priv->parent_store == NULL);
+
+ folder->priv->parent_store = parent_store;
+
+ g_object_add_weak_pointer (
+ G_OBJECT (parent_store), &folder->priv->parent_store);
+}
+
+static void
+folder_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_DESCRIPTION:
+ camel_folder_set_description (
+ CAMEL_FOLDER (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_DISPLAY_NAME:
+ camel_folder_set_display_name (
+ CAMEL_FOLDER (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_FULL_NAME:
+ camel_folder_set_full_name (
+ CAMEL_FOLDER (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_PARENT_STORE:
+ folder_set_parent_store (
+ CAMEL_FOLDER (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+folder_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_DESCRIPTION:
+ g_value_take_string (
+ value, camel_folder_dup_description (
+ CAMEL_FOLDER (object)));
+ return;
+
+ case PROP_DISPLAY_NAME:
+ g_value_take_string (
+ value, camel_folder_dup_display_name (
+ CAMEL_FOLDER (object)));
+ return;
+
+ case PROP_FULL_NAME:
+ g_value_take_string (
+ value, camel_folder_dup_full_name (
+ CAMEL_FOLDER (object)));
+ return;
+
+ case PROP_PARENT_STORE:
+ g_value_set_object (
+ value, camel_folder_get_parent_store (
+ CAMEL_FOLDER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+folder_dispose (GObject *object)
+{
+ CamelFolder *folder;
+
+ folder = CAMEL_FOLDER (object);
+
+ if (folder->priv->parent_store != NULL) {
+ g_object_remove_weak_pointer (
+ G_OBJECT (folder->priv->parent_store),
+ &folder->priv->parent_store);
+ folder->priv->parent_store = NULL;
+ }
+
+ if (folder->summary) {
+ g_object_unref (folder->summary);
+ folder->summary = NULL;
+ }
+
+ /* Chain up to parent's dispose () method. */
+ G_OBJECT_CLASS (camel_folder_parent_class)->dispose (object);
+}
+
+static void
+folder_finalize (GObject *object)
+{
+ CamelFolderPrivate *priv;
+
+ priv = CAMEL_FOLDER_GET_PRIVATE (object);
+
+ g_mutex_clear (&priv->property_lock);
+
+ g_free (priv->full_name);
+ g_free (priv->display_name);
+ g_free (priv->description);
+
+ camel_folder_change_info_free (priv->changed_frozen);
+
+ if (priv->pending_changes != NULL)
+ camel_folder_change_info_free (priv->pending_changes);
+
+ g_rec_mutex_clear (&priv->lock);
+ g_mutex_clear (&priv->change_lock);
+
+ /* Chain up to parent's finalize () method. */
+ G_OBJECT_CLASS (camel_folder_parent_class)->finalize (object);
+}
+
+static gint
+folder_get_message_count (CamelFolder *folder)
+{
+ g_return_val_if_fail (folder->summary != NULL, -1);
+
+ return camel_folder_summary_count (folder->summary);
+}
+
+static CamelMessageFlags
+folder_get_permanent_flags (CamelFolder *folder)
+{
+ return folder->permanent_flags;
+}
+
+static CamelMessageFlags
+folder_get_message_flags (CamelFolder *folder,
+ const gchar *uid)
+{
+ CamelMessageInfo *info;
+ CamelMessageFlags flags;
+
+ g_return_val_if_fail (folder->summary != NULL, 0);
+
+ info = camel_folder_summary_get (folder->summary, uid);
+ if (info == NULL)
+ return 0;
+
+ flags = camel_message_info_get_flags (info);
+ camel_message_info_unref (info);
+
+ return flags;
+}
+
+static gboolean
+folder_set_message_flags (CamelFolder *folder,
+ const gchar *uid,
+ CamelMessageFlags flags,
+ CamelMessageFlags set)
+{
+ CamelMessageInfo *info;
+ gint res;
+
+ g_return_val_if_fail (folder->summary != NULL, FALSE);
+
+ info = camel_folder_summary_get (folder->summary, uid);
+ if (info == NULL)
+ return FALSE;
+
+ res = camel_message_info_set_flags (info, flags, set);
+ camel_message_info_unref (info);
+
+ return res;
+}
+
+static gboolean
+folder_get_message_user_flag (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name)
+{
+ CamelMessageInfo *info;
+ gboolean ret;
+
+ g_return_val_if_fail (folder->summary != NULL, FALSE);
+
+ info = camel_folder_summary_get (folder->summary, uid);
+ if (info == NULL)
+ return FALSE;
+
+ ret = camel_message_info_get_user_flag (info, name);
+ camel_message_info_unref (info);
+
+ return ret;
+}
+
+static void
+folder_set_message_user_flag (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name,
+ gboolean value)
+{
+ CamelMessageInfo *info;
+
+ g_return_if_fail (folder->summary != NULL);
+
+ info = camel_folder_summary_get (folder->summary, uid);
+ if (info == NULL)
+ return;
+
+ camel_message_info_set_user_flag (info, name, value);
+ camel_message_info_unref (info);
+}
+
+static const gchar *
+folder_get_message_user_tag (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name)
+{
+ CamelMessageInfo *info;
+ const gchar *ret;
+
+ g_return_val_if_fail (folder->summary != NULL, NULL);
+
+ info = camel_folder_summary_get (folder->summary, uid);
+ if (info == NULL)
+ return NULL;
+
+ ret = camel_message_info_get_user_tag (info, name);
+ camel_message_info_unref (info);
+
+ return ret;
+}
+
+static void
+folder_set_message_user_tag (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name,
+ const gchar *value)
+{
+ CamelMessageInfo *info;
+
+ g_return_if_fail (folder->summary != NULL);
+
+ info = camel_folder_summary_get (folder->summary, uid);
+ if (info == NULL)
+ return;
+
+ camel_message_info_set_user_tag (info, name, value);
+ camel_message_info_unref (info);
+}
+
+static GPtrArray *
+folder_get_uids (CamelFolder *folder)
+{
+ g_return_val_if_fail (folder->summary != NULL, NULL);
+
+ return camel_folder_summary_get_array (folder->summary);
+}
+
+static GPtrArray *
+folder_get_uncached_uids (CamelFolder *folder,
+ GPtrArray *uids,
+ GError **error)
+{
+ GPtrArray *result;
+ gint i;
+
+ result = g_ptr_array_new ();
+
+ g_ptr_array_set_size (result, uids->len);
+ for (i = 0; i < uids->len; i++)
+ result->pdata[i] =
+ (gpointer) camel_pstring_strdup (uids->pdata[i]);
+
+ return result;
+}
+
+static void
+folder_free_uids (CamelFolder *folder,
+ GPtrArray *array)
+{
+ camel_folder_summary_free_array (array);
+}
+
+static gint
+folder_cmp_uids (CamelFolder *folder,
+ const gchar *uid1,
+ const gchar *uid2)
+{
+ g_return_val_if_fail (uid1 != NULL, 0);
+ g_return_val_if_fail (uid2 != NULL, 0);
+
+ return strtoul (uid1, NULL, 10) - strtoul (uid2, NULL, 10);
+}
+
+static void
+folder_sort_uids (CamelFolder *folder,
+ GPtrArray *uids)
+{
+ g_qsort_with_data (
+ uids->pdata, uids->len,
+ sizeof (gpointer), cmp_array_uids, folder);
+}
+
+static GPtrArray *
+folder_get_summary (CamelFolder *folder)
+{
+ g_return_val_if_fail (folder->summary != NULL, NULL);
+
+ return camel_folder_summary_get_array (folder->summary);
+}
+
+static void
+folder_free_summary (CamelFolder *folder,
+ GPtrArray *array)
+{
+ camel_folder_summary_free_array (array);
+}
+
+static void
+folder_search_free (CamelFolder *folder,
+ GPtrArray *result)
+{
+ gint i;
+
+ for (i = 0; i < result->len; i++)
+ camel_pstring_free (g_ptr_array_index (result, i));
+ g_ptr_array_free (result, TRUE);
+}
+
+static CamelMessageInfo *
+folder_get_message_info (CamelFolder *folder,
+ const gchar *uid)
+{
+ g_return_val_if_fail (folder->summary != NULL, NULL);
+
+ return camel_folder_summary_get (folder->summary, uid);
+}
+
+static void
+folder_delete (CamelFolder *folder)
+{
+ if (folder->summary)
+ camel_folder_summary_clear (folder->summary, NULL);
+}
+
+static void
+folder_rename (CamelFolder *folder,
+ const gchar *new)
+{
+ gchar *tmp;
+
+ d (printf ("CamelFolder:rename ('%s')\n", new));
+
+ camel_folder_set_full_name (folder, new);
+
+ tmp = strrchr (new, '/');
+ camel_folder_set_display_name (folder, (tmp != NULL) ? tmp + 1 : new);
+}
+
+static void
+folder_freeze (CamelFolder *folder)
+{
+ g_return_if_fail (folder->priv->frozen >= 0);
+
+ g_mutex_lock (&folder->priv->change_lock);
+
+ folder->priv->frozen++;
+ if (folder->summary)
+ g_object_freeze_notify (G_OBJECT (folder->summary));
+
+ d (printf ("freeze (%p '%s') = %d\n", folder, folder->full_name, folder->priv->frozen));
+ g_mutex_unlock (&folder->priv->change_lock);
+}
+
+static void
+folder_thaw (CamelFolder *folder)
+{
+ CamelFolderChangeInfo *info = NULL;
+
+ g_return_if_fail (folder->priv->frozen > 0);
+
+ g_mutex_lock (&folder->priv->change_lock);
+
+ folder->priv->frozen--;
+ if (folder->summary)
+ g_object_thaw_notify (G_OBJECT (folder->summary));
+
+ d (printf ("thaw (%p '%s') = %d\n", folder, folder->full_name, folder->priv->frozen));
+
+ if (folder->priv->frozen == 0
+ && camel_folder_change_info_changed (folder->priv->changed_frozen)) {
+ info = folder->priv->changed_frozen;
+ folder->priv->changed_frozen = camel_folder_change_info_new ();
+ }
+
+ g_mutex_unlock (&folder->priv->change_lock);
+
+ if (info) {
+ camel_folder_changed (folder, info);
+ camel_folder_change_info_free (info);
+
+ if (folder->summary)
+ camel_folder_summary_save_to_db (folder->summary, NULL);
+ }
+}
+
+static gboolean
+folder_is_frozen (CamelFolder *folder)
+{
+ return folder->priv->frozen != 0;
+}
+
+static gboolean
+folder_refresh_info_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return TRUE;
+}
+
+static gboolean
+folder_transfer_messages_to_sync (CamelFolder *source,
+ GPtrArray *uids,
+ CamelFolder *dest,
+ gboolean delete_originals,
+ GPtrArray **transferred_uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar **ret_uid = NULL;
+ gint i;
+ GError *local_error = NULL;
+ GCancellable *local_cancellable = camel_operation_new ();
+ gulong handler_id = 0;
+
+ if (transferred_uids) {
+ *transferred_uids = g_ptr_array_new ();
+ g_ptr_array_set_size (*transferred_uids, uids->len);
+ }
+
+ /* to not propagate status messages from sub-functions into UI */
+ if (cancellable)
+ handler_id = g_signal_connect_swapped (cancellable, "cancelled", G_CALLBACK (g_cancellable_cancel), local_cancellable);
+
+ if (delete_originals)
+ camel_operation_push_message (
+ cancellable, _("Moving messages"));
+ else
+ camel_operation_push_message (
+ cancellable, _("Copying messages"));
+
+ if (uids->len > 1) {
+ camel_folder_freeze (dest);
+ if (delete_originals)
+ camel_folder_freeze (source);
+ }
+
+ for (i = 0; i < uids->len && local_error == NULL; i++) {
+ if (transferred_uids)
+ ret_uid = (gchar **) &((*transferred_uids)->pdata[i]);
+ folder_transfer_message_to (
+ source, uids->pdata[i], dest, ret_uid,
+ delete_originals, local_cancellable, &local_error);
+ camel_operation_progress (
+ cancellable, i * 100 / uids->len);
+ }
+
+ if (uids->len > 1) {
+ camel_folder_thaw (dest);
+ if (delete_originals)
+ camel_folder_thaw (source);
+ }
+
+ camel_operation_pop_message (cancellable);
+
+ if (local_error != NULL)
+ g_propagate_error (error, local_error);
+ g_object_unref (local_cancellable);
+ if (cancellable)
+ g_signal_handler_disconnect (cancellable, handler_id);
+
+ return TRUE;
+}
+
+static CamelFolderQuotaInfo *
+folder_get_quota_info_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ _("Quota information not supported for folder '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
+ camel_folder_get_full_name (folder));
+
+ return NULL;
+}
+
+/* Signal callback that stops emission when folder is frozen. */
+static void
+folder_changed (CamelFolder *folder,
+ CamelFolderChangeInfo *info)
+{
+ CamelStore *parent_store;
+ struct _CamelFolderChangeInfoPrivate *p = info->priv;
+ CamelSession *session;
+ CamelFilterDriver *driver = NULL;
+ CamelJunkFilter *junk_filter;
+ GPtrArray *junk = NULL;
+ GPtrArray *notjunk = NULL;
+ GPtrArray *recents = NULL;
+ gint i;
+
+ g_return_if_fail (info != NULL);
+
+ g_mutex_lock (&folder->priv->change_lock);
+ if (folder->priv->frozen) {
+ camel_folder_change_info_cat (folder->priv->changed_frozen, info);
+ g_mutex_unlock (&folder->priv->change_lock);
+ g_signal_stop_emission (folder, signals[CHANGED], 0);
+ return;
+ }
+ g_mutex_unlock (&folder->priv->change_lock);
+
+ parent_store = camel_folder_get_parent_store (folder);
+ session = camel_service_ref_session (CAMEL_SERVICE (parent_store));
+ if (!session)
+ return;
+
+ junk_filter = camel_session_get_junk_filter (session);
+
+ if (junk_filter != NULL && info->uid_changed->len) {
+ CamelMessageFlags flags;
+
+ for (i = 0; i < info->uid_changed->len; i++) {
+ flags = camel_folder_summary_get_info_flags (folder->summary, info->uid_changed->pdata[i]);
+ if (flags != (~0) && (flags & CAMEL_MESSAGE_JUNK_LEARN) != 0) {
+ if (flags & CAMEL_MESSAGE_JUNK) {
+ if (!junk)
+ junk = g_ptr_array_new ();
+ g_ptr_array_add (junk, g_strdup (info->uid_changed->pdata[i]));
+ } else {
+ if (!notjunk)
+ notjunk = g_ptr_array_new ();
+ g_ptr_array_add (notjunk, g_strdup (info->uid_changed->pdata[i]));
+ }
+
+ /* the flag will be unset in the thread, to not block the UI/main thread */
+ }
+ }
+ }
+
+ if ((folder->folder_flags & (CAMEL_FOLDER_FILTER_RECENT | CAMEL_FOLDER_FILTER_JUNK))
+ && p->uid_filter->len > 0)
+ driver = camel_session_get_filter_driver (
+ session,
+ (folder->folder_flags & CAMEL_FOLDER_FILTER_RECENT)
+ ? "incoming" : "junktest", NULL);
+
+ if (driver) {
+ recents = g_ptr_array_new ();
+ for (i = 0; i < p->uid_filter->len; i++)
+ g_ptr_array_add (recents, g_strdup (p->uid_filter->pdata[i]));
+
+ g_ptr_array_set_size (p->uid_filter, 0);
+ }
+
+ if (driver || junk || notjunk) {
+ FolderFilterData *data;
+ gchar *description;
+
+ data = g_slice_new0 (FolderFilterData);
+ data->recents = recents;
+ data->junk = junk;
+ data->notjunk = notjunk;
+ data->folder = g_object_ref (folder);
+ data->driver = driver;
+
+ camel_folder_freeze (folder);
+
+ /* Copy changes back to changed_frozen list to retain
+ * them while we are filtering */
+ g_mutex_lock (&folder->priv->change_lock);
+ camel_folder_change_info_cat (
+ folder->priv->changed_frozen, info);
+ g_mutex_unlock (&folder->priv->change_lock);
+
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ description = g_strdup_printf (_("Filtering folder '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (parent_store)),
+ camel_folder_get_full_name (folder));
+
+ camel_session_submit_job (
+ session, description, (CamelSessionCallback) folder_filter,
+ data, (GDestroyNotify) prepare_folder_filter_data_free);
+
+ g_signal_stop_emission (folder, signals[CHANGED], 0);
+
+ g_free (description);
+ }
+
+ g_object_unref (session);
+}
+
+static void
+camel_folder_class_init (CamelFolderClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelFolderPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = folder_set_property;
+ object_class->get_property = folder_get_property;
+ object_class->dispose = folder_dispose;
+ object_class->finalize = folder_finalize;
+
+ class->get_message_count = folder_get_message_count;
+ class->get_permanent_flags = folder_get_permanent_flags;
+ class->get_message_flags = folder_get_message_flags;
+ class->set_message_flags = folder_set_message_flags;
+ class->get_message_user_flag = folder_get_message_user_flag;
+ class->set_message_user_flag = folder_set_message_user_flag;
+ class->get_message_user_tag = folder_get_message_user_tag;
+ class->set_message_user_tag = folder_set_message_user_tag;
+ class->get_uids = folder_get_uids;
+ class->get_uncached_uids = folder_get_uncached_uids;
+ class->free_uids = folder_free_uids;
+ class->cmp_uids = folder_cmp_uids;
+ class->sort_uids = folder_sort_uids;
+ class->get_summary = folder_get_summary;
+ class->free_summary = folder_free_summary;
+ class->search_free = folder_search_free;
+ class->get_message_info = folder_get_message_info;
+ class->delete_ = folder_delete;
+ class->rename = folder_rename;
+ class->freeze = folder_freeze;
+ class->thaw = folder_thaw;
+ class->is_frozen = folder_is_frozen;
+ class->get_quota_info_sync = folder_get_quota_info_sync;
+ class->refresh_info_sync = folder_refresh_info_sync;
+ class->transfer_messages_to_sync = folder_transfer_messages_to_sync;
+ class->changed = folder_changed;
+
+ /**
+ * CamelFolder:description
+ *
+ * The folder's description.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_DESCRIPTION,
+ g_param_spec_string (
+ "description",
+ "Description",
+ "The folder's description",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ /**
+ * CamelFolder:display-name
+ *
+ * The folder's display name.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_DISPLAY_NAME,
+ g_param_spec_string (
+ "display-name",
+ "Display Name",
+ "The folder's display name",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ /**
+ * CamelFolder:full-name
+ *
+ * The folder's fully qualified name.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_FULL_NAME,
+ g_param_spec_string (
+ "full-name",
+ "Full Name",
+ "The folder's fully qualified name",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ /**
+ * CamelFolder:parent-store
+ *
+ * The #CamelStore to which the folder belongs.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_PARENT_STORE,
+ g_param_spec_object (
+ "parent-store",
+ "Parent Store",
+ "The store to which the folder belongs",
+ CAMEL_TYPE_STORE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * CamelFolder::changed
+ * @folder: the #CamelFolder which emitted the signal
+ **/
+ signals[CHANGED] = g_signal_new (
+ "changed",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CamelFolderClass, changed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ /**
+ * CamelFolder::deleted
+ * @folder: the #CamelFolder which emitted the signal
+ **/
+ signals[DELETED] = g_signal_new (
+ "deleted",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CamelFolderClass, deleted),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * CamelFolder::renamed
+ * @folder: the #CamelFolder which emitted the signal
+ * @old_name: the previous folder name
+ **/
+ signals[RENAMED] = g_signal_new (
+ "renamed",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CamelFolderClass, renamed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+}
+
+static void
+camel_folder_init (CamelFolder *folder)
+{
+ folder->priv = CAMEL_FOLDER_GET_PRIVATE (folder);
+ folder->priv->frozen = 0;
+ folder->priv->changed_frozen = camel_folder_change_info_new ();
+
+ g_rec_mutex_init (&folder->priv->lock);
+ g_mutex_init (&folder->priv->change_lock);
+ g_mutex_init (&folder->priv->property_lock);
+}
+
+G_DEFINE_QUARK (camel-folder-error-quark, camel_folder_error)
+
+/**
+ * camel_folder_set_lock_async:
+ * @folder: a #CamelFolder
+ * @skip_folder_lock:
+ *
+ * FIXME Document me!
+ *
+ * Since: 2.30
+ **/
+void
+camel_folder_set_lock_async (CamelFolder *folder,
+ gboolean skip_folder_lock)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ folder->priv->skip_folder_lock = skip_folder_lock;
+}
+
+/**
+ * camel_folder_get_filename:
+ *
+ * Since: 2.26
+ **/
+gchar *
+camel_folder_get_filename (CamelFolder *folder,
+ const gchar *uid,
+ GError **error)
+{
+ CamelFolderClass *class;
+ gchar *filename;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+ g_return_val_if_fail (uid != NULL, NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->get_filename != NULL, NULL);
+
+ filename = class->get_filename (folder, uid, error);
+ CAMEL_CHECK_GERROR (folder, get_filename, filename != NULL, error);
+
+ return filename;
+}
+
+/**
+ * camel_folder_get_full_name:
+ * @folder: a #CamelFolder
+ *
+ * Returns the fully qualified name of the folder.
+ *
+ * Returns: the fully qualified name of the folder
+ **/
+const gchar *
+camel_folder_get_full_name (CamelFolder *folder)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ return folder->priv->full_name;
+}
+
+/**
+ * camel_folder_dup_full_name:
+ * @folder: a #CamelFolder
+ *
+ * Thread-safe variation of camel_folder_get_full_name().
+ * Use this function when accessing @folder from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelFolder:full-name
+ *
+ * Since: 3.8
+ **/
+gchar *
+camel_folder_dup_full_name (CamelFolder *folder)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ g_mutex_lock (&folder->priv->property_lock);
+
+ protected = camel_folder_get_full_name (folder);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&folder->priv->property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_folder_set_full_name:
+ * @folder: a #CamelFolder
+ * @full_name: a fully qualified name for the folder
+ *
+ * Sets the fully qualified name of the folder.
+ *
+ * Since: 2.32
+ **/
+void
+camel_folder_set_full_name (CamelFolder *folder,
+ const gchar *full_name)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ g_mutex_lock (&folder->priv->property_lock);
+
+ if (g_strcmp0 (folder->priv->full_name, full_name) == 0) {
+ g_mutex_unlock (&folder->priv->property_lock);
+ return;
+ }
+
+ g_free (folder->priv->full_name);
+ folder->priv->full_name = g_strdup (full_name);
+
+ g_mutex_unlock (&folder->priv->property_lock);
+
+ g_object_notify (G_OBJECT (folder), "full-name");
+}
+
+/**
+ * camel_folder_get_display_name:
+ * @folder: a #CamelFolder
+ *
+ * Returns the display name for the folder. The fully qualified name
+ * can be obtained with camel_folder_get_full_name().
+ *
+ * Returns: the display name of the folder
+ *
+ * Since: 3.2
+ **/
+const gchar *
+camel_folder_get_display_name (CamelFolder *folder)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ return folder->priv->display_name;
+}
+
+/**
+ * camel_folder_dup_display_name:
+ * @folder: a #CamelFolder
+ *
+ * Thread-safe variation of camel_folder_get_display_name().
+ * Use this function when accessing @folder from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelFolder:display-name
+ *
+ * Since: 3.8
+ **/
+gchar *
+camel_folder_dup_display_name (CamelFolder *folder)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ g_mutex_lock (&folder->priv->property_lock);
+
+ protected = camel_folder_get_display_name (folder);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&folder->priv->property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_folder_set_display_name:
+ * @folder: a #CamelFolder
+ * @display_name: a display name for the folder
+ *
+ * Sets the display name for the folder.
+ *
+ * Since: 3.2
+ **/
+void
+camel_folder_set_display_name (CamelFolder *folder,
+ const gchar *display_name)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ g_mutex_lock (&folder->priv->property_lock);
+
+ if (g_strcmp0 (folder->priv->display_name, display_name) == 0) {
+ g_mutex_unlock (&folder->priv->property_lock);
+ return;
+ }
+
+ g_free (folder->priv->display_name);
+ folder->priv->display_name = g_strdup (display_name);
+
+ g_mutex_unlock (&folder->priv->property_lock);
+
+ g_object_notify (G_OBJECT (folder), "display-name");
+}
+
+/**
+ * camel_folder_get_description:
+ * @folder: a #CamelFolder
+ *
+ * Returns a description of the folder suitable for displaying to the user.
+ *
+ * Returns: a description of the folder
+ *
+ * Since: 2.32
+ **/
+const gchar *
+camel_folder_get_description (CamelFolder *folder)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ /* Default to full-name if there's no custom description. */
+ if (folder->priv->description == NULL)
+ return camel_folder_get_full_name (folder);
+
+ return folder->priv->description;
+}
+
+/**
+ * camel_folder_dup_description:
+ * @folder: a #CamelFolder
+ *
+ * Thread-safe variation of camel_folder_get_description().
+ * Use this function when accessing @folder from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelFolder:description
+ *
+ * Since: 3.8
+ **/
+gchar *
+camel_folder_dup_description (CamelFolder *folder)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ g_mutex_lock (&folder->priv->property_lock);
+
+ protected = camel_folder_get_description (folder);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&folder->priv->property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_folder_set_description:
+ * @folder: a #CamelFolder
+ * @description: a description of the folder
+ *
+ * Sets a description of the folder suitable for displaying to the user.
+ *
+ * Since: 2.32
+ **/
+void
+camel_folder_set_description (CamelFolder *folder,
+ const gchar *description)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ g_mutex_lock (&folder->priv->property_lock);
+
+ if (g_strcmp0 (folder->priv->description, description) == 0) {
+ g_mutex_unlock (&folder->priv->property_lock);
+ return;
+ }
+
+ g_free (folder->priv->description);
+ folder->priv->description = g_strdup (description);
+
+ g_mutex_unlock (&folder->priv->property_lock);
+
+ g_object_notify (G_OBJECT (folder), "description");
+}
+
+/**
+ * camel_folder_get_parent_store:
+ * @folder: a #CamelFolder
+ *
+ * Returns: (transfer none): the parent #CamelStore of the folder
+ **/
+CamelStore *
+camel_folder_get_parent_store (CamelFolder *folder)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ /* Can be NULL, thus do not use CAMEL_STORE() macro. */
+ return (CamelStore *) (folder->priv->parent_store);
+}
+
+/**
+ * camel_folder_get_message_count:
+ * @folder: a #CamelFolder
+ *
+ * Returns: the number of messages in the folder, or %-1 if unknown
+ **/
+gint
+camel_folder_get_message_count (CamelFolder *folder)
+{
+ CamelFolderClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), -1);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->get_message_count != NULL, -1);
+
+ return class->get_message_count (folder);
+}
+
+/**
+ * camel_folder_get_unread_message_count:
+ * @folder: a #CamelFolder
+ *
+ * DEPRECATED: use camel_object_get() instead.
+ *
+ * Returns: the number of unread messages in the folder, or %-1 if
+ * unknown
+ **/
+gint
+camel_folder_get_unread_message_count (CamelFolder *folder)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), -1);
+ g_return_val_if_fail (folder->summary != NULL, -1);
+
+ return camel_folder_summary_get_unread_count (folder->summary);
+}
+
+/**
+ * camel_folder_get_deleted_message_count:
+ * @folder: a #CamelFolder
+ *
+ * Returns: the number of deleted messages in the folder, or %-1 if
+ * unknown
+ **/
+gint
+camel_folder_get_deleted_message_count (CamelFolder *folder)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), -1);
+ g_return_val_if_fail (folder->summary != NULL, -1);
+
+ return camel_folder_summary_get_deleted_count (folder->summary);
+}
+
+/**
+ * camel_folder_get_permanent_flags:
+ * @folder: a #CamelFolder
+ *
+ * Returns: the set of #CamelMessageFlags that can be permanently
+ * stored on a message between sessions. If it includes
+ * #CAMEL_FLAG_USER, then user-defined flags will be remembered.
+ **/
+CamelMessageFlags
+camel_folder_get_permanent_flags (CamelFolder *folder)
+{
+ CamelFolderClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), 0);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->get_permanent_flags != NULL, 0);
+
+ return class->get_permanent_flags (folder);
+}
+
+/**
+ * camel_folder_get_message_flags:
+ * @folder: a #CamelFolder
+ * @uid: the UID of a message in @folder
+ *
+ * Deprecated: Use camel_folder_get_message_info() instead.
+ *
+ * Returns: the #CamelMessageFlags that are set on the indicated
+ * message.
+ **/
+CamelMessageFlags
+camel_folder_get_message_flags (CamelFolder *folder,
+ const gchar *uid)
+{
+ CamelFolderClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), 0);
+ g_return_val_if_fail (uid != NULL, 0);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->get_message_flags != NULL, 0);
+
+ return class->get_message_flags (folder, uid);
+}
+
+/**
+ * camel_folder_set_message_flags:
+ * @folder: a #CamelFolder
+ * @uid: the UID of a message in @folder
+ * @flags: a set of #CamelMessageFlag values to set
+ * @set: the mask of values in @flags to use.
+ *
+ * Sets those flags specified by @flags to the values specified by @set
+ * on the indicated message. (This may or may not persist after the
+ * folder or store is closed. See camel_folder_get_permanent_flags())
+ *
+ * E.g. to set the deleted flag and clear the draft flag, use
+ * camel_folder_set_message_flags (folder, uid, CAMEL_MESSAGE_DELETED|CAMEL_MESSAGE_DRAFT, CAMEL_MESSAGE_DELETED);
+ *
+ * DEPRECATED: Use camel_message_info_set_flags() on the message info directly
+ * (when it works)
+ *
+ * Returns: %TRUE if the flags were changed or %FALSE otherwise
+ **/
+gboolean
+camel_folder_set_message_flags (CamelFolder *folder,
+ const gchar *uid,
+ CamelMessageFlags flags,
+ CamelMessageFlags set)
+{
+ CamelFolderClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->set_message_flags != NULL, FALSE);
+
+ if ((flags & (CAMEL_MESSAGE_JUNK | CAMEL_MESSAGE_JUNK_LEARN)) == CAMEL_MESSAGE_JUNK) {
+ flags |= CAMEL_MESSAGE_JUNK_LEARN;
+ set &= ~CAMEL_MESSAGE_JUNK_LEARN;
+ }
+
+ return class->set_message_flags (folder, uid, flags, set);
+}
+
+/**
+ * camel_folder_get_message_user_flag:
+ * @folder: a #CamelFolder
+ * @uid: the UID of a message in @folder
+ * @name: the name of a user flag
+ *
+ * DEPRECATED: Use camel_message_info_get_user_flag() on the message
+ * info directly
+ *
+ * Returns: %TRUE if the given user flag is set on the message or
+ * %FALSE otherwise
+ **/
+gboolean
+camel_folder_get_message_user_flag (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name)
+{
+ CamelFolderClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), 0);
+ g_return_val_if_fail (uid != NULL, 0);
+ g_return_val_if_fail (name != NULL, 0);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->get_message_user_flag != NULL, 0);
+
+ return class->get_message_user_flag (folder, uid, name);
+}
+
+/**
+ * camel_folder_set_message_user_flag:
+ * @folder: a #CamelFolder
+ * @uid: the UID of a message in @folder
+ * @name: the name of the user flag to set
+ * @value: the value to set it to
+ *
+ * DEPRECATED: Use camel_message_info_set_user_flag() on the
+ * #CamelMessageInfo directly (when it works)
+ *
+ * Sets the user flag specified by @name to the value specified by @value
+ * on the indicated message. (This may or may not persist after the
+ * folder or store is closed. See camel_folder_get_permanent_flags())
+ **/
+void
+camel_folder_set_message_user_flag (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name,
+ gboolean value)
+{
+ CamelFolderClass *class;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (uid != NULL);
+ g_return_if_fail (name != NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_if_fail (class->set_message_user_flag != NULL);
+
+ class->set_message_user_flag (folder, uid, name, value);
+}
+
+/**
+ * camel_folder_get_message_user_tag:
+ * @folder: a #CamelFolder
+ * @uid: the UID of a message in @folder
+ * @name: the name of a user tag
+ *
+ * DEPRECATED: Use camel_message_info_get_user_tag() on the
+ * #CamelMessageInfo directly.
+ *
+ * Returns: the value of the user tag
+ **/
+const gchar *
+camel_folder_get_message_user_tag (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name)
+{
+ CamelFolderClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+ g_return_val_if_fail (uid != NULL, NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->get_message_user_tag != NULL, NULL);
+
+ /* FIXME: should duplicate string */
+ return class->get_message_user_tag (folder, uid, name);
+}
+
+/**
+ * camel_folder_set_message_user_tag:
+ * @folder: a #CamelFolder
+ * @uid: the UID of a message in @folder
+ * @name: the name of the user tag to set
+ * @value: the value to set it to
+ *
+ * DEPRECATED: Use camel_message_info_set_user_tag() on the
+ * #CamelMessageInfo directly (when it works).
+ *
+ * Sets the user tag specified by @name to the value specified by @value
+ * on the indicated message. (This may or may not persist after the
+ * folder or store is closed. See camel_folder_get_permanent_flags())
+ **/
+void
+camel_folder_set_message_user_tag (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name,
+ const gchar *value)
+{
+ CamelFolderClass *class;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (uid != NULL);
+ g_return_if_fail (name != NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_if_fail (class->set_message_user_tag != NULL);
+
+ class->set_message_user_tag (folder, uid, name, value);
+}
+
+/**
+ * camel_folder_get_message_info:
+ * @folder: a #CamelFolder
+ * @uid: the uid of a message
+ *
+ * Retrieve the #CamelMessageInfo for the specified @uid. This return
+ * must be freed using camel_message_info_unref().
+ *
+ * Returns: the summary information for the indicated message, or %NULL
+ * if the uid does not exist
+ **/
+CamelMessageInfo *
+camel_folder_get_message_info (CamelFolder *folder,
+ const gchar *uid)
+{
+ CamelFolderClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+ g_return_val_if_fail (uid != NULL, NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->get_message_info != NULL, NULL);
+
+ return class->get_message_info (folder, uid);
+}
+
+/* TODO: is this function required anyway? */
+/**
+ * camel_folder_has_summary_capability:
+ * @folder: a #CamelFolder
+ *
+ * Get whether or not the folder has a summary.
+ *
+ * Returns: %TRUE if a summary is available or %FALSE otherwise
+ **/
+gboolean
+camel_folder_has_summary_capability (CamelFolder *folder)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+
+ return folder->folder_flags & CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY;
+}
+
+/* UIDs stuff */
+
+/**
+ * camel_folder_get_uids:
+ * @folder: a #CamelFolder
+ *
+ * Get the list of UIDs available in a folder. This routine is useful
+ * for finding what messages are available when the folder does not
+ * support summaries. The returned array should not be modified, and
+ * must be freed by passing it to camel_folder_free_uids().
+ *
+ * Returns: (element-type utf8) (transfer none): a GPtrArray of UIDs
+ * corresponding to the messages available in the folder
+ **/
+GPtrArray *
+camel_folder_get_uids (CamelFolder *folder)
+{
+ CamelFolderClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->get_uids != NULL, NULL);
+
+ return class->get_uids (folder);
+}
+
+/**
+ * camel_folder_free_uids:
+ * @folder: a #CamelFolder
+ * @array: (element-type utf8): the array of uids to free
+ *
+ * Frees the array of UIDs returned by camel_folder_get_uids().
+ **/
+void
+camel_folder_free_uids (CamelFolder *folder,
+ GPtrArray *array)
+{
+ CamelFolderClass *class;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (array != NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_if_fail (class->free_uids != NULL);
+
+ class->free_uids (folder, array);
+}
+
+/**
+ * camel_folder_get_uncached_uids:
+ * @folder: a #CamelFolder
+ * @uids: (element-type utf8): the array of uids to filter down to uncached ones.
+ *
+ * Returns the known-uncached uids from a list of uids. It may return uids
+ * which are locally cached but should never filter out a uid which is not
+ * locally cached. Free the result by called camel_folder_free_uids().
+ * Frees the array of UIDs returned by camel_folder_get_uids().
+ *
+ * Returns: (element-type utf8) (transfer none):
+ *
+ * Since: 2.26
+ **/
+GPtrArray *
+camel_folder_get_uncached_uids (CamelFolder *folder,
+ GPtrArray *uids,
+ GError **error)
+{
+ CamelFolderClass *class;
+ GPtrArray *uncached_uids;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+ g_return_val_if_fail (uids != NULL, NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->get_uncached_uids != NULL, NULL);
+
+ uncached_uids = class->get_uncached_uids (folder, uids, error);
+ CAMEL_CHECK_GERROR (folder, get_uncached_uids, uncached_uids != NULL, error);
+
+ return uncached_uids;
+}
+
+/**
+ * camel_folder_cmp_uids:
+ * @folder: a #CamelFolder
+ * @uid1: The first uid.
+ * @uid2: the second uid.
+ *
+ * Compares two uids. The return value meaning is the same as in any other compare function.
+ *
+ * Note that the default compare function expects a decimal number at the beginning of a uid,
+ * thus if provider uses different uid values, then it should subclass this function.
+ *
+ * Since: 2.28
+ **/
+gint
+camel_folder_cmp_uids (CamelFolder *folder,
+ const gchar *uid1,
+ const gchar *uid2)
+{
+ CamelFolderClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), 0);
+ g_return_val_if_fail (uid1 != NULL, 0);
+ g_return_val_if_fail (uid2 != NULL, 0);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->cmp_uids != NULL, 0);
+
+ return class->cmp_uids (folder, uid1, uid2);
+}
+
+/**
+ * camel_folder_sort_uids:
+ * @folder: a #CamelFolder
+ * @uids: (element-type utf8): array of uids
+ *
+ * Sorts the array of UIDs.
+ *
+ * Since: 2.24
+ **/
+void
+camel_folder_sort_uids (CamelFolder *folder,
+ GPtrArray *uids)
+{
+ CamelFolderClass *class;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (uids != NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_if_fail (class->sort_uids != NULL);
+
+ class->sort_uids (folder, uids);
+}
+
+/**
+ * camel_folder_get_summary:
+ * @folder: a #CamelFolder
+ *
+ * This returns the summary information for the folder. This array
+ * should not be modified, and must be freed with
+ * camel_folder_free_summary().
+ *
+ * Returns: (element-type CamelMessageInfo) (transfer none): an array of #CamelMessageInfo
+ **/
+GPtrArray *
+camel_folder_get_summary (CamelFolder *folder)
+{
+ CamelFolderClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->get_summary != NULL, NULL);
+
+ return class->get_summary (folder);
+}
+
+/**
+ * camel_folder_free_summary:
+ * @folder: a #CamelFolder
+ * @array: (element-type CamelMessageInfo): the summary array to free
+ *
+ * Frees the summary array returned by camel_folder_get_summary().
+ **/
+void
+camel_folder_free_summary (CamelFolder *folder,
+ GPtrArray *array)
+{
+ CamelFolderClass *class;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (array != NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_if_fail (class->free_summary != NULL);
+
+ class->free_summary (folder, array);
+}
+
+/**
+ * camel_folder_search_by_expression:
+ * @folder: a #CamelFolder
+ * @expression: a search expression
+ * @cancellable: a #GCancellable
+ * @error: return location for a #GError, or %NULL
+ *
+ * Searches the folder for messages matching the given search expression.
+ *
+ * Returns: (element-type utf8) (transfer full): a #GPtrArray of uids of
+ * matching messages. The caller must free the list and each of the elements
+ * when it is done.
+ **/
+GPtrArray *
+camel_folder_search_by_expression (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderClass *class;
+ GPtrArray *matches;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->search_by_expression != NULL, NULL);
+
+ /* NOTE: that it is upto the callee to CAMEL_FOLDER_REC_LOCK */
+
+ matches = class->search_by_expression (folder, expression, cancellable, error);
+ CAMEL_CHECK_GERROR (folder, search_by_expression, matches != NULL, error);
+
+ return matches;
+}
+
+/**
+ * camel_folder_count_by_expression:
+ * @folder: a #CamelFolder
+ * @expression: a search expression
+ * @cancellable: a #GCancellable
+ * @error: return location for a #GError, or %NULL
+ *
+ * Searches the folder for count of messages matching the given search expression.
+ *
+ * Returns: an interger
+ *
+ * Since: 2.26
+ **/
+guint32
+camel_folder_count_by_expression (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), 0);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->count_by_expression != NULL, 0);
+
+ /* NOTE: that it is upto the callee to CAMEL_FOLDER_REC_LOCK */
+
+ return class->count_by_expression (folder, expression, cancellable, error);
+}
+
+/**
+ * camel_folder_search_by_uids:
+ * @folder: a #CamelFolder
+ * @expression: search expression
+ * @uids: (element-type utf8): array of uid's to match against.
+ * @cancellable: a #GCancellable
+ * @error: return location for a #GError, or %NULL
+ *
+ * Search a subset of uid's for an expression match.
+ *
+ * Returns: (element-type utf8) (transfer full): a #GPtrArray of uids of
+ * matching messages. The caller must free the list and each of the elements
+ * when it is done.
+ **/
+GPtrArray *
+camel_folder_search_by_uids (CamelFolder *folder,
+ const gchar *expression,
+ GPtrArray *uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderClass *class;
+ GPtrArray *matches;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->search_by_uids != NULL, NULL);
+
+ /* NOTE: that it is upto the callee to CAMEL_FOLDER_REC_LOCK */
+
+ matches = class->search_by_uids (folder, expression, uids, cancellable, error);
+ CAMEL_CHECK_GERROR (folder, search_by_uids, matches != NULL, error);
+
+ return matches;
+}
+
+/**
+ * camel_folder_search_free:
+ * @folder: a #CamelFolder
+ * @result: (element-type utf8): search results to free
+ *
+ * Free the result of a search as gotten by camel_folder_search() or
+ * camel_folder_search_by_uids().
+ **/
+void
+camel_folder_search_free (CamelFolder *folder,
+ GPtrArray *result)
+{
+ CamelFolderClass *class;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (result != NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_if_fail (class->search_free != NULL);
+
+ /* NOTE: upto the callee to CAMEL_FOLDER_REC_LOCK */
+
+ class->search_free (folder, result);
+}
+
+/**
+ * camel_folder_delete:
+ * @folder: a #CamelFolder
+ *
+ * Marks @folder as deleted and performs any required cleanup.
+ *
+ * This also emits the #CamelFolder::deleted signal from an idle source on
+ * the main loop. The idle source's priority is #G_PRIORITY_HIGH_IDLE.
+ **/
+void
+camel_folder_delete (CamelFolder *folder)
+{
+ CamelFolderClass *class;
+ CamelStore *parent_store;
+ CamelService *service;
+ CamelSession *session;
+ SignalClosure *signal_closure;
+ const gchar *full_name;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_if_fail (class->delete_ != NULL);
+
+ camel_folder_lock (folder);
+ if (folder->folder_flags & CAMEL_FOLDER_HAS_BEEN_DELETED) {
+ camel_folder_unlock (folder);
+ return;
+ }
+
+ folder->folder_flags |= CAMEL_FOLDER_HAS_BEEN_DELETED;
+
+ class->delete_ (folder);
+
+ camel_folder_unlock (folder);
+
+ /* Delete the references of the folder from the DB.*/
+ full_name = camel_folder_get_full_name (folder);
+ parent_store = camel_folder_get_parent_store (folder);
+ camel_db_delete_folder (parent_store->cdb_w, full_name, NULL);
+
+ service = CAMEL_SERVICE (parent_store);
+ session = camel_service_ref_session (service);
+ if (!session)
+ return;
+
+ signal_closure = g_slice_new0 (SignalClosure);
+ g_weak_ref_init (&signal_closure->folder, folder);
+
+ /* Prioritize ahead of GTK+ redraws. */
+ camel_session_idle_add (
+ session, G_PRIORITY_HIGH_IDLE,
+ folder_emit_deleted_cb,
+ signal_closure,
+ (GDestroyNotify) signal_closure_free);
+
+ g_object_unref (session);
+}
+
+/**
+ * camel_folder_rename:
+ * @folder: a #CamelFolder
+ * @new_name: new name for the folder
+ *
+ * Marks @folder as renamed.
+ *
+ * This also emits the #CamelFolder::renamed signal from an idle source on
+ * the main loop. The idle source's priority is #G_PRIORITY_HIGH_IDLE.
+ *
+ * NOTE: This is an internal function used by camel stores, no locking
+ * is performed on the folder.
+ **/
+void
+camel_folder_rename (CamelFolder *folder,
+ const gchar *new_name)
+{
+ CamelFolderClass *class;
+ CamelStore *parent_store;
+ CamelService *service;
+ CamelSession *session;
+ SignalClosure *signal_closure;
+ gchar *old_name;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (new_name != NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_if_fail (class->rename != NULL);
+
+ old_name = g_strdup (camel_folder_get_full_name (folder));
+
+ class->rename (folder, new_name);
+
+ parent_store = camel_folder_get_parent_store (folder);
+ camel_db_rename_folder (parent_store->cdb_w, old_name, new_name, NULL);
+
+ service = CAMEL_SERVICE (parent_store);
+ session = camel_service_ref_session (service);
+ if (!session)
+ return;
+
+ signal_closure = g_slice_new0 (SignalClosure);
+ g_weak_ref_init (&signal_closure->folder, folder);
+ signal_closure->folder_name = old_name; /* transfer ownership */
+
+ /* Prioritize ahead of GTK+ redraws. */
+ camel_session_idle_add (
+ session, G_PRIORITY_HIGH_IDLE,
+ folder_emit_renamed_cb,
+ signal_closure,
+ (GDestroyNotify) signal_closure_free);
+
+ g_object_unref (session);
+}
+
+/**
+ * camel_folder_changed:
+ * @folder: a #CamelFolder
+ * @changes: change information for @folder
+ *
+ * Emits the #CamelFolder::changed signal from an idle source on the
+ * main loop. The idle source's priority is #G_PRIORITY_LOW.
+ *
+ * Since: 2.32
+ **/
+void
+camel_folder_changed (CamelFolder *folder,
+ CamelFolderChangeInfo *changes)
+{
+ CamelFolderChangeInfo *pending_changes;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (changes != NULL);
+
+ if (camel_folder_is_frozen (folder)) {
+ /* folder_changed() will catch this case and pile
+ * the changes into folder->changed_frozen */
+ g_signal_emit (folder, signals[CHANGED], 0, changes);
+ return;
+ }
+
+ /* If a "changed" signal has already been scheduled but not yet
+ * emitted, just append our changes to the pending changes, and
+ * skip scheduling our own "changed" signal. This helps to cut
+ * down on the frequency of signal emissions so virtual folders
+ * won't have to work so hard. */
+
+ g_mutex_lock (&folder->priv->change_lock);
+
+ pending_changes = folder->priv->pending_changes;
+
+ if (pending_changes == NULL) {
+ CamelStore *parent_store;
+ CamelService *service;
+ CamelSession *session;
+ SignalClosure *signal_closure;
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ service = CAMEL_SERVICE (parent_store);
+ session = camel_service_ref_session (service);
+
+ if (session) {
+ pending_changes = camel_folder_change_info_new ();
+ folder->priv->pending_changes = pending_changes;
+
+ signal_closure = g_slice_new0 (SignalClosure);
+ g_weak_ref_init (&signal_closure->folder, folder);
+
+ camel_session_idle_add (
+ session, G_PRIORITY_LOW,
+ folder_emit_changed_cb,
+ signal_closure,
+ (GDestroyNotify) signal_closure_free);
+
+ g_object_unref (session);
+ }
+ }
+
+ camel_folder_change_info_cat (pending_changes, changes);
+
+ g_mutex_unlock (&folder->priv->change_lock);
+}
+
+/**
+ * camel_folder_freeze:
+ * @folder: a #CamelFolder
+ *
+ * Freezes the folder so that a series of operation can be performed
+ * without "folder_changed" signals being emitted. When the folder is
+ * later thawed with camel_folder_thaw(), the suppressed signals will
+ * be emitted.
+ **/
+void
+camel_folder_freeze (CamelFolder *folder)
+{
+ CamelFolderClass *class;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_if_fail (class->freeze != NULL);
+
+ class->freeze (folder);
+}
+
+/**
+ * camel_folder_thaw:
+ * @folder: a #CamelFolder
+ *
+ * Thaws the folder and emits any pending folder_changed
+ * signals.
+ **/
+void
+camel_folder_thaw (CamelFolder *folder)
+{
+ CamelFolderClass *class;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (folder->priv->frozen != 0);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_if_fail (class->thaw != NULL);
+
+ class->thaw (folder);
+}
+
+/**
+ * camel_folder_is_frozen:
+ * @folder: a #CamelFolder
+ *
+ * Returns: whether or not the folder is frozen
+ **/
+gboolean
+camel_folder_is_frozen (CamelFolder *folder)
+{
+ CamelFolderClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->is_frozen != NULL, FALSE);
+
+ return class->is_frozen (folder);
+}
+
+/**
+ * camel_folder_get_frozen_count:
+ * @folder: a #CamelFolder
+ *
+ * Since: 2.32
+ **/
+gint
+camel_folder_get_frozen_count (CamelFolder *folder)
+{
+ /* FIXME This function shouldn't be needed,
+ * but it's used in CamelVeeFolder */
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), 0);
+
+ return folder->priv->frozen;
+}
+
+/**
+ * camel_folder_quota_info_new:
+ * @name: Name of the quota.
+ * @used: Current usage of the quota.
+ * @total: Total available size of the quota.
+ *
+ * Returns: newly allocated #CamelFolderQuotaInfo structure with
+ * initialized values based on the parameters, with next member set to NULL.
+ *
+ * Since: 2.24
+ **/
+CamelFolderQuotaInfo *
+camel_folder_quota_info_new (const gchar *name,
+ guint64 used,
+ guint64 total)
+{
+ CamelFolderQuotaInfo *info;
+
+ info = g_malloc0 (sizeof (CamelFolderQuotaInfo));
+ info->name = g_strdup (name);
+ info->used = used;
+ info->total = total;
+ info->next = NULL;
+
+ return info;
+}
+
+/**
+ * camel_folder_quota_info_clone:
+ * @info: a #CamelFolderQuotaInfo object to clone.
+ *
+ * Makes a copy of the given info and all next-s.
+ *
+ * Since: 2.24
+ **/
+CamelFolderQuotaInfo *
+camel_folder_quota_info_clone (const CamelFolderQuotaInfo *info)
+{
+ CamelFolderQuotaInfo *clone = NULL, *last = NULL;
+ const CamelFolderQuotaInfo *iter;
+
+ for (iter = info; iter != NULL; iter = iter->next) {
+ CamelFolderQuotaInfo *n = camel_folder_quota_info_new (iter->name, iter->used, iter->total);
+
+ if (last)
+ last->next = n;
+ else
+ clone = n;
+
+ last = n;
+ }
+
+ return clone;
+}
+
+/**
+ * camel_folder_quota_info_free:
+ * @info: a #CamelFolderQuotaInfo object to free.
+ *
+ * Frees this and all next objects.
+ *
+ * Since: 2.24
+ **/
+void
+camel_folder_quota_info_free (CamelFolderQuotaInfo *info)
+{
+ CamelFolderQuotaInfo *next = info;
+
+ while (next) {
+ info = next;
+ next = next->next;
+
+ g_free (info->name);
+ g_free (info);
+ }
+}
+
+/**
+ * camel_folder_free_nop:
+ * @folder: a #CamelFolder
+ * @array: an array of uids or #CamelMessageInfo
+ *
+ * "Frees" the provided array by doing nothing. Used by #CamelFolder
+ * subclasses as an implementation for free_uids, or free_summary when
+ * the returned array is "static" information and should not be freed.
+ **/
+void
+camel_folder_free_nop (CamelFolder *folder,
+ GPtrArray *array)
+{
+ ;
+}
+
+/**
+ * camel_folder_free_shallow:
+ * @folder: a #CamelFolder
+ * @array: (element-type utf8): an array of uids or #CamelMessageInfo
+ *
+ * Frees the provided array but not its contents. Used by #CamelFolder
+ * subclasses as an implementation for free_uids or free_summary when
+ * the returned array needs to be freed but its contents come from
+ * "static" information.
+ **/
+void
+camel_folder_free_shallow (CamelFolder *folder,
+ GPtrArray *array)
+{
+ g_ptr_array_free (array, TRUE);
+}
+
+/**
+ * camel_folder_free_deep:
+ * @folder: a #CamelFolder
+ * @array: (element-type utf8): an array of uids
+ *
+ * Frees the provided array and its contents. Used by #CamelFolder
+ * subclasses as an implementation for free_uids when the provided
+ * information was created explicitly by the corresponding get_ call.
+ **/
+void
+camel_folder_free_deep (CamelFolder *folder,
+ GPtrArray *array)
+{
+ gint i;
+
+ g_return_if_fail (array != NULL);
+
+ for (i = 0; i < array->len; i++)
+ g_free (array->pdata[i]);
+ g_ptr_array_free (array, TRUE);
+}
+
+/**
+ * camel_folder_lock:
+ * @folder: a #CamelFolder
+ *
+ * Locks @folder. Unlock it with camel_folder_unlock().
+ *
+ * Since: 2.32
+ **/
+void
+camel_folder_lock (CamelFolder *folder)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ if (folder->priv->skip_folder_lock == FALSE)
+ g_rec_mutex_lock (&folder->priv->lock);
+}
+
+/**
+ * camel_folder_unlock:
+ * @folder: a #CamelFolder
+ *
+ * Unlocks @folder, previously locked with camel_folder_lock().
+ *
+ * Since: 2.32
+ **/
+void
+camel_folder_unlock (CamelFolder *folder)
+{
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ if (folder->priv->skip_folder_lock == FALSE)
+ g_rec_mutex_unlock (&folder->priv->lock);
+}
+
+/**
+ * camel_folder_append_message_sync:
+ * @folder: a #CamelFolder
+ * @message: a #CamelMimeMessage
+ * @info: a #CamelMessageInfo with additional flags/etc to set on the
+ * new message, or %NULL
+ * @appended_uid: if non-%NULL, the UID of the appended message will
+ * be returned here, if it is known
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Appends @message to @folder. Only the flag and tag data from @info
+ * are used. If @info is %NULL, no flags or tags will be set.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_folder_append_message_sync (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->append_message_sync != NULL, FALSE);
+
+ /* Need to connect the service before we can append. */
+ success = folder_maybe_connect_sync (folder, cancellable, error);
+ if (!success)
+ return FALSE;
+
+ camel_folder_lock (folder);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ camel_folder_unlock (folder);
+ return FALSE;
+ }
+
+ success = class->append_message_sync (
+ folder, message, info, appended_uid, cancellable, error);
+ CAMEL_CHECK_GERROR (folder, append_message_sync, success, error);
+
+ camel_folder_unlock (folder);
+
+ return success;
+}
+
+/* Helper for camel_folder_append_message() */
+static void
+folder_append_message_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_folder_append_message_sync (
+ CAMEL_FOLDER (source_object),
+ async_context->message,
+ async_context->info,
+ &async_context->message_uid,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_folder_append_message:
+ * @folder: a #CamelFolder
+ * @message: a #CamelMimeMessage
+ * @info: a #CamelMessageInfo with additional flags/etc to set on the
+ * new message, or %NULL
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Appends @message to @folder asynchronously. Only the flag and tag data
+ * from @info are used. If @info is %NULL, no flags or tags will be set.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_folder_append_message_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_folder_append_message (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->message = g_object_ref (message);
+ async_context->info = camel_message_info_ref (info);
+
+ task = g_task_new (folder, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_folder_append_message);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, folder_append_message_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_folder_append_message_finish:
+ * @folder: a #CamelFolder
+ * @result: a #GAsyncResult
+ * @appended_uid: if non-%NULL, the UID of the appended message will
+ * be returned here, if it is known
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_folder_append_message_finish().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_folder_append_message_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ gchar **appended_uid,
+ GError **error)
+{
+ AsyncContext *async_context;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, folder), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_folder_append_message), FALSE);
+
+ async_context = g_task_get_task_data (G_TASK (result));
+
+ if (!g_task_had_error (G_TASK (result))) {
+ if (appended_uid != NULL) {
+ *appended_uid = async_context->message_uid;
+ async_context->message_uid = NULL;
+ }
+ }
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static gboolean
+camel_folder_maybe_run_db_maintenance (CamelFolder *folder,
+ GError **error)
+{
+ return camel_store_maybe_run_db_maintenance (camel_folder_get_parent_store (folder), error);
+}
+
+/**
+ * camel_folder_expunge_sync:
+ * @folder: a #CamelFolder
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Deletes messages which have been marked as "DELETED".
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_folder_expunge_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->expunge_sync != NULL, FALSE);
+
+ /* Need to connect the service before we can expunge. */
+ success = folder_maybe_connect_sync (folder, cancellable, error);
+ if (!success)
+ return FALSE;
+
+ camel_folder_lock (folder);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ camel_folder_unlock (folder);
+ return FALSE;
+ }
+
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ camel_operation_push_message (cancellable, _("Expunging folder '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
+ camel_folder_get_full_name (folder));
+
+ if (!(folder->folder_flags & CAMEL_FOLDER_HAS_BEEN_DELETED)) {
+ success = class->expunge_sync (folder, cancellable, error);
+ CAMEL_CHECK_GERROR (folder, expunge_sync, success, error);
+
+ if (success)
+ success = camel_folder_maybe_run_db_maintenance (folder, error);
+ }
+
+ camel_operation_pop_message (cancellable);
+
+ camel_folder_unlock (folder);
+
+ return success;
+}
+
+/* Helper for camel_folder_expunge() */
+static void
+folder_expunge_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ GError *local_error = NULL;
+
+ success = camel_folder_expunge_sync (
+ CAMEL_FOLDER (source_object),
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_folder_expunge:
+ * @folder: a #CamelFolder
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously deletes messages which have been marked as "DELETED".
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_folder_expunge_finish() to get the result of the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_folder_expunge (CamelFolder *folder,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ task = g_task_new (folder, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_folder_expunge);
+ g_task_set_priority (task, io_priority);
+
+ g_task_run_in_thread (task, folder_expunge_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_folder_expunge_finish:
+ * @folder: a #CamelFolder
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_folder_expunge().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_folder_expunge_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, folder), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_folder_expunge), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_folder_get_message_sync:
+ * @folder: a #CamelFolder
+ * @message_uid: the message UID
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets the message corresponding to @message_uid from @folder.
+ *
+ * Returns: (transfer none): a #CamelMimeMessage corresponding to the requested UID
+ *
+ * Since: 3.0
+ **/
+CamelMimeMessage *
+camel_folder_get_message_sync (CamelFolder *folder,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderClass *class;
+ CamelMimeMessage *message = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+ g_return_val_if_fail (message_uid != NULL, NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->get_message_sync != NULL, NULL);
+
+ camel_operation_push_message (
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ cancellable, _("Retrieving message '%s' in '%s : %s'"),
+ message_uid, camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
+ camel_folder_get_full_name (folder));
+
+ if (class->get_message_cached) {
+ /* Return cached message, if available locally; this should
+ * not do any network I/O, only check if message is already
+ * downloaded and return it quicker, not being blocked by
+ * the folder's lock. Returning NULL is not considered as
+ * an error, it just means that the message is still
+ * to-be-downloaded. */
+ message = class->get_message_cached (
+ folder, message_uid, cancellable);
+ }
+
+ if (message == NULL) {
+ /* Recover from a dropped connection, unless we're offline. */
+ if (!folder_maybe_connect_sync (folder, cancellable, error))
+ return NULL;
+
+ camel_folder_lock (folder);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ camel_folder_unlock (folder);
+ camel_operation_pop_message (cancellable);
+ return NULL;
+ }
+
+ message = class->get_message_sync (
+ folder, message_uid, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ folder, get_message_sync, message != NULL, error);
+
+ camel_folder_unlock (folder);
+ }
+
+ if (message && camel_mime_message_get_source (message) == NULL) {
+ CamelStore *store;
+ const gchar *uid;
+
+ store = camel_folder_get_parent_store (folder);
+ uid = camel_service_get_uid (CAMEL_SERVICE (store));
+
+ camel_mime_message_set_source (message, uid);
+ }
+
+ camel_operation_pop_message (cancellable);
+
+ if (message != NULL && camel_debug_start (":folder")) {
+ printf (
+ "CamelFolder:get_message ('%s', '%s') =\n",
+ camel_folder_get_full_name (folder), message_uid);
+ camel_mime_message_dump (message, FALSE);
+ camel_debug_end ();
+ }
+
+ return message;
+}
+
+/* Helper for camel_folder_get_message() */
+static void
+folder_get_message_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelMimeMessage *message;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ message = camel_folder_get_message_sync (
+ CAMEL_FOLDER (source_object),
+ async_context->message_uid,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_warn_if_fail (message == NULL);
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_pointer (
+ task, message,
+ (GDestroyNotify) g_object_unref);
+ }
+}
+
+/**
+ * camel_folder_get_message:
+ * @folder: a #CamelFolder
+ * @message_uid: the message UID
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously gets the message corresponding to @message_uid from @folder.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_folder_get_message_finish() to get the result of the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_folder_get_message (CamelFolder *folder,
+ const gchar *message_uid,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (message_uid != NULL);
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->message_uid = g_strdup (message_uid);
+
+ task = g_task_new (folder, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_folder_get_message);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, folder_get_message_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_folder_get_message_finish:
+ * @folder: a #CamelFolder
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError or %NULL
+ *
+ * Finishes the operation started with camel_folder_get_message().
+ *
+ * Returns: (transfer none): a #CamelMimeMessage corresponding to the requested UID
+ *
+ * Since: 3.0
+ **/
+CamelMimeMessage *
+camel_folder_get_message_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, folder), NULL);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_folder_get_message), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/**
+ * camel_folder_get_quota_info_sync:
+ * @folder: a #CamelFolder
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets a list of known quotas for @folder. Free the returned
+ * #CamelFolderQuotaInfo struct with camel_folder_quota_info_free().
+ *
+ * If quotas are not supported for @folder, the function returns %NULL
+ * and sets @error to #G_IO_ERROR_NOT_SUPPORTED.
+ *
+ * Returns: a #CamelFolderQuotaInfo, or %NULL
+ *
+ * Since: 3.2
+ **/
+CamelFolderQuotaInfo *
+camel_folder_get_quota_info_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderClass *class;
+ CamelFolderQuotaInfo *quota_info;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->get_quota_info_sync != NULL, NULL);
+
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ camel_operation_push_message (cancellable, _("Retrieving quota information for '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
+ camel_folder_get_full_name (folder));
+
+ quota_info = class->get_quota_info_sync (folder, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ folder, get_quota_info_sync, quota_info != NULL, error);
+
+ camel_operation_pop_message (cancellable);
+
+ return quota_info;
+}
+
+/* Helper for camel_folder_get_quota_info() */
+static void
+folder_get_quota_info_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelFolderQuotaInfo *quota_info;
+ GError *local_error = NULL;
+
+ quota_info = camel_folder_get_quota_info_sync (
+ CAMEL_FOLDER (source_object),
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_warn_if_fail (quota_info == NULL);
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_pointer (
+ task, quota_info,
+ (GDestroyNotify) camel_folder_quota_info_free);
+ }
+}
+
+/**
+ * camel_folder_get_quota_info:
+ * @folder: a #CamelFolder
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously gets a list of known quotas for @folder.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_folder_get_quota_info_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.2
+ **/
+void
+camel_folder_get_quota_info (CamelFolder *folder,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ task = g_task_new (folder, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_folder_get_quota_info);
+ g_task_set_priority (task, io_priority);
+
+ g_task_run_in_thread (task, folder_get_quota_info_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_folder_get_quota_info_finish:
+ * @folder: a #CamelFolder
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError or %NULL
+ *
+ * Finishes the operation started with camel_folder_get_quota_info().
+ * Free the returned #CamelFolderQuotaInfo struct with
+ * camel_folder_quota_info_free().
+ *
+ * If quotas are not supported for @folder, the function returns %NULL
+ * and sets @error to #G_IO_ERROR_NOT_SUPPORTED.
+ *
+ * Returns: a #CamelFolderQuotaInfo, or %NULL
+ *
+ * Since: 3.2
+ **/
+CamelFolderQuotaInfo *
+camel_folder_get_quota_info_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, folder), NULL);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_folder_get_quota_info), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/**
+ * camel_folder_purge_message_cache_sync:
+ * @folder: a #CamelFolder
+ * @start_uid: the start message UID
+ * @end_uid: the end message UID
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Delete the local cache of all messages between these uids.
+ *
+ * Returns: %TRUE on success, %FALSE on failure
+ *
+ * Since: 3.4
+ **/
+gboolean
+camel_folder_purge_message_cache_sync (CamelFolder *folder,
+ gchar *start_uid,
+ gchar *end_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderClass *class;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+
+ /* Some backends that wont support mobile
+ * mode, won't have this api implemented. */
+ if (class->purge_message_cache_sync == NULL)
+ return FALSE;
+
+ camel_folder_lock (folder);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ camel_folder_unlock (folder);
+ return FALSE;
+ }
+
+ success = class->purge_message_cache_sync (
+ folder, start_uid, end_uid, cancellable, error);
+ CAMEL_CHECK_GERROR (folder, purge_message_cache_sync, success, error);
+
+ camel_folder_unlock (folder);
+
+ return success;
+}
+
+/* Helper for camel_purge_message_cache() */
+static void
+folder_purge_message_cache_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_folder_purge_message_cache_sync (
+ CAMEL_FOLDER (source_object),
+ async_context->start_uid,
+ async_context->end_uid,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_folder_purge_message_cache:
+ * @folder: a #CamelFolder
+ * @start_uid: the start message UID
+ * @end_uid: the end message UID
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Delete the local cache of all messages between these uids.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_folder_purge_message_cache_finish() to get the result of the
+ * operation.
+ *
+ * Since: 3.4
+ **/
+void
+camel_folder_purge_message_cache (CamelFolder *folder,
+ gchar *start_uid,
+ gchar *end_uid,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->start_uid = g_strdup (start_uid);
+ async_context->end_uid = g_strdup (end_uid);
+
+ task = g_task_new (folder, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_folder_purge_message_cache);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, folder_purge_message_cache_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_folder_purge_message_cache_finish:
+ * @folder: a #CamelFolder
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_folder_purge_message_cache().
+ *
+ * Returns: %TRUE on success, %FALSE on failure
+ *
+ * Since: 3.4
+ **/
+gboolean
+camel_folder_purge_message_cache_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, folder), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_folder_purge_message_cache), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_folder_refresh_info_sync:
+ * @folder: a #CamelFolder
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronizes a folder's summary with its backing store.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_folder_refresh_info_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->refresh_info_sync != NULL, FALSE);
+
+ /* Need to connect the service before we can refresh. */
+ success = folder_maybe_connect_sync (folder, cancellable, error);
+ if (!success)
+ return FALSE;
+
+ camel_folder_lock (folder);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ camel_folder_unlock (folder);
+ return FALSE;
+ }
+
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ camel_operation_push_message (cancellable, _("Refreshing folder '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
+ camel_folder_get_full_name (folder));
+
+ success = class->refresh_info_sync (folder, cancellable, error);
+ CAMEL_CHECK_GERROR (folder, refresh_info_sync, success, error);
+
+ camel_operation_pop_message (cancellable);
+
+ camel_folder_unlock (folder);
+
+ return success;
+}
+
+/* Helper for camel_folder_refresh_info() */
+static void
+folder_refresh_info_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ GError *local_error = NULL;
+
+ success = camel_folder_refresh_info_sync (
+ CAMEL_FOLDER (source_object),
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_folder_refresh_info:
+ * @folder: a #CamelFolder
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously synchronizes a folder's summary with its backing store.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_folder_refresh_info_finish() to get the result of the operation.
+ *
+ * Since: 3.2
+ **/
+void
+camel_folder_refresh_info (CamelFolder *folder,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ task = g_task_new (folder, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_folder_refresh_info);
+ g_task_set_priority (task, io_priority);
+
+ g_task_run_in_thread (task, folder_refresh_info_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_folder_refresh_info_finish:
+ * @folder: a #CamelFolder
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_folder_refresh_info().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_folder_refresh_info_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, folder), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_folder_refresh_info), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_folder_synchronize_sync:
+ * @folder: a #CamelFolder
+ * @expunge: whether to expunge after synchronizing
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronizes any changes that have been made to @folder to its
+ * backing store, optionally expunging deleted messages as well.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_folder_synchronize_sync (CamelFolder *folder,
+ gboolean expunge,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->synchronize_sync != NULL, FALSE);
+
+ /* Need to connect the service before we can synchronize. */
+ success = folder_maybe_connect_sync (folder, cancellable, error);
+ if (!success)
+ return FALSE;
+
+ camel_folder_lock (folder);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ camel_folder_unlock (folder);
+ return FALSE;
+ }
+
+ if (!(folder->folder_flags & CAMEL_FOLDER_HAS_BEEN_DELETED)) {
+ success = class->synchronize_sync (
+ folder, expunge, cancellable, error);
+ CAMEL_CHECK_GERROR (folder, synchronize_sync, success, error);
+
+ if (success && expunge)
+ success = camel_folder_maybe_run_db_maintenance (folder, error);
+ }
+
+ camel_folder_unlock (folder);
+
+ return success;
+}
+
+/* Helper for camel_folder_synchronize() */
+static void
+folder_synchronize_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_folder_synchronize_sync (
+ CAMEL_FOLDER (source_object),
+ async_context->expunge,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_folder_synchronize:
+ * @folder: a #CamelFolder
+ * @expunge: whether to expunge after synchronizing
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Synchronizes any changes that have been made to @folder to its backing
+ * store asynchronously, optionally expunging deleted messages as well.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_folder_synchronize_finish() to get the result of the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_folder_synchronize (CamelFolder *folder,
+ gboolean expunge,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->expunge = expunge;
+
+ task = g_task_new (folder, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_folder_synchronize);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, folder_synchronize_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_folder_synchronize_finish:
+ * @folder: a #CamelFolder
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_folder_synchronize().
+ *
+ * Returns: %TRUE on sucess, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_folder_synchronize_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, folder), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_folder_synchronize), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_folder_synchronize_message_sync:
+ * @folder: a #CamelFolder
+ * @message_uid: a message UID
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Ensure that a message identified by @message_uid has been synchronized in
+ * @folder so that calling camel_folder_get_message() on it later will work
+ * in offline mode.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_folder_synchronize_message_sync (CamelFolder *folder,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderClass *class;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (message_uid != NULL, FALSE);
+
+ class = CAMEL_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->get_message_sync != NULL, FALSE);
+
+ camel_folder_lock (folder);
+
+ /* Check for cancellation after locking. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ camel_folder_unlock (folder);
+ return FALSE;
+ }
+
+ /* Use the sync_message method if the class implements it. */
+ if (class->synchronize_message_sync != NULL) {
+ success = class->synchronize_message_sync (
+ folder, message_uid, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ folder, synchronize_message_sync, success, error);
+ } else {
+ CamelMimeMessage *message;
+
+ message = class->get_message_sync (
+ folder, message_uid, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ folder, get_message_sync, message != NULL, error);
+
+ if (message != NULL) {
+ g_object_unref (message);
+ success = TRUE;
+ }
+ }
+
+ camel_folder_unlock (folder);
+
+ return success;
+}
+
+/* Helper for camel_folder_synchronize_message() */
+static void
+folder_synchronize_message_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_folder_synchronize_message_sync (
+ CAMEL_FOLDER (source_object),
+ async_context->message_uid,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_folder_synchronize_message:
+ * @folder: a #CamelFolder
+ * @message_uid: a message UID
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously ensure that a message identified by @message_uid has been
+ * synchronized in @folder so that calling camel_folder_get_message() on it
+ * later will work in offline mode.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_folder_synchronize_message_finish() to get the result of the
+ * operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_folder_synchronize_message (CamelFolder *folder,
+ const gchar *message_uid,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (message_uid != NULL);
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->message_uid = g_strdup (message_uid);
+
+ task = g_task_new (folder, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_folder_synchronize_message);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, folder_synchronize_message_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_folder_synchronize_message_finish:
+ * @folder: a #CamelFolder
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_folder_synchronize_message().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_folder_synchronize_message_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, folder), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_folder_synchronize_message), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_folder_transfer_messages_to_sync:
+ * @source: the source #CamelFolder
+ * @message_uids: (element-type utf8): message UIDs in @source
+ * @destination: the destination #CamelFolder
+ * @delete_originals: whether or not to delete the original messages
+ * @transferred_uids: (element-type utf8) (out): if non-%NULL, the UIDs of the
+ * resulting messages in @destination will be stored here,
+ * if known.
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Copies or moves messages from one folder to another. If the
+ * @source and @destination folders have the same parent_store, this
+ * may be more efficient than using camel_folder_append_message_sync().
+ *
+ * Returns: %TRUE on success, %FALSE on failure
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_folder_transfer_messages_to_sync (CamelFolder *source,
+ GPtrArray *message_uids,
+ CamelFolder *destination,
+ gboolean delete_originals,
+ GPtrArray **transferred_uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderClass *class;
+ CamelStore *source_store;
+ CamelStore *destination_store;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (source), FALSE);
+ g_return_val_if_fail (CAMEL_IS_FOLDER (destination), FALSE);
+ g_return_val_if_fail (message_uids != NULL, FALSE);
+
+ if (source == destination || message_uids->len == 0)
+ return TRUE;
+
+ source_store = camel_folder_get_parent_store (source);
+ destination_store = camel_folder_get_parent_store (destination);
+
+ /* Need to connect both services before we can transfer. */
+ success = folder_maybe_connect_sync (destination, cancellable, error);
+ if (success && source_store != destination_store)
+ success = folder_maybe_connect_sync (source, cancellable, error);
+ if (!success)
+ return FALSE;
+
+ if (source_store == destination_store) {
+ /* If either folder is a vtrash, we need to use the
+ * vtrash transfer method. */
+ if (CAMEL_IS_VTRASH_FOLDER (destination))
+ class = CAMEL_FOLDER_GET_CLASS (destination);
+ else
+ class = CAMEL_FOLDER_GET_CLASS (source);
+ success = class->transfer_messages_to_sync (
+ source, message_uids, destination, delete_originals,
+ transferred_uids, cancellable, error);
+ } else {
+ success = folder_transfer_messages_to_sync (
+ source, message_uids, destination, delete_originals,
+ transferred_uids, cancellable, error);
+ }
+
+ return success;
+}
+
+/* Helper for folder_transfer_messages_to_thread() */
+static void
+folder_transfer_messages_to_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_folder_transfer_messages_to_sync (
+ CAMEL_FOLDER (source_object),
+ async_context->message_uids,
+ async_context->destination,
+ async_context->delete_originals,
+ &async_context->transferred_uids,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_folder_transfer_messages_to:
+ * @source: the source #CamelFolder
+ * @message_uids: (element-type utf8): message UIDs in @source
+ * @destination: the destination #CamelFolder
+ * @delete_originals: whether or not to delete the original messages
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously copies or moves messages from one folder to another.
+ * If the @source or @destination folders have the same parent store,
+ * this may be more efficient than using camel_folder_append_message().
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_folder_transfer_messages_to_finish() to get the result of the
+ * operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_folder_transfer_messages_to (CamelFolder *source,
+ GPtrArray *message_uids,
+ CamelFolder *destination,
+ gboolean delete_originals,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+ guint ii;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (source));
+ g_return_if_fail (CAMEL_IS_FOLDER (destination));
+ g_return_if_fail (message_uids != NULL);
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->message_uids = g_ptr_array_new ();
+ async_context->destination = g_object_ref (destination);
+ async_context->delete_originals = delete_originals;
+
+ for (ii = 0; ii < message_uids->len; ii++)
+ g_ptr_array_add (
+ async_context->message_uids,
+ g_strdup (message_uids->pdata[ii]));
+
+ task = g_task_new (source, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_folder_transfer_messages_to);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, folder_transfer_messages_to_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_folder_transfer_messages_to_finish:
+ * @source: a #CamelFolder
+ * @result: a #GAsyncResult
+ * @transferred_uids: (element-type utf8) (out): if non-%NULL, the UIDs of the
+ * resulting messages in @destination will be stored here,
+ * if known.
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_folder_transfer_messages_to().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_folder_transfer_messages_to_finish (CamelFolder *source,
+ GAsyncResult *result,
+ GPtrArray **transferred_uids,
+ GError **error)
+{
+ AsyncContext *async_context;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (source), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, source), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_folder_transfer_messages_to), FALSE);
+
+ async_context = g_task_get_task_data (G_TASK (result));
+
+ if (!g_task_had_error (G_TASK (result))) {
+ if (transferred_uids != NULL) {
+ *transferred_uids = async_context->transferred_uids;
+ async_context->transferred_uids = NULL;
+ }
+ }
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_folder_prepare_content_refresh:
+ * @folder: a #CamelFolder
+ *
+ * Lets the @folder know that it should refresh its content
+ * the next time from fresh. This is useful for remote accounts,
+ * to fully re-check the folder content against the server.
+ *
+ * Since: 3.22
+ **/
+void
+camel_folder_prepare_content_refresh (CamelFolder *folder)
+{
+ CamelFolderClass *klass;
+
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ klass = CAMEL_FOLDER_GET_CLASS (folder);
+
+ if (klass->prepare_content_refresh)
+ klass->prepare_content_refresh (folder);
+}
+
+/**
+ * camel_folder_change_info_new:
+ *
+ * Create a new folder change info structure.
+ *
+ * Change info structures are not MT-SAFE and must be
+ * locked for exclusive access externally.
+ *
+ * Returns: a new #CamelFolderChangeInfo
+ **/
+CamelFolderChangeInfo *
+camel_folder_change_info_new (void)
+{
+ CamelFolderChangeInfo *info;
+
+ info = g_slice_new (CamelFolderChangeInfo);
+ info->uid_added = g_ptr_array_new ();
+ info->uid_removed = g_ptr_array_new ();
+ info->uid_changed = g_ptr_array_new ();
+ info->uid_recent = g_ptr_array_new ();
+ info->priv = g_slice_new (struct _CamelFolderChangeInfoPrivate);
+ info->priv->uid_stored = g_hash_table_new (g_str_hash, g_str_equal);
+ info->priv->uid_source = NULL;
+ info->priv->uid_filter = g_ptr_array_new ();
+ info->priv->uid_pool = camel_mempool_new (512, 256, CAMEL_MEMPOOL_ALIGN_BYTE);
+
+ return info;
+}
+
+/**
+ * camel_folder_change_info_add_source:
+ * @info: a #CamelFolderChangeInfo
+ * @uid: a uid
+ *
+ * Add a source uid for generating a changeset.
+ **/
+void
+camel_folder_change_info_add_source (CamelFolderChangeInfo *info,
+ const gchar *uid)
+{
+ struct _CamelFolderChangeInfoPrivate *p;
+
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (uid != NULL);
+
+ p = info->priv;
+
+ if (p->uid_source == NULL)
+ p->uid_source = g_hash_table_new (g_str_hash, g_str_equal);
+
+ if (g_hash_table_lookup (p->uid_source, uid) == NULL)
+ g_hash_table_insert (p->uid_source, camel_mempool_strdup (p->uid_pool, uid), GINT_TO_POINTER (1));
+}
+
+/**
+ * camel_folder_change_info_add_source_list:
+ * @info: a #CamelFolderChangeInfo
+ * @list: (element-type utf8) (transfer container): a list of uids
+ *
+ * Add a list of source uid's for generating a changeset.
+ **/
+void
+camel_folder_change_info_add_source_list (CamelFolderChangeInfo *info,
+ const GPtrArray *list)
+{
+ struct _CamelFolderChangeInfoPrivate *p;
+ gint i;
+
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (list != NULL);
+
+ p = info->priv;
+
+ if (p->uid_source == NULL)
+ p->uid_source = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (i = 0; i < list->len; i++) {
+ gchar *uid = list->pdata[i];
+
+ if (g_hash_table_lookup (p->uid_source, uid) == NULL)
+ g_hash_table_insert (p->uid_source, camel_mempool_strdup (p->uid_pool, uid), GINT_TO_POINTER (1));
+ }
+}
+
+/**
+ * camel_folder_change_info_add_update:
+ * @info: a #CamelFolderChangeInfo
+ * @uid: a uid
+ *
+ * Add a uid from the updated list, used to generate a changeset diff.
+ **/
+void
+camel_folder_change_info_add_update (CamelFolderChangeInfo *info,
+ const gchar *uid)
+{
+ struct _CamelFolderChangeInfoPrivate *p;
+ gchar *key;
+ gint value;
+
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (uid != NULL);
+
+ p = info->priv;
+
+ if (p->uid_source == NULL) {
+ camel_folder_change_info_add_uid (info, uid);
+ return;
+ }
+
+ if (g_hash_table_lookup_extended (p->uid_source, uid, (gpointer) &key, (gpointer) &value)) {
+ g_hash_table_remove (p->uid_source, key);
+ } else {
+ camel_folder_change_info_add_uid (info, uid);
+ }
+}
+
+/**
+ * camel_folder_change_info_add_update_list:
+ * @info: a #CamelFolderChangeInfo
+ * @list: (element-type utf8) (transfer container): a list of uids
+ *
+ * Add a list of uid's from the updated list.
+ **/
+void
+camel_folder_change_info_add_update_list (CamelFolderChangeInfo *info,
+ const GPtrArray *list)
+{
+ gint i;
+
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (list != NULL);
+
+ for (i = 0; i < list->len; i++)
+ camel_folder_change_info_add_update (info, list->pdata[i]);
+}
+
+static void
+change_info_remove (gchar *key,
+ gpointer value,
+ CamelFolderChangeInfo *info)
+{
+ struct _CamelFolderChangeInfoPrivate *p = info->priv;
+ GPtrArray *olduids;
+ gchar *olduid;
+
+ if (g_hash_table_lookup_extended (p->uid_stored, key, (gpointer) &olduid, (gpointer) &olduids)) {
+ /* if it was added/changed them removed, then remove it */
+ if (olduids != info->uid_removed) {
+ g_ptr_array_remove_fast (olduids, olduid);
+ g_ptr_array_add (info->uid_removed, olduid);
+ g_hash_table_insert (p->uid_stored, olduid, info->uid_removed);
+ }
+ return;
+ }
+
+ /* we dont need to copy this, as they've already been copied into our pool */
+ g_ptr_array_add (info->uid_removed, key);
+ g_hash_table_insert (p->uid_stored, key, info->uid_removed);
+}
+
+/**
+ * camel_folder_change_info_build_diff:
+ * @info: a #CamelFolderChangeInfo
+ *
+ * Compare the source uid set to the updated uid set and generate the
+ * differences into the added and removed lists.
+ **/
+void
+camel_folder_change_info_build_diff (CamelFolderChangeInfo *info)
+{
+ struct _CamelFolderChangeInfoPrivate *p;
+
+ g_return_if_fail (info != NULL);
+
+ p = info->priv;
+
+ if (p->uid_source) {
+ g_hash_table_foreach (p->uid_source, (GHFunc) change_info_remove, info);
+ g_hash_table_destroy (p->uid_source);
+ p->uid_source = NULL;
+ }
+}
+
+static void
+change_info_recent_uid (CamelFolderChangeInfo *info,
+ const gchar *uid)
+{
+ struct _CamelFolderChangeInfoPrivate *p;
+ GPtrArray *olduids;
+ gchar *olduid;
+
+ p = info->priv;
+
+ /* always add to recent, but dont let anyone else know */
+ if (!g_hash_table_lookup_extended (p->uid_stored, uid, (gpointer *) &olduid, (gpointer *) &olduids)) {
+ olduid = camel_mempool_strdup (p->uid_pool, uid);
+ }
+ g_ptr_array_add (info->uid_recent, olduid);
+}
+
+static void
+change_info_filter_uid (CamelFolderChangeInfo *info,
+ const gchar *uid)
+{
+ struct _CamelFolderChangeInfoPrivate *p;
+ GPtrArray *olduids;
+ gchar *olduid;
+
+ p = info->priv;
+
+ /* always add to filter, but dont let anyone else know */
+ if (!g_hash_table_lookup_extended (p->uid_stored, uid, (gpointer *) &olduid, (gpointer *) &olduids)) {
+ olduid = camel_mempool_strdup (p->uid_pool, uid);
+ }
+ g_ptr_array_add (p->uid_filter, olduid);
+}
+
+static void
+change_info_cat (CamelFolderChangeInfo *info,
+ GPtrArray *source,
+ void (*add)(CamelFolderChangeInfo *info,
+ const gchar *uid))
+{
+ gint i;
+
+ for (i = 0; i < source->len; i++)
+ add (info, source->pdata[i]);
+}
+
+/**
+ * camel_folder_change_info_cat:
+ * @info: a #CamelFolderChangeInfo to append to
+ * @src: a #CamelFolderChangeInfo to append from
+ *
+ * Concatenate one change info onto antoher. Can be used to copy them
+ * too.
+ **/
+void
+camel_folder_change_info_cat (CamelFolderChangeInfo *info,
+ CamelFolderChangeInfo *source)
+{
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (source != NULL);
+
+ change_info_cat (info, source->uid_added, camel_folder_change_info_add_uid);
+ change_info_cat (info, source->uid_removed, camel_folder_change_info_remove_uid);
+ change_info_cat (info, source->uid_changed, camel_folder_change_info_change_uid);
+ change_info_cat (info, source->uid_recent, change_info_recent_uid);
+ change_info_cat (info, source->priv->uid_filter, change_info_filter_uid);
+}
+
+/**
+ * camel_folder_change_info_add_uid:
+ * @info: a #CamelFolderChangeInfo
+ * @uid: a uid
+ *
+ * Add a new uid to the changeinfo.
+ **/
+void
+camel_folder_change_info_add_uid (CamelFolderChangeInfo *info,
+ const gchar *uid)
+{
+ struct _CamelFolderChangeInfoPrivate *p;
+ GPtrArray *olduids;
+ gchar *olduid;
+
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (uid != NULL);
+
+ p = info->priv;
+
+ if (g_hash_table_lookup_extended (p->uid_stored, uid, (gpointer) &olduid, (gpointer) &olduids)) {
+ /* if it was removed then added, promote it to a changed */
+ /* if it was changed then added, leave as changed */
+ if (olduids == info->uid_removed) {
+ g_ptr_array_remove_fast (olduids, olduid);
+ g_ptr_array_add (info->uid_changed, olduid);
+ g_hash_table_insert (p->uid_stored, olduid, info->uid_changed);
+ }
+ return;
+ }
+
+ olduid = camel_mempool_strdup (p->uid_pool, uid);
+ g_ptr_array_add (info->uid_added, olduid);
+ g_hash_table_insert (p->uid_stored, olduid, info->uid_added);
+}
+
+/**
+ * camel_folder_change_info_remove_uid:
+ * @info: a #CamelFolderChangeInfo
+ * @uid: a uid
+ *
+ * Add a uid to the removed uid list.
+ **/
+void
+camel_folder_change_info_remove_uid (CamelFolderChangeInfo *info,
+ const gchar *uid)
+{
+ struct _CamelFolderChangeInfoPrivate *p;
+ GPtrArray *olduids;
+ gchar *olduid;
+
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (uid != NULL);
+
+ p = info->priv;
+
+ if (g_hash_table_lookup_extended (p->uid_stored, uid, (gpointer) &olduid, (gpointer) &olduids)) {
+ /* if it was added/changed them removed, then remove it */
+ if (olduids != info->uid_removed) {
+ g_ptr_array_remove_fast (olduids, olduid);
+ g_ptr_array_add (info->uid_removed, olduid);
+ g_hash_table_insert (p->uid_stored, olduid, info->uid_removed);
+ }
+ return;
+ }
+
+ olduid = camel_mempool_strdup (p->uid_pool, uid);
+ g_ptr_array_add (info->uid_removed, olduid);
+ g_hash_table_insert (p->uid_stored, olduid, info->uid_removed);
+}
+
+/**
+ * camel_folder_change_info_change_uid:
+ * @info: a #CamelFolderChangeInfo
+ * @uid: a uid
+ *
+ * Add a uid to the changed uid list.
+ **/
+void
+camel_folder_change_info_change_uid (CamelFolderChangeInfo *info,
+ const gchar *uid)
+{
+ struct _CamelFolderChangeInfoPrivate *p;
+ GPtrArray *olduids;
+ gchar *olduid;
+
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (uid != NULL);
+
+ p = info->priv;
+
+ if (g_hash_table_lookup_extended (p->uid_stored, uid, (gpointer) &olduid, (gpointer) &olduids)) {
+ /* if we have it already, leave it as that */
+ return;
+ }
+
+ olduid = camel_mempool_strdup (p->uid_pool, uid);
+ g_ptr_array_add (info->uid_changed, olduid);
+ g_hash_table_insert (p->uid_stored, olduid, info->uid_changed);
+}
+
+/**
+ * camel_folder_change_info_recent_uid:
+ * @info: a #CamelFolderChangeInfo
+ * @uid: a uid
+ *
+ * Add a recent uid to the changedinfo.
+ * This will also add the uid to the uid_filter array for potential
+ * filtering
+ **/
+void
+camel_folder_change_info_recent_uid (CamelFolderChangeInfo *info,
+ const gchar *uid)
+{
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (uid != NULL);
+
+ change_info_recent_uid (info, uid);
+ change_info_filter_uid (info, uid);
+}
+
+/**
+ * camel_folder_change_info_changed:
+ * @info: a #CamelFolderChangeInfo
+ *
+ * Gets whether or not there have been any changes.
+ *
+ * Returns: %TRUE if the changeset contains any changes or %FALSE
+ * otherwise
+ **/
+gboolean
+camel_folder_change_info_changed (CamelFolderChangeInfo *info)
+{
+ g_return_val_if_fail (info != NULL, FALSE);
+
+ return (info->uid_added->len || info->uid_removed->len || info->uid_changed->len || info->uid_recent->len);
+}
+
+/**
+ * camel_folder_change_info_clear:
+ * @info: a #CamelFolderChangeInfo
+ *
+ * Empty out the change info; called after changes have been
+ * processed.
+ **/
+void
+camel_folder_change_info_clear (CamelFolderChangeInfo *info)
+{
+ struct _CamelFolderChangeInfoPrivate *p;
+
+ g_return_if_fail (info != NULL);
+
+ p = info->priv;
+
+ g_ptr_array_set_size (info->uid_added, 0);
+ g_ptr_array_set_size (info->uid_removed, 0);
+ g_ptr_array_set_size (info->uid_changed, 0);
+ g_ptr_array_set_size (info->uid_recent, 0);
+ if (p->uid_source) {
+ g_hash_table_destroy (p->uid_source);
+ p->uid_source = NULL;
+ }
+ g_hash_table_destroy (p->uid_stored);
+ p->uid_stored = g_hash_table_new (g_str_hash, g_str_equal);
+ g_ptr_array_set_size (p->uid_filter, 0);
+ camel_mempool_flush (p->uid_pool, TRUE);
+}
+
+/**
+ * camel_folder_change_info_free:
+ * @info: a #CamelFolderChangeInfo
+ *
+ * Free memory associated with the folder change info lists.
+ **/
+void
+camel_folder_change_info_free (CamelFolderChangeInfo *info)
+{
+ struct _CamelFolderChangeInfoPrivate *p;
+
+ g_return_if_fail (info != NULL);
+
+ p = info->priv;
+
+ if (p->uid_source)
+ g_hash_table_destroy (p->uid_source);
+
+ g_hash_table_destroy (p->uid_stored);
+ g_ptr_array_free (p->uid_filter, TRUE);
+ camel_mempool_destroy (p->uid_pool);
+ g_slice_free (struct _CamelFolderChangeInfoPrivate, p);
+
+ g_ptr_array_free (info->uid_added, TRUE);
+ g_ptr_array_free (info->uid_removed, TRUE);
+ g_ptr_array_free (info->uid_changed, TRUE);
+ g_ptr_array_free (info->uid_recent, TRUE);
+ g_slice_free (CamelFolderChangeInfo, info);
+}
diff --git a/src/camel/camel-folder.h b/src/camel/camel-folder.h
new file mode 100644
index 000000000..df2cdbb83
--- /dev/null
+++ b/src/camel/camel-folder.h
@@ -0,0 +1,579 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-folder.h: Abstract class for an email folder
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_FOLDER_H
+#define CAMEL_FOLDER_H
+
+#include <camel/camel-enums.h>
+#include <camel/camel-folder-summary.h>
+#include <camel/camel-object.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_FOLDER \
+ (camel_folder_get_type ())
+#define CAMEL_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_FOLDER, CamelFolder))
+#define CAMEL_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_FOLDER, CamelFolderClass))
+#define CAMEL_IS_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_FOLDER))
+#define CAMEL_IS_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_FOLDER))
+#define CAMEL_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_FOLDER, CamelFolderClass))
+
+/**
+ * CAMEL_FOLDER_ERROR:
+ *
+ * Since: 2.32
+ **/
+#define CAMEL_FOLDER_ERROR \
+ (camel_folder_error_quark ())
+
+G_BEGIN_DECLS
+
+struct _CamelStore;
+
+typedef struct _CamelFolderChangeInfo CamelFolderChangeInfo;
+typedef struct _CamelFolderChangeInfoPrivate CamelFolderChangeInfoPrivate;
+
+typedef struct _CamelFolder CamelFolder;
+typedef struct _CamelFolderClass CamelFolderClass;
+typedef struct _CamelFolderPrivate CamelFolderPrivate;
+
+/**
+ * CamelFolderError:
+ *
+ * Since: 2.32
+ **/
+typedef enum {
+ CAMEL_FOLDER_ERROR_INVALID,
+ CAMEL_FOLDER_ERROR_INVALID_STATE,
+ CAMEL_FOLDER_ERROR_NON_EMPTY,
+ CAMEL_FOLDER_ERROR_NON_UID,
+ CAMEL_FOLDER_ERROR_INSUFFICIENT_PERMISSION,
+ CAMEL_FOLDER_ERROR_INVALID_PATH,
+ CAMEL_FOLDER_ERROR_INVALID_UID,
+ CAMEL_FOLDER_ERROR_SUMMARY_INVALID
+} CamelFolderError;
+
+/**
+ * CamelFetchType:
+ *
+ * Since: 3.4
+ **/
+typedef enum {
+ CAMEL_FETCH_OLD_MESSAGES,
+ CAMEL_FETCH_NEW_MESSAGES
+} CamelFetchType;
+
+struct _CamelFolderChangeInfo {
+ GPtrArray *uid_added;
+ GPtrArray *uid_removed;
+ GPtrArray *uid_changed;
+ GPtrArray *uid_recent;
+
+ CamelFolderChangeInfoPrivate *priv;
+};
+
+typedef struct _CamelFolderQuotaInfo CamelFolderQuotaInfo;
+
+/**
+ * CamelFolderQuotaInfo:
+ *
+ * Since: 2.24
+ **/
+struct _CamelFolderQuotaInfo {
+ gchar *name;
+ guint64 used;
+ guint64 total;
+
+ struct _CamelFolderQuotaInfo *next;
+};
+
+struct _CamelFolder {
+ CamelObject parent;
+ CamelFolderPrivate *priv;
+
+ CamelFolderSummary *summary;
+
+ CamelFolderFlags folder_flags;
+ CamelMessageFlags permanent_flags;
+
+ /* Future ABI expansion */
+ gpointer later[4];
+};
+
+struct _CamelFolderClass {
+ CamelObjectClass parent_class;
+
+ /* Non-Blocking Methods */
+ gint (*get_message_count) (CamelFolder *folder);
+ CamelMessageFlags
+ (*get_permanent_flags) (CamelFolder *folder);
+ CamelMessageFlags
+ (*get_message_flags) (CamelFolder *folder,
+ const gchar *uid);
+ gboolean (*set_message_flags) (CamelFolder *folder,
+ const gchar *uid,
+ CamelMessageFlags flags,
+ CamelMessageFlags set);
+ gboolean (*get_message_user_flag)(CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name);
+ void (*set_message_user_flag)(CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name,
+ gboolean value);
+ const gchar * (*get_message_user_tag) (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name);
+ void (*set_message_user_tag) (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name,
+ const gchar *value);
+ GPtrArray * (*get_uids) (CamelFolder *folder);
+ void (*free_uids) (CamelFolder *folder,
+ GPtrArray *array);
+ gint (*cmp_uids) (CamelFolder *folder,
+ const gchar *uid1,
+ const gchar *uid2);
+ void (*sort_uids) (CamelFolder *folder,
+ GPtrArray *uids);
+ GPtrArray * (*get_summary) (CamelFolder *folder);
+ void (*free_summary) (CamelFolder *folder,
+ GPtrArray *array);
+ gboolean (*has_search_capability)(CamelFolder *folder);
+ GPtrArray * (*search_by_expression) (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error);
+ GPtrArray * (*search_by_uids) (CamelFolder *folder,
+ const gchar *expression,
+ GPtrArray *uids,
+ GCancellable *cancellable,
+ GError **error);
+ void (*search_free) (CamelFolder *folder,
+ GPtrArray *result);
+ CamelMessageInfo *
+ (*get_message_info) (CamelFolder *folder,
+ const gchar *uid);
+ void (*delete_) (CamelFolder *folder);
+ void (*rename) (CamelFolder *folder,
+ const gchar *new_name);
+ void (*freeze) (CamelFolder *folder);
+ void (*thaw) (CamelFolder *folder);
+ gboolean (*is_frozen) (CamelFolder *folder);
+ guint32 (*count_by_expression) (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error);
+ GPtrArray * (*get_uncached_uids) (CamelFolder *folder,
+ GPtrArray *uids,
+ GError **error);
+ gchar * (*get_filename) (CamelFolder *folder,
+ const gchar *uid,
+ GError **error);
+ CamelMimeMessage *
+ (*get_message_cached) (CamelFolder *folder,
+ const gchar *message_uid,
+ GCancellable *cancellable);
+
+ /* Synchronous I/O Methods */
+ gboolean (*append_message_sync) (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*expunge_sync) (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error);
+ CamelMimeMessage *
+ (*get_message_sync) (CamelFolder *folder,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error);
+ CamelFolderQuotaInfo *
+ (*get_quota_info_sync) (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*purge_message_cache_sync)
+ (CamelFolder *folder,
+ gchar *start_uid,
+ gchar *end_uid,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*refresh_info_sync) (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*synchronize_sync) (CamelFolder *folder,
+ gboolean expunge,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*synchronize_message_sync)
+ (CamelFolder *folder,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*transfer_messages_to_sync)
+ (CamelFolder *source,
+ GPtrArray *message_uids,
+ CamelFolder *destination,
+ gboolean delete_originals,
+ GPtrArray **transferred_uids,
+ GCancellable *cancellable,
+ GError **error);
+ void (*prepare_content_refresh)
+ (CamelFolder *folder);
+
+ /* Reserved slots for methods. */
+ gpointer reserved_for_methods[19];
+
+ /* Signals */
+ void (*changed) (CamelFolder *folder,
+ CamelFolderChangeInfo *changes);
+ void (*deleted) (CamelFolder *folder);
+ void (*renamed) (CamelFolder *folder,
+ const gchar *old_name);
+};
+
+GType camel_folder_get_type (void);
+GQuark camel_folder_error_quark (void) G_GNUC_CONST;
+void camel_folder_set_lock_async (CamelFolder *folder,
+ gboolean skip_folder_lock);
+struct _CamelStore *
+ camel_folder_get_parent_store (CamelFolder *folder);
+const gchar * camel_folder_get_full_name (CamelFolder *folder);
+gchar * camel_folder_dup_full_name (CamelFolder *folder);
+void camel_folder_set_full_name (CamelFolder *folder,
+ const gchar *full_name);
+const gchar * camel_folder_get_display_name (CamelFolder *folder);
+gchar * camel_folder_dup_display_name (CamelFolder *folder);
+void camel_folder_set_display_name (CamelFolder *folder,
+ const gchar *display_name);
+const gchar * camel_folder_get_description (CamelFolder *folder);
+gchar * camel_folder_dup_description (CamelFolder *folder);
+void camel_folder_set_description (CamelFolder *folder,
+ const gchar *description);
+CamelMessageFlags
+ camel_folder_get_permanent_flags
+ (CamelFolder *folder);
+#ifndef CAMEL_DISABLE_DEPRECATED
+CamelMessageFlags
+ camel_folder_get_message_flags (CamelFolder *folder,
+ const gchar *uid);
+gboolean camel_folder_set_message_flags (CamelFolder *folder,
+ const gchar *uid,
+ CamelMessageFlags flags,
+ CamelMessageFlags set);
+gboolean camel_folder_get_message_user_flag
+ (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name);
+void camel_folder_set_message_user_flag
+ (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name,
+ gboolean value);
+const gchar * camel_folder_get_message_user_tag
+ (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name);
+void camel_folder_set_message_user_tag
+ (CamelFolder *folder,
+ const gchar *uid,
+ const gchar *name,
+ const gchar *value);
+#endif /* CAMEL_DISABLE_DEPRECATED */
+gboolean camel_folder_has_summary_capability
+ (CamelFolder *folder);
+gint camel_folder_get_message_count (CamelFolder *folder);
+#ifndef CAMEL_DISABLE_DEPRECATED
+gint camel_folder_get_unread_message_count
+ (CamelFolder *folder);
+#endif
+gint camel_folder_get_deleted_message_count
+ (CamelFolder *folder);
+GPtrArray * camel_folder_get_summary (CamelFolder *folder);
+void camel_folder_free_summary (CamelFolder *folder,
+ GPtrArray *array);
+
+#define camel_folder_delete_message(folder, uid) \
+ (camel_folder_set_message_flags ( \
+ folder, uid, \
+ CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, \
+ CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN))
+
+GPtrArray * camel_folder_get_uids (CamelFolder *folder);
+void camel_folder_free_uids (CamelFolder *folder,
+ GPtrArray *array);
+GPtrArray * camel_folder_get_uncached_uids (CamelFolder *folder,
+ GPtrArray *uids,
+ GError **error);
+gint camel_folder_cmp_uids (CamelFolder *folder,
+ const gchar *uid1,
+ const gchar *uid2);
+void camel_folder_sort_uids (CamelFolder *folder,
+ GPtrArray *uids);
+GPtrArray * camel_folder_search_by_expression
+ (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error);
+GPtrArray * camel_folder_search_by_uids (CamelFolder *folder,
+ const gchar *expression,
+ GPtrArray *uids,
+ GCancellable *cancellable,
+ GError **error);
+void camel_folder_search_free (CamelFolder *folder,
+ GPtrArray *result);
+guint32 camel_folder_count_by_expression (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error);
+CamelMessageInfo *
+ camel_folder_get_message_info (CamelFolder *folder,
+ const gchar *uid);
+void camel_folder_delete (CamelFolder *folder);
+void camel_folder_rename (CamelFolder *folder,
+ const gchar *new_name);
+void camel_folder_changed (CamelFolder *folder,
+ CamelFolderChangeInfo *changes);
+void camel_folder_freeze (CamelFolder *folder);
+void camel_folder_thaw (CamelFolder *folder);
+gboolean camel_folder_is_frozen (CamelFolder *folder);
+gint camel_folder_get_frozen_count (CamelFolder *folder);
+
+GType camel_folder_quota_info_get_type (void) G_GNUC_CONST;
+CamelFolderQuotaInfo *
+ camel_folder_quota_info_new (const gchar *name,
+ guint64 used,
+ guint64 total);
+CamelFolderQuotaInfo *
+ camel_folder_quota_info_clone (const CamelFolderQuotaInfo *info);
+void camel_folder_quota_info_free (CamelFolderQuotaInfo *info);
+void camel_folder_free_nop (CamelFolder *folder,
+ GPtrArray *array);
+void camel_folder_free_shallow (CamelFolder *folder,
+ GPtrArray *array);
+void camel_folder_free_deep (CamelFolder *folder,
+ GPtrArray *array);
+gchar * camel_folder_get_filename (CamelFolder *folder,
+ const gchar *uid,
+ GError **error);
+void camel_folder_lock (CamelFolder *folder);
+void camel_folder_unlock (CamelFolder *folder);
+
+gboolean camel_folder_append_message_sync
+ (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error);
+void camel_folder_append_message (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_folder_append_message_finish
+ (CamelFolder *folder,
+ GAsyncResult *result,
+ gchar **appended_uid,
+ GError **error);
+gboolean camel_folder_expunge_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error);
+void camel_folder_expunge (CamelFolder *folder,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_folder_expunge_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error);
+CamelMimeMessage *
+ camel_folder_get_message_sync (CamelFolder *folder,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error);
+void camel_folder_get_message (CamelFolder *folder,
+ const gchar *message_uid,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelMimeMessage *
+ camel_folder_get_message_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error);
+CamelFolderQuotaInfo *
+ camel_folder_get_quota_info_sync
+ (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error);
+void camel_folder_get_quota_info (CamelFolder *folder,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelFolderQuotaInfo *
+ camel_folder_get_quota_info_finish
+ (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_folder_purge_message_cache_sync
+ (CamelFolder *folder,
+ gchar *start_uid,
+ gchar *end_uid,
+ GCancellable *cancellable,
+ GError **error);
+void camel_folder_purge_message_cache
+ (CamelFolder *folder,
+ gchar *start_uid,
+ gchar *end_uid,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_folder_purge_message_cache_finish
+ (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_folder_refresh_info_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error);
+void camel_folder_refresh_info (CamelFolder *folder,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_folder_refresh_info_finish
+ (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_folder_synchronize_sync (CamelFolder *folder,
+ gboolean expunge,
+ GCancellable *cancellable,
+ GError **error);
+void camel_folder_synchronize (CamelFolder *folder,
+ gboolean expunge,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_folder_synchronize_finish (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_folder_synchronize_message_sync
+ (CamelFolder *folder,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error);
+void camel_folder_synchronize_message
+ (CamelFolder *folder,
+ const gchar *message_uid,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_folder_synchronize_message_finish
+ (CamelFolder *folder,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_folder_transfer_messages_to_sync
+ (CamelFolder *source,
+ GPtrArray *message_uids,
+ CamelFolder *destination,
+ gboolean delete_originals,
+ GPtrArray **transferred_uids,
+ GCancellable *cancellable,
+ GError **error);
+void camel_folder_transfer_messages_to
+ (CamelFolder *source,
+ GPtrArray *message_uids,
+ CamelFolder *destination,
+ gboolean delete_originals,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_folder_transfer_messages_to_finish
+ (CamelFolder *source,
+ GAsyncResult *result,
+ GPtrArray **transferred_uids,
+ GError **error);
+void camel_folder_prepare_content_refresh
+ (CamelFolder *folder);
+
+/* update functions for change info */
+CamelFolderChangeInfo *
+ camel_folder_change_info_new (void);
+void camel_folder_change_info_clear (CamelFolderChangeInfo *info);
+void camel_folder_change_info_free (CamelFolderChangeInfo *info);
+gboolean camel_folder_change_info_changed (CamelFolderChangeInfo *info);
+
+/* for building diff's automatically */
+void camel_folder_change_info_add_source
+ (CamelFolderChangeInfo *info,
+ const gchar *uid);
+void camel_folder_change_info_add_source_list
+ (CamelFolderChangeInfo *info,
+ const GPtrArray *list);
+void camel_folder_change_info_add_update
+ (CamelFolderChangeInfo *info,
+ const gchar *uid);
+void camel_folder_change_info_add_update_list
+ (CamelFolderChangeInfo *info,
+ const GPtrArray *list);
+void camel_folder_change_info_build_diff
+ (CamelFolderChangeInfo *info);
+
+/* for manipulating diff's directly */
+void camel_folder_change_info_cat (CamelFolderChangeInfo *info,
+ CamelFolderChangeInfo *src);
+void camel_folder_change_info_add_uid (CamelFolderChangeInfo *info,
+ const gchar *uid);
+void camel_folder_change_info_remove_uid
+ (CamelFolderChangeInfo *info,
+ const gchar *uid);
+void camel_folder_change_info_change_uid
+ (CamelFolderChangeInfo *info,
+ const gchar *uid);
+void camel_folder_change_info_recent_uid
+ (CamelFolderChangeInfo *info,
+ const gchar *uid);
+
+G_END_DECLS
+
+#endif /* CAMEL_FOLDER_H */
diff --git a/src/camel/camel-gpg-context.c b/src/camel/camel-gpg-context.c
new file mode 100644
index 000000000..b53927275
--- /dev/null
+++ b/src/camel/camel-gpg-context.c
@@ -0,0 +1,2738 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+/* 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
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#ifndef G_OS_WIN32
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <termios.h>
+#endif
+
+#include "camel-debug.h"
+#include "camel-gpg-context.h"
+#include "camel-iconv.h"
+#include "camel-internet-address.h"
+#include "camel-mime-filter-canon.h"
+#include "camel-mime-filter-charset.h"
+#include "camel-mime-part.h"
+#include "camel-multipart-encrypted.h"
+#include "camel-multipart-signed.h"
+#include "camel-operation.h"
+#include "camel-session.h"
+#include "camel-stream-filter.h"
+#include "camel-stream-fs.h"
+#include "camel-stream-mem.h"
+#include "camel-stream-null.h"
+#include "camel-string-utils.h"
+
+#define d(x)
+
+#ifdef GPG_LOG
+static gint logid;
+#endif
+
+#define CHECK_CALL(x) G_STMT_START { \
+ if ((x) == -1) { \
+ g_debug ("%s: Call of '" #x "' failed: %s", G_STRFUNC, g_strerror (errno)); \
+ } \
+ } G_STMT_END
+
+#define CAMEL_GPG_CONTEXT_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_GPG_CONTEXT, CamelGpgContextPrivate))
+
+struct _CamelGpgContextPrivate {
+ gboolean always_trust;
+ gboolean prefer_inline;
+};
+
+enum {
+ PROP_0,
+ PROP_ALWAYS_TRUST,
+ PROP_PREFER_INLINE
+};
+
+G_DEFINE_TYPE (CamelGpgContext, camel_gpg_context, CAMEL_TYPE_CIPHER_CONTEXT)
+
+enum _GpgCtxMode {
+ GPG_CTX_MODE_SIGN,
+ GPG_CTX_MODE_VERIFY,
+ GPG_CTX_MODE_ENCRYPT,
+ GPG_CTX_MODE_DECRYPT
+};
+
+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;
+
+ GSList *userids;
+ gchar *sigfile;
+ GPtrArray *recipients;
+ CamelCipherHash hash;
+
+ gint stdin_fd;
+ gint stdout_fd;
+ gint stderr_fd;
+ gint status_fd;
+ gint passwd_fd; /* only needed for sign/decrypt */
+
+ /* status-fd buffer */
+ guchar *statusbuf;
+ guchar *statusptr;
+ guint statusleft;
+
+ gchar *need_id;
+ gchar *passwd;
+
+ CamelStream *istream;
+ CamelStream *ostream;
+
+ GByteArray *diagbuf;
+ CamelStream *diagnostics;
+
+ gchar *photos_filename;
+ gchar *viewer_cmd;
+
+ gint exit_status;
+
+ guint exited : 1;
+ guint complete : 1;
+ guint seen_eof1 : 1;
+ guint seen_eof2 : 1;
+ guint always_trust : 1;
+ guint prefer_inline : 1;
+ guint armor : 1;
+ guint need_passwd : 1;
+ guint send_passwd : 1;
+ guint load_photos : 1;
+
+ guint bad_passwds : 2;
+ guint anonymous_recipient : 1;
+
+ guint hadsig : 1;
+ guint badsig : 1;
+ guint errsig : 1;
+ guint goodsig : 1;
+ guint validsig : 1;
+ guint nopubkey : 1;
+ guint nodata : 1;
+ guint trust : 3;
+ guint processing : 1;
+ guint bad_decrypt : 1;
+ guint noseckey : 1;
+ GString *signers;
+ GHashTable *signers_keyid;
+
+ guint diagflushed : 1;
+
+ guint utf8 : 1;
+
+ guint padding : 10;
+};
+
+static struct _GpgCtx *
+gpg_ctx_new (CamelCipherContext *context)
+{
+ struct _GpgCtx *gpg;
+ const gchar *charset;
+ CamelStream *stream;
+ CamelSession *session;
+
+ session = camel_cipher_context_get_session (context);
+
+ gpg = g_new (struct _GpgCtx, 1);
+ gpg->mode = GPG_CTX_MODE_SIGN;
+ gpg->session = g_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->userids = NULL;
+ gpg->sigfile = NULL;
+ gpg->recipients = NULL;
+ gpg->hash = CAMEL_CIPHER_HASH_DEFAULT;
+ gpg->always_trust = FALSE;
+ gpg->prefer_inline = FALSE;
+ gpg->armor = FALSE;
+ gpg->load_photos = FALSE;
+ gpg->photos_filename = NULL;
+ gpg->viewer_cmd = NULL;
+
+ 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->anonymous_recipient = FALSE;
+ 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->processing = FALSE;
+ gpg->bad_decrypt = FALSE;
+ gpg->noseckey = FALSE;
+ gpg->signers = NULL;
+ gpg->signers_keyid = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ gpg->istream = NULL;
+ gpg->ostream = NULL;
+
+ gpg->diagbuf = g_byte_array_new ();
+ gpg->diagflushed = FALSE;
+
+ stream = camel_stream_mem_new_with_byte_array (gpg->diagbuf);
+
+ if ((charset = camel_iconv_locale_charset ()) && g_ascii_strcasecmp (charset, "UTF-8") != 0) {
+ CamelMimeFilter *filter;
+ CamelStream *fstream;
+
+ gpg->utf8 = FALSE;
+
+ if ((filter = camel_mime_filter_charset_new (charset, "UTF-8"))) {
+ fstream = camel_stream_filter_new (stream);
+ camel_stream_filter_add (
+ CAMEL_STREAM_FILTER (fstream), filter);
+ g_object_unref (filter);
+ g_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_prefer_inline (struct _GpgCtx *gpg,
+ gboolean prefer_inline)
+{
+ gpg->prefer_inline = prefer_inline;
+}
+
+static void
+gpg_ctx_set_userid (struct _GpgCtx *gpg,
+ const gchar *userid)
+{
+ g_slist_free_full (gpg->userids, g_free);
+ gpg->userids = NULL;
+
+ if (userid && *userid) {
+ gchar **uids = g_strsplit (userid, " ", -1);
+
+ if (!uids) {
+ gpg->userids = g_slist_append (gpg->userids, g_strdup (userid));
+ } else {
+ gint ii;
+
+ for (ii = 0; uids[ii]; ii++) {
+ const gchar *uid = uids[ii];
+
+ if (*uid) {
+ gpg->userids = g_slist_append (gpg->userids, g_strdup (uid));
+ }
+ }
+
+ g_strfreev (uids);
+ }
+ }
+}
+
+static void
+gpg_ctx_add_recipient (struct _GpgCtx *gpg,
+ const gchar *keyid)
+{
+ gchar *safe_keyid;
+
+ if (gpg->mode != GPG_CTX_MODE_ENCRYPT)
+ return;
+
+ if (!gpg->recipients)
+ gpg->recipients = g_ptr_array_new ();
+
+ g_return_if_fail (keyid != NULL);
+
+ /* If the recipient looks like an email address,
+ * enclose it in brackets to ensure an exact match. */
+ if (strchr (keyid, '@') != NULL) {
+ safe_keyid = g_strdup_printf ("<%s>", keyid);
+ } else {
+ safe_keyid = g_strdup (keyid);
+ }
+
+ g_ptr_array_add (gpg->recipients, safe_keyid);
+}
+
+static void
+gpg_ctx_set_sigfile (struct _GpgCtx *gpg,
+ const gchar *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_load_photos (struct _GpgCtx *gpg,
+ gboolean load_photos)
+{
+ gpg->load_photos = load_photos;
+}
+
+static void
+gpg_ctx_set_istream (struct _GpgCtx *gpg,
+ CamelStream *istream)
+{
+ g_object_ref (istream);
+ if (gpg->istream)
+ g_object_unref (gpg->istream);
+ gpg->istream = istream;
+}
+
+static void
+gpg_ctx_set_ostream (struct _GpgCtx *gpg,
+ CamelStream *ostream)
+{
+ g_object_ref (ostream);
+ if (gpg->ostream)
+ g_object_unref (gpg->ostream);
+ gpg->ostream = ostream;
+ gpg->seen_eof1 = FALSE;
+}
+
+static const gchar *
+gpg_ctx_get_diagnostics (struct _GpgCtx *gpg)
+{
+ if (!gpg->diagflushed) {
+ gpg->diagflushed = TRUE;
+ camel_stream_flush (gpg->diagnostics, NULL, NULL);
+ if (gpg->diagbuf->len == 0)
+ return NULL;
+
+ g_byte_array_append (gpg->diagbuf, (guchar *) "", 1);
+ }
+
+ return (const gchar *) 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)
+{
+ gint i;
+
+ if (gpg == NULL)
+ return;
+
+ if (gpg->session)
+ g_object_unref (gpg->session);
+
+ g_hash_table_foreach (gpg->userid_hint, userid_hint_free, NULL);
+ g_hash_table_destroy (gpg->userid_hint);
+
+ g_slist_free_full (gpg->userids, g_free);
+
+ 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)
+ g_object_unref (gpg->istream);
+
+ if (gpg->ostream)
+ g_object_unref (gpg->ostream);
+
+ g_object_unref (gpg->diagnostics);
+
+ if (gpg->signers)
+ g_string_free (gpg->signers, TRUE);
+
+ g_hash_table_destroy (gpg->signers_keyid);
+ if (gpg->photos_filename)
+ g_unlink (gpg->photos_filename);
+
+ g_free (gpg->photos_filename);
+ g_free (gpg->viewer_cmd);
+
+ g_free (gpg);
+}
+
+static const gchar *
+gpg_ctx_get_executable_name (void)
+{
+ static gint index = -1;
+ static gchar preset_binary[512 + 1];
+ const gchar *names[] = {
+ "",
+ "gpg2", /* Prefer gpg2, which the seahorse might use too */
+ "gpg",
+ NULL
+ };
+
+ names[0] = preset_binary;
+
+ if (index == -1) {
+ GSettings *settings;
+ gchar *path;
+
+ settings = g_settings_new ("org.gnome.evolution-data-server");
+ path = g_settings_get_string (settings, "camel-gpg-binary");
+ g_clear_object (&settings);
+
+ preset_binary[0] = 0;
+
+ if (path && *path && g_file_test (path, G_FILE_TEST_IS_REGULAR)) {
+ if (strlen (path) > 512) {
+ g_warning ("%s: Path is longer than expected (max 512), ignoring it; value:'%s'", G_STRFUNC, path);
+ } else {
+ strcpy (preset_binary, path);
+ }
+ }
+
+ g_free (path);
+
+ for (index = 0; names[index]; index++) {
+ if (!*(names[index]))
+ continue;
+
+ path = g_find_program_in_path (names[index]);
+
+ if (path) {
+ g_free (path);
+ break;
+ }
+ }
+
+ if (!names[index])
+ index = 1;
+ }
+
+ return names[index];
+}
+
+#ifndef G_OS_WIN32
+
+static const gchar *
+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_SHA256:
+ return "--digest-algo=SHA256";
+ case CAMEL_CIPHER_HASH_SHA384:
+ return "--digest-algo=SHA384";
+ case CAMEL_CIPHER_HASH_SHA512:
+ return "--digest-algo=SHA512";
+ case CAMEL_CIPHER_HASH_RIPEMD160:
+ return "--digest-algo=RIPEMD160";
+ default:
+ return NULL;
+ }
+}
+
+static GPtrArray *
+gpg_ctx_get_argv (struct _GpgCtx *gpg,
+ gint status_fd,
+ gchar **sfd,
+ gint passwd_fd,
+ gchar **pfd)
+{
+ const gchar *hash_str;
+ GPtrArray *argv;
+ gchar *buf;
+ gint i;
+
+ argv = g_ptr_array_new ();
+ g_ptr_array_add (argv, (guint8 *) gpg_ctx_get_executable_name ());
+
+ g_ptr_array_add (argv, (guint8 *) "--verbose");
+ g_ptr_array_add (argv, (guint8 *) "--no-secmem-warning");
+ g_ptr_array_add (argv, (guint8 *) "--no-greeting");
+ g_ptr_array_add (argv, (guint8 *) "--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, (guint8 *) "--batch");
+ g_ptr_array_add (argv, (guint8 *) "--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);
+ }
+
+ if (gpg->load_photos) {
+ if (!gpg->viewer_cmd) {
+ gint filefd;
+
+ filefd = g_file_open_tmp ("camel-gpg-photo-state-XXXXXX", &gpg->photos_filename, NULL);
+ if (filefd) {
+ gchar *viewer_filename;
+
+ close (filefd);
+
+ viewer_filename = g_build_filename (CAMEL_LIBEXECDIR, "camel-gpg-photo-saver", NULL);
+ gpg->viewer_cmd = g_strdup_printf ("%s --state \"%s\" --photo \"%%i\" --keyid \"%%K\" --type \"%%t\"", viewer_filename, gpg->photos_filename);
+ g_free (viewer_filename);
+ }
+ }
+
+ if (gpg->viewer_cmd) {
+ g_ptr_array_add (argv, (guint8 *) "--verify-options");
+ g_ptr_array_add (argv, (guint8 *) "show-photos");
+
+ g_ptr_array_add (argv, (guint8 *) "--photo-viewer");
+ g_ptr_array_add (argv, (guint8 *) gpg->viewer_cmd);
+ }
+ }
+
+ switch (gpg->mode) {
+ case GPG_CTX_MODE_SIGN:
+ if (gpg->prefer_inline) {
+ g_ptr_array_add (argv, (guint8 *) "--clearsign");
+ } else {
+ g_ptr_array_add (argv, (guint8 *) "--sign");
+ g_ptr_array_add (argv, (guint8 *) "--detach");
+ if (gpg->armor)
+ g_ptr_array_add (argv, (guint8 *) "--armor");
+ }
+ hash_str = gpg_hash_str (gpg->hash);
+ if (hash_str)
+ g_ptr_array_add (argv, (guint8 *) hash_str);
+ if (gpg->userids) {
+ GSList *uiter;
+
+ for (uiter = gpg->userids; uiter; uiter = uiter->next) {
+ g_ptr_array_add (argv, (guint8 *) "-u");
+ g_ptr_array_add (argv, (guint8 *) uiter->data);
+ }
+ }
+ g_ptr_array_add (argv, (guint8 *) "--output");
+ g_ptr_array_add (argv, (guint8 *) "-");
+ break;
+ case GPG_CTX_MODE_VERIFY:
+ if (!camel_session_get_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, (guint8 *) "--keyserver-options");
+ g_ptr_array_add (argv, (guint8 *) "no-auto-key-retrieve");
+ }
+ g_ptr_array_add (argv, (guint8 *) "--verify");
+ if (gpg->sigfile)
+ g_ptr_array_add (argv, gpg->sigfile);
+ g_ptr_array_add (argv, (guint8 *) "-");
+ break;
+ case GPG_CTX_MODE_ENCRYPT:
+ g_ptr_array_add (argv, (guint8 *) "--encrypt");
+ if (gpg->armor || gpg->prefer_inline)
+ g_ptr_array_add (argv, (guint8 *) "--armor");
+ if (gpg->always_trust)
+ g_ptr_array_add (argv, (guint8 *) "--always-trust");
+ if (gpg->userids) {
+ GSList *uiter;
+
+ for (uiter = gpg->userids; uiter; uiter = uiter->next) {
+ g_ptr_array_add (argv, (guint8 *) "-u");
+ g_ptr_array_add (argv, (guint8 *) uiter->data);
+ }
+ }
+ if (gpg->recipients) {
+ for (i = 0; i < gpg->recipients->len; i++) {
+ g_ptr_array_add (argv, (guint8 *) "-r");
+ g_ptr_array_add (argv, gpg->recipients->pdata[i]);
+ }
+ }
+ g_ptr_array_add (argv, (guint8 *) "--output");
+ g_ptr_array_add (argv, (guint8 *) "-");
+ break;
+ case GPG_CTX_MODE_DECRYPT:
+ g_ptr_array_add (argv, (guint8 *) "--decrypt");
+ g_ptr_array_add (argv, (guint8 *) "--output");
+ g_ptr_array_add (argv, (guint8 *) "-");
+ break;
+ }
+
+ g_ptr_array_add (argv, NULL);
+
+ return argv;
+}
+
+#endif
+
+static gboolean
+gpg_ctx_op_start (struct _GpgCtx *gpg,
+ GError **error)
+{
+#ifndef G_OS_WIN32
+ gchar *status_fd = NULL, *passwd_fd = NULL;
+ gint i, maxfd, errnosave, fds[10];
+ GPtrArray *argv;
+ gint 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]) {
+ if (fcntl (i, F_SETFD, FD_CLOEXEC) == -1) {
+ /* Do nothing here. Cannot use CHECK_CALL() macro here, because
+ it makes the process stuck, possibly due to the debug print. */
+ }
+ }
+ }
+
+ /* run gpg */
+ execvp (gpg_ctx_get_executable_name (), (gchar **) 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);
+ CHECK_CALL (fcntl (gpg->passwd_fd, F_SETFL, flags | O_NONBLOCK));
+ }
+
+ flags = fcntl (gpg->stdin_fd, F_GETFL);
+ CHECK_CALL (fcntl (gpg->stdin_fd, F_SETFL, flags | O_NONBLOCK));
+
+ flags = fcntl (gpg->stdout_fd, F_GETFL);
+ CHECK_CALL (fcntl (gpg->stdout_fd, F_SETFL, flags | O_NONBLOCK));
+
+ flags = fcntl (gpg->stderr_fd, F_GETFL);
+ CHECK_CALL (fcntl (gpg->stderr_fd, F_SETFL, flags | O_NONBLOCK));
+
+ flags = fcntl (gpg->status_fd, F_GETFL);
+ CHECK_CALL (fcntl (gpg->status_fd, F_SETFL, flags | O_NONBLOCK));
+
+ return TRUE;
+
+exception:
+
+ errnosave = errno;
+
+ for (i = 0; i < 10; i++) {
+ if (fds[i] != -1)
+ close (fds[i]);
+ }
+
+ errno = errnosave;
+#else
+ /* FIXME: Port me */
+ g_warning ("%s: Not implemented", G_STRFUNC);
+
+ errno = EINVAL;
+#endif
+
+ if (errno != 0)
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Failed to execute gpg: %s"),
+ g_strerror (errno));
+ else
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Failed to execute gpg: %s"), _("Unknown"));
+
+ return FALSE;
+}
+
+#ifndef G_OS_WIN32
+
+static const gchar *
+next_token (const gchar *in,
+ gchar **token)
+{
+ const gchar *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 void
+gpg_ctx_extract_signer_from_status (struct _GpgCtx *gpg,
+ const gchar *status)
+{
+ const gchar *tmp;
+
+ g_return_if_fail (gpg != NULL);
+ g_return_if_fail (status != NULL);
+
+ /* there's a key ID, then the email address */
+ tmp = status;
+
+ status = strchr (status, ' ');
+ if (status) {
+ gchar *keyid;
+ const gchar *str = status + 1;
+ const gchar *eml = strchr (str, '<');
+
+ keyid = g_strndup (tmp, status - tmp);
+
+ if (eml && eml > str) {
+ eml--;
+ if (strchr (str, ' ') >= eml)
+ eml = NULL;
+ } else {
+ eml = NULL;
+ }
+
+ if (gpg->signers) {
+ g_string_append (gpg->signers, ", ");
+ } else {
+ gpg->signers = g_string_new ("");
+ }
+
+ if (eml) {
+ g_string_append (gpg->signers, "\"");
+ g_string_append_len (gpg->signers, str, eml - str);
+ g_string_append (gpg->signers, "\"");
+ g_string_append (gpg->signers, eml);
+ } else {
+ g_string_append (gpg->signers, str);
+ }
+
+ g_hash_table_insert (gpg->signers_keyid, g_strdup (str), keyid);
+ }
+}
+
+static gint
+gpg_ctx_parse_status (struct _GpgCtx *gpg,
+ GError **error)
+{
+ register guchar *inptr;
+ const guchar *status;
+ gsize nread, nwritten;
+ gint 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 ((const gchar *) status, "[GNUPG:] ", 9) != 0) {
+ gchar *message;
+
+ message = g_locale_to_utf8 (
+ (const gchar *) status, -1, NULL, NULL, NULL);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Unexpected GnuPG status message encountered:\n\n%s"),
+ message);
+ g_free (message);
+
+ return -1;
+ }
+
+ status += 9;
+
+ if (!strncmp ((gchar *) status, "ENC_TO ", 7)) {
+ gchar *key = NULL;
+
+ status += 7;
+
+ next_token ((gchar *) status, &key);
+ if (key) {
+ gboolean all_zero = *key == '0';
+ gint i = 0;
+
+ while (key[i] && all_zero) {
+ all_zero = key[i] == '0';
+ i++;
+ }
+
+ gpg->anonymous_recipient = all_zero;
+
+ g_free (key);
+ }
+ } else if (!strncmp ((gchar *) status, "USERID_HINT ", 12)) {
+ gchar *hint, *user;
+
+ status += 12;
+ status = (const guchar *) next_token ((gchar *) status, &hint);
+ if (!hint) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("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 ((gchar *) status, -1, &nread, &nwritten, NULL)))
+ user = g_strdup ((gchar *) status);
+
+ g_strstrip (user);
+
+ g_hash_table_insert (gpg->userid_hint, hint, user);
+ } else if (!strncmp ((gchar *) status, "NEED_PASSPHRASE ", 16)) {
+ gchar *userid;
+
+ status += 16;
+
+ next_token ((gchar *) status, &userid);
+ if (!userid) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Failed to parse gpg passphrase request."));
+ return -1;
+ }
+
+ g_free (gpg->need_id);
+ gpg->need_id = userid;
+ } else if (!strncmp ((gchar *) status, "NEED_PASSPHRASE_PIN ", 20)) {
+ gchar *userid;
+
+ status += 20;
+
+ next_token ((gchar *) status, &userid);
+ if (!userid) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Failed to parse gpg passphrase request."));
+ return -1;
+ }
+
+ g_free (gpg->need_id);
+ gpg->need_id = userid;
+ } else if (!strncmp ((gchar *) status, "GET_HIDDEN ", 11)) {
+ const gchar *name = NULL;
+ gchar *prompt, *passwd;
+ guint32 flags;
+ GError *local_error = NULL;
+
+ status += 11;
+
+ if (gpg->need_id && !(name = g_hash_table_lookup (gpg->userid_hint, gpg->need_id)))
+ name = gpg->need_id;
+ else if (!name)
+ name = "";
+
+ if (!strncmp ((gchar *) status, "passphrase.pin.ask", 18)) {
+ prompt = g_markup_printf_escaped (
+ _("You need a PIN to unlock the key for your\n"
+ "SmartCard: \"%s\""), name);
+ } else if (!strncmp ((gchar *) status, "passphrase.enter", 16)) {
+ prompt = g_markup_printf_escaped (
+ _("You need a passphrase to unlock the key for\n"
+ "user: \"%s\""), name);
+ } else {
+ next_token ((gchar *) status, &prompt);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Unexpected request from GnuPG for '%s'"),
+ prompt);
+ g_free (prompt);
+ return -1;
+ }
+
+ if (gpg->anonymous_recipient) {
+ gchar *tmp = prompt;
+
+ /* FIXME Reword prompt message. */
+ prompt = g_strconcat (
+ tmp, "\n",
+ _("Note the encrypted content doesn't contain "
+ "information about a recipient, thus there "
+ "will be a password prompt for each of stored "
+ "private key."), NULL);
+
+ g_free (tmp);
+ }
+
+ flags = CAMEL_SESSION_PASSWORD_SECRET | CAMEL_SESSION_PASSPHRASE;
+ if ((passwd = camel_session_get_password (gpg->session, NULL, prompt, gpg->need_id, flags, &local_error))) {
+ if (!gpg->utf8) {
+ gchar *opasswd = passwd;
+
+ if ((passwd = g_locale_to_utf8 (passwd, -1, &nread, &nwritten, NULL))) {
+ memset (opasswd, 0, strlen (opasswd));
+ g_free (opasswd);
+ } else {
+ passwd = opasswd;
+ }
+ }
+
+ gpg->passwd = g_strdup_printf ("%s\n", passwd);
+ memset (passwd, 0, strlen (passwd));
+ g_free (passwd);
+
+ gpg->send_passwd = TRUE;
+ } else {
+ if (local_error == NULL)
+ g_set_error (
+ error, G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Cancelled"));
+ g_propagate_error (error, local_error);
+
+ return -1;
+ }
+
+ g_free (prompt);
+ } else if (!strncmp ((gchar *) status, "GOOD_PASSPHRASE", 15)) {
+ gpg->bad_passwds = 0;
+ } else if (!strncmp ((gchar *) status, "BAD_PASSPHRASE", 14)) {
+ /* with anonymous recipient is user asked for his/her password for each stored key,
+ * thus here cannot be counted wrong passwords */
+ if (!gpg->anonymous_recipient) {
+ gpg->bad_passwds++;
+
+ camel_session_forget_password (gpg->session, NULL, gpg->need_id, error);
+
+ if (gpg->bad_passwds == 3) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Failed to unlock secret key: "
+ "3 bad passphrases given."));
+ return -1;
+ }
+ }
+ } else if (!strncmp ((const gchar *) status, "UNEXPECTED ", 11)) {
+ /* this is an error */
+ gchar *message;
+
+ message = g_locale_to_utf8 (
+ (const gchar *) status + 11, -1, NULL, NULL, NULL);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Unexpected response from GnuPG: %s"), message);
+ g_free (message);
+
+ return -1;
+ } else if (!strncmp ((gchar *) 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 {
+ if (!strncmp ((gchar *) status, "BEGIN_", 6)) {
+ gpg->processing = TRUE;
+ } else if (!strncmp ((gchar *) status, "END_", 4)) {
+ gpg->processing = FALSE;
+ }
+
+ /* check to see if we are complete */
+ switch (gpg->mode) {
+ case GPG_CTX_MODE_SIGN:
+ if (!strncmp ((gchar *) status, "SIG_CREATED ", 12)) {
+ /* SIG_CREATED <type> <pubkey algo> <hash algo> <class> <timestamp> <key fpr> */
+ const gchar *str, *p;
+ gint i = 0;
+
+ str = (const gchar *) status + 12;
+ while (p = strchr (str, ' '), i < 2 && p) {
+ str = p + 1;
+ i++;
+ }
+
+ if (*str && i == 2) {
+ struct {
+ gint gpg_hash_algo;
+ CamelCipherHash camel_hash_algo;
+ } hash_algos[] = {
+ /* the rest are deprecated/not supported by gpg any more */
+ { 2, CAMEL_CIPHER_HASH_SHA1 },
+ { 3, CAMEL_CIPHER_HASH_RIPEMD160 },
+ { 8, CAMEL_CIPHER_HASH_SHA256 },
+ { 9, CAMEL_CIPHER_HASH_SHA384 },
+ { 10, CAMEL_CIPHER_HASH_SHA512 }
+ };
+
+ gint gpg_hash = strtoul (str, NULL, 10);
+
+ for (i = 0; i < G_N_ELEMENTS (hash_algos); i++) {
+ if (hash_algos[i].gpg_hash_algo == gpg_hash) {
+ gpg->hash = hash_algos[i].camel_hash_algo;
+ break;
+ }
+ }
+ }
+ }
+ break;
+ case GPG_CTX_MODE_DECRYPT:
+ if (!strncmp ((gchar *) status, "BEGIN_DECRYPTION", 16)) {
+ gpg->bad_decrypt = FALSE;
+ /* nothing to do... but we know to expect data on stdout soon */
+ break;
+ } else if (!strncmp ((gchar *) status, "END_DECRYPTION", 14)) {
+ /* nothing to do, but we know the end is near? */
+ break;
+ } else if (!strncmp ((gchar *) status, "NO_SECKEY ", 10)) {
+ gpg->noseckey = TRUE;
+ break;
+ } else if (!strncmp ((gchar *) status, "DECRYPTION_FAILED", 17)) {
+ gpg->bad_decrypt = TRUE;
+ break;
+ }
+ /* let if fall through to verify possible signatures too */
+ /* break; */
+ case GPG_CTX_MODE_VERIFY:
+ if (!strncmp ((gchar *) status, "TRUST_", 6)) {
+ status += 6;
+ if (!strncmp ((gchar *) status, "NEVER", 5)) {
+ gpg->trust = GPG_TRUST_NEVER;
+ } else if (!strncmp ((gchar *) status, "MARGINAL", 8)) {
+ gpg->trust = GPG_TRUST_MARGINAL;
+ } else if (!strncmp ((gchar *) status, "FULLY", 5)) {
+ gpg->trust = GPG_TRUST_FULLY;
+ } else if (!strncmp ((gchar *) status, "ULTIMATE", 8)) {
+ gpg->trust = GPG_TRUST_ULTIMATE;
+ } else if (!strncmp ((gchar *) status, "UNDEFINED", 9)) {
+ gpg->trust = GPG_TRUST_UNDEFINED;
+ }
+ } else if (!strncmp ((gchar *) status, "GOODSIG ", 8)) {
+ gpg->goodsig = TRUE;
+ gpg->hadsig = TRUE;
+
+ gpg_ctx_extract_signer_from_status (gpg, (const gchar *) status + 8);
+ } else if (!strncmp ((gchar *) status, "EXPKEYSIG ", 10)) {
+ gpg_ctx_extract_signer_from_status (gpg, (const gchar *) status + 10);
+ } else if (!strncmp ((gchar *) status, "VALIDSIG ", 9)) {
+ gpg->validsig = TRUE;
+ } else if (!strncmp ((gchar *) status, "BADSIG ", 7)) {
+ gpg->badsig = FALSE;
+ gpg->hadsig = TRUE;
+
+ gpg_ctx_extract_signer_from_status (gpg, (const gchar *) status + 7);
+ } else if (!strncmp ((gchar *) status, "ERRSIG ", 7)) {
+ /* Note: NO_PUBKEY often comes after an ERRSIG */
+ gpg->errsig = FALSE;
+ gpg->hadsig = TRUE;
+ } else if (!strncmp ((gchar *) status, "NO_PUBKEY ", 10)) {
+ gpg->nopubkey = TRUE;
+ }
+ break;
+ case GPG_CTX_MODE_ENCRYPT:
+ if (!strncmp ((gchar *) status, "BEGIN_ENCRYPTION", 16)) {
+ /* nothing to do... but we know to expect data on stdout soon */
+ } else if (!strncmp ((gchar *) status, "END_ENCRYPTION", 14)) {
+ /* nothing to do, but we know the end is near? */
+ } else if (!strncmp ((gchar *) status, "NO_RECP", 7)) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Failed to encrypt: No valid recipients specified."));
+ return -1;
+ }
+ 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;
+}
+
+#endif
+
+#define status_backup(gpg, start, len) G_STMT_START { \
+ if (gpg->statusleft <= len) { \
+ guint 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)
+{
+#ifndef G_OS_WIN32
+ pid_t retval;
+ gint 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);
+ }
+#endif
+}
+
+static gint
+gpg_ctx_op_step (struct _GpgCtx *gpg,
+ GCancellable *cancellable,
+ GError **error)
+{
+#ifndef G_OS_WIN32
+ GPollFD polls[6];
+ gint status, i;
+ gboolean read_data = FALSE, wrote_data = FALSE;
+
+ for (i = 0; i < 6; i++) {
+ polls[i].fd = -1;
+ polls[i].events = 0;
+ }
+
+ if (!gpg->seen_eof1) {
+ polls[0].fd = gpg->stdout_fd;
+ polls[0].events = G_IO_IN;
+ }
+
+ if (!gpg->seen_eof2) {
+ polls[1].fd = gpg->stderr_fd;
+ polls[1].events = G_IO_IN;
+ }
+
+ if (!gpg->complete) {
+ polls[2].fd = gpg->status_fd;
+ polls[2].events = G_IO_IN;
+ }
+
+ polls[3].fd = gpg->stdin_fd;
+ polls[3].events = G_IO_OUT;
+ polls[4].fd = gpg->passwd_fd;
+ polls[4].events = G_IO_OUT;
+ polls[5].fd = g_cancellable_get_fd (cancellable);
+ polls[5].events = G_IO_IN;
+
+ do {
+ for (i = 0; i < 6; i++)
+ polls[i].revents = 0;
+ status = g_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 & G_IO_IN) &&
+ g_cancellable_set_error_if_cancelled (cancellable, error)) {
+
+ 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 & (G_IO_IN | G_IO_HUP)) {
+ /* read the status message and decide what to do... */
+ gchar buffer[4096];
+ gssize nread;
+
+ d (printf ("reading from gpg's status-fd...\n"));
+
+ do {
+ nread = read (gpg->status_fd, buffer, sizeof (buffer));
+ d (printf (" read %d bytes (%.*s)\n", (gint) nread, (gint) nread, 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, error) == -1)
+ return -1;
+ } else {
+ gpg->complete = TRUE;
+ }
+ }
+
+ if ((polls[0].revents & (G_IO_IN | G_IO_HUP)) && gpg->ostream) {
+ gchar buffer[4096];
+ gssize nread;
+
+ d (printf ("reading gpg's stdout...\n"));
+
+ do {
+ nread = read (gpg->stdout_fd, buffer, sizeof (buffer));
+ d (printf (" read %d bytes (%.*s)\n", (gint) nread, (gint) nread, buffer));
+ } while (nread == -1 && (errno == EINTR || errno == EAGAIN));
+ if (nread == -1)
+ goto exception;
+
+ if (nread > 0) {
+ gsize written = camel_stream_write (
+ gpg->ostream, buffer, (gsize)
+ nread, cancellable, error);
+ if (written != nread)
+ return -1;
+ } else {
+ gpg->seen_eof1 = TRUE;
+ }
+
+ read_data = TRUE;
+ }
+
+ if (polls[1].revents & (G_IO_IN | G_IO_HUP)) {
+ gchar buffer[4096];
+ gssize nread;
+
+ d (printf ("reading gpg's stderr...\n"));
+
+ do {
+ nread = read (gpg->stderr_fd, buffer, sizeof (buffer));
+ d (printf (" read %d bytes (%.*s)\n", (gint) nread, (gint) nread, buffer));
+ } while (nread == -1 && (errno == EINTR || errno == EAGAIN));
+ if (nread == -1)
+ goto exception;
+
+ if (nread > 0) {
+ camel_stream_write (
+ gpg->diagnostics, buffer,
+ nread, cancellable, error);
+ } else {
+ gpg->seen_eof2 = TRUE;
+ }
+ }
+
+ if ((polls[4].revents & (G_IO_OUT | G_IO_HUP)) && gpg->need_passwd && gpg->send_passwd) {
+ gssize w, nwritten = 0;
+ gsize 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 & (G_IO_OUT | G_IO_HUP)) && gpg->istream) {
+ gchar buffer[4096];
+ gssize 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), cancellable, NULL);
+ if (nread > 0) {
+ gssize 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", (gint) nwritten, (gint) nread));
+ wrote_data = TRUE;
+ }
+
+ if (camel_stream_eos (gpg->istream)) {
+ d (printf ("closing gpg's stdin\n"));
+ close (gpg->stdin_fd);
+ gpg->stdin_fd = -1;
+ }
+ }
+
+ if (gpg->need_id && !gpg->processing && !read_data && !wrote_data) {
+ /* do not ask more than hundred times per second when looking for a pass phrase,
+ * in case user has the use-agent set, it'll not use the all CPU when
+ * agent is asking for a pass phrase, instead of us */
+ g_usleep (G_USEC_PER_SEC / 100);
+ }
+
+ return 0;
+
+ exception:
+ /* always called on an i/o error */
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Failed to execute gpg: %s"), g_strerror (errno));
+ gpg_ctx_op_cancel (gpg);
+#endif
+ 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;
+ gint 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 gint
+gpg_ctx_op_wait (struct _GpgCtx *gpg)
+{
+#ifndef G_OS_WIN32
+ sigset_t mask, omask;
+ pid_t retval;
+ gint 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;
+#else
+ return -1;
+#endif
+}
+
+static gchar *
+swrite (CamelMimePart *sigpart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GFile *file;
+ GFileIOStream *base_stream = NULL;
+ CamelStream *stream = NULL;
+ CamelDataWrapper *wrapper;
+ gchar *path = NULL;
+ gint ret;
+
+ file = g_file_new_tmp ("evolution-pgp.XXXXXX", &base_stream, error);
+
+ /* Sanity check. */
+ g_return_val_if_fail (
+ ((file != NULL) && (base_stream != NULL)) ||
+ ((file == NULL) && (base_stream == NULL)), NULL);
+
+ if (base_stream != NULL) {
+ stream = camel_stream_new (G_IO_STREAM (base_stream));
+ g_object_unref (base_stream);
+ }
+
+ if (stream == NULL)
+ return NULL;
+
+ wrapper = camel_medium_get_content (CAMEL_MEDIUM (sigpart));
+ if (wrapper == NULL)
+ wrapper = CAMEL_DATA_WRAPPER (sigpart);
+
+ ret = camel_data_wrapper_decode_to_stream_sync (
+ wrapper, stream, cancellable, error);
+ if (ret != -1) {
+ ret = camel_stream_flush (stream, cancellable, error);
+ if (ret != -1)
+ ret = camel_stream_close (stream, cancellable, error);
+ }
+
+ if (ret != -1)
+ path = g_file_get_path (file);
+
+ g_object_unref (file);
+ g_object_unref (stream);
+
+ return path;
+}
+
+static const gchar *
+gpg_context_find_photo (GHashTable *photos, /* keyid ~> filename in tmp */
+ GHashTable *signers_keyid, /* signer ~> keyid */
+ const gchar *name,
+ const gchar *email)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+ const gchar *keyid = NULL;
+
+ if (!photos || !signers_keyid || ((!name || !*name) && (!email || !*email)))
+ return NULL;
+
+ g_hash_table_iter_init (&iter, signers_keyid);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ if ((email && *email && strstr (key, email)) ||
+ (name && *name && strstr (key, name))) {
+ keyid = value;
+ break;
+ }
+ }
+
+ if (keyid) {
+ const gchar *filename;
+
+ filename = g_hash_table_lookup (photos, keyid);
+ if (filename)
+ return camel_pstring_strdup (filename);
+ }
+
+ return NULL;
+}
+
+static void
+camel_gpg_context_free_photo_filename (gpointer ptr)
+{
+ gchar *tmp_filename = g_strdup (ptr);
+
+ camel_pstring_free (ptr);
+
+ if (!camel_pstring_contains (tmp_filename) &&
+ g_file_test (tmp_filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
+ g_unlink (tmp_filename);
+ }
+
+ g_free (tmp_filename);
+}
+
+static gpointer
+camel_gpg_context_clone_photo_filename (gpointer ptr)
+{
+ return (gpointer) camel_pstring_strdup (ptr);
+}
+
+static void
+add_signers (CamelCipherValidity *validity,
+ const GString *signers,
+ GHashTable *signers_keyid,
+ const gchar *photos_filename)
+{
+ CamelInternetAddress *address;
+ GHashTable *photos = NULL;
+ gint i, count;
+
+ g_return_if_fail (validity != NULL);
+
+ if (!signers || !signers->str || !*signers->str)
+ return;
+
+ address = camel_internet_address_new ();
+ g_return_if_fail (address != NULL);
+
+ if (photos_filename) {
+ /* A short file is expected */
+ gchar *content = NULL;
+ GError *error = NULL;
+
+ if (g_file_get_contents (photos_filename, &content, NULL, &error)) {
+ gchar **lines;
+ gint ii;
+
+ /* Each line is encoded as: KeyID\tPhotoFilename */
+ lines = g_strsplit (content, "\n", -1);
+
+ for (ii = 0; lines && lines[ii]; ii++) {
+ gchar *line, *filename;
+
+ line = lines[ii];
+ filename = strchr (line, '\t');
+
+ if (filename) {
+ *filename = '\0';
+ filename++;
+ }
+
+ if (filename && g_file_test (filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
+ if (!photos)
+ photos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, camel_gpg_context_free_photo_filename);
+
+ g_hash_table_insert (photos, g_strdup (line), (gpointer) camel_pstring_strdup (filename));
+ }
+ }
+
+ g_strfreev (lines);
+ } else {
+ g_warning ("CamelGPGContext: Failed to open photos file '%s': %s", photos_filename, error ? error->message : "Unknown error");
+ }
+
+ g_free (content);
+ g_clear_error (&error);
+ }
+
+ count = camel_address_decode (CAMEL_ADDRESS (address), signers->str);
+ for (i = 0; i < count; i++) {
+ const gchar *name = NULL, *email = NULL;
+ const gchar *photo_filename; /* allocated on the string pool */
+ gint index;
+
+ if (!camel_internet_address_get (address, i, &name, &email))
+ break;
+
+ photo_filename = gpg_context_find_photo (photos, signers_keyid, name, email);
+ index = camel_cipher_validity_add_certinfo (validity, CAMEL_CIPHER_VALIDITY_SIGN, name, email);
+
+ if (index != -1 && photo_filename) {
+ camel_cipher_validity_set_certinfo_property (validity, CAMEL_CIPHER_VALIDITY_SIGN, index,
+ CAMEL_CIPHER_CERT_INFO_PROPERTY_PHOTO_FILENAME, (gpointer) photo_filename,
+ camel_gpg_context_free_photo_filename, camel_gpg_context_clone_photo_filename);
+ } else if (photo_filename) {
+ camel_gpg_context_free_photo_filename ((gpointer) photo_filename);
+ }
+ }
+
+ if (photos)
+ g_hash_table_destroy (photos);
+ g_object_unref (address);
+}
+
+/* ********************************************************************** */
+
+static void
+gpg_context_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ALWAYS_TRUST:
+ camel_gpg_context_set_always_trust (
+ CAMEL_GPG_CONTEXT (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_PREFER_INLINE:
+ camel_gpg_context_set_prefer_inline (
+ CAMEL_GPG_CONTEXT (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+gpg_context_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ALWAYS_TRUST:
+ g_value_set_boolean (
+ value,
+ camel_gpg_context_get_always_trust (
+ CAMEL_GPG_CONTEXT (object)));
+ return;
+
+ case PROP_PREFER_INLINE:
+ g_value_set_boolean (
+ value,
+ camel_gpg_context_get_prefer_inline (
+ CAMEL_GPG_CONTEXT (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static const gchar *
+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_SHA256:
+ return "pgp-sha256";
+ case CAMEL_CIPHER_HASH_SHA384:
+ return "pgp-sha384";
+ case CAMEL_CIPHER_HASH_SHA512:
+ return "pgp-sha512";
+ 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 gchar *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-sha256"))
+ return CAMEL_CIPHER_HASH_SHA256;
+ else if (!strcmp (id, "pgp-sha384"))
+ return CAMEL_CIPHER_HASH_SHA384;
+ else if (!strcmp (id, "pgp-sha512"))
+ return CAMEL_CIPHER_HASH_SHA512;
+ 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;
+}
+
+static gboolean
+gpg_context_decode_to_stream (CamelMimePart *part,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelDataWrapper *wrapper;
+
+ wrapper = camel_medium_get_content (CAMEL_MEDIUM (part));
+
+ /* This is when encrypting already signed part */
+ if (CAMEL_IS_MIME_PART (wrapper))
+ wrapper = camel_medium_get_content (CAMEL_MEDIUM (wrapper));
+
+ if (camel_data_wrapper_decode_to_stream_sync (wrapper, stream, cancellable, error) == -1 ||
+ camel_stream_flush (stream, cancellable, error) == -1)
+ return FALSE;
+
+ /* Reset stream position to beginning. */
+ if (G_IS_SEEKABLE (stream))
+ g_seekable_seek (G_SEEKABLE (stream), 0, G_SEEK_SET, NULL, NULL);
+
+ return TRUE;
+}
+
+static gboolean
+gpg_sign_sync (CamelCipherContext *context,
+ const gchar *userid,
+ CamelCipherHash hash,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct _GpgCtx *gpg = NULL;
+ CamelCipherContextClass *class;
+ CamelGpgContext *ctx = (CamelGpgContext *) context;
+ CamelStream *ostream = camel_stream_mem_new (), *istream;
+ CamelDataWrapper *dw;
+ CamelContentType *ct;
+ CamelMimePart *sigpart;
+ CamelMultipartSigned *mps;
+ gboolean success = FALSE;
+ gboolean prefer_inline;
+
+ /* Note: see rfc2015 or rfc3156, section 5 */
+
+ class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context);
+
+ prefer_inline = ctx->priv->prefer_inline &&
+ camel_content_type_is (camel_mime_part_get_content_type (ipart), "text", "plain");
+
+ /* FIXME: stream this, we stream output at least */
+ istream = camel_stream_mem_new ();
+ if ((prefer_inline && !gpg_context_decode_to_stream (ipart, istream, cancellable, error)) ||
+ (!prefer_inline && camel_cipher_canonical_to_stream (
+ ipart, CAMEL_MIME_FILTER_CANON_STRIP |
+ CAMEL_MIME_FILTER_CANON_CRLF |
+ CAMEL_MIME_FILTER_CANON_FROM,
+ istream, NULL, error) == -1)) {
+ g_prefix_error (
+ error, _("Could not generate signing data: "));
+ goto fail;
+ }
+
+#ifdef GPG_LOG
+ if (camel_debug_start ("gpg:sign")) {
+ gchar *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, NULL);
+ if (out) {
+ printf ("Writing gpg signing data to '%s'\n", name);
+ camel_stream_write_to_stream (istream, out, NULL, NULL);
+ g_seekable_seek (
+ G_SEEKABLE (istream), 0,
+ G_SEEK_SET, NULL, NULL);
+ g_object_unref (out);
+ }
+ g_free (name);
+ camel_debug_end ();
+ }
+#endif
+
+ gpg = gpg_ctx_new (context);
+ 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);
+ gpg_ctx_set_prefer_inline (gpg, prefer_inline);
+
+ if (!gpg_ctx_op_start (gpg, error))
+ goto fail;
+
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, cancellable, error) == -1) {
+ gpg_ctx_op_cancel (gpg);
+ goto fail;
+ }
+ }
+
+ if (gpg_ctx_op_wait (gpg) != 0) {
+ const gchar *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, "%s",
+ (diagnostics != NULL && *diagnostics != '\0') ?
+ diagnostics : _("Failed to execute gpg."));
+
+ goto fail;
+ }
+
+ success = TRUE;
+
+ dw = camel_data_wrapper_new ();
+ g_seekable_seek (G_SEEKABLE (ostream), 0, G_SEEK_SET, NULL, NULL);
+ camel_data_wrapper_construct_from_stream_sync (dw, ostream, NULL, NULL);
+
+ if (gpg->prefer_inline) {
+ CamelTransferEncoding encoding;
+
+ encoding = camel_mime_part_get_encoding (ipart);
+
+ if (encoding != CAMEL_TRANSFER_ENCODING_BASE64 &&
+ encoding != CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE)
+ encoding = CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
+
+ sigpart = camel_mime_part_new ();
+ ct = camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (ipart));
+ camel_data_wrapper_set_mime_type_field (dw, ct);
+
+ camel_mime_part_set_encoding (sigpart, encoding);
+ camel_medium_set_content ((CamelMedium *) sigpart, dw);
+ g_object_unref (dw);
+
+ camel_medium_set_content ((CamelMedium *) opart, (CamelDataWrapper *) sigpart);
+
+ g_object_unref (sigpart);
+ } else {
+ 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 ((CamelMedium *) sigpart, dw);
+ g_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_context_hash_to_id (context, hash == CAMEL_CIPHER_HASH_DEFAULT ? gpg->hash : hash));
+ camel_content_type_set_param (ct, "protocol", class->sign_protocol);
+ camel_data_wrapper_set_mime_type_field ((CamelDataWrapper *) mps, ct);
+ camel_content_type_unref (ct);
+ camel_multipart_set_boundary ((CamelMultipart *) mps, NULL);
+
+ camel_multipart_signed_set_signature (mps, sigpart);
+ camel_multipart_signed_set_content_stream (mps, istream);
+
+ g_object_unref (sigpart);
+
+ camel_medium_set_content ((CamelMedium *) opart, (CamelDataWrapper *) mps);
+
+ g_object_unref (mps);
+ }
+
+ g_seekable_seek (G_SEEKABLE (istream), 0, G_SEEK_SET, NULL, NULL);
+
+fail:
+ g_object_unref (ostream);
+
+ if (gpg)
+ gpg_ctx_free (gpg);
+
+ return success;
+}
+
+static CamelCipherValidity *
+gpg_verify_sync (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelCipherContextClass *class;
+ CamelCipherValidity *validity;
+ const gchar *diagnostics = NULL;
+ struct _GpgCtx *gpg = NULL;
+ gchar *sigfile = NULL;
+ CamelContentType *ct;
+ CamelMimePart *sigpart;
+ CamelStream *istream = NULL, *canon_stream;
+ CamelMultipart *mps;
+ CamelStream *filter;
+ CamelMimeFilter *canon;
+
+ class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context);
+
+ mps = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) ipart);
+ ct = ((CamelDataWrapper *) mps)->mime_type;
+
+ /* Inline signature (using our fake mime type) or PGP/Mime signature */
+ if (camel_content_type_is (ct, "multipart", "signed")) {
+ /* PGP/Mime Signature */
+ const gchar *tmp;
+
+ tmp = camel_content_type_param (ct, "protocol");
+ if (!CAMEL_IS_MULTIPART_SIGNED (mps)
+ || tmp == NULL
+ || g_ascii_strcasecmp (tmp, class->sign_protocol) != 0) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot verify message signature: "
+ "Incorrect message format"));
+ return NULL;
+ }
+
+ if (!(istream = camel_multipart_signed_get_content_stream ((CamelMultipartSigned *) mps, NULL))) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot verify message signature: "
+ "Incorrect message format"));
+ return NULL;
+ }
+
+ if (!(sigpart = camel_multipart_get_part (mps, CAMEL_MULTIPART_SIGNED_SIGNATURE))) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot verify message signature: "
+ "Incorrect message format"));
+ g_object_unref (istream);
+ return NULL;
+ }
+ } else if (camel_content_type_is (ct, "application", "x-inlinepgp-signed")) {
+ /* Inline Signed */
+ CamelDataWrapper *content;
+ content = camel_medium_get_content ((CamelMedium *) ipart);
+ istream = camel_stream_mem_new ();
+ if (!camel_data_wrapper_decode_to_stream_sync (
+ content, istream, cancellable, error))
+ goto exception;
+ g_seekable_seek (
+ G_SEEKABLE (istream), 0, G_SEEK_SET, NULL, NULL);
+ sigpart = NULL;
+ } else {
+ /* Invalid Mimetype */
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot verify message signature: "
+ "Incorrect message format"));
+ return NULL;
+ }
+
+ /* Now start the real work of verifying the message */
+#ifdef GPG_LOG
+ if (camel_debug_start ("gpg:sign")) {
+ gchar *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, NULL);
+ if (out) {
+ printf ("Writing gpg verify data to '%s'\n", name);
+ camel_stream_write_to_stream (istream, out, NULL, NULL);
+ g_seekable_seek (
+ G_SEEKABLE (istream),
+ 0, G_SEEK_SET, NULL, NULL);
+ g_object_unref (out);
+ }
+
+ g_free (name);
+
+ if (sigpart) {
+ 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, NULL);
+ if (out) {
+ printf ("Writing gpg verify signature to '%s'\n", name);
+ camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (sigpart),
+ out, NULL, NULL);
+ g_object_unref (out);
+ }
+ g_free (name);
+ }
+ camel_debug_end ();
+ }
+#endif
+
+ if (sigpart) {
+ sigfile = swrite (sigpart, cancellable, error);
+ if (sigfile == NULL) {
+ g_prefix_error (
+ error, _("Cannot verify message signature: "));
+ goto exception;
+ }
+ }
+
+ g_seekable_seek (G_SEEKABLE (istream), 0, G_SEEK_SET, NULL, NULL);
+
+ canon_stream = camel_stream_mem_new ();
+
+ /* strip trailing white-spaces */
+ filter = camel_stream_filter_new (istream);
+ canon = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_CRLF | CAMEL_MIME_FILTER_CANON_STRIP);
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (filter), canon);
+ g_object_unref (canon);
+
+ camel_stream_write_to_stream (filter, canon_stream, NULL, NULL);
+
+ g_object_unref (filter);
+ g_object_unref (istream);
+ istream = NULL;
+
+ g_seekable_seek (G_SEEKABLE (canon_stream), 0, G_SEEK_SET, NULL, NULL);
+
+ gpg = gpg_ctx_new (context);
+ gpg_ctx_set_mode (gpg, GPG_CTX_MODE_VERIFY);
+ gpg_ctx_set_load_photos (gpg, camel_cipher_can_load_photos ());
+ if (sigfile)
+ gpg_ctx_set_sigfile (gpg, sigfile);
+ gpg_ctx_set_istream (gpg, canon_stream);
+
+ if (!gpg_ctx_op_start (gpg, error))
+ goto exception;
+
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, cancellable, error) == -1) {
+ gpg_ctx_op_cancel (gpg);
+ goto exception;
+ }
+ }
+
+ /* report error only when no data or didn't found signature */
+ if (gpg_ctx_op_wait (gpg) != 0 && (gpg->nodata || !gpg->hadsig)) {
+ const gchar *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, "%s",
+ (diagnostics != NULL && *diagnostics != '\0') ?
+ diagnostics : _("Failed to execute gpg."));
+ goto exception;
+ }
+
+ 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 || gpg->trust == GPG_TRUST_MARGINAL)
+ 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 if (gpg->nopubkey) {
+ validity->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY;
+ } else {
+ validity->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_BAD;
+ }
+
+ add_signers (validity, gpg->signers, gpg->signers_keyid, gpg->photos_filename);
+
+ gpg_ctx_free (gpg);
+
+ if (sigfile) {
+ g_unlink (sigfile);
+ g_free (sigfile);
+ }
+
+ g_object_unref (canon_stream);
+
+ return validity;
+
+ exception:
+
+ if (gpg != NULL)
+ gpg_ctx_free (gpg);
+
+ if (istream)
+ g_object_unref (istream);
+
+ if (sigfile) {
+ g_unlink (sigfile);
+ g_free (sigfile);
+ }
+
+ return NULL;
+}
+
+static gboolean
+gpg_encrypt_sync (CamelCipherContext *context,
+ const gchar *userid,
+ GPtrArray *recipients,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelCipherContextClass *class;
+ CamelGpgContext *ctx = (CamelGpgContext *) context;
+ struct _GpgCtx *gpg;
+ CamelStream *istream, *ostream, *vstream;
+ CamelMimePart *encpart, *verpart;
+ CamelDataWrapper *dw;
+ CamelContentType *ct;
+ CamelMultipartEncrypted *mpe;
+ gboolean success = FALSE;
+ gboolean prefer_inline;
+ gint i;
+
+ class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context);
+
+ prefer_inline = ctx->priv->prefer_inline &&
+ camel_content_type_is (camel_mime_part_get_content_type (ipart), "text", "plain");
+
+ ostream = camel_stream_mem_new ();
+ istream = camel_stream_mem_new ();
+ if ((prefer_inline && !gpg_context_decode_to_stream (ipart, istream, cancellable, error)) ||
+ (!prefer_inline && camel_cipher_canonical_to_stream (
+ ipart, CAMEL_MIME_FILTER_CANON_CRLF, istream, NULL, error) == -1)) {
+ g_prefix_error (
+ error, _("Could not generate encrypting data: "));
+ goto fail1;
+ }
+
+ gpg = gpg_ctx_new (context);
+ 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->priv->always_trust);
+ gpg_ctx_set_prefer_inline (gpg, prefer_inline);
+
+ for (i = 0; i < recipients->len; i++) {
+ gpg_ctx_add_recipient (gpg, recipients->pdata[i]);
+ }
+
+ if (!gpg_ctx_op_start (gpg, error))
+ goto fail;
+
+ /* FIXME: move this to a common routine */
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, cancellable, error) == -1) {
+ gpg_ctx_op_cancel (gpg);
+ goto fail;
+ }
+ }
+
+ if (gpg_ctx_op_wait (gpg) != 0) {
+ const gchar *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, "%s",
+ (diagnostics != NULL && *diagnostics != '\0') ?
+ diagnostics : _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ success = TRUE;
+
+ dw = camel_data_wrapper_new ();
+ g_seekable_seek (G_SEEKABLE (ostream), 0, G_SEEK_SET, NULL, NULL);
+ camel_data_wrapper_construct_from_stream_sync (dw, ostream, NULL, NULL);
+
+ if (gpg->prefer_inline) {
+ CamelTransferEncoding encoding;
+
+ encoding = camel_mime_part_get_encoding (ipart);
+
+ if (encoding != CAMEL_TRANSFER_ENCODING_BASE64 &&
+ encoding != CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE)
+ encoding = CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
+
+ encpart = camel_mime_part_new ();
+ ct = camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (ipart));
+ camel_data_wrapper_set_mime_type_field (dw, ct);
+
+ camel_mime_part_set_encoding (encpart, encoding);
+ camel_medium_set_content ((CamelMedium *) encpart, dw);
+ g_object_unref (dw);
+
+ camel_medium_set_content ((CamelMedium *) opart, (CamelDataWrapper *) encpart);
+
+ g_object_unref (encpart);
+ } else {
+ 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 ((CamelMedium *) encpart, dw);
+ g_object_unref (dw);
+
+ camel_mime_part_set_description (encpart, _("This is a digitally encrypted message part"));
+
+ vstream = camel_stream_mem_new ();
+ camel_stream_write_string (vstream, "Version: 1\n", NULL, NULL);
+ g_seekable_seek (G_SEEKABLE (vstream), 0, G_SEEK_SET, NULL, NULL);
+
+ verpart = camel_mime_part_new ();
+ dw = camel_data_wrapper_new ();
+ camel_data_wrapper_set_mime_type (dw, class->encrypt_protocol);
+ camel_data_wrapper_construct_from_stream_sync (
+ dw, vstream, NULL, NULL);
+ g_object_unref (vstream);
+ camel_medium_set_content ((CamelMedium *) verpart, dw);
+ g_object_unref (dw);
+
+ mpe = camel_multipart_encrypted_new ();
+ ct = camel_content_type_new ("multipart", "encrypted");
+ camel_content_type_set_param (ct, "protocol", class->encrypt_protocol);
+ camel_data_wrapper_set_mime_type_field ((CamelDataWrapper *) mpe, ct);
+ camel_content_type_unref (ct);
+ camel_multipart_set_boundary ((CamelMultipart *) mpe, NULL);
+
+ camel_multipart_add_part ((CamelMultipart *) mpe, verpart);
+ g_object_unref (verpart);
+ camel_multipart_add_part ((CamelMultipart *) mpe, encpart);
+ g_object_unref (encpart);
+
+ camel_medium_set_content ((CamelMedium *) opart, (CamelDataWrapper *) mpe);
+
+ g_object_unref (mpe);
+ }
+fail:
+ gpg_ctx_free (gpg);
+fail1:
+ g_object_unref (istream);
+ g_object_unref (ostream);
+
+ return success;
+}
+
+static CamelCipherValidity *
+gpg_decrypt_sync (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct _GpgCtx *gpg = NULL;
+ CamelCipherValidity *valid = NULL;
+ CamelStream *ostream, *istream;
+ CamelDataWrapper *content;
+ CamelMimePart *encrypted;
+ CamelMultipart *mp;
+ CamelContentType *ct;
+ gboolean success;
+
+ if (!ipart) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot decrypt message: Incorrect message format"));
+ return NULL;
+ }
+
+ content = camel_medium_get_content ((CamelMedium *) ipart);
+
+ if (!content) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot decrypt message: Incorrect message format"));
+ return NULL;
+ }
+
+ ct = camel_data_wrapper_get_mime_type_field (content);
+ /* Encrypted part (using our fake mime type) or PGP/Mime multipart */
+ if (camel_content_type_is (ct, "multipart", "encrypted")) {
+ mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) ipart);
+ if (!(encrypted = camel_multipart_get_part (mp, CAMEL_MULTIPART_ENCRYPTED_CONTENT))) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Failed to decrypt MIME part: "
+ "protocol error"));
+ return NULL;
+ }
+
+ content = camel_medium_get_content ((CamelMedium *) encrypted);
+ } else if (camel_content_type_is (ct, "application", "x-inlinepgp-encrypted")) {
+ content = camel_medium_get_content ((CamelMedium *) ipart);
+ } else {
+ /* Invalid Mimetype */
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot decrypt message: Incorrect message format"));
+ return NULL;
+ }
+
+ istream = camel_stream_mem_new ();
+ if (!camel_data_wrapper_decode_to_stream_sync (
+ content, istream, cancellable, error)) {
+ g_object_unref (istream);
+ return NULL;
+ }
+
+ g_seekable_seek (G_SEEKABLE (istream), 0, G_SEEK_SET, NULL, NULL);
+
+ ostream = camel_stream_mem_new ();
+ camel_stream_mem_set_secure ((CamelStreamMem *) ostream);
+
+ gpg = gpg_ctx_new (context);
+ gpg_ctx_set_mode (gpg, GPG_CTX_MODE_DECRYPT);
+ gpg_ctx_set_load_photos (gpg, camel_cipher_can_load_photos ());
+ gpg_ctx_set_istream (gpg, istream);
+ gpg_ctx_set_ostream (gpg, ostream);
+
+ gpg->bad_decrypt = TRUE;
+
+ if (!gpg_ctx_op_start (gpg, error))
+ goto fail;
+
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, cancellable, error) == -1) {
+ gpg_ctx_op_cancel (gpg);
+ goto fail;
+ }
+ }
+
+ /* Report errors only if nothing was decrypted; missing sender's key used
+ * for signature of a signed and encrypted messages causes GPG to return
+ * failure, thus count with it.
+ */
+ if (gpg_ctx_op_wait (gpg) != 0 && (gpg->nodata || (gpg->bad_decrypt && !gpg->noseckey))) {
+ const gchar *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, "%s",
+ (diagnostics != NULL && *diagnostics != '\0') ?
+ diagnostics : _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ g_seekable_seek (G_SEEKABLE (ostream), 0, G_SEEK_SET, NULL, NULL);
+
+ if (gpg->bad_decrypt && gpg->noseckey) {
+ success = FALSE;
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Failed to decrypt MIME part: Secret key not found"));
+ } else if (camel_content_type_is (ct, "multipart", "encrypted")) {
+ CamelDataWrapper *dw;
+ CamelStream *null = camel_stream_null_new ();
+
+ /* Multipart encrypted - parse a full mime part */
+ success = camel_data_wrapper_construct_from_stream_sync (
+ CAMEL_DATA_WRAPPER (opart),
+ ostream, NULL, error);
+
+ dw = camel_medium_get_content ((CamelMedium *) opart);
+ if (!camel_data_wrapper_decode_to_stream_sync (
+ dw, null, cancellable, NULL)) {
+ /* nothing had been decoded from the stream, it doesn't
+ * contain any header, like Content-Type or such, thus
+ * write it as a message body */
+ success = camel_data_wrapper_construct_from_stream_sync (
+ dw, ostream, cancellable, error);
+ }
+
+ g_object_unref (null);
+ } else {
+ /* Inline signed - raw data (may not be a mime part) */
+ CamelDataWrapper *dw;
+ dw = camel_data_wrapper_new ();
+ success = camel_data_wrapper_construct_from_stream_sync (
+ dw, ostream, NULL, error);
+ camel_data_wrapper_set_mime_type (dw, "application/octet-stream");
+ camel_medium_set_content ((CamelMedium *) opart, dw);
+ g_object_unref (dw);
+ /* Set mime/type of this new part to application/octet-stream to force type snooping */
+ camel_mime_part_set_content_type (opart, "application/octet-stream");
+ }
+
+ if (success) {
+ 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_NEED_PUBLIC_KEY;
+ } else {
+ valid->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_BAD;
+ }
+
+ add_signers (valid, gpg->signers, gpg->signers_keyid, gpg->photos_filename);
+ }
+ }
+
+ fail:
+ g_object_unref (ostream);
+ g_object_unref (istream);
+ gpg_ctx_free (gpg);
+
+ return valid;
+}
+
+static void
+camel_gpg_context_class_init (CamelGpgContextClass *class)
+{
+ GObjectClass *object_class;
+ CamelCipherContextClass *cipher_context_class;
+
+ g_type_class_add_private (class, sizeof (CamelGpgContextPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = gpg_context_set_property;
+ object_class->get_property = gpg_context_get_property;
+
+ cipher_context_class = CAMEL_CIPHER_CONTEXT_CLASS (class);
+ cipher_context_class->sign_protocol = "application/pgp-signature";
+ cipher_context_class->encrypt_protocol = "application/pgp-encrypted";
+ cipher_context_class->key_protocol = "application/pgp-keys";
+ cipher_context_class->hash_to_id = gpg_hash_to_id;
+ cipher_context_class->id_to_hash = gpg_id_to_hash;
+ cipher_context_class->sign_sync = gpg_sign_sync;
+ cipher_context_class->verify_sync = gpg_verify_sync;
+ cipher_context_class->encrypt_sync = gpg_encrypt_sync;
+ cipher_context_class->decrypt_sync = gpg_decrypt_sync;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ALWAYS_TRUST,
+ g_param_spec_boolean (
+ "always-trust",
+ "Always Trust",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PREFER_INLINE,
+ g_param_spec_boolean (
+ "prefer-inline",
+ "Prefer Inline",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+camel_gpg_context_init (CamelGpgContext *context)
+{
+ context->priv = CAMEL_GPG_CONTEXT_GET_PRIVATE (context);
+}
+
+/**
+ * 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)
+{
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+
+ return g_object_new (
+ CAMEL_TYPE_GPG_CONTEXT,
+ "session", session, NULL);
+}
+
+/**
+ * camel_gpg_context_get_always_trust:
+ * @context: a #CamelGpgContext
+ *
+ * Since: 2.32
+ **/
+gboolean
+camel_gpg_context_get_always_trust (CamelGpgContext *context)
+{
+ g_return_val_if_fail (CAMEL_IS_GPG_CONTEXT (context), FALSE);
+
+ return context->priv->always_trust;
+}
+
+/**
+ * camel_gpg_context_set_always_trust:
+ * @context: 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 *context,
+ gboolean always_trust)
+{
+ g_return_if_fail (CAMEL_IS_GPG_CONTEXT (context));
+
+ if (context->priv->always_trust == always_trust)
+ return;
+
+ context->priv->always_trust = always_trust;
+
+ g_object_notify (G_OBJECT (context), "always-trust");
+}
+
+/**
+ * camel_gpg_context_get_prefer_inline:
+ * @context: a #CamelGpgContext
+ *
+ * Returns: Whether prefer inline sign/encrypt (%TRUE), or as multiparts (%FALSE)
+ *
+ * Since: 3.20
+ **/
+gboolean
+camel_gpg_context_get_prefer_inline (CamelGpgContext *context)
+{
+ g_return_val_if_fail (CAMEL_IS_GPG_CONTEXT (context), FALSE);
+
+ return context->priv->prefer_inline;
+}
+
+/**
+ * camel_gpg_context_set_prefer_inline:
+ * @context: gpg context
+ * @prefer_inline: whether prefer inline sign/encrypt
+ *
+ * Sets the @prefer_inline flag on the gpg context.
+ *
+ * Since: 3.20
+ **/
+void
+camel_gpg_context_set_prefer_inline (CamelGpgContext *context,
+ gboolean prefer_inline)
+{
+ g_return_if_fail (CAMEL_IS_GPG_CONTEXT (context));
+
+ if (context->priv->prefer_inline == prefer_inline)
+ return;
+
+ context->priv->prefer_inline = prefer_inline;
+
+ g_object_notify (G_OBJECT (context), "prefer-inline");
+}
diff --git a/src/camel/camel-gpg-context.h b/src/camel/camel-gpg-context.h
new file mode 100644
index 000000000..763bc2316
--- /dev/null
+++ b/src/camel/camel-gpg-context.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_GPG_CONTEXT_H
+#define CAMEL_GPG_CONTEXT_H
+
+#include <camel/camel-cipher-context.h>
+#include <camel/camel-session.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_GPG_CONTEXT \
+ (camel_gpg_context_get_type ())
+#define CAMEL_GPG_CONTEXT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_GPG_CONTEXT, CamelGpgContext))
+#define CAMEL_GPG_CONTEXT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_GPG_CONTEXT, CamelGpgContextClass))
+#define CAMEL_IS_GPG_CONTEXT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_GPG_CONTEXT))
+#define CAMEL_IS_GPG_CONTEXT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_GPG_CONTEXT))
+#define CAMEL_GPG_CONTEXT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_GPG_CONTEXT, CamelGpgContextClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelGpgContext CamelGpgContext;
+typedef struct _CamelGpgContextClass CamelGpgContextClass;
+typedef struct _CamelGpgContextPrivate CamelGpgContextPrivate;
+
+struct _CamelGpgContext {
+ CamelCipherContext parent;
+ CamelGpgContextPrivate *priv;
+};
+
+struct _CamelGpgContextClass {
+ CamelCipherContextClass parent_class;
+};
+
+GType camel_gpg_context_get_type (void);
+CamelCipherContext *
+ camel_gpg_context_new (CamelSession *session);
+gboolean camel_gpg_context_get_always_trust
+ (CamelGpgContext *context);
+void camel_gpg_context_set_always_trust
+ (CamelGpgContext *context,
+ gboolean always_trust);
+gboolean camel_gpg_context_get_prefer_inline
+ (CamelGpgContext *context);
+void camel_gpg_context_set_prefer_inline
+ (CamelGpgContext *context,
+ gboolean prefer_inline);
+
+G_END_DECLS
+
+#endif /* CAMEL_GPG_CONTEXT_H */
diff --git a/src/camel/camel-gpg-photo-saver.c b/src/camel/camel-gpg-photo-saver.c
new file mode 100644
index 000000000..4746a7391
--- /dev/null
+++ b/src/camel/camel-gpg-photo-saver.c
@@ -0,0 +1,117 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <stdio.h>
+
+static gchar *state_filename = NULL;
+static gchar *photo_filename = NULL;
+static gchar *keyid = NULL;
+static gchar *img_type = NULL;
+
+static GOptionEntry entries[] = {
+ { "state", 's', 0, G_OPTION_ARG_STRING, &state_filename,
+ "State file, where to write info about the photo.", NULL },
+ { "photo", 'p', 0, G_OPTION_ARG_STRING, &photo_filename,
+ "Photo file name.", NULL },
+ { "keyid", 'k', 0, G_OPTION_ARG_STRING, &keyid,
+ "Key ID for the photo.", NULL },
+ { "type", 't', 0, G_OPTION_ARG_STRING, &img_type,
+ "Extension of the image type (e.g. \"jpg\").", NULL },
+ { NULL }
+};
+
+gint
+main (gint argc, gchar *argv[])
+{
+ GOptionContext *context;
+ GError *error = NULL;
+ gint res = 0;
+
+ context = g_option_context_new ("Camel GPG Photo Saver");
+ g_option_context_add_main_entries (context, entries, NULL);
+ if (!g_option_context_parse (context, &argc, &argv, &error)) {
+ g_option_context_free (context);
+ g_warning ("Failed to parse options: %s", error ? error->message : "Unknown error");
+ g_clear_error (&error);
+
+ return 1;
+ }
+
+ if (!state_filename || !*state_filename || !photo_filename || !*photo_filename || !keyid || !*keyid || !img_type || !*img_type) {
+ g_warning ("Expects all four parameters");
+ g_option_context_free (context);
+
+ return 2;
+ }
+
+ if (g_file_test (photo_filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
+ GFile *source, *destination;
+ gchar *tmp_filename = NULL;
+ gchar *tmp_template;
+ gint tmp_file;
+
+ tmp_template = g_strconcat ("camel-gpg-photo-XXXXXX.", img_type, NULL);
+ tmp_file = g_file_open_tmp (tmp_template, &tmp_filename, &error);
+ g_free (tmp_template);
+
+ if (tmp_file == -1) {
+ g_warning ("Failed to open temporary file: %s", error ? error->message : "Unknown error");
+ g_option_context_free (context);
+ g_clear_error (&error);
+
+ return 3;
+ }
+
+ close (tmp_file);
+
+ source = g_file_new_for_path (photo_filename);
+ destination = g_file_new_for_path (tmp_filename);
+
+ if (!g_file_copy (source, destination, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error)) {
+ g_warning ("Failed to copy file '%s' to '%s': %s", photo_filename, tmp_filename, error ? error->message : "Unknown error");
+ res = 4;
+ } else {
+ FILE *state = fopen (state_filename, "ab");
+ if (state) {
+ fprintf (state, "%s\t%s\n", keyid, tmp_filename);
+ fclose (state);
+ } else {
+ g_unlink (tmp_filename);
+
+ g_warning ("Failed to open state file '%s' for append", state_filename);
+ res = 5;
+ }
+ }
+
+ g_free (tmp_filename);
+ g_clear_object (&source);
+ g_clear_object (&destination);
+ g_clear_error (&error);
+ } else {
+ g_warning ("Photo file '%s' does not exist", photo_filename);
+ res = 6;
+ }
+
+ g_option_context_free (context);
+
+ return res;
+}
diff --git a/src/camel/camel-html-parser.c b/src/camel/camel-html-parser.c
new file mode 100644
index 000000000..1e5d30d5c
--- /dev/null
+++ b/src/camel/camel-html-parser.c
@@ -0,0 +1,788 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/* WARNING
+ *
+ * DO NOT USE THIS CODE OUTSIDE OF CAMEL
+ *
+ * IT IS SUBJECT TO CHANGE OR MAY VANISH AT ANY TIME
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "camel-html-parser.h"
+
+/* if defined, must also compile in dump_tag() below somewhere */
+#define d(x)
+
+/* Parser definitions, see below object code for details */
+
+struct _CamelHTMLParserPrivate {
+ gchar *inbuf,
+ *inptr,
+ *inend,
+ *start;
+ CamelHTMLParserState state;
+ gchar *charset;
+ gint eof;
+ GString *tag;
+ GString *ent;
+ gchar ent_utf8[8];
+ gint attr;
+ GPtrArray *attrs;
+ GPtrArray *values;
+ gint quote;
+};
+
+static void tokenize_setup (void);
+static CamelHTMLParserPrivate *tokenize_init (void);
+static void tokenize_free (CamelHTMLParserPrivate *p);
+static gint tokenize_step (CamelHTMLParserPrivate *p, gchar **datap, gint *lenp);
+
+G_DEFINE_TYPE (CamelHTMLParser, camel_html_parser, G_TYPE_OBJECT)
+
+/* ********************************************************************** */
+
+static void
+html_parser_finalize (GObject *object)
+{
+ CamelHTMLParser *parser = CAMEL_HTML_PARSER (object);
+
+ tokenize_free (parser->priv);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_html_parser_parent_class)->finalize (object);
+}
+
+static void
+camel_html_parser_class_init (CamelHTMLParserClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = html_parser_finalize;
+
+ tokenize_setup ();
+}
+
+static void
+camel_html_parser_init (CamelHTMLParser *parser)
+{
+ parser->priv = tokenize_init ();
+}
+
+/**
+ * camel_html_parser_new:
+ *
+ * Create a new CamelHTMLParser object.
+ *
+ * Returns: A new CamelHTMLParser widget.
+ **/
+CamelHTMLParser *
+camel_html_parser_new (void)
+{
+ return g_object_new (CAMEL_TYPE_HTML_PARSER, NULL);
+}
+
+void camel_html_parser_set_data (CamelHTMLParser *hp, const gchar *start, gint len, gint last)
+{
+ CamelHTMLParserPrivate *p = hp->priv;
+
+ p->inptr = p->inbuf = (gchar *) start;
+ p->inend = (gchar *) start + len;
+ p->eof = last;
+}
+
+CamelHTMLParserState camel_html_parser_step (CamelHTMLParser *hp, const gchar **datap, gint *lenp)
+{
+ return tokenize_step (hp->priv, (gchar **) datap, lenp);
+}
+
+const gchar *camel_html_parser_left (CamelHTMLParser *hp, gint *lenp)
+{
+ CamelHTMLParserPrivate *p = hp->priv;
+
+ if (lenp)
+ *lenp = p->inend - p->inptr;
+
+ return p->inptr;
+}
+
+const gchar *camel_html_parser_tag (CamelHTMLParser *hp)
+{
+ return hp->priv->tag->str;
+}
+
+const gchar *camel_html_parser_attr (CamelHTMLParser *hp, const gchar *name)
+{
+ gint 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;
+}
+
+/**
+ * camel_html_parser_attr_list:
+ * @values: (element-type utf8) (inout):
+ *
+ * Returns: (element-type utf8) (transfer none):
+ *
+ **/
+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 {
+ guint val;
+ const gchar *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 tokenize_setup (void)
+{
+ gint i;
+
+ if (entities == NULL) {
+ entities = g_hash_table_new (g_str_hash, g_str_equal);
+ for (i = 0; i < G_N_ELEMENTS (entity_map); i++) {
+ g_hash_table_insert (entities, (gchar *) entity_map[i].name, GUINT_TO_POINTER (entity_map[i].val));
+ }
+ }
+}
+
+static CamelHTMLParserPrivate *tokenize_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)
+ tokenize_setup ();
+
+ return p;
+}
+
+static void tokenize_free (CamelHTMLParserPrivate *p)
+{
+ gint 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 gint convert_entity (const gchar *e, gchar *ent)
+{
+ guint 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)
+{
+ gint 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 gint tokenize_step (CamelHTMLParserPrivate *p, gchar **datap, gint *lenp)
+{
+ gchar *in = p->inptr;
+ gchar *inend = p->inend;
+ gchar c;
+ gint state = p->state, ret, len;
+ gchar *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_len (p->values->pdata[p->attr], p->ent_utf8, len);
+ }
+ } 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/src/camel/camel-html-parser.h b/src/camel/camel-html-parser.h
new file mode 100644
index 000000000..b299a4d5d
--- /dev/null
+++ b/src/camel/camel-html-parser.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/* WARNING
+ *
+ * DO NOT USE THIS CODE OUTSIDE OF CAMEL
+ *
+ * IT IS SUBJECT TO CHANGE OR MAY VANISH AT ANY TIME
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_HTML_PARSER_H
+#define CAMEL_HTML_PARSER_H
+
+#include <glib-object.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_HTML_PARSER \
+ (camel_html_parser_get_type ())
+#define CAMEL_HTML_PARSER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_HTML_PARSER, CamelHTMLParser))
+#define CAMEL_HTML_PARSER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_HTML_PARSER, CamelHTMLParserClass))
+#define CAMEL_IS_HTML_PARSER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_HTML_PARSER))
+#define CAMEL_IS_HTML_PARSER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_HTML_PARSER))
+#define CAMEL_HTML_PARSER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_HTML_PARSER, CamelHTMLParserClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelHTMLParser CamelHTMLParser;
+typedef struct _CamelHTMLParserClass CamelHTMLParserClass;
+typedef struct _CamelHTMLParserPrivate CamelHTMLParserPrivate;
+
+/* Parser/tokeniser states */
+typedef enum _camel_html_parser_t {
+ CAMEL_HTML_PARSER_DATA, /* raw data */
+ CAMEL_HTML_PARSER_ENT, /* entity in data */
+ CAMEL_HTML_PARSER_ELEMENT, /* element (tag + attributes scanned) */
+ CAMEL_HTML_PARSER_TAG, /* tag */
+ CAMEL_HTML_PARSER_DTDENT, /* dtd entity? <! blah blah > */
+ CAMEL_HTML_PARSER_COMMENT0, /* start of comment */
+ CAMEL_HTML_PARSER_COMMENT, /* body of comment */
+ CAMEL_HTML_PARSER_ATTR0, /* start of attribute */
+ CAMEL_HTML_PARSER_ATTR, /* attribute */
+ CAMEL_HTML_PARSER_VAL0, /* start of value */
+ CAMEL_HTML_PARSER_VAL, /* value */
+ CAMEL_HTML_PARSER_VAL_ENT, /* entity in value */
+ CAMEL_HTML_PARSER_EOD, /* end of current data */
+ CAMEL_HTML_PARSER_EOF /* end of file */
+} CamelHTMLParserState;
+
+struct _CamelHTMLParser {
+ GObject parent;
+ CamelHTMLParserPrivate *priv;
+};
+
+struct _CamelHTMLParserClass {
+ GObjectClass parent_class;
+};
+
+GType camel_html_parser_get_type (void);
+CamelHTMLParser *camel_html_parser_new (void);
+
+void camel_html_parser_set_data (CamelHTMLParser *hp, const gchar *start, gint len, gint last);
+CamelHTMLParserState camel_html_parser_step (CamelHTMLParser *hp, const gchar **datap, gint *lenp);
+const gchar *camel_html_parser_left (CamelHTMLParser *hp, gint *lenp);
+const gchar *camel_html_parser_tag (CamelHTMLParser *hp);
+const gchar *camel_html_parser_attr (CamelHTMLParser *hp, const gchar *name);
+const GPtrArray *camel_html_parser_attr_list (CamelHTMLParser *hp, const GPtrArray **values);
+
+G_END_DECLS
+
+#endif /* CAMEL_HTML_PARSER_H */
diff --git a/src/camel/camel-iconv.c b/src/camel/camel-iconv.c
new file mode 100644
index 000000000..ed5b2fee6
--- /dev/null
+++ b/src/camel/camel-iconv.c
@@ -0,0 +1,560 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-iconv.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffery Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <locale.h>
+
+#ifdef HAVE_CODESET
+#include <langinfo.h>
+#endif
+
+#include "camel-iconv.h"
+#include "iconv-detect.h"
+
+#define cd(x)
+
+G_LOCK_DEFINE_STATIC (iconv);
+
+struct _iconv_cache_node {
+ struct _iconv_cache *parent;
+
+ gint busy;
+ GIConv ip;
+};
+
+struct _iconv_cache {
+ gchar *conv;
+ GQueue open; /* stores iconv_cache_nodes, busy ones up front */
+};
+
+#define E_ICONV_CACHE_SIZE (16)
+
+static GQueue iconv_cache_list = G_QUEUE_INIT;
+static GHashTable *iconv_cache;
+static GHashTable *iconv_cache_open;
+
+static GHashTable *iconv_charsets = NULL;
+static gchar *locale_charset = NULL;
+static gchar *locale_lang = NULL;
+
+struct {
+ const gchar *charset;
+ const gchar *iconv_name;
+} known_iconv_charsets[] = {
+#if 0
+ /* charset name, iconv-friendly charset name */
+ { "iso-8859-1", "iso-8859-1" },
+ { "iso8859-1", "iso-8859-1" },
+ /* the above mostly serves as an example for iso-style charsets,
+ * but we have code that will populate the iso-*'s if/when they
+ * show up in camel_iconv_charset_name () so I'm
+ * not going to bother putting them all in here... */
+ { "windows-cp1251", "cp1251" },
+ { "windows-1251", "cp1251" },
+ { "cp1251", "cp1251" },
+ /* the above mostly serves as an example for windows-style
+ * charsets, but we have code that will parse and convert them
+ * to their cp#### equivalents if/when they show up in
+ * camel_iconv_charset_name () so I'm not going to bother
+ * putting them all in here either... */
+#endif
+ /* charset name (lowercase!), iconv-friendly name (sometimes case sensitive) */
+ { "utf-8", "UTF-8" },
+
+ /* 10646 is a special case, its usually UCS-2 big endian */
+ /* This might need some checking but should be ok for solaris/linux */
+ { "iso-10646-1", "UCS-2BE" },
+ { "iso_10646-1", "UCS-2BE" },
+ { "iso10646-1", "UCS-2BE" },
+ { "iso-10646", "UCS-2BE" },
+ { "iso_10646", "UCS-2BE" },
+ { "iso10646", "UCS-2BE" },
+
+ { "ks_c_5601-1987", "EUC-KR" },
+
+ /* FIXME: Japanese/Korean/Chinese stuff needs checking */
+ { "euckr-0", "EUC-KR" },
+ { "5601", "EUC-KR" },
+ { "zh_TW-euc", "EUC-TW" },
+ { "zh_CN.euc", "gb18030" },
+ { "zh_TW-big5", "BIG5" },
+ { "euc-cn", "gb18030" },
+ { "big5-0", "BIG5" },
+ { "big5.eten-0", "BIG5" },
+ { "big5hkscs-0", "BIG5HKSCS" },
+ { "gb2312-0", "gb18030" },
+ { "gb2312.1980-0", "gb18030" },
+ { "gb-2312", "gb18030" },
+ { "gb2312", "gb18030" },
+ { "gb18030-0", "gb18030" },
+ { "gbk-0", "GBK" },
+
+ { "eucjp-0", "eucJP" },
+ { "ujis-0", "ujis" },
+ { "jisx0208.1983-0","SJIS" },
+ { "jisx0212.1990-0","SJIS" },
+ { "pck", "SJIS" },
+ { NULL, NULL }
+};
+
+static const gchar *
+e_strdown (gchar *str)
+{
+ register gchar *s = str;
+
+ while (*s) {
+ if (*s >= 'A' && *s <= 'Z')
+ *s += 0x20;
+ s++;
+ }
+
+ return str;
+}
+
+static const gchar *
+e_strup (gchar *str)
+{
+ register gchar *s = str;
+
+ while (*s) {
+ if (*s >= 'a' && *s <= 'z')
+ *s -= 0x20;
+ s++;
+ }
+
+ return str;
+}
+
+static void
+locale_parse_lang (const gchar *locale)
+{
+ gchar *codeset, *lang;
+
+ if ((codeset = strchr (locale, '.')))
+ lang = g_strndup (locale, codeset - locale);
+ else
+ lang = g_strdup (locale);
+
+ /* validate the language */
+ if (strlen (lang) >= 2) {
+ if (lang[2] == '-' || lang[2] == '_') {
+ /* canonicalise the lang */
+ e_strdown (lang);
+
+ /* validate the country code */
+ if (strlen (lang + 3) > 2) {
+ /* invalid country code */
+ lang[2] = '\0';
+ } else {
+ lang[2] = '-';
+ e_strup (lang + 3);
+ }
+ } else if (lang[2] != '\0') {
+ /* invalid language */
+ g_free (lang);
+ lang = NULL;
+ }
+
+ locale_lang = lang;
+ } else {
+ /* invalid language */
+ locale_lang = NULL;
+ g_free (lang);
+ }
+}
+
+/* NOTE: Owns the lock on return if keep is TRUE !*/
+static void
+iconv_init (gint keep)
+{
+ gchar *from, *to, *locale;
+ gint i;
+
+ G_LOCK (iconv);
+
+ if (iconv_charsets != NULL) {
+ if (!keep)
+ G_UNLOCK (iconv);
+ return;
+ }
+
+ iconv_charsets = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (i = 0; known_iconv_charsets[i].charset != NULL; i++) {
+ from = g_strdup (known_iconv_charsets[i].charset);
+ to = g_strdup (known_iconv_charsets[i].iconv_name);
+ e_strdown (from);
+ g_hash_table_insert (iconv_charsets, from, to);
+ }
+
+ iconv_cache = g_hash_table_new (g_str_hash, g_str_equal);
+ iconv_cache_open = g_hash_table_new (NULL, NULL);
+
+#ifndef G_OS_WIN32
+ locale = setlocale (LC_ALL, NULL);
+#else
+ locale = g_win32_getlocale ();
+#endif
+
+ if (!locale || !strcmp (locale, "C") || !strcmp (locale, "POSIX")) {
+ /* The locale "C" or "POSIX" is a portable locale; its
+ * LC_CTYPE part corresponds to the 7-bit ASCII character
+ * set.
+ */
+
+ locale_charset = NULL;
+ locale_lang = NULL;
+ } else {
+#ifdef G_OS_WIN32
+ g_get_charset (&locale_charset);
+ locale_charset = g_strdup (locale_charset);
+ e_strdown (locale_charset);
+#else
+#ifdef HAVE_CODESET
+ locale_charset = g_strdup (nl_langinfo (CODESET));
+ e_strdown (locale_charset);
+#else
+ /* A locale name is typically of the form language[_terri-
+ * tory][.codeset][@modifier], where language is an ISO 639
+ * language code, territory is an ISO 3166 country code, and
+ * codeset is a character set or encoding identifier like
+ * ISO-8859-1 or UTF-8.
+ */
+ gchar *codeset, *p;
+
+ codeset = strchr (locale, '.');
+ if (codeset) {
+ codeset++;
+
+ /* ; is a hack for debian systems and / is a hack for Solaris systems */
+ for (p = codeset; *p && !strchr ("@;/", *p); p++);
+ locale_charset = g_strndup (codeset, p - codeset);
+ e_strdown (locale_charset);
+ } else {
+ /* charset unknown */
+ locale_charset = NULL;
+ }
+#endif
+#endif /* !G_OS_WIN32 */
+
+ /* parse the locale lang */
+ locale_parse_lang (locale);
+
+ }
+
+#ifdef G_OS_WIN32
+ g_free (locale);
+#endif
+ if (!keep)
+ G_UNLOCK (iconv);
+}
+
+const gchar *
+camel_iconv_charset_name (const gchar *charset)
+{
+ gchar *name, *ret, *tmp;
+ gsize name_len;
+
+ if (charset == NULL)
+ return NULL;
+
+ name_len = strlen (charset) + 1;
+ name = g_alloca (name_len);
+ g_strlcpy (name, charset, name_len);
+ e_strdown (name);
+
+ iconv_init (TRUE);
+ ret = g_hash_table_lookup (iconv_charsets, name);
+ if (ret != NULL) {
+ G_UNLOCK (iconv);
+ return ret;
+ }
+
+ /* Unknown, try canonicalise some basic charset types to something that should work */
+ if (strncmp (name, "iso", 3) == 0) {
+ /* Convert iso-nnnn-n or isonnnn-n or iso_nnnn-n to iso-nnnn-n or isonnnn-n */
+ gint iso, codepage;
+ gchar *p;
+
+ tmp = name + 3;
+ if (*tmp == '-' || *tmp == '_')
+ tmp++;
+
+ iso = strtoul (tmp, &p, 10);
+
+ if (iso == 10646) {
+ /* they all become ICONV_10646 */
+ ret = g_strdup (ICONV_10646);
+ } else {
+ tmp = p;
+ if (*tmp == '-' || *tmp == '_')
+ tmp++;
+
+ codepage = strtoul (tmp, &p, 10);
+
+ if (p > tmp) {
+ /* codepage is numeric */
+#ifdef __aix__
+ if (codepage == 13)
+ ret = g_strdup ("IBM-921");
+ else
+#endif /* __aix__ */
+ ret = g_strdup_printf (ICONV_ISO_D_FORMAT, iso, codepage);
+ } else {
+ /* codepage is a string - probably iso-2022-jp or something */
+ ret = g_strdup_printf (ICONV_ISO_S_FORMAT, iso, p);
+ }
+ }
+ } else if (strncmp (name, "windows-", 8) == 0) {
+ /* Convert windows-nnnnn or windows-cpnnnnn to cpnnnn */
+ tmp = name + 8;
+ if (!strncmp (tmp, "cp", 2))
+ tmp+=2;
+ ret = g_strdup_printf ("CP%s", tmp);
+ } else if (strncmp (name, "microsoft-", 10) == 0) {
+ /* Convert microsoft-nnnnn or microsoft-cpnnnnn to cpnnnn */
+ tmp = name + 10;
+ if (!strncmp (tmp, "cp", 2))
+ tmp+=2;
+ ret = g_strdup_printf ("CP%s", tmp);
+ } else {
+ /* Just assume its ok enough as is, case and all */
+ ret = g_strdup (charset);
+ }
+
+ g_hash_table_insert (iconv_charsets, g_strdup (name), ret);
+ G_UNLOCK (iconv);
+
+ return ret;
+}
+
+static void
+flush_entry (struct _iconv_cache *ic)
+{
+ struct _iconv_cache_node *in;
+
+ while ((in = g_queue_pop_head (&ic->open)) != NULL) {
+ if (in->ip != (GIConv) -1) {
+ g_hash_table_remove (iconv_cache_open, in->ip);
+ g_iconv_close (in->ip);
+ }
+ g_free (in);
+ }
+
+ g_free (ic->conv);
+ g_free (ic);
+}
+
+/* This should run pretty quick, its called a lot */
+GIConv
+camel_iconv_open (const gchar *oto,
+ const gchar *ofrom)
+{
+ const gchar *to, *from;
+ gchar *tofrom;
+ gsize tofrom_len;
+ struct _iconv_cache *ic;
+ struct _iconv_cache_node *in;
+ gint errnosav;
+ GIConv ip;
+
+ if (oto == NULL || ofrom == NULL) {
+ errno = EINVAL;
+ return (GIConv) -1;
+ }
+
+ to = camel_iconv_charset_name (oto);
+ from = camel_iconv_charset_name (ofrom);
+ tofrom_len = strlen (to) + strlen (from) + 2;
+ tofrom = g_alloca (tofrom_len);
+ g_snprintf (tofrom, tofrom_len, "%s%%%s", to, from);
+
+ G_LOCK (iconv);
+
+ ic = g_hash_table_lookup (iconv_cache, tofrom);
+ if (ic) {
+ g_queue_remove (&iconv_cache_list, ic);
+ } else {
+ GList *link;
+
+ link = g_queue_peek_tail_link (&iconv_cache_list);
+
+ while (link != NULL && iconv_cache_list.length > E_ICONV_CACHE_SIZE) {
+ GList *prev = g_list_previous (link);
+
+ ic = (struct _iconv_cache *) link->data;
+ in = g_queue_peek_head (&ic->open);
+
+ if (in != NULL && !in->busy) {
+ cd (printf ("Flushing iconv converter '%s'\n", ic->conv));
+ g_queue_delete_link (&iconv_cache_list, link);
+ g_hash_table_remove (iconv_cache, ic->conv);
+ flush_entry (ic);
+ }
+
+ link = prev;
+ }
+
+ ic = g_malloc (sizeof (*ic));
+ g_queue_init (&ic->open);
+ ic->conv = g_strdup (tofrom);
+ g_hash_table_insert (iconv_cache, ic->conv, ic);
+
+ cd (printf ("Creating iconv converter '%s'\n", ic->conv));
+ }
+
+ g_queue_push_head (&iconv_cache_list, ic);
+
+ /* If we have a free iconv, use it */
+ in = g_queue_peek_tail (&ic->open);
+ if (in != NULL && !in->busy) {
+ cd (printf ("using existing iconv converter '%s'\n", ic->conv));
+ ip = in->ip;
+ if (ip != (GIConv) -1) {
+ /* work around some broken iconv implementations
+ * that die if the length arguments are NULL
+ */
+ gsize buggy_iconv_len = 0;
+ gchar *buggy_iconv_buf = NULL;
+
+ /* resets the converter */
+ g_iconv (ip, &buggy_iconv_buf, &buggy_iconv_len, &buggy_iconv_buf, &buggy_iconv_len);
+ in->busy = TRUE;
+ g_queue_remove (&ic->open, in);
+ g_queue_push_head (&ic->open, in);
+ }
+ } else {
+ cd (printf ("creating new iconv converter '%s'\n", ic->conv));
+ ip = g_iconv_open (to, from);
+ in = g_malloc (sizeof (*in));
+ in->ip = ip;
+ in->parent = ic;
+ g_queue_push_head (&ic->open, in);
+ if (ip != (GIConv) -1) {
+ g_hash_table_insert (iconv_cache_open, ip, in);
+ in->busy = TRUE;
+ } else {
+ errnosav = errno;
+ g_warning ("Could not open converter for '%s' to '%s' charset", from, to);
+ in->busy = FALSE;
+ errno = errnosav;
+ }
+ }
+
+ G_UNLOCK (iconv);
+
+ return ip;
+}
+
+gsize
+camel_iconv (GIConv cd,
+ const gchar **inbuf,
+ gsize *inbytesleft,
+ gchar **outbuf,
+ gsize *outbytesleft)
+{
+ return g_iconv (cd, (gchar **) inbuf, inbytesleft, outbuf, outbytesleft);
+}
+
+void
+camel_iconv_close (GIConv ip)
+{
+ struct _iconv_cache_node *in;
+
+ if (ip == (GIConv) -1)
+ return;
+
+ G_LOCK (iconv);
+ in = g_hash_table_lookup (iconv_cache_open, ip);
+ if (in) {
+ cd (printf ("closing iconv converter '%s'\n", in->parent->conv));
+ g_queue_remove (&in->parent->open, in);
+ in->busy = FALSE;
+ g_queue_push_tail (&in->parent->open, in);
+ } else {
+ g_warning ("trying to close iconv i dont know about: %p", ip);
+ g_iconv_close (ip);
+ }
+ G_UNLOCK (iconv);
+}
+
+const gchar *
+camel_iconv_locale_charset (void)
+{
+ iconv_init (FALSE);
+
+ return locale_charset;
+}
+
+const gchar *
+camel_iconv_locale_language (void)
+{
+ iconv_init (FALSE);
+
+ return locale_lang;
+}
+
+/* map CJKR charsets to their language code */
+/* NOTE: only support charset names that will be returned by
+ * camel_iconv_charset_name() so that we don't have to keep track of all
+ * the aliases too. */
+static struct {
+ const gchar *charset;
+ const gchar *lang;
+} cjkr_lang_map[] = {
+ { "Big5", "zh" },
+ { "BIG5HKSCS", "zh" },
+ { "gb2312", "zh" },
+ { "gb18030", "zh" },
+ { "gbk", "zh" },
+ { "euc-tw", "zh" },
+ { "iso-2022-jp", "ja" },
+ { "sjis", "ja" },
+ { "ujis", "ja" },
+ { "eucJP", "ja" },
+ { "euc-jp", "ja" },
+ { "euc-kr", "ko" },
+ { "koi8-r", "ru" },
+ { "koi8-u", "uk" }
+};
+
+const gchar *
+camel_iconv_charset_language (const gchar *charset)
+{
+ gint i;
+
+ if (!charset)
+ return NULL;
+
+ charset = camel_iconv_charset_name (charset);
+ for (i = 0; i < G_N_ELEMENTS (cjkr_lang_map); i++) {
+ if (!g_ascii_strcasecmp (cjkr_lang_map[i].charset, charset))
+ return cjkr_lang_map[i].lang;
+ }
+
+ return NULL;
+}
diff --git a/src/camel/camel-iconv.h b/src/camel/camel-iconv.h
new file mode 100644
index 000000000..d59d33773
--- /dev/null
+++ b/src/camel/camel-iconv.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_ICONV_H
+#define CAMEL_ICONV_H
+
+#include <sys/types.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+const gchar * camel_iconv_locale_charset (void);
+const gchar * camel_iconv_locale_language (void);
+
+const gchar * camel_iconv_charset_name (const gchar *charset);
+const gchar * camel_iconv_charset_language (const gchar *charset);
+
+GIConv camel_iconv_open (const gchar *to,
+ const gchar *from);
+gsize camel_iconv (GIConv cd,
+ const gchar **inbuf,
+ gsize *inleft,
+ gchar **outbuf,
+ gsize *outleft);
+void camel_iconv_close (GIConv cd);
+
+G_END_DECLS
+
+#endif /* CAMEL_ICONV_H */
diff --git a/src/camel/camel-index-control.c b/src/camel/camel-index-control.c
new file mode 100644
index 000000000..f09b3fef1
--- /dev/null
+++ b/src/camel/camel-index-control.c
@@ -0,0 +1,223 @@
+/* Command to manually force a compression/dump of an index file
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "camel-object.h"
+#include "camel-text-index.h"
+
+extern gint camel_init (const gchar *certdb_dir, gboolean nss_init);
+
+G_GNUC_NORETURN static void
+do_usage (gchar *argv0)
+{
+ fprintf (stderr, "Usage: %s [ compress | dump | info ] file(s) ...\n", argv0);
+ fprintf (stderr, " compress - compress (an) index file(s)\n");
+ fprintf (stderr, " dump - dump (an) index file's content to stdout\n");
+ fprintf (stderr, " info - dump summary info to stdout\n");
+ exit (1);
+}
+
+static gint
+do_compress (gint argc,
+ gchar **argv)
+{
+ gint i;
+ CamelIndex *idx;
+
+ for (i = 2; i < argc; i++) {
+ printf ("Opening index file: %s\n", argv[i]);
+ idx = (CamelIndex *) camel_text_index_new (argv[i], O_RDWR);
+ if (idx) {
+ printf (" Compressing ...\n");
+ if (camel_index_compress (idx) == -1) {
+ g_object_unref (idx);
+ return 1;
+ }
+ g_object_unref (idx);
+ } else {
+ printf (" Failed: %s\n", g_strerror (errno));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static gint
+do_dump (gint argc,
+ gchar **argv)
+{
+ gint i;
+ CamelIndex *idx;
+
+ for (i = 2; i < argc; i++) {
+ printf ("Opening index file: %s\n", argv[i]);
+ idx = (CamelIndex *) camel_text_index_new (argv[i], O_RDONLY);
+ if (idx) {
+ printf (" Dumping ...\n");
+ camel_text_index_dump ((CamelTextIndex *) idx);
+ g_object_unref (idx);
+ } else {
+ printf (" Failed: %s\n", g_strerror (errno));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static gint
+do_info (gint argc,
+ gchar **argv)
+{
+ gint i;
+ CamelIndex *idx;
+
+ for (i = 2; i < argc; i++) {
+ printf ("Opening index file: %s\n", argv[i]);
+ idx = (CamelIndex *) camel_text_index_new (argv[i], O_RDONLY);
+ if (idx) {
+ camel_text_index_info ((CamelTextIndex *) idx);
+ g_object_unref (idx);
+ } else {
+ printf (" Failed: %s\n", g_strerror (errno));
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static gint
+do_check (gint argc,
+ gchar **argv)
+{
+ gint i;
+ CamelIndex *idx;
+
+ for (i = 2; i < argc; i++) {
+ printf ("Opening index file: %s\n", argv[i]);
+ idx = (CamelIndex *) camel_text_index_new (argv[i], O_RDONLY);
+ if (idx) {
+ camel_text_index_validate ((CamelTextIndex *) idx);
+ g_object_unref (idx);
+ } else {
+ printf (" Failed: %s\n", g_strerror (errno));
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static gint do_perf (gint argc, gchar **argv);
+
+gint main (gint argc, gchar **argv)
+{
+ if (argc < 2)
+ do_usage (argv[0]);
+
+ camel_init (NULL, 0);
+
+ if (!strcmp (argv[1], "compress"))
+ return do_compress (argc, argv);
+ else if (!strcmp (argv[1], "dump"))
+ return do_dump (argc, argv);
+ else if (!strcmp (argv[1], "info"))
+ return do_info (argc, argv);
+ else if (!strcmp (argv[1], "check"))
+ return do_check (argc, argv);
+ else if (!strcmp (argv[1], "perf"))
+ return do_perf (argc, argv);
+
+ do_usage (argv[0]);
+ return 1;
+}
+
+#include <dirent.h>
+#include "camel-stream-null.h"
+#include "camel-stream-filter.h"
+#include "camel-mime-filter-index.h"
+#include "camel-stream-fs.h"
+
+static gint
+do_perf (gint argc,
+ gchar **argv)
+{
+ CamelIndex *idx;
+ DIR *dir;
+ const gchar *path = "/home/notzed/evolution/local/Inbox/mbox/cur";
+ struct dirent *d;
+ CamelStream *null, *filter, *stream;
+ CamelMimeFilter *filter_index;
+ gchar *name;
+ CamelIndexName *idn;
+
+ dir = opendir (path);
+ if (dir == NULL) {
+ perror ("open dir");
+ return 1;
+ }
+
+ idx = (CamelIndex *) camel_text_index_new (
+ "/tmp/index", O_TRUNC | O_CREAT | O_RDWR);
+ if (idx == NULL) {
+ perror ("open index");
+ closedir (dir);
+ return 1;
+ }
+
+ null = camel_stream_null_new ();
+ filter = camel_stream_filter_new (null);
+ g_object_unref (null);
+ filter_index = camel_mime_filter_index_new (idx);
+ camel_stream_filter_add ((CamelStreamFilter *) filter, filter_index);
+
+ while ((d = readdir (dir))) {
+ printf ("indexing '%s'\n", d->d_name);
+
+ idn = camel_index_add_name (idx, d->d_name);
+ camel_mime_filter_index_set_name (
+ CAMEL_MIME_FILTER_INDEX (filter_index), idn);
+ name = g_strdup_printf ("%s/%s", path, d->d_name);
+ stream = camel_stream_fs_new_with_name (name, O_RDONLY, 0, NULL);
+ camel_stream_write_to_stream (stream, filter, NULL, NULL);
+ g_object_unref (stream);
+ g_free (name);
+
+ camel_index_write_name (idx, idn);
+ g_object_unref (idn);
+ camel_mime_filter_index_set_name (
+ CAMEL_MIME_FILTER_INDEX (filter_index), NULL);
+ }
+
+ closedir (dir);
+
+ camel_index_sync (idx);
+ g_object_unref (idx);
+
+ g_object_unref (filter);
+ g_object_unref (filter_index);
+
+ return 0;
+}
diff --git a/src/camel/camel-index.c b/src/camel/camel-index.c
new file mode 100644
index 000000000..8af941530
--- /dev/null
+++ b/src/camel/camel-index.c
@@ -0,0 +1,455 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "camel-index.h"
+#include "camel-object.h"
+
+#define w(x)
+#define io(x)
+#define d(x) /*(printf ("%s (%d): ", __FILE__, __LINE__),(x))*/
+
+#define CAMEL_INDEX_VERSION (0x01)
+
+#define CAMEL_INDEX_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_INDEX, CamelIndexPrivate))
+
+struct _CamelIndexPrivate {
+ gpointer dummy;
+};
+
+/* ********************************************************************** */
+/* CamelIndex */
+/* ********************************************************************** */
+
+G_DEFINE_TYPE (CamelIndex, camel_index, G_TYPE_OBJECT)
+
+static void
+index_finalize (GObject *object)
+{
+ CamelIndex *index = CAMEL_INDEX (object);
+
+ g_free (index->path);
+
+ /* Chain up to parent's finalize () method. */
+ G_OBJECT_CLASS (camel_index_parent_class)->finalize (object);
+}
+
+static void
+camel_index_class_init (CamelIndexClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelIndexPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = index_finalize;
+}
+
+static void
+camel_index_init (CamelIndex *index)
+{
+ index->priv = CAMEL_INDEX_GET_PRIVATE (index);
+ index->version = CAMEL_INDEX_VERSION;
+}
+
+void
+camel_index_construct (CamelIndex *idx,
+ const gchar *path,
+ gint flags)
+{
+ g_free (idx->path);
+ idx->path = g_strdup_printf ("%s.index", path);
+ idx->flags = flags;
+}
+
+gint
+camel_index_rename (CamelIndex *idx,
+ const gchar *path)
+{
+ CamelIndexClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_INDEX (idx), -1);
+
+ class = CAMEL_INDEX_GET_CLASS (idx);
+ g_return_val_if_fail (class->rename != NULL, -1);
+
+ if ((idx->state & CAMEL_INDEX_DELETED) == 0)
+ return class->rename (idx, path);
+ else {
+ errno = ENOENT;
+ return -1;
+ }
+}
+
+/**
+ * camel_index_set_normalize:
+ * @index: a #CamelIndex
+ * @func: (scope call): normalization function
+ * @user_data: user data for @func
+ *
+ * Since: 2.32
+ **/
+void
+camel_index_set_normalize (CamelIndex *index,
+ CamelIndexNorm func,
+ gpointer user_data)
+{
+ g_return_if_fail (CAMEL_IS_INDEX (index));
+
+ index->normalize = func;
+ index->normalize_data = user_data;
+}
+
+gint
+camel_index_sync (CamelIndex *idx)
+{
+ CamelIndexClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_INDEX (idx), -1);
+
+ class = CAMEL_INDEX_GET_CLASS (idx);
+ g_return_val_if_fail (class->sync != NULL, -1);
+
+ if ((idx->state & CAMEL_INDEX_DELETED) == 0)
+ return class->sync (idx);
+ else {
+ errno = ENOENT;
+ return -1;
+ }
+}
+
+gint
+camel_index_compress (CamelIndex *idx)
+{
+ CamelIndexClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_INDEX (idx), -1);
+
+ class = CAMEL_INDEX_GET_CLASS (idx);
+ g_return_val_if_fail (class->compress != NULL, -1);
+
+ if ((idx->state & CAMEL_INDEX_DELETED) == 0)
+ return class->compress (idx);
+ else {
+ errno = ENOENT;
+ return -1;
+ }
+}
+
+gint
+camel_index_delete (CamelIndex *idx)
+{
+ CamelIndexClass *class;
+ gint ret;
+
+ g_return_val_if_fail (CAMEL_IS_INDEX (idx), -1);
+
+ class = CAMEL_INDEX_GET_CLASS (idx);
+ g_return_val_if_fail (class->delete_ != NULL, -1);
+
+ if ((idx->state & CAMEL_INDEX_DELETED) == 0) {
+ ret = class->delete_ (idx);
+ idx->state |= CAMEL_INDEX_DELETED;
+ } else {
+ errno = ENOENT;
+ ret = -1;
+ }
+
+ return ret;
+}
+
+gint
+camel_index_has_name (CamelIndex *idx,
+ const gchar *name)
+{
+ CamelIndexClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_INDEX (idx), FALSE);
+
+ class = CAMEL_INDEX_GET_CLASS (idx);
+ g_return_val_if_fail (class->has_name != NULL, FALSE);
+
+ if ((idx->state & CAMEL_INDEX_DELETED) == 0)
+ return class->has_name (idx, name);
+ else
+ return FALSE;
+}
+
+/**
+ * camel_index_add_name:
+ * @index: a #CamelIndex
+ *
+ * Returns: (transfer none) (nullable):
+ **/
+CamelIndexName *
+camel_index_add_name (CamelIndex *index,
+ const gchar *name)
+{
+ CamelIndexClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_INDEX (index), NULL);
+
+ class = CAMEL_INDEX_GET_CLASS (index);
+ g_return_val_if_fail (class->add_name != NULL, NULL);
+
+ if ((index->state & CAMEL_INDEX_DELETED) == 0)
+ return class->add_name (index, name);
+ else
+ return NULL;
+}
+
+gint
+camel_index_write_name (CamelIndex *idx,
+ CamelIndexName *idn)
+{
+ CamelIndexClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_INDEX (idx), -1);
+
+ class = CAMEL_INDEX_GET_CLASS (idx);
+ g_return_val_if_fail (class->write_name != NULL, -1);
+
+ if ((idx->state & CAMEL_INDEX_DELETED) == 0)
+ return class->write_name (idx, idn);
+ else {
+ errno = ENOENT;
+ return -1;
+ }
+}
+
+/**
+ * camel_index_find_name:
+ * @index: a #CamelIndex
+ *
+ * Returns: (transfer none) (nullable):
+ **/
+CamelIndexCursor *
+camel_index_find_name (CamelIndex *index,
+ const gchar *name)
+{
+ CamelIndexClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_INDEX (index), NULL);
+
+ class = CAMEL_INDEX_GET_CLASS (index);
+ g_return_val_if_fail (class->find_name != NULL, NULL);
+
+ if ((index->state & CAMEL_INDEX_DELETED) == 0)
+ return class->find_name (index, name);
+ else
+ return NULL;
+}
+
+void
+camel_index_delete_name (CamelIndex *idx,
+ const gchar *name)
+{
+ CamelIndexClass *class;
+
+ g_return_if_fail (CAMEL_IS_INDEX (idx));
+
+ class = CAMEL_INDEX_GET_CLASS (idx);
+ g_return_if_fail (class->delete_name != NULL);
+
+ if ((idx->state & CAMEL_INDEX_DELETED) == 0)
+ class->delete_name (idx, name);
+}
+
+/**
+ * camel_index_find:
+ * @index: a #CamelIndex
+ *
+ * Returns: (transfer none) (nullable):
+ **/
+CamelIndexCursor *
+camel_index_find (CamelIndex *index,
+ const gchar *word)
+{
+ CamelIndexClass *class;
+ CamelIndexCursor *ret;
+ gchar *b = (gchar *) word;
+
+ g_return_val_if_fail (CAMEL_IS_INDEX (index), NULL);
+
+ class = CAMEL_INDEX_GET_CLASS (index);
+ g_return_val_if_fail (class->find != NULL, NULL);
+
+ if ((index->state & CAMEL_INDEX_DELETED) != 0)
+ return NULL;
+
+ if (index->normalize)
+ b = index->normalize (index, word, index->normalize_data);
+
+ ret = class->find (index, b);
+
+ if (b != word)
+ g_free (b);
+
+ return ret;
+}
+
+/**
+ * camel_index_words:
+ * @index: a #CamelIndex
+ *
+ * Returns: (transfer none) (nullable):
+ **/
+CamelIndexCursor *
+camel_index_words (CamelIndex *index)
+{
+ CamelIndexClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_INDEX (index), NULL);
+
+ class = CAMEL_INDEX_GET_CLASS (index);
+ g_return_val_if_fail (class->words != NULL, NULL);
+
+ if ((index->state & CAMEL_INDEX_DELETED) == 0)
+ return class->words (index);
+ else
+ return NULL;
+}
+
+/* ********************************************************************** */
+/* CamelIndexName */
+/* ********************************************************************** */
+
+G_DEFINE_TYPE (CamelIndexName, camel_index_name, G_TYPE_OBJECT)
+
+static void
+index_name_dispose (GObject *object)
+{
+ CamelIndexName *index_name = CAMEL_INDEX_NAME (object);
+
+ if (index_name->index != NULL) {
+ g_object_unref (index_name->index);
+ index_name->index = NULL;
+ }
+
+ /* Chain up to parent's dispose () method. */
+ G_OBJECT_CLASS (camel_index_name_parent_class)->dispose (object);
+}
+
+static void
+camel_index_name_class_init (CamelIndexNameClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = index_name_dispose;
+}
+
+static void
+camel_index_name_init (CamelIndexName *index_name)
+{
+}
+
+void
+camel_index_name_add_word (CamelIndexName *idn,
+ const gchar *word)
+{
+ CamelIndexNameClass *class;
+ gchar *b = (gchar *) word;
+
+ g_return_if_fail (CAMEL_IS_INDEX_NAME (idn));
+
+ class = CAMEL_INDEX_NAME_GET_CLASS (idn);
+ g_return_if_fail (class->add_word != NULL);
+
+ if (idn->index->normalize)
+ b = idn->index->normalize (idn->index, word, idn->index->normalize_data);
+
+ class->add_word (idn, b);
+
+ if (b != word)
+ g_free (b);
+}
+
+gsize
+camel_index_name_add_buffer (CamelIndexName *idn,
+ const gchar *buffer,
+ gsize len)
+{
+ CamelIndexNameClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_INDEX_NAME (idn), 0);
+
+ class = CAMEL_INDEX_NAME_GET_CLASS (idn);
+ g_return_val_if_fail (class->add_buffer != NULL, 0);
+
+ return class->add_buffer (idn, buffer, len);
+}
+
+/* ********************************************************************** */
+/* CamelIndexCursor */
+/* ********************************************************************** */
+
+G_DEFINE_TYPE (CamelIndexCursor, camel_index_cursor, G_TYPE_OBJECT)
+
+static void
+index_cursor_dispose (GObject *object)
+{
+ CamelIndexCursor *index_cursor = CAMEL_INDEX_CURSOR (object);
+
+ if (index_cursor->index != NULL) {
+ g_object_unref (index_cursor->index);
+ index_cursor->index = NULL;
+ }
+
+ /* Chain up to parent's dispose () method. */
+ G_OBJECT_CLASS (camel_index_cursor_parent_class)->dispose (object);
+}
+
+static void
+camel_index_cursor_class_init (CamelIndexCursorClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = index_cursor_dispose;
+}
+
+static void
+camel_index_cursor_init (CamelIndexCursor *index_cursor)
+{
+}
+
+const gchar *
+camel_index_cursor_next (CamelIndexCursor *idc)
+{
+ CamelIndexCursorClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_INDEX_CURSOR (idc), NULL);
+
+ class = CAMEL_INDEX_CURSOR_GET_CLASS (idc);
+ g_return_val_if_fail (class->next != NULL, NULL);
+
+ return class->next (idc);
+}
+
diff --git a/src/camel/camel-index.h b/src/camel/camel-index.h
new file mode 100644
index 000000000..5bec6cfa3
--- /dev/null
+++ b/src/camel/camel-index.h
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_INDEX_H
+#define CAMEL_INDEX_H
+
+#include <glib-object.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_INDEX \
+ (camel_index_get_type ())
+#define CAMEL_INDEX(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_INDEX, CamelIndex))
+#define CAMEL_INDEX_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_INDEX, CamelIndexClass))
+#define CAMEL_IS_INDEX(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_INDEX))
+#define CAMEL_IS_INDEX_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_INDEX))
+#define CAMEL_INDEX_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_INDEX, CamelIndexClass))
+
+#define CAMEL_TYPE_INDEX_NAME \
+ (camel_index_name_get_type ())
+#define CAMEL_INDEX_NAME(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_INDEX_NAME, CamelIndexName))
+#define CAMEL_INDEX_NAME_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_INDEX_NAME, CamelIndexNameClass))
+#define CAMEL_IS_INDEX_NAME(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_INDEX_NAME))
+#define CAMEL_IS_INDEX_NAME_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_INDEX_NAME))
+#define CAMEL_INDEX_NAME_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_INDEX_NAME, CamelIndexNameClass))
+
+#define CAMEL_TYPE_INDEX_CURSOR \
+ (camel_index_cursor_get_type ())
+#define CAMEL_INDEX_CURSOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_INDEX_CURSOR, CamelIndexCursor))
+#define CAMEL_INDEX_CURSOR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_INDEX_CURSOR, CamelIndexCursorClass))
+#define CAMEL_IS_INDEX_CURSOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_INDEX_CURSOR))
+#define CAMEL_IS_INDEX_CURSOR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_INDEX_CURSOR))
+#define CAMEL_INDEX_CURSOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_INDEX_CURSOR, CamelIndexCursorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIndex CamelIndex;
+typedef struct _CamelIndexClass CamelIndexClass;
+typedef struct _CamelIndexPrivate CamelIndexPrivate;
+
+typedef struct _CamelIndexName CamelIndexName;
+typedef struct _CamelIndexNameClass CamelIndexNameClass;
+typedef struct _CamelIndexNamePrivate CamelIndexNamePrivate;
+
+typedef struct _CamelIndexCursor CamelIndexCursor;
+typedef struct _CamelIndexCursorClass CamelIndexCursorClass;
+typedef struct _CamelIndexCursorPrivate CamelIndexCursorPrivate;
+
+typedef gchar * (*CamelIndexNorm)(CamelIndex *index, const gchar *word, gpointer data);
+
+/* ********************************************************************** */
+
+struct _CamelIndexCursor {
+ GObject parent;
+ CamelIndexCursorPrivate *priv;
+
+ CamelIndex *index;
+};
+
+struct _CamelIndexCursorClass {
+ GObjectClass parent;
+
+ const gchar * (*next) (CamelIndexCursor *idc);
+};
+
+GType camel_index_cursor_get_type (void);
+const gchar *camel_index_cursor_next (CamelIndexCursor *idc);
+
+/* ********************************************************************** */
+
+struct _CamelIndexName {
+ GObject parent;
+ CamelIndexNamePrivate *priv;
+
+ CamelIndex *index;
+
+ gchar *name; /* name being indexed */
+
+ GByteArray *buffer; /* used for normalisation */
+ GHashTable *words; /* unique list of words */
+};
+
+struct _CamelIndexNameClass {
+ GObjectClass parent;
+
+ void (*add_word)(CamelIndexName *name, const gchar *word);
+ gsize (*add_buffer)(CamelIndexName *name, const gchar *buffer, gsize len);
+};
+
+GType camel_index_name_get_type (void);
+void camel_index_name_add_word (CamelIndexName *name, const gchar *word);
+gsize camel_index_name_add_buffer (CamelIndexName *name, const gchar *buffer, gsize len);
+
+/* ********************************************************************** */
+
+struct _CamelIndex {
+ GObject parent;
+ CamelIndexPrivate *priv;
+
+ gchar *path;
+ guint32 version;
+ guint32 flags; /* open flags */
+ guint32 state;
+
+ CamelIndexNorm normalize;
+ gpointer normalize_data;
+};
+
+struct _CamelIndexClass {
+ GObjectClass parent_class;
+
+ gint (*sync) (CamelIndex *index);
+ gint (*compress) (CamelIndex *index);
+ gint (*delete_) (CamelIndex *index);
+ gint (*rename) (CamelIndex *index,
+ const gchar *path);
+ gint (*has_name) (CamelIndex *index,
+ const gchar *name);
+ CamelIndexName *(*add_name) (CamelIndex *index,
+ const gchar *name);
+ gint (*write_name) (CamelIndex *index,
+ CamelIndexName *idn);
+ CamelIndexCursor *
+ (*find_name) (CamelIndex *index,
+ const gchar *name);
+ void (*delete_name) (CamelIndex *index,
+ const gchar *name);
+ CamelIndexCursor *
+ (*find) (CamelIndex *index,
+ const gchar *word);
+ CamelIndexCursor *
+ (*words) (CamelIndex *index);
+};
+
+/* flags, stored in 'state', set with set_state */
+#define CAMEL_INDEX_DELETED (1 << 0)
+
+GType camel_index_get_type (void);
+void camel_index_construct (CamelIndex *index,
+ const gchar *path,
+ gint flags);
+gint camel_index_rename (CamelIndex *index,
+ const gchar *path);
+void camel_index_set_normalize (CamelIndex *index,
+ CamelIndexNorm func,
+ gpointer user_data);
+gint camel_index_sync (CamelIndex *index);
+gint camel_index_compress (CamelIndex *index);
+gint camel_index_delete (CamelIndex *index);
+gint camel_index_has_name (CamelIndex *index,
+ const gchar *name);
+CamelIndexName *camel_index_add_name (CamelIndex *index,
+ const gchar *name);
+gint camel_index_write_name (CamelIndex *index,
+ CamelIndexName *idn);
+CamelIndexCursor *
+ camel_index_find_name (CamelIndex *index,
+ const gchar *name);
+void camel_index_delete_name (CamelIndex *index,
+ const gchar *name);
+CamelIndexCursor *
+ camel_index_find (CamelIndex *index,
+ const gchar *word);
+CamelIndexCursor *
+ camel_index_words (CamelIndex *index);
+
+G_END_DECLS
+
+#endif /* CAMEL_INDEX_H */
diff --git a/src/camel/camel-internet-address.c b/src/camel/camel-internet-address.c
new file mode 100644
index 000000000..42f87ca29
--- /dev/null
+++ b/src/camel/camel-internet-address.c
@@ -0,0 +1,607 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-internet-address.h"
+#include "camel-mime-utils.h"
+#include "camel-net-utils.h"
+
+#define d(x)
+
+struct _address {
+ gchar *name;
+ gchar *address;
+};
+
+G_DEFINE_TYPE (CamelInternetAddress, camel_internet_address, CAMEL_TYPE_ADDRESS)
+
+static gint
+internet_address_decode (CamelAddress *a,
+ const gchar *raw)
+{
+ CamelHeaderAddress *ha, *n;
+ gint count = a->addresses->len;
+
+ /* Should probably use its own decoder or something */
+ ha = camel_header_address_decode (raw, NULL);
+ if (ha) {
+ n = ha;
+ while (n) {
+ if (n->type == CAMEL_HEADER_ADDRESS_NAME) {
+ camel_internet_address_add ((CamelInternetAddress *) a, n->name, n->v.addr);
+ } else if (n->type == CAMEL_HEADER_ADDRESS_GROUP) {
+ CamelHeaderAddress *g = n->v.members;
+ while (g) {
+ if (g->type == CAMEL_HEADER_ADDRESS_NAME)
+ camel_internet_address_add ((CamelInternetAddress *) a, g->name, g->v.addr);
+ /* otherwise, it's an error, infact */
+ g = g->next;
+ }
+ }
+ n = n->next;
+ }
+ camel_header_address_list_clear (&ha);
+ }
+
+ return a->addresses->len - count;
+}
+
+static gchar *
+internet_address_encode (CamelAddress *a)
+{
+ gint i;
+ GString *out;
+ gchar *ret;
+ gint len = 6; /* "From: ", assume longer of the address headers */
+
+ if (a->addresses->len == 0)
+ return NULL;
+
+ out = g_string_new ("");
+
+ for (i = 0; i < a->addresses->len; i++) {
+ struct _address *addr = g_ptr_array_index (a->addresses, i);
+ gchar *enc;
+
+ if (i != 0)
+ g_string_append (out, ", ");
+
+ enc = camel_internet_address_encode_address (&len, addr->name, addr->address);
+ g_string_append (out, enc);
+ g_free (enc);
+ }
+
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ return ret;
+}
+
+static gint
+internet_address_unformat (CamelAddress *a,
+ const gchar *raw)
+{
+ gchar *buffer, *p, *name, *addr;
+ gint c;
+ gint count = a->addresses->len;
+
+ if (raw == NULL)
+ return 0;
+
+ d (printf ("unformatting address: %s\n", raw));
+
+ /* we copy, so we can modify as we go */
+ buffer = g_strdup (raw);
+
+ /* this can be simpler than decode, since there are much fewer rules */
+ p = buffer;
+ name = NULL;
+ addr = p;
+ do {
+ c = (guchar) * p++;
+ switch (c) {
+ /* removes quotes, they should only be around the total name anyway */
+ case '"':
+ p[-1] = ' ';
+ while (*p)
+ if (*p == '"') {
+ *p++ = ' ';
+ break;
+ } else {
+ p++;
+ }
+ break;
+ case '<':
+ if (name == NULL)
+ name = addr;
+ addr = p;
+ addr[-1] = 0;
+ while (*p && *p != '>')
+ p++;
+ if (*p == 0)
+ break;
+ p++;
+ /* falls through */
+ case ',':
+ p[-1] = 0;
+ /* falls through */
+ case 0:
+ if (name)
+ name = g_strstrip (name);
+ addr = g_strstrip (addr);
+ if (addr[0]) {
+ d (printf ("found address: '%s' <%s>\n", name, addr));
+ camel_internet_address_add ((CamelInternetAddress *) a, name, addr);
+ }
+ name = NULL;
+ addr = p;
+ break;
+ }
+ } while (c);
+
+ g_free (buffer);
+
+ return a->addresses->len - count;
+}
+
+static gchar *
+internet_address_format (CamelAddress *a)
+{
+ gint i;
+ GString *out;
+ gchar *ret;
+
+ if (a->addresses->len == 0)
+ return NULL;
+
+ out = g_string_new ("");
+
+ for (i = 0; i < a->addresses->len; i++) {
+ struct _address *addr = g_ptr_array_index (a->addresses, i);
+ gchar *enc;
+
+ if (i != 0)
+ g_string_append (out, ", ");
+
+ enc = camel_internet_address_format_address (addr->name, addr->address);
+ g_string_append (out, enc);
+ g_free (enc);
+ }
+
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ return ret;
+}
+
+static void
+internet_address_remove (CamelAddress *a,
+ gint index)
+{
+ struct _address *addr;
+
+ if (index < 0 || index >= a->addresses->len)
+ return;
+
+ addr = g_ptr_array_index (a->addresses, index);
+ g_free (addr->name);
+ g_free (addr->address);
+ g_free (addr);
+ g_ptr_array_remove_index (a->addresses, index);
+}
+
+static gint
+internet_address_cat (CamelAddress *dest,
+ CamelAddress *source)
+{
+ gint i;
+
+ g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (source), -1);
+
+ for (i = 0; i < source->addresses->len; i++) {
+ struct _address *addr = g_ptr_array_index (source->addresses, i);
+ camel_internet_address_add ((CamelInternetAddress *) dest, addr->name, addr->address);
+ }
+
+ return i;
+}
+
+static void
+camel_internet_address_class_init (CamelInternetAddressClass *class)
+{
+ CamelAddressClass *address_class;
+
+ address_class = CAMEL_ADDRESS_CLASS (class);
+ address_class->decode = internet_address_decode;
+ address_class->encode = internet_address_encode;
+ address_class->unformat = internet_address_unformat;
+ address_class->format = internet_address_format;
+ address_class->remove = internet_address_remove;
+ address_class->cat = internet_address_cat;
+}
+
+static void
+camel_internet_address_init (CamelInternetAddress *internet_address)
+{
+}
+
+/**
+ * camel_internet_address_new:
+ *
+ * Create a new #CamelInternetAddress object.
+ *
+ * Returns: a new #CamelInternetAddress object
+ **/
+CamelInternetAddress *
+camel_internet_address_new (void)
+{
+ return g_object_new (CAMEL_TYPE_INTERNET_ADDRESS, NULL);
+}
+
+/**
+ * camel_internet_address_add:
+ * @addr: a #CamelInternetAddress object
+ * @name: name associated with the new address
+ * @address: routing address associated with the new address
+ *
+ * Add a new internet address to @addr.
+ *
+ * Returns: the index of added entry
+ **/
+gint
+camel_internet_address_add (CamelInternetAddress *addr,
+ const gchar *name,
+ const gchar *address)
+{
+ struct _address *new;
+ gint index;
+
+ g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), -1);
+
+ new = g_malloc (sizeof (*new));
+ new->name = g_strdup (name);
+ new->address = g_strdup (address);
+ index = ((CamelAddress *) addr)->addresses->len;
+ g_ptr_array_add (((CamelAddress *) addr)->addresses, new);
+
+ return index;
+}
+
+/**
+ * camel_internet_address_get:
+ * @addr: a #CamelInternetAddress object
+ * @index: address's array index
+ * @namep: holder for the returned name, or %NULL, if not required.
+ * @addressp: holder for the returned address, or %NULL, if not required.
+ *
+ * Get the address at @index.
+ *
+ * Returns: %TRUE if such an address exists, or %FALSE otherwise
+ **/
+gboolean
+camel_internet_address_get (CamelInternetAddress *addr,
+ gint index,
+ const gchar **namep,
+ const gchar **addressp)
+{
+ struct _address *a;
+
+ g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), FALSE);
+
+ if (index < 0 || index >= ((CamelAddress *) addr)->addresses->len)
+ return FALSE;
+
+ a = g_ptr_array_index (((CamelAddress *) addr)->addresses, index);
+ if (namep)
+ *namep = a->name;
+ if (addressp)
+ *addressp = a->address;
+ return TRUE;
+}
+
+/**
+ * camel_internet_address_find_name:
+ * @addr: a #CamelInternetAddress object
+ * @name: name to lookup
+ * @addressp: holder for address part, or %NULL, if not required.
+ *
+ * Find address by real name.
+ *
+ * Returns: the index of the address matching the name, or %-1 if no
+ * match was found
+ **/
+gint
+camel_internet_address_find_name (CamelInternetAddress *addr,
+ const gchar *name,
+ const gchar **addressp)
+{
+ struct _address *a;
+ gint i, len;
+
+ g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), -1);
+
+ len = ((CamelAddress *) addr)->addresses->len;
+ for (i = 0; i < len; i++) {
+ a = g_ptr_array_index (((CamelAddress *) addr)->addresses, i);
+ if (a->name && !strcmp (a->name, name)) {
+ if (addressp)
+ *addressp = a->address;
+ return i;
+ }
+ }
+ return -1;
+}
+
+static gboolean
+domain_contains_only_ascii (const gchar *address,
+ gint *at_pos)
+{
+ gint pos;
+ gboolean all_ascii = TRUE;
+
+ g_return_val_if_fail (address != NULL, TRUE);
+ g_return_val_if_fail (at_pos != NULL, TRUE);
+
+ *at_pos = -1;
+ for (pos = 0; address[pos]; pos++) {
+ all_ascii = all_ascii && address[pos] > 0;
+ if (*at_pos == -1 && address[pos] == '@') {
+ *at_pos = pos;
+ all_ascii = TRUE;
+ }
+ }
+
+ /* Do not change anything when there is no domain part
+ of the email address */
+ return all_ascii || *at_pos == -1;
+}
+
+/**
+ * camel_internet_address_ensure_ascii_domains:
+ * @addr: a #CamelInternetAddress
+ *
+ * Ensures that all email address' domains will be ASCII encoded,
+ * which means that any non-ASCII letters will be properly encoded.
+ * This includes IDN (Internationalized Domain Names).
+ *
+ * Since: 3.16
+ **/
+void
+camel_internet_address_ensure_ascii_domains (CamelInternetAddress *addr)
+{
+ struct _address *a;
+ gint i, len;
+
+ g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr));
+
+ len = ((CamelAddress *) addr)->addresses->len;
+ for (i = 0; i < len; i++) {
+ gint at_pos = -1;
+ a = g_ptr_array_index (((CamelAddress *) addr)->addresses, i);
+ if (a->address && !domain_contains_only_ascii (a->address, &at_pos)) {
+ gchar *address, *domain;
+
+ domain = camel_host_idna_to_ascii (a->address + at_pos + 1);
+ if (at_pos >= 0) {
+ gchar *name = g_strndup (a->address, at_pos);
+ address = g_strconcat (name, "@", domain, NULL);
+ } else {
+ address = domain;
+ domain = NULL;
+ }
+
+ g_free (domain);
+ g_free (a->address);
+ a->address = address;
+ }
+ }
+}
+
+/**
+ * camel_internet_address_find_address:
+ * @addr: a #CamelInternetAddress object
+ * @address: address to lookup
+ * @namep: holder for the matching name, or %NULL, if not required.
+ *
+ * Find an address by address.
+ *
+ * Returns: the index of the address, or %-1 if not found
+ **/
+gint
+camel_internet_address_find_address (CamelInternetAddress *addr,
+ const gchar *address,
+ const gchar **namep)
+{
+ struct _address *a;
+ gint i, len;
+
+ g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), -1);
+
+ len = ((CamelAddress *) addr)->addresses->len;
+ for (i = 0; i < len; i++) {
+ a = g_ptr_array_index (((CamelAddress *) addr)->addresses, i);
+ if (!strcmp (a->address, address)) {
+ if (namep)
+ *namep = a->name;
+ return i;
+ }
+ }
+ return -1;
+}
+
+static void
+cia_encode_addrspec (GString *out,
+ const gchar *addr)
+{
+ const gchar *at, *p;
+
+ at = strchr (addr, '@');
+ if (at == NULL)
+ goto append;
+
+ p = addr;
+ while (p < at) {
+ gchar c = *p++;
+
+ /* strictly by rfc, we should split local parts on dots.
+ * however i think 2822 changes this, and not many clients grok it, so
+ * just quote the whole local part if need be */
+ if (!(camel_mime_is_atom (c) || c == '.')) {
+ g_string_append_c (out, '"');
+
+ p = addr;
+ while (p < at) {
+ c = *p++;
+ if (c == '"' || c == '\\')
+ g_string_append_c (out, '\\');
+ g_string_append_c (out, c);
+ }
+ g_string_append_c (out, '"');
+ g_string_append (out, p);
+
+ return;
+ }
+ }
+
+append:
+ g_string_append (out, addr);
+}
+
+/**
+ * camel_internet_address_encode_address:
+ * @len: the length of the line the address is being appended to
+ * @name: the unencoded real name associated with the address
+ * @addr: the routing address
+ *
+ * Encode a single address ready for internet usage. Header folding
+ * as per rfc822 is also performed, based on the length *@len. If @len
+ * is %NULL, then no folding will occur.
+ *
+ * Note: The value at *@in will be updated based on any linewrapping done
+ *
+ * Returns: the encoded address
+ **/
+gchar *
+camel_internet_address_encode_address (gint *inlen,
+ const gchar *real,
+ const gchar *addr)
+{
+ gchar *name;
+ gchar *ret = NULL;
+ gint len = 0;
+ GString *out;
+
+ g_return_val_if_fail (addr, NULL);
+
+ name = camel_header_encode_phrase ((const guchar *) real);
+ out = g_string_new ("");
+
+ if (inlen != NULL)
+ len = *inlen;
+
+ if (name && name[0]) {
+ if (inlen != NULL && (strlen (name) + len) > CAMEL_FOLD_SIZE) {
+ gchar *folded = camel_header_address_fold (name, len);
+ gchar *last;
+ g_string_append (out, folded);
+ g_free (folded);
+ last = strrchr (out->str, '\n');
+ if (last)
+ len = last - (out->str + out->len);
+ else
+ len = out->len;
+ } else {
+ g_string_append (out, name);
+ len += strlen (name);
+ }
+ }
+
+ /* NOTE: Strictly speaking, we could and should split the
+ * internal address up if we need to, on atom or specials
+ * boundaries - however, to aid interoperability with mailers
+ * that will probably not handle this case, we will just move
+ * the whole address to its own line. */
+ if (inlen != NULL && (strlen (addr) + len) > CAMEL_FOLD_SIZE) {
+ g_string_append (out, "\n\t");
+ len = 1;
+ }
+
+ len -= out->len;
+
+ if (name && name[0])
+ g_string_append_printf (out, " <");
+ cia_encode_addrspec (out, addr);
+ if (name && name[0])
+ g_string_append_printf (out, ">");
+
+ len += out->len;
+
+ if (inlen != NULL)
+ *inlen = len;
+
+ g_free (name);
+
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ return ret;
+}
+
+/**
+ * camel_internet_address_format_address:
+ * @name: a name, quotes may be stripped from it
+ * @addr: an rfc822 routing address
+ *
+ * Function to format a single address, suitable for display.
+ *
+ * Returns: a nicely formatted string containing the rfc822 address
+ **/
+gchar *
+camel_internet_address_format_address (const gchar *name,
+ const gchar *addr)
+{
+ gchar *ret = NULL;
+
+ g_return_val_if_fail (addr, NULL);
+
+ if (name && name[0]) {
+ const gchar *p = name;
+ gchar *o, c;
+
+ while ((c = *p++)) {
+ if (c == '\"' || c == ',') {
+ o = ret = g_malloc (strlen (name) + 3 + strlen (addr) + 3 + 1);
+ p = name;
+ *o++ = '\"';
+ while ((c = *p++))
+ if (c != '\"')
+ *o++ = c;
+ *o++ = '\"';
+ sprintf (o, " <%s>", addr);
+ d (printf ("encoded '%s' => '%s'\n", name, ret));
+ return ret;
+ }
+ }
+ ret = g_strdup_printf ("%s <%s>", name, addr);
+ } else
+ ret = g_strdup (addr);
+
+ return ret;
+}
diff --git a/src/camel/camel-internet-address.h b/src/camel/camel-internet-address.h
new file mode 100644
index 000000000..4253abb6b
--- /dev/null
+++ b/src/camel/camel-internet-address.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_INTERNET_ADDRESS_H
+#define CAMEL_INTERNET_ADDRESS_H
+
+#include <camel/camel-address.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_INTERNET_ADDRESS \
+ (camel_internet_address_get_type ())
+#define CAMEL_INTERNET_ADDRESS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_INTERNET_ADDRESS, CamelInternetAddress))
+#define CAMEL_INTERNET_ADDRESS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_INTERNET_ADDRESS, CamelInternetAddressClass))
+#define CAMEL_IS_INTERNET_ADDRESS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_INTERNET_ADDRESS))
+#define CAMEL_IS_INTERNET_ADDRESS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_INTERNET_ADDRESS))
+#define CAMEL_INTERNET_ADDRESS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_INTERNET_ADDRESS, CamelInternetAddressClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelInternetAddress CamelInternetAddress;
+typedef struct _CamelInternetAddressClass CamelInternetAddressClass;
+typedef struct _CamelInternetAddressPrivate CamelInternetAddressPrivate;
+
+struct _CamelInternetAddress {
+ CamelAddress parent;
+ CamelInternetAddressPrivate *priv;
+};
+
+struct _CamelInternetAddressClass {
+ CamelAddressClass parent_class;
+};
+
+GType camel_internet_address_get_type (void);
+CamelInternetAddress *
+ camel_internet_address_new (void);
+gint camel_internet_address_add (CamelInternetAddress *addr,
+ const gchar *name,
+ const gchar *address);
+gboolean camel_internet_address_get (CamelInternetAddress *addr,
+ gint index,
+ const gchar **namep,
+ const gchar **addressp);
+gint camel_internet_address_find_name (CamelInternetAddress *addr,
+ const gchar *name,
+ const gchar **addressp);
+gint camel_internet_address_find_address
+ (CamelInternetAddress *addr,
+ const gchar *address,
+ const gchar **namep);
+void camel_internet_address_ensure_ascii_domains
+ (CamelInternetAddress *addr);
+
+/* utility functions, for network/display formatting */
+gchar * camel_internet_address_encode_address
+ (gint *len,
+ const gchar *name,
+ const gchar *addr);
+gchar * camel_internet_address_format_address
+ (const gchar *name,
+ const gchar *addr);
+
+G_END_DECLS
+
+#endif /* CAMEL_INTERNET_ADDRESS_H */
diff --git a/src/camel/camel-junk-filter.c b/src/camel/camel-junk-filter.c
new file mode 100644
index 000000000..54fa5449d
--- /dev/null
+++ b/src/camel/camel-junk-filter.c
@@ -0,0 +1,177 @@
+/*
+ * camel-junk-filter.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-operation.h"
+#include "camel-junk-filter.h"
+
+G_DEFINE_INTERFACE (CamelJunkFilter, camel_junk_filter, G_TYPE_OBJECT)
+
+static void
+camel_junk_filter_default_init (CamelJunkFilterInterface *iface)
+{
+}
+
+/**
+ * camel_junk_filter_classify:
+ * @junk_filter: a #CamelJunkFilter
+ * @message: a #CamelMimeMessage
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Classifies @message as junk, not junk or inconclusive.
+ *
+ * If an error occurs, the function sets @error and returns
+ * %CAMEL_JUNK_STATUS_ERROR.
+ *
+ * Returns: the junk status determined by @junk_filter
+ *
+ * Since: 3.2
+ **/
+CamelJunkStatus
+camel_junk_filter_classify (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelJunkFilterInterface *iface;
+
+ g_return_val_if_fail (CAMEL_IS_JUNK_FILTER (junk_filter), 0);
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), 0);
+
+ iface = CAMEL_JUNK_FILTER_GET_INTERFACE (junk_filter);
+ g_return_val_if_fail (iface->classify != NULL, 0);
+
+ return iface->classify (
+ junk_filter, message, cancellable, error);
+}
+
+/**
+ * camel_junk_filter_learn_junk:
+ * @junk_filter: a #CamelJunkFilter
+ * @message: a #CamelMimeMessage
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Instructs @junk_filter to classify @message as junk. If using an
+ * adaptive junk filtering algorithm, explicitly marking @message as
+ * junk will influence the classification of future messages.
+ *
+ * If an error occurs, the function sets @error and returns %FALSE.
+ *
+ * Returns: %TRUE if @message was successfully classified
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_junk_filter_learn_junk (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelJunkFilterInterface *iface;
+
+ g_return_val_if_fail (CAMEL_IS_JUNK_FILTER (junk_filter), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
+
+ iface = CAMEL_JUNK_FILTER_GET_INTERFACE (junk_filter);
+ g_return_val_if_fail (iface->learn_junk != NULL, FALSE);
+
+ return iface->learn_junk (
+ junk_filter, message, cancellable, error);
+}
+
+/**
+ * camel_junk_filter_learn_not_junk:
+ * @junk_filter: a #CamelJunkFilter
+ * @message: a #CamelMimeMessage
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Instructs @junk_filter to classify @message as not junk. If using an
+ * adaptive junk filtering algorithm, explicitly marking @message as not
+ * junk will influence the classification of future messages.
+ *
+ * If an error occurs, the function sets @error and returns %FALSE.
+ *
+ * Returns: %TRUE if @message was successfully classified
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_junk_filter_learn_not_junk (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelJunkFilterInterface *iface;
+
+ g_return_val_if_fail (CAMEL_IS_JUNK_FILTER (junk_filter), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
+
+ iface = CAMEL_JUNK_FILTER_GET_INTERFACE (junk_filter);
+ g_return_val_if_fail (iface->learn_not_junk != NULL, FALSE);
+
+ return iface->learn_not_junk (
+ junk_filter, message, cancellable, error);
+}
+
+/**
+ * camel_junk_filter_synchronize:
+ * @junk_filter: a #CamelJunkFilter
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Instructs @junk_filter to flush any in-memory caches to disk, if
+ * applicable. When filtering many messages, delaying this step until
+ * all messages have been classified can improve performance.
+ *
+ * If an error occurs, the function sets @error and returns %FALSE.
+ *
+ * Returns: %TRUE if @junk_filter was successfully synchronized
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_junk_filter_synchronize (CamelJunkFilter *junk_filter,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelJunkFilterInterface *iface;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_JUNK_FILTER (junk_filter), FALSE);
+
+ /* This method is optional. */
+ iface = CAMEL_JUNK_FILTER_GET_INTERFACE (junk_filter);
+
+ if (iface->synchronize != NULL) {
+ camel_operation_push_message (
+ cancellable, _("Synchronizing junk database"));
+
+ success = iface->synchronize (
+ junk_filter, cancellable, error);
+
+ camel_operation_pop_message (cancellable);
+ }
+
+ return success;
+}
+
diff --git a/src/camel/camel-junk-filter.h b/src/camel/camel-junk-filter.h
new file mode 100644
index 000000000..b27064fba
--- /dev/null
+++ b/src/camel/camel-junk-filter.h
@@ -0,0 +1,100 @@
+/*
+ * camel-junk-filter.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_JUNK_FILTER_H
+#define CAMEL_JUNK_FILTER_H
+
+#include <camel/camel-enums.h>
+#include <camel/camel-mime-message.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_JUNK_FILTER \
+ (camel_junk_filter_get_type ())
+#define CAMEL_JUNK_FILTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_JUNK_FILTER, CamelJunkFilter))
+#define CAMEL_JUNK_FILTER_INTERFACE(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_JUNK_FILTER, CamelJunkFilterInterface))
+#define CAMEL_IS_JUNK_FILTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_JUNK_FILTER))
+#define CAMEL_IS_JUNK_FILTER_INTERFACE(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_JUNK_FILTER))
+#define CAMEL_JUNK_FILTER_GET_INTERFACE(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE \
+ ((obj), CAMEL_TYPE_JUNK_FILTER, CamelJunkFilterInterface))
+
+G_BEGIN_DECLS
+
+/**
+ * CamelJunkFilter:
+ *
+ * Since: 3.2
+ **/
+typedef struct _CamelJunkFilter CamelJunkFilter;
+typedef struct _CamelJunkFilterInterface CamelJunkFilterInterface;
+
+struct _CamelJunkFilterInterface {
+ GTypeInterface parent_interface;
+
+ /* Required Methods */
+ CamelJunkStatus (*classify) (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*learn_junk) (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*learn_not_junk) (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* Optional Methods */
+ gboolean (*synchronize) (CamelJunkFilter *junk_filter,
+ GCancellable *cancellable,
+ GError **error);
+};
+
+GType camel_junk_filter_get_type (void) G_GNUC_CONST;
+CamelJunkStatus camel_junk_filter_classify (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_junk_filter_learn_junk (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_junk_filter_learn_not_junk
+ (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_junk_filter_synchronize (CamelJunkFilter *junk_filter,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_JUNK_FILTER_H */
diff --git a/src/camel/camel-local-settings.c b/src/camel/camel-local-settings.c
new file mode 100644
index 000000000..800d64b06
--- /dev/null
+++ b/src/camel/camel-local-settings.c
@@ -0,0 +1,220 @@
+/*
+ * camel-local-settings.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-local-settings.h"
+
+#include <string.h>
+
+#define CAMEL_LOCAL_SETTINGS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_LOCAL_SETTINGS, CamelLocalSettingsPrivate))
+
+struct _CamelLocalSettingsPrivate {
+ GMutex property_lock;
+ gchar *path;
+};
+
+enum {
+ PROP_0,
+ PROP_PATH
+};
+
+G_DEFINE_TYPE (
+ CamelLocalSettings,
+ camel_local_settings,
+ CAMEL_TYPE_STORE_SETTINGS)
+
+static void
+local_settings_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PATH:
+ camel_local_settings_set_path (
+ CAMEL_LOCAL_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+local_settings_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PATH:
+ g_value_take_string (
+ value,
+ camel_local_settings_dup_path (
+ CAMEL_LOCAL_SETTINGS (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+local_settings_finalize (GObject *object)
+{
+ CamelLocalSettingsPrivate *priv;
+
+ priv = CAMEL_LOCAL_SETTINGS_GET_PRIVATE (object);
+
+ g_mutex_clear (&priv->property_lock);
+
+ g_free (priv->path);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_local_settings_parent_class)->finalize (object);
+}
+
+static void
+camel_local_settings_class_init (CamelLocalSettingsClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelLocalSettingsPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = local_settings_set_property;
+ object_class->get_property = local_settings_get_property;
+ object_class->finalize = local_settings_finalize;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PATH,
+ g_param_spec_string (
+ "path",
+ "Path",
+ "File path to the local store",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_local_settings_init (CamelLocalSettings *settings)
+{
+ settings->priv = CAMEL_LOCAL_SETTINGS_GET_PRIVATE (settings);
+ g_mutex_init (&settings->priv->property_lock);
+}
+
+/**
+ * camel_local_settings_get_path:
+ * @settings: a #CamelLocalSettings
+ *
+ * Returns the file path to the root of the local mail store.
+ *
+ * Returns: the file path to the local store
+ *
+ * Since: 3.4
+ **/
+const gchar *
+camel_local_settings_get_path (CamelLocalSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_LOCAL_SETTINGS (settings), NULL);
+
+ return settings->priv->path;
+}
+
+/**
+ * camel_local_settings_dup_path:
+ * @settings: a #CamelLocalSettings
+ *
+ * Thread-safe variation of camel_local_settings_get_path().
+ * Use this function when accessing @settings from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelLocalSettings:path
+ *
+ * Since: 3.4
+ **/
+gchar *
+camel_local_settings_dup_path (CamelLocalSettings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_LOCAL_SETTINGS (settings), NULL);
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ protected = camel_local_settings_get_path (settings);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_local_settings_set_path:
+ * @settings: a #CamelLocalSettings
+ * @path: the file path to the local store
+ *
+ * Sets the file path to the root of the local mail store. Any
+ * trailing directory separator characters will be stripped off
+ * of the #CamelLocalSettings:path property.
+ *
+ * Since: 3.4
+ **/
+void
+camel_local_settings_set_path (CamelLocalSettings *settings,
+ const gchar *path)
+{
+ gsize length = 0;
+ gchar *new_path;
+
+ g_return_if_fail (CAMEL_IS_LOCAL_SETTINGS (settings));
+
+ /* Exclude trailing directory separators. */
+ if (path != NULL) {
+ length = strlen (path);
+ while (length > 0) {
+ if (G_IS_DIR_SEPARATOR (path[length - 1]))
+ length--;
+ else
+ break;
+ }
+ }
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ new_path = g_strndup (path, length);
+
+ if (g_strcmp0 (settings->priv->path, new_path) == 0) {
+ g_mutex_unlock (&settings->priv->property_lock);
+ g_free (new_path);
+ return;
+ }
+
+ g_free (settings->priv->path);
+ settings->priv->path = new_path;
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ g_object_notify (G_OBJECT (settings), "path");
+}
+
diff --git a/src/camel/camel-local-settings.h b/src/camel/camel-local-settings.h
new file mode 100644
index 000000000..6ae5d1dca
--- /dev/null
+++ b/src/camel/camel-local-settings.h
@@ -0,0 +1,78 @@
+/*
+ * camel-local-settings.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_LOCAL_SETTINGS_H
+#define CAMEL_LOCAL_SETTINGS_H
+
+#include <camel/camel-store-settings.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_LOCAL_SETTINGS \
+ (camel_local_settings_get_type ())
+#define CAMEL_LOCAL_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_LOCAL_SETTINGS, CamelLocalSettings))
+#define CAMEL_LOCAL_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_LOCAL_SETTINGS, CamelLocalSettingsClass))
+#define CAMEL_IS_LOCAL_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_LOCAL_SETTINGS))
+#define CAMEL_IS_LOCAL_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_LOCAL_SETTINGS))
+#define CAMEL_LOCAL_SETTINGS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_LOCAL_SETTINGS, CamelLocalSettingsClass))
+
+G_BEGIN_DECLS
+
+/**
+ * CamelLocalSettings:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.4
+ **/
+typedef struct _CamelLocalSettings CamelLocalSettings;
+typedef struct _CamelLocalSettingsClass CamelLocalSettingsClass;
+typedef struct _CamelLocalSettingsPrivate CamelLocalSettingsPrivate;
+
+struct _CamelLocalSettings {
+ CamelStoreSettings parent;
+ CamelLocalSettingsPrivate *priv;
+};
+
+struct _CamelLocalSettingsClass {
+ CamelStoreSettingsClass parent_class;
+};
+
+GType camel_local_settings_get_type (void) G_GNUC_CONST;
+const gchar * camel_local_settings_get_path (CamelLocalSettings *settings);
+gchar * camel_local_settings_dup_path (CamelLocalSettings *settings);
+void camel_local_settings_set_path (CamelLocalSettings *settings,
+ const gchar *path);
+
+G_END_DECLS
+
+#endif /* CAMEL_LOCAL_SETTINGS_H */
+
diff --git a/src/camel/camel-lock-client.c b/src/camel/camel-lock-client.c
new file mode 100644
index 000000000..a08341e88
--- /dev/null
+++ b/src/camel/camel-lock-client.c
@@ -0,0 +1,352 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-lock-client.h"
+#include "camel-lock-helper.h"
+#include "camel-object.h"
+
+#define d(x)
+
+#define CHECK_CALL(x) G_STMT_START { \
+ if ((x) == -1) { \
+ g_debug ("%s: Call of '" #x "' failed: %s", G_STRFUNC, g_strerror (errno)); \
+ } \
+ } G_STMT_END
+
+static GMutex lock_lock;
+#define LOCK() g_mutex_lock(&lock_lock)
+#define UNLOCK() g_mutex_unlock(&lock_lock)
+
+static gint lock_sequence;
+static gint lock_helper_pid = -1;
+static gint lock_stdin_pipe[2], lock_stdout_pipe[2];
+
+static gint read_n (gint fd, gpointer buffer, gint inlen)
+{
+ gchar *p = buffer;
+ gint 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 gint write_n (gint fd, gpointer buffer, gint inlen)
+{
+ gchar *p = buffer;
+ gint 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;
+}
+
+static gint
+lock_helper_init (GError **error)
+{
+ gint i, dupfd1, dupfd2;
+
+ lock_stdin_pipe[0] = -1;
+ lock_stdin_pipe[1] = -1;
+ lock_stdout_pipe[0] = -1;
+ lock_stdout_pipe[1] = -1;
+ if (pipe (lock_stdin_pipe) == -1
+ || pipe (lock_stdout_pipe) == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot build locking helper pipe: %s"),
+ g_strerror (errno));
+ if (lock_stdin_pipe[0] != -1)
+ close (lock_stdin_pipe[0]);
+ if (lock_stdin_pipe[1] != -1)
+ close (lock_stdin_pipe[1]);
+ if (lock_stdout_pipe[0] != -1)
+ close (lock_stdout_pipe[0]);
+ if (lock_stdout_pipe[1] != -1)
+ close (lock_stdout_pipe[1]);
+
+ return -1;
+ }
+
+ lock_helper_pid = fork ();
+ switch (lock_helper_pid) {
+ case -1:
+ close (lock_stdin_pipe[0]);
+ close (lock_stdin_pipe[1]);
+ close (lock_stdout_pipe[0]);
+ close (lock_stdout_pipe[1]);
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot fork locking helper: %s"),
+ g_strerror (errno));
+ return -1;
+ case 0:
+ close (STDIN_FILENO);
+ dupfd1 = dup (lock_stdin_pipe[0]);
+ close (STDOUT_FILENO);
+ dupfd2 = dup (lock_stdout_pipe[1]);
+ close (lock_stdin_pipe[0]);
+ close (lock_stdin_pipe[1]);
+ close (lock_stdout_pipe[0]);
+ close (lock_stdout_pipe[1]);
+ for (i = 3; i < 255; i++)
+ close (i);
+ execl (CAMEL_LIBEXECDIR "/camel-lock-helper-" API_VERSION, "camel-lock-helper", NULL);
+
+ if (dupfd1 != -1)
+ close (dupfd1);
+ if (dupfd2 != -1)
+ close (dupfd2);
+
+ /* it'll pick this up when it tries to use us */
+ exit (255);
+ default:
+ close (lock_stdin_pipe[0]);
+ close (lock_stdout_pipe[1]);
+
+ /* so the child knows when we vanish */
+ CHECK_CALL (fcntl (lock_stdin_pipe[1], F_SETFD, FD_CLOEXEC));
+ CHECK_CALL (fcntl (lock_stdout_pipe[0], F_SETFD, FD_CLOEXEC));
+ }
+
+ return 0;
+}
+
+gint
+camel_lock_helper_lock (const gchar *path,
+ GError **error)
+{
+ struct _CamelLockHelperMsg *msg;
+ gint len = strlen (path);
+ gint res = -1;
+ gint retry = 3;
+
+ LOCK ();
+
+ if (lock_helper_pid == -1) {
+ if (lock_helper_init (error) == -1) {
+ UNLOCK ();
+ return -1;
+ }
+ }
+
+ msg = alloca (len + sizeof (*msg));
+again:
+ msg->magic = CAMEL_LOCK_HELPER_MAGIC;
+ msg->seq = lock_sequence;
+ msg->id = CAMEL_LOCK_HELPER_LOCK;
+ msg->data = len;
+ memcpy (msg + 1, path, len);
+
+ write_n (lock_stdin_pipe[1], msg, len + sizeof (*msg));
+
+ do {
+ /* should also have a timeout here? cancellation? */
+ len = read_n (lock_stdout_pipe[0], msg, sizeof (*msg));
+ if (len == 0) {
+ /* child quit, do we try ressurect it? */
+ res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
+ /* if the child exited, this should get it, waidpid returns 0 if the child hasn't */
+ if (waitpid (lock_helper_pid, NULL, WNOHANG) > 0) {
+ lock_helper_pid = -1;
+ close (lock_stdout_pipe[0]);
+ close (lock_stdin_pipe[1]);
+ lock_stdout_pipe[0] = -1;
+ lock_stdin_pipe[1] = -1;
+ }
+ goto fail;
+ }
+
+ if (msg->magic != CAMEL_LOCK_HELPER_RETURN_MAGIC
+ || msg->seq > lock_sequence) {
+ res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
+ d (printf ("lock child protocol error\n"));
+ g_set_error (
+ error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC,
+ _("Could not lock '%s': protocol "
+ "error with lock-helper"), path);
+ goto fail;
+ }
+ } while (msg->seq < lock_sequence);
+
+ if (msg->seq == lock_sequence) {
+ switch (msg->id) {
+ case CAMEL_LOCK_HELPER_STATUS_OK:
+ d (printf ("lock child locked ok, id is %d\n", msg->data));
+ res = msg->data;
+ break;
+ default:
+ g_set_error (
+ error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC,
+ _("Could not lock '%s'"), path);
+ d (printf ("locking failed ! status = %d\n", msg->id));
+ break;
+ }
+ } else if (retry > 0) {
+ d (printf ("sequence failure, lost message? retry?\n"));
+ retry--;
+ goto again;
+ } else {
+ g_set_error (
+ error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC,
+ _("Could not lock '%s': protocol "
+ "error with lock-helper"), path);
+ }
+
+fail:
+ lock_sequence++;
+
+ UNLOCK ();
+
+ return res;
+}
+
+gint camel_lock_helper_unlock (gint lockid)
+{
+ struct _CamelLockHelperMsg *msg;
+ gint res = -1;
+ gint retry = 3;
+ gint len;
+
+ d (printf ("unlocking lock id %d\n", lockid));
+
+ LOCK ();
+
+ /* impossible to unlock if we haven't locked yet */
+ if (lock_helper_pid == -1) {
+ UNLOCK ();
+ return -1;
+ }
+
+ msg = alloca (sizeof (*msg));
+again:
+ msg->magic = CAMEL_LOCK_HELPER_MAGIC;
+ msg->seq = lock_sequence;
+ msg->id = CAMEL_LOCK_HELPER_UNLOCK;
+ msg->data = lockid;
+
+ write_n (lock_stdin_pipe[1], msg, sizeof (*msg));
+
+ do {
+ /* should also have a timeout here? cancellation? */
+ len = read_n (lock_stdout_pipe[0], msg, sizeof (*msg));
+ if (len == 0) {
+ /* child quit, do we try ressurect it? */
+ res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
+ if (waitpid (lock_helper_pid, NULL, WNOHANG) > 0) {
+ lock_helper_pid = -1;
+ close (lock_stdout_pipe[0]);
+ close (lock_stdin_pipe[1]);
+ lock_stdout_pipe[0] = -1;
+ lock_stdin_pipe[1] = -1;
+ }
+ goto fail;
+ }
+
+ if (msg->magic != CAMEL_LOCK_HELPER_RETURN_MAGIC
+ || msg->seq > lock_sequence) {
+ goto fail;
+ }
+ } while (msg->seq < lock_sequence);
+
+ if (msg->seq == lock_sequence) {
+ switch (msg->id) {
+ case CAMEL_LOCK_HELPER_STATUS_OK:
+ d (printf ("lock child unlocked ok\n"));
+ res = 0;
+ break;
+ default:
+ d (printf ("locking failed !\n"));
+ break;
+ }
+ } else if (retry > 0) {
+ d (printf ("sequence failure, lost message? retry?\n"));
+ lock_sequence++;
+ retry--;
+ goto again;
+ }
+
+fail:
+ lock_sequence++;
+
+ UNLOCK ();
+
+ return res;
+}
+
+#if 0
+gint main (gint argc, gchar **argv)
+{
+ gint id1, id2;
+
+ d (printf ("locking started\n"));
+ lock_helper_init ();
+
+ id1 = camel_lock_helper_lock ("1 path 1");
+ if (id1 != -1) {
+ d (printf ("lock ok, unlock\n"));
+ camel_lock_helper_unlock (id1);
+ }
+
+ id1 = camel_lock_helper_lock ("2 path 1");
+ id2 = camel_lock_helper_lock ("2 path 2");
+ camel_lock_helper_unlock (id2);
+ camel_lock_helper_unlock (id1);
+
+ id1 = camel_lock_helper_lock ("3 path 1");
+ id2 = camel_lock_helper_lock ("3 path 2");
+ camel_lock_helper_unlock (id1);
+}
+#endif
diff --git a/src/camel/camel-lock-client.h b/src/camel/camel-lock-client.h
new file mode 100644
index 000000000..0df2f0064
--- /dev/null
+++ b/src/camel/camel-lock-client.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/* defines protocol for lock helper process ipc */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_LOCK_CLIENT_H
+#define CAMEL_LOCK_CLIENT_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gint camel_lock_helper_lock (const gchar *path , GError **error);
+gint camel_lock_helper_unlock (gint lockid);
+
+G_END_DECLS
+
+#endif /* CAMEL_LOCK_HELPER_H */
diff --git a/src/camel/camel-lock-helper.c b/src/camel/camel-lock-helper.c
new file mode 100644
index 000000000..f68429c5c
--- /dev/null
+++ b/src/camel/camel-lock-helper.c
@@ -0,0 +1,398 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/* lock helper process */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <utime.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <glib/gstdio.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;
+ gint id;
+ gint depth;
+ time_t stamp; /* when last updated */
+ gchar path[1];
+};
+
+static gint 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 gint
+read_n (gint fd,
+ gpointer buffer,
+ gint inlen)
+{
+ gchar *p = buffer;
+ gint 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 gint
+write_n (gint fd,
+ gpointer buffer,
+ gint inlen)
+{
+ gchar *p = buffer;
+ gint 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;
+}
+
+gchar *gettext (const gchar *msgid);
+
+gchar *
+gettext (const gchar *msgid)
+{
+ return NULL;
+}
+
+static gint
+lock_path (const gchar *path,
+ guint32 *lockid)
+{
+ struct _lock_info *info = NULL;
+ gint 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 (g_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) {
+ if (seteuid (lock_real_uid) == -1) {
+ g_warn_if_reached ();
+ }
+ res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
+ goto fail;
+ }
+ if (seteuid (lock_real_uid) == -1) {
+ g_warn_if_reached ();
+ }
+ } 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;
+ }
+
+ g_strlcpy (info->path, path, strlen (path) + 1);
+ info->id = lock_id;
+ info->depth = 1;
+ info->next = lock_info_list;
+ info->stamp = time (NULL);
+ 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 gint
+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) {
+ if (seteuid (lock_root_uid) == -1) {
+ g_warn_if_reached ();
+ }
+ camel_unlock_dot (info->path);
+ if (seteuid (lock_real_uid) == -1) {
+ g_warn_if_reached ();
+ }
+ } 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 gchar *path)
+{
+ gchar *name;
+ gsize name_len;
+
+ /* we could also check that we haven't had our lock stolen from us here */
+
+ name_len = strlen (path) + 10;
+ name = alloca (name_len);
+ g_snprintf (name, name_len, "%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) {
+ if (seteuid (lock_real_uid) == -1) {
+ g_warn_if_reached ();
+ }
+ }
+#endif
+}
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ struct _CamelLockHelperMsg msg;
+ gint len;
+ gint res;
+ gchar *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 (NULL);
+ 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 (NULL);
+ 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/src/camel/camel-lock-helper.h b/src/camel/camel-lock-helper.h
new file mode 100644
index 000000000..609486d64
--- /dev/null
+++ b/src/camel/camel-lock-helper.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/* defines protocol for lock helper process ipc */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_LOCK_HELPER_H
+#define CAMEL_LOCK_HELPER_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+struct _CamelLockHelperMsg {
+ guint32 magic;
+ guint32 seq;
+ guint32 id;
+ guint32 data;
+};
+
+/* magic values */
+enum {
+ CAMEL_LOCK_HELPER_MAGIC = 0xABADF00D,
+ CAMEL_LOCK_HELPER_RETURN_MAGIC = 0xDEADBEEF
+};
+
+/* return status */
+enum {
+ CAMEL_LOCK_HELPER_STATUS_OK = 0,
+ CAMEL_LOCK_HELPER_STATUS_PROTOCOL,
+ CAMEL_LOCK_HELPER_STATUS_NOMEM,
+ CAMEL_LOCK_HELPER_STATUS_SYSTEM,
+ CAMEL_LOCK_HELPER_STATUS_INVALID /* not allowed to lock/doesn't exist etc */
+};
+
+/* commands */
+enum {
+ CAMEL_LOCK_HELPER_LOCK = 0xf0f,
+ CAMEL_LOCK_HELPER_UNLOCK = 0xf0f0
+};
+
+/* seconds between lock refreshes */
+#define CAMEL_DOT_LOCK_REFRESH (30)
+
+G_END_DECLS
+
+#endif /* CAMEL_LOCK_HELPER_H */
diff --git a/src/camel/camel-lock.c b/src/camel/camel-lock.c
new file mode 100644
index 000000000..6bbda777c
--- /dev/null
+++ b/src/camel/camel-lock.c
@@ -0,0 +1,449 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/stat.h>
+
+#ifdef USE_DOT_LOCKING
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#endif
+
+#ifdef USE_FCNTL_LOCKING
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+
+#ifdef USE_FLOCK_LOCKING
+#include <sys/file.h>
+#endif
+
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#ifdef G_OS_WIN32
+#include <windows.h>
+#endif
+
+#include "camel-lock.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+#define CHECK_CALL(x) G_STMT_START { \
+ if ((x) == -1) { \
+ g_debug ("%s: Call of '" #x "' failed: %s", G_STRFUNC, g_strerror (errno)); \
+ } \
+ } G_STMT_END
+
+/**
+ * camel_lock_dot:
+ * @path:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Create an exclusive lock using .lock semantics.
+ * All locks are equivalent to write locks (exclusive).
+ *
+ * Returns: -1 on error, sets @ex appropriately.
+ **/
+gint
+camel_lock_dot (const gchar *path,
+ GError **error)
+{
+#ifdef USE_DOT_LOCKING
+ gchar *locktmp, *lock;
+ gsize lock_len = 0;
+ gsize locktmp_len = 0;
+ gint retry = 0;
+ gint 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_len = strlen (path) + strlen (".lock") + 1;
+ lock = alloca (lock_len);
+ g_snprintf (lock, lock_len, "%s.lock", path);
+ locktmp_len = strlen (path) + strlen ("XXXXXX") + 1;
+ locktmp = alloca (locktmp_len);
+
+ 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);
+
+ g_snprintf (locktmp, locktmp_len, "%sXXXXXX", path);
+ fdtmp = g_mkstemp (locktmp);
+ if (fdtmp == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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 (g_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 (g_stat (lock, &st) == 0) {
+ time_t now = time (NULL);
+ printf ("There is an existing lock %" G_GINT64_FORMAT "seconds old\n", (gint64) now - (gint64) 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));
+
+ g_set_error (
+ error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Timed out trying to get lock file on %s. "
+ "Try again later."), path);
+ return -1;
+#else /* !USE_DOT_LOCKING */
+ return 0;
+#endif
+}
+
+/**
+ * camel_unlock_dot:
+ * @path:
+ *
+ * Attempt to unlock a .lock lock.
+ **/
+void
+camel_unlock_dot (const gchar *path)
+{
+#ifdef USE_DOT_LOCKING
+ gchar *lock;
+ gsize lock_len;
+
+ lock_len = strlen (path) + strlen (".lock") + 1;
+ lock = alloca (lock_len);
+ g_snprintf (lock, lock_len, "%s.lock", path);
+ d (printf ("unlocking %s\n", lock));
+ CHECK_CALL (unlink (lock));
+#endif
+}
+
+/**
+ * camel_lock_fcntl:
+ * @fd:
+ * @type:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Create a lock using fcntl(2).
+ *
+ * @type is CAMEL_LOCK_WRITE or CAMEL_LOCK_READ,
+ * to create exclusive or shared read locks
+ *
+ * Returns: -1 on error.
+ **/
+gint
+camel_lock_fcntl (gint fd,
+ CamelLockType type,
+ GError **error)
+{
+#ifdef USE_FCNTL_LOCKING
+ 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) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Failed to get lock using fcntl(2): %s"),
+ g_strerror (errno));
+ return -1;
+ } else {
+ static gint 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 (gint fd)
+{
+#ifdef USE_FCNTL_LOCKING
+ struct flock lock;
+
+ d (printf ("fcntl unlocking %d\n", fd));
+
+ memset (&lock, 0, sizeof (lock));
+ lock.l_type = F_UNLCK;
+ CHECK_CALL (fcntl (fd, F_SETLK, &lock));
+#endif
+}
+
+/**
+ * camel_lock_flock:
+ * @fd:
+ * @type:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Create a lock using flock(2).
+ *
+ * @type is CAMEL_LOCK_WRITE or CAMEL_LOCK_READ,
+ * to create exclusive or shared read locks
+ *
+ * Returns: -1 on error.
+ **/
+gint
+camel_lock_flock (gint fd,
+ CamelLockType type,
+ GError **error)
+{
+#ifdef USE_FLOCK_LOCKING
+ gint 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) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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 (gint fd)
+{
+#ifdef USE_FLOCK_LOCKING
+ d (printf ("flock unlocking %d\n", fd));
+
+ CHECK_CALL (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.
+ * @error: return location for a #GError, or %NULL
+ *
+ * Attempt to lock a folder, multiple attempts will be made using all
+ * locking strategies available.
+ *
+ * Returns: -1 on error, @ex will describe the locking system that failed.
+ **/
+gint
+camel_lock_folder (const gchar *path,
+ gint fd,
+ CamelLockType type,
+ GError **error)
+{
+ gint retry = 0;
+
+ while (retry < CAMEL_LOCK_RETRY) {
+ if (retry > 0)
+ g_usleep (CAMEL_LOCK_DELAY * 1000000);
+
+ if (camel_lock_fcntl (fd, type, error) == 0) {
+ if (camel_lock_flock (fd, type, error) == 0) {
+ if (camel_lock_dot (path, error) == 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 gchar *path,
+ gint fd)
+{
+ camel_unlock_dot (path);
+ camel_unlock_flock (fd);
+ camel_unlock_fcntl (fd);
+}
+
+#if 0
+gint
+main (gint argc,
+ gchar **argv)
+{
+ GError *error = NULL;
+ gint fd1, fd2;
+
+#if 0
+ if (camel_lock_dot ("mylock", &error) == 0) {
+ if (camel_lock_dot ("mylock", &error) == 0) {
+ printf ("Got lock twice?\n");
+ } else {
+ printf ("failed to get lock 2: %s\n", error->message);
+ }
+ camel_unlock_dot ("mylock");
+ } else {
+ printf ("failed to get lock 1: %s\n", error->message);
+ }
+
+ if (error != NULL)
+ g_clear_error (&error);
+#endif
+
+ fd1 = open ("mylock", O_RDWR);
+ if (fd1 == -1) {
+ printf ("Could not open lock file (mylock): %s", g_strerror (errno));
+ return 1;
+ }
+ fd2 = open ("mylock", O_RDWR);
+ if (fd2 == -1) {
+ printf ("Could not open lock file (mylock): %s", g_strerror (errno));
+ close (fd1);
+ return 1;
+ }
+
+ if (camel_lock_fcntl (fd1, CAMEL_LOCK_WRITE, &error) == 0) {
+ printf ("got fcntl write lock once\n");
+ g_usleep (5000000);
+ if (camel_lock_fcntl (fd2, CAMEL_LOCK_WRITE, &error) == 0) {
+ printf ("got fcntl write lock twice!\n");
+ } else {
+ printf ("failed to get write lock: %s\n", error->message);
+ }
+
+ if (error != NULL)
+ g_clear_error (&error);
+
+ if (camel_lock_fcntl (fd2, CAMEL_LOCK_READ, &error) == 0) {
+ printf ("got fcntl read lock as well?\n");
+ camel_unlock_fcntl (fd2);
+ } else {
+ printf ("failed to get read lock: %s\n", error->message);
+ }
+
+ if (error != NULL)
+ g_clear_error (&error);
+ camel_unlock_fcntl (fd1);
+ } else {
+ printf ("failed to get write lock at all: %s\n", error->message);
+ }
+
+ if (camel_lock_fcntl (fd1, CAMEL_LOCK_READ, &error) == 0) {
+ printf ("got fcntl read lock once\n");
+ g_usleep (5000000);
+ if (camel_lock_fcntl (fd2, CAMEL_LOCK_WRITE, &error) == 0) {
+ printf ("got fcntl write lock too?!\n");
+ } else {
+ printf ("failed to get write lock: %s\n", error->message);
+ }
+
+ if (error != NULL)
+ g_clear_error (&error);
+
+ if (camel_lock_fcntl (fd2, CAMEL_LOCK_READ, &error) == 0) {
+ printf ("got fcntl read lock twice\n");
+ camel_unlock_fcntl (fd2);
+ } else {
+ printf ("failed to get read lock: %s\n", error->message);
+ }
+
+ if (error != NULL)
+ g_clear_error (&error);
+ camel_unlock_fcntl (fd1);
+ }
+
+ close (fd1);
+ close (fd2);
+
+ return 0;
+}
+#endif
diff --git a/src/camel/camel-lock.h b/src/camel/camel-lock.h
new file mode 100644
index 000000000..669a38d2a
--- /dev/null
+++ b/src/camel/camel-lock.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_LOCK_H
+#define CAMEL_LOCK_H
+
+#include <glib.h>
+
+/* for .lock locking, retry, delay and stale counts */
+#define CAMEL_LOCK_DOT_RETRY (5) /* number of times to retry lock */
+#define CAMEL_LOCK_DOT_DELAY (2) /* delay between locking retries */
+#define CAMEL_LOCK_DOT_STALE (60) /* seconds before a lock becomes stale */
+
+/* for locking folders, retry/interretry delay */
+#define CAMEL_LOCK_RETRY (5) /* number of times to retry lock */
+#define CAMEL_LOCK_DELAY (2) /* delay between locking retries */
+
+G_BEGIN_DECLS
+
+typedef enum {
+ CAMEL_LOCK_READ,
+ CAMEL_LOCK_WRITE
+} CamelLockType;
+
+/* specific locking strategies */
+gint camel_lock_dot (const gchar *path, GError **error);
+gint camel_lock_fcntl (gint fd, CamelLockType type, GError **error);
+gint camel_lock_flock (gint fd, CamelLockType type, GError **error);
+
+void camel_unlock_dot (const gchar *path);
+void camel_unlock_fcntl (gint fd);
+void camel_unlock_flock (gint fd);
+
+/* lock a folder in a standard way */
+gint camel_lock_folder (const gchar *path, gint fd, CamelLockType type, GError **error);
+void camel_unlock_folder (const gchar *path, gint fd);
+
+G_END_DECLS
+
+#endif /* CAMEL_LOCK_H */
diff --git a/src/camel/camel-medium.c b/src/camel/camel-medium.c
new file mode 100644
index 000000000..dc0d5d0a5
--- /dev/null
+++ b/src/camel/camel-medium.c
@@ -0,0 +1,371 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camelMedium.c : Abstract class for a medium
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <stdio.h>
+
+#include "camel-medium.h"
+
+#define d(x)
+
+#define CAMEL_MEDIUM_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MEDIUM, CamelMediumPrivate))
+
+struct _CamelMediumPrivate {
+ /* The content of the medium, as opposed to our parent
+ * CamelDataWrapper, which wraps both the headers and
+ * the content. */
+ CamelDataWrapper *content;
+};
+
+enum {
+ PROP_0,
+ PROP_CONTENT
+};
+
+G_DEFINE_ABSTRACT_TYPE (CamelMedium, camel_medium, CAMEL_TYPE_DATA_WRAPPER)
+
+static void
+medium_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONTENT:
+ camel_medium_set_content (
+ CAMEL_MEDIUM (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+medium_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONTENT:
+ g_value_set_object (
+ value, camel_medium_get_content (
+ CAMEL_MEDIUM (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+medium_dispose (GObject *object)
+{
+ CamelMediumPrivate *priv;
+
+ priv = CAMEL_MEDIUM_GET_PRIVATE (object);
+
+ if (priv->content != NULL) {
+ g_object_unref (priv->content);
+ priv->content = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_medium_parent_class)->dispose (object);
+}
+
+static gboolean
+medium_is_offline (CamelDataWrapper *data_wrapper)
+{
+ CamelDataWrapper *content;
+
+ content = camel_medium_get_content (CAMEL_MEDIUM (data_wrapper));
+
+ return CAMEL_DATA_WRAPPER_CLASS (camel_medium_parent_class)->is_offline (data_wrapper) ||
+ camel_data_wrapper_is_offline (content);
+}
+
+static void
+medium_set_content (CamelMedium *medium,
+ CamelDataWrapper *content)
+{
+ if (medium->priv->content == content)
+ return;
+
+ if (content != NULL)
+ g_object_ref (content);
+
+ if (medium->priv->content != NULL)
+ g_object_unref (medium->priv->content);
+
+ medium->priv->content = content;
+
+ g_object_notify (G_OBJECT (medium), "content");
+}
+
+static CamelDataWrapper *
+medium_get_content (CamelMedium *medium)
+{
+ return medium->priv->content;
+}
+
+static void
+camel_medium_class_init (CamelMediumClass *class)
+{
+ GObjectClass *object_class;
+ CamelDataWrapperClass *data_wrapper_class;
+
+ g_type_class_add_private (class, sizeof (CamelMediumPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = medium_set_property;
+ object_class->get_property = medium_get_property;
+ object_class->dispose = medium_dispose;
+
+ data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (class);
+ data_wrapper_class->is_offline = medium_is_offline;
+
+ class->set_content = medium_set_content;
+ class->get_content = medium_get_content;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CONTENT,
+ g_param_spec_object (
+ "content",
+ "Content",
+ NULL,
+ CAMEL_TYPE_DATA_WRAPPER,
+ G_PARAM_READWRITE));
+}
+
+static void
+camel_medium_init (CamelMedium *medium)
+{
+ medium->priv = CAMEL_MEDIUM_GET_PRIVATE (medium);
+}
+
+/**
+ * camel_medium_add_header:
+ * @medium: a #CamelMedium object
+ * @name: name of the header
+ * @value: value of the header
+ *
+ * Adds a header to a #CamelMedium.
+ **/
+void
+camel_medium_add_header (CamelMedium *medium,
+ const gchar *name,
+ gconstpointer value)
+{
+ CamelMediumClass *class;
+
+ g_return_if_fail (CAMEL_IS_MEDIUM (medium));
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (value != NULL);
+
+ class = CAMEL_MEDIUM_GET_CLASS (medium);
+ g_return_if_fail (class->add_header != NULL);
+
+ class->add_header (medium, name, value);
+}
+
+/**
+ * camel_medium_set_header:
+ * @medium: a #CamelMedium object
+ * @name: name of the header
+ * @value: value of the header
+ *
+ * Sets the value of a header. Any other occurances of the header
+ * will be removed. Setting a %NULL header can be used to remove
+ * the header also.
+ **/
+void
+camel_medium_set_header (CamelMedium *medium,
+ const gchar *name,
+ gconstpointer value)
+{
+ CamelMediumClass *class;
+
+ g_return_if_fail (CAMEL_IS_MEDIUM (medium));
+ g_return_if_fail (name != NULL);
+
+ if (value == NULL) {
+ camel_medium_remove_header (medium, name);
+ return;
+ }
+
+ class = CAMEL_MEDIUM_GET_CLASS (medium);
+ g_return_if_fail (class->set_header != NULL);
+
+ class->set_header (medium, name, value);
+}
+
+/**
+ * camel_medium_remove_header:
+ * @medium: a #CamelMedium
+ * @name: the name of the header
+ *
+ * Removes the named header from the medium. All occurances of the
+ * header are removed.
+ **/
+void
+camel_medium_remove_header (CamelMedium *medium,
+ const gchar *name)
+{
+ CamelMediumClass *class;
+
+ g_return_if_fail (CAMEL_IS_MEDIUM (medium));
+ g_return_if_fail (name != NULL);
+
+ class = CAMEL_MEDIUM_GET_CLASS (medium);
+ g_return_if_fail (class->remove_header != NULL);
+
+ class->remove_header (medium, name);
+}
+
+/**
+ * camel_medium_get_header:
+ * @medium: a #CamelMedium
+ * @name: the name of the header
+ *
+ * Gets the value of the named header in the medium, or %NULL if
+ * it is unset. The caller should not modify or free the data.
+ *
+ * If the header occurs more than once, only retrieve the first
+ * instance of the header. For multi-occuring headers, use
+ * :get_headers().
+ *
+ * Returns: (transfer none) (nullable): the value of the named header, or %NULL
+ **/
+gconstpointer
+camel_medium_get_header (CamelMedium *medium,
+ const gchar *name)
+{
+ CamelMediumClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_MEDIUM (medium), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ class = CAMEL_MEDIUM_GET_CLASS (medium);
+ g_return_val_if_fail (class->get_header != NULL, NULL);
+
+ return class->get_header (medium, name);
+}
+
+/**
+ * camel_medium_get_headers:
+ * @medium: a #CamelMedium object
+ *
+ * Gets an array of all header name/value pairs (as
+ * CamelMediumHeader structures). The values will be decoded
+ * to UTF-8 for any headers that are recognized by Camel. The
+ * caller should not modify the returned data.
+ *
+ * Returns: (element-type CamelMediumHeader) (transfer full): the array of
+ * headers, which must be freed with camel_medium_free_headers().
+ **/
+GArray *
+camel_medium_get_headers (CamelMedium *medium)
+{
+ CamelMediumClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_MEDIUM (medium), NULL);
+
+ class = CAMEL_MEDIUM_GET_CLASS (medium);
+ g_return_val_if_fail (class->get_headers != NULL, NULL);
+
+ return class->get_headers (medium);
+}
+
+/**
+ * camel_medium_free_headers:
+ * @medium: a #CamelMedium object
+ * @headers: (element-type CamelMediumHeader): an array of headers returned
+ * from camel_medium_get_headers()
+ *
+ * Frees @headers.
+ **/
+void
+camel_medium_free_headers (CamelMedium *medium,
+ GArray *headers)
+{
+ CamelMediumClass *class;
+
+ g_return_if_fail (CAMEL_IS_MEDIUM (medium));
+ g_return_if_fail (headers != NULL);
+
+ class = CAMEL_MEDIUM_GET_CLASS (medium);
+ g_return_if_fail (class->free_headers != NULL);
+
+ class->free_headers (medium, headers);
+}
+
+/**
+ * camel_medium_get_content:
+ * @medium: a #CamelMedium object
+ *
+ * Gets a data wrapper that represents the content of the medium,
+ * without its headers.
+ *
+ * Returns: (transfer none) (nullable): a #CamelDataWrapper containing
+ * @medium's content. Can return NULL.
+ **/
+CamelDataWrapper *
+camel_medium_get_content (CamelMedium *medium)
+{
+ CamelMediumClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_MEDIUM (medium), NULL);
+
+ class = CAMEL_MEDIUM_GET_CLASS (medium);
+ g_return_val_if_fail (class->get_content != NULL, NULL);
+
+ return class->get_content (medium);
+}
+
+/**
+ * camel_medium_set_content:
+ * @medium: a #CamelMedium object
+ * @content: a #CamelDataWrapper object
+ *
+ * Sets the content of @medium to be @content.
+ **/
+void
+camel_medium_set_content (CamelMedium *medium,
+ CamelDataWrapper *content)
+{
+ CamelMediumClass *class;
+
+ g_return_if_fail (CAMEL_IS_MEDIUM (medium));
+
+ if (content != NULL)
+ g_return_if_fail (CAMEL_IS_DATA_WRAPPER (content));
+
+ class = CAMEL_MEDIUM_GET_CLASS (medium);
+ g_return_if_fail (class->set_content != NULL);
+
+ class->set_content (medium, content);
+}
diff --git a/src/camel/camel-medium.h b/src/camel/camel-medium.h
new file mode 100644
index 000000000..3d83723bc
--- /dev/null
+++ b/src/camel/camel-medium.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-medium.h : class for a medium object
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MEDIUM_H
+#define CAMEL_MEDIUM_H
+
+#include <camel/camel-data-wrapper.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MEDIUM \
+ (camel_medium_get_type ())
+#define CAMEL_MEDIUM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MEDIUM, CamelMedium))
+#define CAMEL_MEDIUM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MEDIUM, CamelMediumClass))
+#define CAMEL_IS_MEDIUM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MEDIUM))
+#define CAMEL_IS_MEDIUM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MEDIUM))
+#define CAMEL_MEDIUM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MEDIUM, CamelMediumClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMedium CamelMedium;
+typedef struct _CamelMediumClass CamelMediumClass;
+typedef struct _CamelMediumPrivate CamelMediumPrivate;
+
+typedef struct {
+ const gchar *name;
+ const gchar *value;
+} CamelMediumHeader;
+
+struct _CamelMedium {
+ CamelDataWrapper parent;
+ CamelMediumPrivate *priv;
+};
+
+struct _CamelMediumClass {
+ CamelDataWrapperClass parent_class;
+
+ void (*add_header) (CamelMedium *medium,
+ const gchar *name,
+ gconstpointer value);
+ void (*set_header) (CamelMedium *medium,
+ const gchar *name,
+ gconstpointer value);
+ void (*remove_header) (CamelMedium *medium,
+ const gchar *name);
+ gconstpointer (*get_header) (CamelMedium *medium,
+ const gchar *name);
+ GArray * (*get_headers) (CamelMedium *medium);
+ void (*free_headers) (CamelMedium *medium,
+ GArray *headers);
+ CamelDataWrapper *
+ (*get_content) (CamelMedium *medium);
+ void (*set_content) (CamelMedium *medium,
+ CamelDataWrapper *content);
+};
+
+GType camel_medium_get_type (void);
+void camel_medium_add_header (CamelMedium *medium,
+ const gchar *name,
+ gconstpointer value);
+void camel_medium_set_header (CamelMedium *medium,
+ const gchar *name,
+ gconstpointer value);
+void camel_medium_remove_header (CamelMedium *medium,
+ const gchar *name);
+gconstpointer camel_medium_get_header (CamelMedium *medium,
+ const gchar *name);
+GArray * camel_medium_get_headers (CamelMedium *medium);
+void camel_medium_free_headers (CamelMedium *medium,
+ GArray *headers);
+CamelDataWrapper *
+ camel_medium_get_content (CamelMedium *medium);
+void camel_medium_set_content (CamelMedium *medium,
+ CamelDataWrapper *content);
+
+G_END_DECLS
+
+#endif /* CAMEL_MEDIUM_H */
diff --git a/src/camel/camel-memchunk.c b/src/camel/camel-memchunk.c
new file mode 100644
index 000000000..d99f3dea3
--- /dev/null
+++ b/src/camel/camel-memchunk.c
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jacob Berkman <jacob@ximian.com>
+ */
+
+#include "camel-memchunk.h"
+
+#include <string.h> /* memset() */
+
+/*#define TIMEIT*/
+
+#ifdef TIMEIT
+#include <sys/time.h>
+#include <unistd.h>
+
+struct timeval timeit_start;
+
+static void
+time_start (const gchar *desc)
+{
+ gettimeofday (&timeit_start, NULL);
+ printf ("starting: %s\n", desc);
+}
+
+static void
+time_end (const gchar *desc)
+{
+ gulong diff;
+ struct timeval end;
+
+ gettimeofday (&end, NULL);
+ diff = end.tv_sec * 1000 + end.tv_usec / 1000;
+ diff -= timeit_start.tv_sec * 1000 + timeit_start.tv_usec / 1000;
+ printf (
+ "%s took %ld.%03ld seconds\n",
+ desc, diff / 1000, diff % 1000);
+}
+#else
+#define time_start(x)
+#define time_end(x)
+#endif
+
+typedef struct _MemChunkFreeNode {
+ struct _MemChunkFreeNode *next;
+ guint atoms;
+} MemChunkFreeNode;
+
+/**
+ * CamelMemChunk:
+ *
+ * Since: 3.4
+ **/
+struct _CamelMemChunk {
+ guint blocksize; /* number of atoms in a block */
+ guint atomsize; /* size of each atom */
+ GPtrArray *blocks; /* blocks of raw memory */
+ struct _MemChunkFreeNode *free;
+};
+
+/**
+ * camel_memchunk_new:
+ * @atomcount: the number of atoms stored in a single malloc'd block of memory
+ * @atomsize: the size of each allocation
+ *
+ * Create a new #CamelMemChunk header. Memchunks are an efficient way to
+ * allocate and deallocate identical sized blocks of memory quickly, and
+ * space efficiently.
+ *
+ * camel_memchunks are effectively the same as gmemchunks, only faster (much),
+ * and they use less memory overhead for housekeeping.
+ *
+ * Returns: a new #CamelMemChunk
+ *
+ * Since: 3.4
+ **/
+CamelMemChunk *
+camel_memchunk_new (gint atomcount,
+ gint atomsize)
+{
+ CamelMemChunk *memchunk = g_malloc (sizeof (*memchunk));
+
+ memchunk->blocksize = atomcount;
+ memchunk->atomsize = MAX (atomsize, sizeof (MemChunkFreeNode));
+ memchunk->blocks = g_ptr_array_new ();
+ memchunk->free = NULL;
+
+ return memchunk;
+}
+
+/**
+ * camel_memchunk_alloc:
+ * @memchunk: an #CamelMemChunk
+ *
+ * Allocate a new atom size block of memory from an #CamelMemChunk.
+ * Free the returned atom with camel_memchunk_free().
+ *
+ * Returns: (transfer full): an allocated block of memory
+ *
+ * Since: 3.4
+ **/
+gpointer
+camel_memchunk_alloc (CamelMemChunk *memchunk)
+{
+ gchar *b;
+ MemChunkFreeNode *f;
+ gpointer mem;
+
+ f = memchunk->free;
+ if (f) {
+ f->atoms--;
+ if (f->atoms > 0) {
+ mem = ((gchar *) f) + (f->atoms * memchunk->atomsize);
+ } else {
+ mem = f;
+ memchunk->free = memchunk->free->next;
+ }
+ return mem;
+ } else {
+ b = g_malloc (memchunk->blocksize * memchunk->atomsize);
+ g_ptr_array_add (memchunk->blocks, b);
+ f = (MemChunkFreeNode *) &b[memchunk->atomsize];
+ f->atoms = memchunk->blocksize - 1;
+ f->next = NULL;
+ memchunk->free = f;
+ return b;
+ }
+}
+
+/**
+ * camel_memchunk_alloc0:
+ * @memchunk: an #CamelMemChunk
+ *
+ * Allocate a new atom size block of memory from an #CamelMemChunk,
+ * and fill the memory with zeros. Free the returned atom with
+ * camel_memchunk_free().
+ *
+ * Returns: (transfer full): an allocated block of memory
+ *
+ * Since: 3.4
+ **/
+gpointer
+camel_memchunk_alloc0 (CamelMemChunk *memchunk)
+{
+ gpointer mem;
+
+ mem = camel_memchunk_alloc (memchunk);
+ memset (mem, 0, memchunk->atomsize);
+
+ return mem;
+}
+
+/**
+ * camel_memchunk_free:
+ * @memchunk: an #CamelMemChunk
+ * @mem: address of atom to free
+ *
+ * Free a single atom back to the free pool of atoms in the given
+ * memchunk.
+ *
+ * Since: 3.4
+ **/
+void
+camel_memchunk_free (CamelMemChunk *memchunk,
+ gpointer mem)
+{
+ MemChunkFreeNode *f;
+
+ /* Put the location back in the free list. If we knew if the
+ * preceeding or following cells were free, we could merge the
+ * free nodes, but it doesn't really add much. */
+ f = mem;
+ f->next = memchunk->free;
+ memchunk->free = f;
+ f->atoms = 1;
+
+ /* We could store the free list sorted - we could then do the above,
+ * and also probably improve the locality of reference properties for
+ * the allocator. (And it would simplify some other algorithms at
+ * that, but slow this one down significantly.) */
+}
+
+/**
+ * camel_memchunk_empty:
+ * @memchunk: an #CamelMemChunk
+ *
+ * Clean out the memchunk buffers. Marks all allocated memory as free blocks,
+ * but does not give it back to the system. Can be used if the memchunk
+ * is to be used repeatedly.
+ *
+ * Since: 3.4
+ **/
+void
+camel_memchunk_empty (CamelMemChunk *memchunk)
+{
+ MemChunkFreeNode *f, *h = NULL;
+ gint i;
+
+ for (i = 0; i < memchunk->blocks->len; i++) {
+ f = (MemChunkFreeNode *) memchunk->blocks->pdata[i];
+ f->atoms = memchunk->blocksize;
+ f->next = h;
+ h = f;
+ }
+
+ memchunk->free = h;
+}
+
+struct _cleaninfo {
+ struct _cleaninfo *next;
+ gchar *base;
+ gint count;
+ gint size; /* just so tree_search has it, sigh */
+};
+
+static gint
+tree_compare (struct _cleaninfo *a,
+ struct _cleaninfo *b)
+{
+ if (a->base < b->base)
+ return -1;
+ else if (a->base > b->base)
+ return 1;
+ return 0;
+}
+
+static gint
+tree_search (struct _cleaninfo *a,
+ gchar *mem)
+{
+ if (a->base <= mem) {
+ if (mem < &a->base[a->size])
+ return 0;
+ return 1;
+ }
+ return -1;
+}
+
+/**
+ * camel_memchunk_clean:
+ * @memchunk: an #CamelMemChunk
+ *
+ * Scan all empty blocks and check for blocks which can be free'd
+ * back to the system.
+ *
+ * This routine may take a while to run if there are many allocated
+ * memory blocks (if the total number of allocations is many times
+ * greater than atomcount).
+ *
+ * Since: 3.4
+ **/
+void
+camel_memchunk_clean (CamelMemChunk *memchunk)
+{
+ GTree *tree;
+ gint i;
+ MemChunkFreeNode *f;
+ struct _cleaninfo *ci, *hi = NULL;
+
+ f = memchunk->free;
+ if (memchunk->blocks->len == 0 || f == NULL)
+ return;
+
+ /* first, setup the tree/list so we can map free block addresses to block addresses */
+ tree = g_tree_new ((GCompareFunc) tree_compare);
+ for (i = 0; i < memchunk->blocks->len; i++) {
+ ci = alloca (sizeof (*ci));
+ ci->count = 0;
+ ci->base = memchunk->blocks->pdata[i];
+ ci->size = memchunk->blocksize * memchunk->atomsize;
+ g_tree_insert (tree, ci, ci);
+ ci->next = hi;
+ hi = ci;
+ }
+
+ /* now, scan all free nodes, and count them in their tree node */
+ while (f) {
+ ci = g_tree_search (tree, (GCompareFunc) tree_search, f);
+ if (ci) {
+ ci->count += f->atoms;
+ } else {
+ g_warning ("error, can't find free node in memory block\n");
+ }
+ f = f->next;
+ }
+
+ /* if any nodes are all free, free & unlink them */
+ ci = hi;
+ while (ci) {
+ if (ci->count == memchunk->blocksize) {
+ MemChunkFreeNode *prev = NULL;
+
+ f = memchunk->free;
+ while (f) {
+ if (tree_search (ci, (gpointer) f) == 0) {
+ /* prune this node from our free-node list */
+ if (prev)
+ prev->next = f->next;
+ else
+ memchunk->free = f->next;
+ } else {
+ prev = f;
+ }
+
+ f = f->next;
+ }
+
+ g_ptr_array_remove_fast (memchunk->blocks, ci->base);
+ g_free (ci->base);
+ }
+ ci = ci->next;
+ }
+
+ g_tree_destroy (tree);
+}
+
+/**
+ * camel_memchunk_destroy:
+ * @memchunk: an #CamelMemChunk
+ *
+ * Free the memchunk header, and all associated memory.
+ *
+ * Since: 3.4
+ **/
+void
+camel_memchunk_destroy (CamelMemChunk *memchunk)
+{
+ gint i;
+
+ if (memchunk == NULL)
+ return;
+
+ for (i = 0; i < memchunk->blocks->len; i++)
+ g_free (memchunk->blocks->pdata[i]);
+
+ g_ptr_array_free (memchunk->blocks, TRUE);
+
+ g_free (memchunk);
+}
+
+#if 0
+
+#define CHUNK_SIZE (20)
+#define CHUNK_COUNT (32)
+
+#define s(x)
+
+main ()
+{
+ gint i;
+ MemChunk *mc;
+ gpointer mem, *last;
+ GMemChunk *gmc;
+ struct _EStrv *s;
+
+ s = strv_new (8);
+ s = strv_set (s, 1, "Testing 1");
+ s = strv_set (s, 2, "Testing 2");
+ s = strv_set (s, 3, "Testing 3");
+ s = strv_set (s, 4, "Testing 4");
+ s = strv_set (s, 5, "Testing 5");
+ s = strv_set (s, 6, "Testing 7");
+
+ for (i = 0; i < 8; i++) {
+ printf ("s[%d] = %s\n", i, strv_get (s, i));
+ }
+
+ s (sleep (5));
+
+ printf ("packing ...\n");
+ s = strv_pack (s);
+
+ for (i = 0; i < 8; i++) {
+ printf ("s[%d] = %s\n", i, strv_get (s, i));
+ }
+
+ printf ("setting ...\n");
+
+ s = strv_set_ref (s, 1, "Testing 1 x");
+
+ for (i = 0; i < 8; i++) {
+ printf ("s[%d] = %s\n", i, strv_get (s, i));
+ }
+
+ printf ("packing ...\n");
+ s = strv_pack (s);
+
+ for (i = 0; i < 8; i++) {
+ printf ("s[%d] = %s\n", i, strv_get (s, i));
+ }
+
+ strv_free (s);
+
+#if 0
+ time_start ("Using memchunks");
+ mc = memchunk_new (CHUNK_COUNT, CHUNK_SIZE);
+ for (i = 0; i < 1000000; i++) {
+ mem = memchunk_alloc (mc);
+ if ((i & 1) == 0)
+ memchunk_free (mc, mem);
+ }
+ s (sleep (10));
+ memchunk_destroy (mc);
+ time_end ("allocating 1000000 memchunks, freeing 500k");
+
+ time_start ("Using gmemchunks");
+ gmc = g_mem_chunk_new ("memchunk", CHUNK_SIZE, CHUNK_SIZE * CHUNK_COUNT, G_ALLOC_AND_FREE);
+ for (i = 0; i < 1000000; i++) {
+ mem = g_mem_chunk_alloc (gmc);
+ if ((i & 1) == 0)
+ g_mem_chunk_free (gmc, mem);
+ }
+ s (sleep (10));
+ g_mem_chunk_destroy (gmc);
+ time_end ("allocating 1000000 gmemchunks, freeing 500k");
+
+ time_start ("Using memchunks");
+ mc = memchunk_new (CHUNK_COUNT, CHUNK_SIZE);
+ for (i = 0; i < 1000000; i++) {
+ mem = memchunk_alloc (mc);
+ }
+ s (sleep (10));
+ memchunk_destroy (mc);
+ time_end ("allocating 1000000 memchunks");
+
+ time_start ("Using gmemchunks");
+ gmc = g_mem_chunk_new ("memchunk", CHUNK_SIZE, CHUNK_COUNT * CHUNK_SIZE, G_ALLOC_ONLY);
+ for (i = 0; i < 1000000; i++) {
+ mem = g_mem_chunk_alloc (gmc);
+ }
+ s (sleep (10));
+ g_mem_chunk_destroy (gmc);
+ time_end ("allocating 1000000 gmemchunks");
+
+ time_start ("Using malloc");
+ for (i = 0; i < 1000000; i++) {
+ malloc (CHUNK_SIZE);
+ }
+ time_end ("allocating 1000000 malloc");
+#endif
+
+}
+
+#endif
diff --git a/src/camel/camel-memchunk.h b/src/camel/camel-memchunk.h
new file mode 100644
index 000000000..682892ccc
--- /dev/null
+++ b/src/camel/camel-memchunk.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jacob Berkman <jacob@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MEMCHUNK_H
+#define CAMEL_MEMCHUNK_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/* memchunks - allocate/free fixed-size blocks of memory */
+/* this is like gmemchunk, only faster and less overhead (only 4 bytes for every atomcount allocations) */
+typedef struct _CamelMemChunk CamelMemChunk;
+
+CamelMemChunk * camel_memchunk_new (gint atomcount,
+ gint atomsize);
+gpointer camel_memchunk_alloc (CamelMemChunk *memchunk);
+gpointer camel_memchunk_alloc0 (CamelMemChunk *memchunk);
+void camel_memchunk_free (CamelMemChunk *memchunk,
+ gpointer mem);
+void camel_memchunk_empty (CamelMemChunk *memchunk);
+void camel_memchunk_clean (CamelMemChunk *memchunk);
+void camel_memchunk_destroy (CamelMemChunk *memchunk);
+
+G_END_DECLS
+
+#endif /* CAMEL_MEMCHUNK_H */
diff --git a/src/camel/camel-mempool.c b/src/camel/camel-mempool.c
new file mode 100644
index 000000000..bc2fade38
--- /dev/null
+++ b/src/camel/camel-mempool.c
@@ -0,0 +1,220 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-mempool.h"
+
+#include <string.h>
+
+typedef struct _MemPoolNode {
+ struct _MemPoolNode *next;
+
+ gint free;
+} MemPoolNode;
+
+typedef struct _MemPoolThresholdNode {
+ struct _MemPoolThresholdNode *next;
+} MemPoolThresholdNode;
+
+#define ALIGNED_SIZEOF(t) ((sizeof (t) + G_MEM_ALIGN - 1) & -G_MEM_ALIGN)
+
+struct _CamelMemPool {
+ gint blocksize;
+ gint threshold;
+ guint align;
+ struct _MemPoolNode *blocks;
+ struct _MemPoolThresholdNode *threshold_blocks;
+};
+
+/**
+ * camel_mempool_new:
+ * @blocksize: The base blocksize to use for all system alocations.
+ * @threshold: If the allocation exceeds the threshold, then it is
+ * allocated separately and stored in a separate list.
+ * @flags: Alignment options: CAMEL_MEMPOOL_ALIGN_STRUCT uses native
+ * struct alignment, CAMEL_MEMPOOL_ALIGN_WORD aligns to 16 bits (2 bytes),
+ * and CAMEL_MEMPOOL_ALIGN_BYTE aligns to the nearest byte. The default
+ * is to align to native structures.
+ *
+ * Create a new mempool header. Mempools can be used to efficiently
+ * allocate data which can then be freed as a whole.
+ *
+ * Mempools can also be used to efficiently allocate arbitrarily
+ * aligned data (such as strings) without incurring the space overhead
+ * of aligning each allocation (which is not required for strings).
+ *
+ * However, each allocation cannot be freed individually, only all
+ * or nothing.
+ *
+ * Returns:
+ *
+ * Since: 2.32
+ **/
+CamelMemPool *
+camel_mempool_new (gint blocksize,
+ gint threshold,
+ CamelMemPoolFlags flags)
+{
+ CamelMemPool *pool;
+
+ pool = g_slice_new0 (CamelMemPool);
+ if (threshold >= blocksize)
+ threshold = blocksize * 2 / 3;
+ pool->blocksize = blocksize;
+ pool->threshold = threshold;
+ pool->blocks = NULL;
+ pool->threshold_blocks = NULL;
+
+ switch (flags & CAMEL_MEMPOOL_ALIGN_MASK) {
+ case CAMEL_MEMPOOL_ALIGN_STRUCT:
+ default:
+ pool->align = G_MEM_ALIGN - 1;
+ break;
+ case CAMEL_MEMPOOL_ALIGN_WORD:
+ pool->align = 2 - 1;
+ break;
+ case CAMEL_MEMPOOL_ALIGN_BYTE:
+ pool->align = 1 - 1;
+ }
+ return pool;
+}
+
+/**
+ * camel_mempool_alloc:
+ * @pool: a #CamelMemPool
+ * @size:
+ *
+ * Allocate a new data block in the mempool. Size will
+ * be rounded up to the mempool's alignment restrictions
+ * before being used.
+ *
+ * Since: 2.32
+ **/
+gpointer
+camel_mempool_alloc (CamelMemPool *pool,
+ register gint size)
+{
+ size = (size + pool->align) & (~(pool->align));
+ if (size >= pool->threshold) {
+ MemPoolThresholdNode *n;
+
+ n = g_malloc (ALIGNED_SIZEOF (*n) + size);
+ n->next = pool->threshold_blocks;
+ pool->threshold_blocks = n;
+ return (gchar *) n + ALIGNED_SIZEOF (*n);
+ } else {
+ register MemPoolNode *n;
+
+ n = pool->blocks;
+ if (n && n->free >= size) {
+ n->free -= size;
+ return (gchar *) n + ALIGNED_SIZEOF (*n) + n->free;
+ }
+
+ /* maybe we could do some sort of the free blocks based on size, but
+ * it doubt its worth it at all */
+
+ n = g_malloc (ALIGNED_SIZEOF (*n) + pool->blocksize);
+ n->next = pool->blocks;
+ pool->blocks = n;
+ n->free = pool->blocksize - size;
+ return (gchar *) n + ALIGNED_SIZEOF (*n) + n->free;
+ }
+}
+
+/**
+ * camel_mempool_strdup:
+ * @pool: a #CamelMemPool
+ * @str:
+ *
+ * Since: 2.32
+ **/
+gchar *
+camel_mempool_strdup (CamelMemPool *pool,
+ const gchar *str)
+{
+ gchar *out;
+ gsize out_len;
+
+ out_len = strlen (str) + 1;
+ out = camel_mempool_alloc (pool, out_len);
+ g_strlcpy (out, str, out_len);
+
+ return out;
+}
+
+/**
+ * camel_mempool_flush:
+ * @pool: a #CamelMemPool
+ * @freeall: free all system allocated blocks as well
+ *
+ * Flush used memory and mark allocated blocks as free.
+ *
+ * If @freeall is %TRUE, then all allocated blocks are free'd
+ * as well. Otherwise only blocks above the threshold are
+ * actually freed, and the others are simply marked as empty.
+ *
+ * Since: 2.32
+ **/
+void
+camel_mempool_flush (CamelMemPool *pool,
+ gint freeall)
+{
+ MemPoolThresholdNode *tn, *tw;
+ MemPoolNode *pw, *pn;
+
+ tw = pool->threshold_blocks;
+ while (tw) {
+ tn = tw->next;
+ g_free (tw);
+ tw = tn;
+ }
+ pool->threshold_blocks = NULL;
+
+ if (freeall) {
+ pw = pool->blocks;
+ while (pw) {
+ pn = pw->next;
+ g_free (pw);
+ pw = pn;
+ }
+ pool->blocks = NULL;
+ } else {
+ pw = pool->blocks;
+ while (pw) {
+ pw->free = pool->blocksize;
+ pw = pw->next;
+ }
+ }
+}
+
+/**
+ * camel_mempool_destroy:
+ * @pool: a #CamelMemPool
+ *
+ * Free all memory associated with a mempool.
+ *
+ * Since: 2.32
+ **/
+void
+camel_mempool_destroy (CamelMemPool *pool)
+{
+ if (pool) {
+ camel_mempool_flush (pool, 1);
+ g_slice_free (CamelMemPool, pool);
+ }
+}
diff --git a/src/camel/camel-mempool.h b/src/camel/camel-mempool.h
new file mode 100644
index 000000000..8825209d0
--- /dev/null
+++ b/src/camel/camel-mempool.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MEMPOOL_H
+#define CAMEL_MEMPOOL_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/* mempools - allocate variable sized blocks of memory, and free as one */
+/* allocation is very fast, but cannot be freed individually */
+
+/**
+ * CamelMemPool:
+ *
+ * Since: 2.32
+ **/
+typedef struct _CamelMemPool CamelMemPool;
+
+/**
+ * CamelMemPoolFlags:
+ * @CAMEL_MEMPOOL_ALIGN_STRUCT:
+ * Allocate to native structure alignment
+ * @CAMEL_MEMPOOL_ALIGN_WORD:
+ * Allocate to words - 16 bit alignment
+ * @CAMEL_MEMPOOL_ALIGN_BYTE:
+ * Allocate to bytes - 8 bit alignment
+ * @CAMEL_MEMPOOL_ALIGN_MASK:
+ * Which bits determine the alignment information
+ *
+ * Since: 2.32
+ **/
+typedef enum {
+ CAMEL_MEMPOOL_ALIGN_STRUCT,
+ CAMEL_MEMPOOL_ALIGN_WORD,
+ CAMEL_MEMPOOL_ALIGN_BYTE,
+ CAMEL_MEMPOOL_ALIGN_MASK = 0x3
+} CamelMemPoolFlags;
+
+CamelMemPool * camel_mempool_new (gint blocksize,
+ gint threshold,
+ CamelMemPoolFlags flags);
+gpointer camel_mempool_alloc (CamelMemPool *pool,
+ gint size);
+gchar * camel_mempool_strdup (CamelMemPool *pool,
+ const gchar *str);
+void camel_mempool_flush (CamelMemPool *pool,
+ gint freeall);
+void camel_mempool_destroy (CamelMemPool *pool);
+
+G_END_DECLS
+
+#endif /* CAMEL_MEMPOOL_H */
diff --git a/src/camel/camel-mime-filter-basic.c b/src/camel/camel-mime-filter-basic.c
new file mode 100644
index 000000000..6f7f000e5
--- /dev/null
+++ b/src/camel/camel-mime-filter-basic.c
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include <string.h>
+
+#include "camel-mime-filter-basic.h"
+#include "camel-mime-utils.h"
+
+#define CAMEL_MIME_FILTER_BASIC_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_BASIC, CamelMimeFilterBasicPrivate))
+
+struct _CamelMimeFilterBasicPrivate {
+ CamelMimeFilterBasicType type;
+ guchar uubuf[60];
+ gint state;
+ gint save;
+};
+
+G_DEFINE_TYPE (CamelMimeFilterBasic, camel_mime_filter_basic, CAMEL_TYPE_MIME_FILTER)
+
+/* here we do all of the basic mime filtering */
+static void
+mime_filter_basic_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterBasicPrivate *priv;
+ gsize newlen;
+
+ priv = CAMEL_MIME_FILTER_BASIC_GET_PRIVATE (mime_filter);
+
+ switch (priv->type) {
+ case CAMEL_MIME_FILTER_BASIC_BASE64_ENC:
+ /* wont go to more than 2x size (overly conservative) */
+ camel_mime_filter_set_size (
+ mime_filter, len * 2 + 6, FALSE);
+ newlen = g_base64_encode_step (
+ (const guchar *) in, len,
+ TRUE,
+ mime_filter->outbuf,
+ &priv->state,
+ &priv->save);
+ g_return_if_fail (newlen <= len * 2 + 6);
+ break;
+ case CAMEL_MIME_FILTER_BASIC_QP_ENC:
+ /* *4 is overly conservative, but will do */
+ camel_mime_filter_set_size (
+ mime_filter, len * 4 + 4, FALSE);
+ newlen = camel_quoted_encode_step (
+ (guchar *) in, len,
+ (guchar *) mime_filter->outbuf,
+ &priv->state,
+ (gint *) &priv->save);
+ g_return_if_fail (newlen <= len * 4 + 4);
+ break;
+ case CAMEL_MIME_FILTER_BASIC_UU_ENC:
+ /* won't go to more than 2 * (x + 2) + 62 */
+ camel_mime_filter_set_size (
+ mime_filter, (len + 2) * 2 + 62, FALSE);
+ newlen = camel_uuencode_step (
+ (guchar *) in, len,
+ (guchar *) mime_filter->outbuf,
+ priv->uubuf,
+ &priv->state,
+ (guint32 *) &priv->save);
+ g_return_if_fail (newlen <= (len + 2) * 2 + 62);
+ break;
+ case CAMEL_MIME_FILTER_BASIC_BASE64_DEC:
+ /* output can't possibly exceed the input size */
+ camel_mime_filter_set_size (mime_filter, len + 3, FALSE);
+ newlen = g_base64_decode_step (
+ in, len,
+ (guchar *) mime_filter->outbuf,
+ &priv->state,
+ (guint *) &priv->save);
+ g_return_if_fail (newlen <= len + 3);
+ break;
+ case CAMEL_MIME_FILTER_BASIC_QP_DEC:
+ /* output can't possibly exceed the input size */
+ camel_mime_filter_set_size (mime_filter, len + 2, FALSE);
+ newlen = camel_quoted_decode_step (
+ (guchar *) in, len,
+ (guchar *) mime_filter->outbuf,
+ &priv->state,
+ (gint *) &priv->save);
+ g_return_if_fail (newlen <= len + 2);
+ break;
+ case CAMEL_MIME_FILTER_BASIC_UU_DEC:
+ if (!(priv->state & CAMEL_UUDECODE_STATE_BEGIN)) {
+ const gchar *inptr, *inend;
+ gsize left;
+
+ inptr = in;
+ inend = inptr + len;
+
+ while (inptr < inend) {
+ left = inend - inptr;
+ if (left < 6) {
+ if (!strncmp (inptr, "begin ", left))
+ camel_mime_filter_backup (mime_filter, inptr, left);
+ break;
+ } else if (!strncmp (inptr, "begin ", 6)) {
+ for (in = inptr; inptr < inend && *inptr != '\n'; inptr++);
+ if (inptr < inend) {
+ inptr++;
+ priv->state |= CAMEL_UUDECODE_STATE_BEGIN;
+ /* we can start uudecoding... */
+ in = inptr;
+ len = inend - in;
+ } else {
+ camel_mime_filter_backup (mime_filter, in, left);
+ }
+ break;
+ }
+
+ /* go to the next line */
+ for (; inptr < inend && *inptr != '\n'; inptr++);
+
+ if (inptr < inend)
+ inptr++;
+ }
+ }
+
+ if ((priv->state & CAMEL_UUDECODE_STATE_BEGIN) && !(priv->state & CAMEL_UUDECODE_STATE_END)) {
+ /* "begin <mode> <filename>\n" has been
+ * found, so we can now start decoding */
+ camel_mime_filter_set_size (
+ mime_filter, len + 3, FALSE);
+ newlen = camel_uudecode_step (
+ (guchar *) in, len,
+ (guchar *) mime_filter->outbuf,
+ &priv->state,
+ (guint32 *) &priv->save);
+ } else {
+ newlen = 0;
+ }
+ break;
+ default:
+ g_warning ("unknown type %u in CamelMimeFilterBasic", priv->type);
+ goto donothing;
+ }
+
+ *out = mime_filter->outbuf;
+ *outlen = newlen;
+ *outprespace = mime_filter->outpre;
+
+ return;
+donothing:
+ *out = (gchar *) in;
+ *outlen = len;
+ *outprespace = prespace;
+}
+
+static void
+mime_filter_basic_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterBasicPrivate *priv;
+ gsize newlen = 0;
+
+ priv = CAMEL_MIME_FILTER_BASIC_GET_PRIVATE (mime_filter);
+
+ switch (priv->type) {
+ case CAMEL_MIME_FILTER_BASIC_BASE64_ENC:
+ /* wont go to more than 2x size (overly conservative) */
+ camel_mime_filter_set_size (
+ mime_filter, len * 2 + 6, FALSE);
+ if (len > 0)
+ newlen += g_base64_encode_step (
+ (const guchar *) in, len,
+ TRUE,
+ mime_filter->outbuf,
+ &priv->state,
+ &priv->save);
+ newlen += g_base64_encode_close (
+ TRUE,
+ mime_filter->outbuf,
+ &priv->state,
+ &priv->save);
+ g_return_if_fail (newlen <= len * 2 + 6);
+ break;
+ case CAMEL_MIME_FILTER_BASIC_QP_ENC:
+ /* *4 is definetly more than needed ... */
+ camel_mime_filter_set_size (
+ mime_filter, len * 4 + 4, FALSE);
+ newlen = camel_quoted_encode_close (
+ (guchar *) in, len,
+ (guchar *) mime_filter->outbuf,
+ &priv->state,
+ &priv->save);
+ g_return_if_fail (newlen <= len * 4 + 4);
+ break;
+ case CAMEL_MIME_FILTER_BASIC_UU_ENC:
+ /* won't go to more than 2 * (x + 2) + 62 */
+ camel_mime_filter_set_size (
+ mime_filter, (len + 2) * 2 + 62, FALSE);
+ newlen = camel_uuencode_close (
+ (guchar *) in, len,
+ (guchar *) mime_filter->outbuf,
+ priv->uubuf,
+ &priv->state,
+ (guint32 *) &priv->save);
+ g_return_if_fail (newlen <= (len + 2) * 2 + 62);
+ break;
+ case CAMEL_MIME_FILTER_BASIC_BASE64_DEC:
+ /* output can't possibly exceed the input size */
+ camel_mime_filter_set_size (mime_filter, len, FALSE);
+ newlen = g_base64_decode_step (
+ in, len,
+ (guchar *) mime_filter->outbuf,
+ &priv->state,
+ (guint *) &priv->save);
+ g_return_if_fail (newlen <= len);
+ break;
+ case CAMEL_MIME_FILTER_BASIC_QP_DEC:
+ /* output can't possibly exceed the input size,
+ * well unless its not really qp, then +2 max */
+ camel_mime_filter_set_size (mime_filter, len + 2, FALSE);
+ newlen = camel_quoted_decode_step (
+ (guchar *) in, len,
+ (guchar *) mime_filter->outbuf,
+ &priv->state,
+ (gint *) &priv->save);
+ g_return_if_fail (newlen <= len + 2);
+ break;
+ case CAMEL_MIME_FILTER_BASIC_UU_DEC:
+ if ((priv->state & CAMEL_UUDECODE_STATE_BEGIN) && !(priv->state & CAMEL_UUDECODE_STATE_END)) {
+ /* "begin <mode> <filename>\n" has been
+ * found, so we can now start decoding */
+ camel_mime_filter_set_size (
+ mime_filter, len + 3, FALSE);
+ newlen = camel_uudecode_step (
+ (guchar *) in, len,
+ (guchar *) mime_filter->outbuf,
+ &priv->state,
+ (guint32 *) &priv->save);
+ } else {
+ newlen = 0;
+ }
+ break;
+ default:
+ g_warning ("unknown type %u in CamelMimeFilterBasic", priv->type);
+ goto donothing;
+ }
+
+ *out = mime_filter->outbuf;
+ *outlen = newlen;
+ *outprespace = mime_filter->outpre;
+
+ return;
+donothing:
+ *out = (gchar *) in;
+ *outlen = len;
+ *outprespace = prespace;
+}
+
+/* should this 'flush' outstanding state/data bytes? */
+static void
+mime_filter_basic_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterBasicPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_BASIC_GET_PRIVATE (mime_filter);
+
+ switch (priv->type) {
+ case CAMEL_MIME_FILTER_BASIC_QP_ENC:
+ priv->state = -1;
+ break;
+ default:
+ priv->state = 0;
+ }
+ priv->save = 0;
+}
+
+static void
+camel_mime_filter_basic_class_init (CamelMimeFilterBasicClass *class)
+{
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterBasicPrivate));
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_basic_filter;
+ mime_filter_class->complete = mime_filter_basic_complete;
+ mime_filter_class->reset = mime_filter_basic_reset;
+}
+
+static void
+camel_mime_filter_basic_init (CamelMimeFilterBasic *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_BASIC_GET_PRIVATE (filter);
+}
+
+/**
+ * camel_mime_filter_basic_new:
+ * @type: a #CamelMimeFilterBasicType type
+ *
+ * Create a new #CamelMimeFilterBasic object of type @type.
+ *
+ * Returns: a new #CamelMimeFilterBasic object
+ **/
+CamelMimeFilter *
+camel_mime_filter_basic_new (CamelMimeFilterBasicType type)
+{
+ CamelMimeFilter *new;
+
+ switch (type) {
+ case CAMEL_MIME_FILTER_BASIC_BASE64_ENC:
+ case CAMEL_MIME_FILTER_BASIC_QP_ENC:
+ case CAMEL_MIME_FILTER_BASIC_BASE64_DEC:
+ case CAMEL_MIME_FILTER_BASIC_QP_DEC:
+ case CAMEL_MIME_FILTER_BASIC_UU_ENC:
+ case CAMEL_MIME_FILTER_BASIC_UU_DEC:
+ new = g_object_new (CAMEL_TYPE_MIME_FILTER_BASIC, NULL);
+ CAMEL_MIME_FILTER_BASIC (new)->priv->type = type;
+ break;
+ default:
+ g_warning ("Invalid type of CamelMimeFilterBasic requested: %u", type);
+ new = NULL;
+ break;
+ }
+ camel_mime_filter_reset (new);
+
+ return new;
+}
+
diff --git a/src/camel/camel-mime-filter-basic.h b/src/camel/camel-mime-filter-basic.h
new file mode 100644
index 000000000..a3936327c
--- /dev/null
+++ b/src/camel/camel-mime-filter-basic.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_BASIC_H
+#define CAMEL_MIME_FILTER_BASIC_H
+
+#include <camel/camel-enums.h>
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_BASIC \
+ (camel_mime_filter_basic_get_type ())
+#define CAMEL_MIME_FILTER_BASIC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_BASIC, CamelMimeFilterBasic))
+#define CAMEL_MIME_FILTER_BASIC_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_BASIC, CamelMimeFilterBasicClass))
+#define CAMEL_IS_MIME_FILTER_BASIC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_BASIC))
+#define CAMEL_IS_MIME_FILTER_BASIC_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_BASIC))
+#define CAMEL_MIME_FILTER_BASIC_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_BASIC, CamelMimeFilterBasicClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterBasic CamelMimeFilterBasic;
+typedef struct _CamelMimeFilterBasicClass CamelMimeFilterBasicClass;
+typedef struct _CamelMimeFilterBasicPrivate CamelMimeFilterBasicPrivate;
+
+struct _CamelMimeFilterBasic {
+ CamelMimeFilter parent;
+ CamelMimeFilterBasicPrivate *priv;
+};
+
+struct _CamelMimeFilterBasicClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_basic_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_basic_new (CamelMimeFilterBasicType type);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_BASIC_H */
diff --git a/src/camel/camel-mime-filter-bestenc.c b/src/camel/camel-mime-filter-bestenc.c
new file mode 100644
index 000000000..1a377ed15
--- /dev/null
+++ b/src/camel/camel-mime-filter-bestenc.c
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-mime-filter-bestenc.h"
+
+#define CAMEL_MIME_FILTER_BESTENC_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_BESTENC, CamelMimeFilterBestencPrivate))
+
+struct _CamelMimeFilterBestencPrivate {
+
+ guint flags; /* our creation flags */
+
+ guint count0; /* count of NUL characters */
+ guint count8; /* count of 8 bit characters */
+ guint total; /* total characters read */
+
+ guint lastc; /* the last character read */
+ gint crlfnoorder; /* if crlf's occurred where they shouldn't have */
+
+ gint startofline; /* are we at the start of a new line? */
+
+ gint fromcount;
+ gchar fromsave[6]; /* save a few characters if we found an \n near the end of the buffer */
+ gint hadfrom; /* did we encounter a "\nFrom " in the data? */
+
+ guint countline; /* current count of characters on a given line */
+ guint maxline; /* max length of any line */
+
+ CamelCharset charset; /* used to determine the best charset to use */
+};
+
+G_DEFINE_TYPE (CamelMimeFilterBestenc, camel_mime_filter_bestenc, CAMEL_TYPE_MIME_FILTER)
+
+static void
+mime_filter_bestenc_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterBestencPrivate *priv;
+ register guchar *p, *pend;
+
+ priv = CAMEL_MIME_FILTER_BESTENC_GET_PRIVATE (mime_filter);
+
+ if (len == 0)
+ goto donothing;
+
+ if (priv->flags & CAMEL_BESTENC_GET_ENCODING) {
+ register guint /* hopefully reg's are assinged in the order they appear? */
+ c,
+ lastc = priv->lastc,
+ countline = priv->countline,
+ count0 = priv->count0,
+ count8 = priv->count8;
+
+ /* Check ^From lines first call, or have the start of a new line waiting? */
+ if ((priv->flags & CAMEL_BESTENC_NO_FROM) && !priv->hadfrom
+ && (priv->fromcount > 0 || priv->startofline)) {
+ if (priv->fromcount + len >=5) {
+ memcpy (&priv->fromsave[priv->fromcount], in, 5 - priv->fromcount);
+ priv->hadfrom = strncmp (priv->fromsave, "From ", 5) == 0;
+ priv->fromcount = 0;
+ } else {
+ memcpy (&priv->fromsave[priv->fromcount], in, len);
+ priv->fromcount += len;
+ }
+ }
+
+ priv->startofline = FALSE;
+
+ /* See rfc2045 section 2 for definitions of 7bit/8bit/binary */
+ p = (guchar *) in;
+ pend = p + len;
+ while (p < pend) {
+ c = *p++;
+ /* check for 8 bit characters */
+ if (c & 0x80)
+ count8++;
+
+ /* check for nul's */
+ if (c == 0)
+ count0++;
+
+ /* check for wild '\r's in a unix format stream */
+ if (c == '\r' && (priv->flags & CAMEL_BESTENC_LF_IS_CRLF)) {
+ priv->crlfnoorder = TRUE;
+ }
+
+ /* check for end of line */
+ if (c == '\n') {
+ /* check for wild '\n's in canonical format stream */
+ if (lastc == '\r' || (priv->flags & CAMEL_BESTENC_LF_IS_CRLF)) {
+ if (countline > priv->maxline)
+ priv->maxline = countline;
+ countline = 0;
+
+ /* Check for "^From " lines */
+ if ((priv->flags & CAMEL_BESTENC_NO_FROM) && !priv->hadfrom) {
+ if (pend - p >= 5) {
+ priv->hadfrom = strncmp ((gchar *) p, (gchar *) "From ", 5) == 0;
+ } else if (pend - p == 0) {
+ priv->startofline = TRUE;
+ } else {
+ priv->fromcount = pend - p;
+ memcpy (priv->fromsave, p, pend - p);
+ }
+ }
+ } else {
+ priv->crlfnoorder = TRUE;
+ }
+ } else {
+ countline++;
+ }
+ lastc = c;
+ }
+ priv->count8 = count8;
+ priv->count0 = count0;
+ priv->countline = countline;
+ priv->lastc = lastc;
+ }
+
+ priv->total += len;
+
+ if (priv->flags & CAMEL_BESTENC_GET_CHARSET)
+ camel_charset_step (&priv->charset, in, len);
+
+donothing:
+ *out = (gchar *) in;
+ *outlen = len;
+ *outprespace = prespace;
+}
+
+static void
+mime_filter_bestenc_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterBestencPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_BESTENC_GET_PRIVATE (mime_filter);
+
+ mime_filter_bestenc_filter (
+ mime_filter, in, len, prespace, out, outlen, outprespace);
+
+ if (priv->countline > priv->maxline)
+ priv->maxline = priv->countline;
+ priv->countline = 0;
+}
+
+static void
+mime_filter_bestenc_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterBestencPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_BESTENC_GET_PRIVATE (mime_filter);
+
+ priv->count0 = 0;
+ priv->count8 = 0;
+ priv->countline = 0;
+ priv->total = 0;
+ priv->lastc = ~0;
+ priv->crlfnoorder = FALSE;
+ priv->fromcount = 0;
+ priv->hadfrom = FALSE;
+ priv->startofline = TRUE;
+
+ camel_charset_init (&priv->charset);
+}
+
+static void
+camel_mime_filter_bestenc_class_init (CamelMimeFilterBestencClass *class)
+{
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterBestencPrivate));
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_bestenc_filter;
+ mime_filter_class->complete = mime_filter_bestenc_complete;
+ mime_filter_class->reset = mime_filter_bestenc_reset;
+}
+
+static void
+camel_mime_filter_bestenc_init (CamelMimeFilterBestenc *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_BESTENC_GET_PRIVATE (filter);
+
+ mime_filter_bestenc_reset (CAMEL_MIME_FILTER (filter));
+}
+
+/**
+ * camel_mime_filter_bestenc_new:
+ * @flags: a bitmask of data required.
+ *
+ * Create a new #CamelMimeFilterBestenc object.
+ *
+ * Returns: a new #CamelMimeFilterBestenc object
+ **/
+CamelMimeFilter *
+camel_mime_filter_bestenc_new (guint flags)
+{
+ CamelMimeFilter *new;
+
+ new = g_object_new (CAMEL_TYPE_MIME_FILTER_BESTENC, NULL);
+ CAMEL_MIME_FILTER_BESTENC (new)->priv->flags = flags;
+
+ return new;
+}
+
+/**
+ * camel_mime_filter_bestenc_get_best_encoding:
+ * @filter: a #CamelMimeFilterBestenc object
+ * @required: maximum level of output encoding allowed.
+ *
+ * Get the best encoding, given specific constraints, that can be used to
+ * encode a stream of bytes.
+ *
+ * Returns: the best encoding to use
+ **/
+CamelTransferEncoding
+camel_mime_filter_bestenc_get_best_encoding (CamelMimeFilterBestenc *filter,
+ CamelBestencEncoding required)
+{
+ CamelMimeFilterBestencPrivate *priv;
+ CamelTransferEncoding bestenc;
+ gint istext;
+
+ priv = CAMEL_MIME_FILTER_BESTENC_GET_PRIVATE (filter);
+
+ istext = (required & CAMEL_BESTENC_TEXT) ? 1 : 0;
+ required = required & ~CAMEL_BESTENC_TEXT;
+
+#if 0
+ printf ("count0 = %d, count8 = %d, total = %d\n", priv->count0, priv->count8, priv->total);
+ printf ("maxline = %d, crlfnoorder = %s\n", priv->maxline, priv->crlfnoorder?"TRUE":"FALSE");
+ printf (" %d%% require encoding?\n", (priv->count0 + priv->count8) * 100 / priv->total);
+#endif
+
+ /* if we're not allowed to have From lines and we had one, use an encoding
+ * that will never let it show. Unfortunately only base64 can at present,
+ * although qp could be modified to allow it too */
+ if ((priv->flags & CAMEL_BESTENC_NO_FROM) && priv->hadfrom)
+ return CAMEL_TRANSFER_ENCODING_BASE64;
+
+ /* if we need to encode, see how we do it */
+ if (required == CAMEL_BESTENC_BINARY)
+ bestenc = CAMEL_TRANSFER_ENCODING_BINARY;
+ else if (istext && (priv->count0 == 0 && priv->count8 < (priv->total * 17 / 100)))
+ bestenc = CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
+ else
+ bestenc = CAMEL_TRANSFER_ENCODING_BASE64;
+
+ /* if we have nocrlf order, or long lines, we need to encode always */
+ if (priv->crlfnoorder || priv->maxline >= 998)
+ return bestenc;
+
+ /* if we have no 8 bit chars or nul's, we can just use 7 bit */
+ if (priv->count8 + priv->count0 == 0)
+ return CAMEL_TRANSFER_ENCODING_7BIT;
+
+ /* otherwise, we see if we can use 8 bit, or not */
+ switch (required) {
+ case CAMEL_BESTENC_7BIT:
+ return bestenc;
+ case CAMEL_BESTENC_8BIT:
+ case CAMEL_BESTENC_BINARY:
+ default:
+ if (priv->count0 == 0)
+ return CAMEL_TRANSFER_ENCODING_8BIT;
+ else
+ return bestenc;
+ }
+
+}
+
+/**
+ * camel_mime_filter_bestenc_get_best_charset:
+ * @filter: a #CamelMimeFilterBestenc object
+ *
+ * Gets the best charset that can be used to contain this content.
+ *
+ * Returns: the name of the best charset to use to encode the input
+ * text filtered by @filter
+ **/
+const gchar *
+camel_mime_filter_bestenc_get_best_charset (CamelMimeFilterBestenc *filter)
+{
+ g_return_val_if_fail (CAMEL_IS_MIME_FILTER_BESTENC (filter), NULL);
+
+ return camel_charset_best_name (&filter->priv->charset);
+}
+
+/**
+ * camel_mime_filter_bestenc_set_flags:
+ * @filter: a #CamelMimeFilterBestenc object
+ * @flags: bestenc filter flags
+ *
+ * Set the flags for subsequent operations.
+ **/
+void
+camel_mime_filter_bestenc_set_flags (CamelMimeFilterBestenc *filter,
+ guint flags)
+{
+ g_return_if_fail (CAMEL_IS_MIME_FILTER_BESTENC (filter));
+
+ filter->priv->flags = flags;
+}
diff --git a/src/camel/camel-mime-filter-bestenc.h b/src/camel/camel-mime-filter-bestenc.h
new file mode 100644
index 000000000..9d5f95b14
--- /dev/null
+++ b/src/camel/camel-mime-filter-bestenc.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_BESTENC_H
+#define CAMEL_MIME_FILTER_BESTENC_H
+
+#include <camel/camel-mime-filter.h>
+#include <camel/camel-mime-part.h>
+#include <camel/camel-charset-map.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_BESTENC \
+ (camel_mime_filter_bestenc_get_type ())
+#define CAMEL_MIME_FILTER_BESTENC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_BESTENC, CamelMimeFilterBestenc))
+#define CAMEL_MIME_FILTER_BESTENC_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_BESTENC, CamelMimeFilterBestencClass))
+#define CAMEL_IS_MIME_FILTER_BESTENC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_BESTENC))
+#define CAMEL_IS_MIME_FILTER_BESTENC_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_BESTENC))
+#define CAMEL_MIME_FILTER_BESTENC_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_BESTENC, CamelMimeFilterBestencClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterBestenc CamelMimeFilterBestenc;
+typedef struct _CamelMimeFilterBestencClass CamelMimeFilterBestencClass;
+typedef struct _CamelMimeFilterBestencPrivate CamelMimeFilterBestencPrivate;
+
+typedef enum _CamelBestencRequired {
+ CAMEL_BESTENC_GET_ENCODING = 1 << 0,
+ CAMEL_BESTENC_GET_CHARSET = 1 << 1,
+
+ /* do we treat 'lf' as if it were crlf? */
+ CAMEL_BESTENC_LF_IS_CRLF = 1 << 8,
+ /* do we not allow "From " to appear at the start of a line in any part? */
+ CAMEL_BESTENC_NO_FROM = 1 << 9
+} CamelBestencRequired;
+
+typedef enum _CamelBestencEncoding {
+ CAMEL_BESTENC_7BIT,
+ CAMEL_BESTENC_8BIT,
+ CAMEL_BESTENC_BINARY,
+
+ /* is the content stream to be treated as text? */
+ CAMEL_BESTENC_TEXT = 1 << 8
+} CamelBestencEncoding;
+
+struct _CamelMimeFilterBestenc {
+ CamelMimeFilter parent;
+ CamelMimeFilterBestencPrivate *priv;
+};
+
+struct _CamelMimeFilterBestencClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_bestenc_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_bestenc_new (guint flags);
+CamelTransferEncoding
+ camel_mime_filter_bestenc_get_best_encoding
+ (CamelMimeFilterBestenc *filter,
+ CamelBestencEncoding required);
+const gchar * camel_mime_filter_bestenc_get_best_charset
+ (CamelMimeFilterBestenc *filter);
+void camel_mime_filter_bestenc_set_flags
+ (CamelMimeFilterBestenc *filter,
+ guint flags);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_BESTENC_H */
diff --git a/src/camel/camel-mime-filter-canon.c b/src/camel/camel-mime-filter-canon.c
new file mode 100644
index 000000000..d5a6e84fe
--- /dev/null
+++ b/src/camel/camel-mime-filter-canon.c
@@ -0,0 +1,215 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+/* canonicalisation filter, used for secure mime incoming and outgoing */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <string.h>
+
+#include "camel-mime-filter-canon.h"
+
+#define CAMEL_MIME_FILTER_CANON_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_CANON, CamelMimeFilterCanonPrivate))
+
+struct _CamelMimeFilterCanonPrivate {
+ guint32 flags;
+};
+
+G_DEFINE_TYPE (
+ CamelMimeFilterCanon,
+ camel_mime_filter_canon,
+ CAMEL_TYPE_MIME_FILTER)
+
+static void
+mime_filter_canon_run (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace,
+ gint last)
+{
+ CamelMimeFilterCanonPrivate *priv;
+ register guchar *inptr, c;
+ const guchar *inend, *start;
+ gchar *starto;
+ register gchar *o;
+ gint lf = 0;
+
+ priv = CAMEL_MIME_FILTER_CANON_GET_PRIVATE (mime_filter);
+
+ /* first, work out how much space we need */
+ inptr = (guchar *) in;
+ inend = (const guchar *) (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 (mime_filter, len + lf * 3 + 4, FALSE);
+
+ o = mime_filter->outbuf;
+ inptr = (guchar *) 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 (priv->flags & CAMEL_MIME_FILTER_CANON_FROM && c == 'F') {
+ inptr++;
+ if (inptr < inend - 4) {
+ if (strncmp ((gchar *) 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 (priv->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 (priv->flags & CAMEL_MIME_FILTER_CANON_CRLF) {
+ if (o[-1] != '\r')
+ *o++ = '\r';
+ } else {
+ if (o[-1] == '\r')
+ o--;
+ }
+ } else if (priv->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 - mime_filter->outbuf;
+ } else {
+ camel_mime_filter_backup (
+ mime_filter, (const gchar *) start, inend - start);
+ *outlen = starto - mime_filter->outbuf;
+ }
+
+ *out = mime_filter->outbuf;
+ *outprespace = mime_filter->outpre;
+}
+
+static void
+mime_filter_canon_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ mime_filter_canon_run (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, FALSE);
+}
+
+static void
+mime_filter_canon_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ mime_filter_canon_run (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, TRUE);
+}
+
+static void
+mime_filter_canon_reset (CamelMimeFilter *mime_filter)
+{
+ /* no-op */
+}
+
+static void
+camel_mime_filter_canon_class_init (CamelMimeFilterCanonClass *class)
+{
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterCanonPrivate));
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_canon_filter;
+ mime_filter_class->complete = mime_filter_canon_complete;
+ mime_filter_class->reset = mime_filter_canon_reset;
+}
+
+static void
+camel_mime_filter_canon_init (CamelMimeFilterCanon *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_CANON_GET_PRIVATE (filter);
+}
+
+/**
+ * camel_mime_filter_canon_new:
+ * @flags: bitwise flags defining the behaviour of the filter
+ *
+ * Create a new filter to canonicalise an input stream.
+ *
+ * Returns: a new #CamelMimeFilterCanon
+ **/
+CamelMimeFilter *
+camel_mime_filter_canon_new (guint32 flags)
+{
+ CamelMimeFilter *filter;
+
+ filter = g_object_new (CAMEL_TYPE_MIME_FILTER_CANON, NULL);
+ CAMEL_MIME_FILTER_CANON (filter)->priv->flags = flags;
+
+ return filter;
+}
diff --git a/src/camel/camel-mime-filter-canon.h b/src/camel/camel-mime-filter-canon.h
new file mode 100644
index 000000000..7a725d0d2
--- /dev/null
+++ b/src/camel/camel-mime-filter-canon.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_CANON_H
+#define CAMEL_MIME_FILTER_CANON_H
+
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_CANON \
+ (camel_mime_filter_canon_get_type ())
+#define CAMEL_MIME_FILTER_CANON(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_CANON, CamelMimeFilterCanon))
+#define CAMEL_MIME_FILTER_CANON_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_CANON, CamelMimeFilterCanonClass))
+#define CAMEL_IS_MIME_FILTER_CANON(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_CANON))
+#define CAMEL_IS_MIME_FILTER_CANON_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_CANON))
+#define CAMEL_MIME_FILTER_CANON_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_CANON, CamelMimeFilterCanonClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterCanon CamelMimeFilterCanon;
+typedef struct _CamelMimeFilterCanonClass CamelMimeFilterCanonClass;
+typedef struct _CamelMimeFilterCanonPrivate CamelMimeFilterCanonPrivate;
+
+enum {
+ CAMEL_MIME_FILTER_CANON_CRLF = (1 << 0), /* canoncialise end of line to crlf, otherwise canonicalise to lf only */
+ CAMEL_MIME_FILTER_CANON_FROM = (1 << 1), /* escape "^From " using quoted-printable semantics into "=46rom " */
+ CAMEL_MIME_FILTER_CANON_STRIP = (1 << 2) /* strip trailing space */
+};
+
+struct _CamelMimeFilterCanon {
+ CamelMimeFilter parent;
+ CamelMimeFilterCanonPrivate *priv;
+};
+
+struct _CamelMimeFilterCanonClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_canon_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_canon_new (guint32 flags);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_CANON_H */
diff --git a/src/camel/camel-mime-filter-charset.c b/src/camel/camel-mime-filter-charset.c
new file mode 100644
index 000000000..c3b9d3d60
--- /dev/null
+++ b/src/camel/camel-mime-filter-charset.c
@@ -0,0 +1,299 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include "camel-charset-map.h"
+#include "camel-iconv.h"
+#include "camel-mime-filter-charset.h"
+
+#define CAMEL_MIME_FILTER_CHARSET_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_CHARSET, CamelMimeFilterCharsetPrivate))
+
+#define d(x)
+#define w(x)
+
+struct _CamelMimeFilterCharsetPrivate {
+ iconv_t ic;
+ gchar *from;
+ gchar *to;
+};
+
+G_DEFINE_TYPE (CamelMimeFilterCharset, camel_mime_filter_charset, CAMEL_TYPE_MIME_FILTER)
+
+static void
+mime_filter_charset_finalize (GObject *object)
+{
+ CamelMimeFilterCharsetPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_CHARSET_GET_PRIVATE (object);
+
+ g_free (priv->from);
+ g_free (priv->to);
+
+ if (priv->ic != (iconv_t) -1) {
+ camel_iconv_close (priv->ic);
+ priv->ic = (iconv_t) -1;
+ }
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_mime_filter_charset_parent_class)->finalize (object);
+}
+
+static void
+mime_filter_charset_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterCharsetPrivate *priv;
+ gsize inleft, outleft, converted = 0;
+ const gchar *inbuf;
+ gchar *outbuf;
+
+ priv = CAMEL_MIME_FILTER_CHARSET_GET_PRIVATE (mime_filter);
+
+ if (priv->ic == (iconv_t) -1)
+ goto noop;
+
+ camel_mime_filter_set_size (mime_filter, len * 5 + 16, FALSE);
+ outbuf = mime_filter->outbuf;
+ outleft = mime_filter->outsize;
+
+ inbuf = in;
+ inleft = len;
+
+ if (inleft > 0) {
+ do {
+ converted = camel_iconv (priv->ic, &inbuf, &inleft, &outbuf, &outleft);
+ if (converted == (gsize) -1) {
+ if (errno == E2BIG) {
+ /*
+ * E2BIG There is not sufficient room at *outbuf.
+ *
+ * We just need to grow our outbuffer and try again.
+ */
+
+ converted = outbuf - mime_filter->outbuf;
+ camel_mime_filter_set_size (mime_filter, inleft * 5 + mime_filter->outsize + 16, TRUE);
+ outbuf = mime_filter->outbuf + converted;
+ outleft = mime_filter->outsize - converted;
+ } else if (errno == EILSEQ) {
+ /*
+ * EILSEQ An invalid multibyte sequence has been encountered
+ * in the input.
+ *
+ * What we do here is eat the invalid bytes in the sequence and continue
+ */
+
+ inbuf++;
+ inleft--;
+ } else if (errno == EINVAL) {
+ /*
+ * EINVAL An incomplete multibyte sequence has been encoun­
+ * tered in the input.
+ *
+ * We assume that this can only happen if we've run out of
+ * bytes for a multibyte sequence, if not we're in trouble.
+ */
+
+ break;
+ } else
+ goto noop;
+ }
+ } while (((gint) inleft) > 0);
+ }
+
+ /* flush the iconv conversion */
+ while (camel_iconv (priv->ic, NULL, NULL, &outbuf, &outleft) == (gsize) -1) {
+ if (errno != E2BIG)
+ break;
+
+ converted = outbuf - mime_filter->outbuf;
+ camel_mime_filter_set_size (mime_filter, mime_filter->outsize + 16, TRUE);
+ outbuf = mime_filter->outbuf + converted;
+ outleft = mime_filter->outsize - converted;
+ }
+
+ *out = mime_filter->outbuf;
+ *outlen = mime_filter->outsize - outleft;
+ *outprespace = mime_filter->outpre;
+
+ return;
+
+ noop:
+
+ *out = (gchar *) in;
+ *outlen = len;
+ *outprespace = prespace;
+}
+
+static void
+mime_filter_charset_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterCharsetPrivate *priv;
+ gsize inleft, outleft, converted = 0;
+ const gchar *inbuf;
+ gchar *outbuf;
+
+ priv = CAMEL_MIME_FILTER_CHARSET_GET_PRIVATE (mime_filter);
+
+ if (priv->ic == (iconv_t) -1)
+ goto noop;
+
+ camel_mime_filter_set_size (mime_filter, len * 5 + 16, FALSE);
+ outbuf = mime_filter->outbuf + converted;
+ outleft = mime_filter->outsize - converted;
+
+ inbuf = in;
+ inleft = len;
+
+ do {
+ converted = camel_iconv (priv->ic, &inbuf, &inleft, &outbuf, &outleft);
+ if (converted == (gsize) -1) {
+ if (errno == E2BIG || errno == EINVAL)
+ break;
+
+ if (errno == EILSEQ) {
+ /*
+ * EILSEQ An invalid multibyte sequence has been encountered
+ * in the input.
+ *
+ * What we do here is eat the invalid bytes in the sequence and continue
+ */
+
+ inbuf++;
+ inleft--;
+ } else {
+ /* unknown error condition */
+ goto noop;
+ }
+ }
+ } while (((gint) inleft) > 0);
+
+ if (((gint) inleft) > 0) {
+ /* We've either got an E2BIG or EINVAL. Save the
+ * remainder of the buffer as we'll process this next
+ * time through */
+ camel_mime_filter_backup (mime_filter, inbuf, inleft);
+ }
+
+ *out = mime_filter->outbuf;
+ *outlen = outbuf - mime_filter->outbuf;
+ *outprespace = mime_filter->outpre;
+
+ return;
+
+ noop:
+
+ *out = (gchar *) in;
+ *outlen = len;
+ *outprespace = prespace;
+}
+
+static void
+mime_filter_charset_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterCharsetPrivate *priv;
+ gchar buf[16];
+ gchar *buffer;
+ gsize outlen = 16;
+
+ priv = CAMEL_MIME_FILTER_CHARSET_GET_PRIVATE (mime_filter);
+
+ /* what happens with the output bytes if this resets the state? */
+ if (priv->ic != (iconv_t) -1) {
+ buffer = buf;
+ camel_iconv (priv->ic, NULL, NULL, &buffer, &outlen);
+ }
+}
+
+static void
+camel_mime_filter_charset_class_init (CamelMimeFilterCharsetClass *class)
+{
+ GObjectClass *object_class;
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterCharsetPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = mime_filter_charset_finalize;
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_charset_filter;
+ mime_filter_class->complete = mime_filter_charset_complete;
+ mime_filter_class->reset = mime_filter_charset_reset;
+}
+
+static void
+camel_mime_filter_charset_init (CamelMimeFilterCharset *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_CHARSET_GET_PRIVATE (filter);
+ filter->priv->ic = (iconv_t) -1;
+}
+
+/**
+ * camel_mime_filter_charset_new:
+ * @from_charset: charset to convert from
+ * @to_charset: charset to convert to
+ *
+ * Create a new #CamelMimeFiletrCharset object to convert text from
+ * @from_charset to @to_charset.
+ *
+ * Returns: a new #CamelMimeFilterCharset object
+ **/
+CamelMimeFilter *
+camel_mime_filter_charset_new (const gchar *from_charset,
+ const gchar *to_charset)
+{
+ CamelMimeFilter *new;
+ CamelMimeFilterCharsetPrivate *priv;
+
+ new = g_object_new (CAMEL_TYPE_MIME_FILTER_CHARSET, NULL);
+ priv = CAMEL_MIME_FILTER_CHARSET_GET_PRIVATE (new);
+
+ priv->ic = camel_iconv_open (to_charset, from_charset);
+ if (priv->ic == (iconv_t) -1) {
+ w (g_warning (
+ "Cannot create charset conversion from %s to %s: %s",
+ from_charset ? from_charset : "(null)",
+ to_charset ? to_charset : "(null)",
+ g_strerror (errno)));
+ g_object_unref (new);
+ new = NULL;
+ } else {
+ priv->from = g_strdup (from_charset);
+ priv->to = g_strdup (to_charset);
+ }
+
+ return new;
+}
diff --git a/src/camel/camel-mime-filter-charset.h b/src/camel/camel-mime-filter-charset.h
new file mode 100644
index 000000000..a3336ddaa
--- /dev/null
+++ b/src/camel/camel-mime-filter-charset.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_CHARSET_H
+#define CAMEL_MIME_FILTER_CHARSET_H
+
+#include <camel/camel-mime-filter.h>
+#include <iconv.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_CHARSET \
+ (camel_mime_filter_charset_get_type ())
+#define CAMEL_MIME_FILTER_CHARSET(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_CHARSET, CamelMimeFilterCharset))
+#define CAMEL_MIME_FILTER_CHARSET_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_CHARSET, CamelMimeFilterCharsetClass))
+#define CAMEL_IS_MIME_FILTER_CHARSET(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_CHARSET))
+#define CAMEL_IS_MIME_FILTER_CHARSET_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_CHARSET))
+#define CAMEL_MIME_FILTER_CHARSET_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_CHARSET, CamelMimeFilterCharsetClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterCharset CamelMimeFilterCharset;
+typedef struct _CamelMimeFilterCharsetClass CamelMimeFilterCharsetClass;
+typedef struct _CamelMimeFilterCharsetPrivate CamelMimeFilterCharsetPrivate;
+
+struct _CamelMimeFilterCharset {
+ CamelMimeFilter parent;
+ CamelMimeFilterCharsetPrivate *priv;
+};
+
+struct _CamelMimeFilterCharsetClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_charset_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_charset_new (const gchar *from_charset,
+ const gchar *to_charset);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_CHARSET_H */
diff --git a/src/camel/camel-mime-filter-crlf.c b/src/camel/camel-mime-filter-crlf.c
new file mode 100644
index 000000000..4d3d20b27
--- /dev/null
+++ b/src/camel/camel-mime-filter-crlf.c
@@ -0,0 +1,201 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "camel-mime-filter-crlf.h"
+
+#define CAMEL_MIME_FILTER_CRLF_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_CRLF, CamelMimeFilterCRLFPrivate))
+
+struct _CamelMimeFilterCRLFPrivate {
+ CamelMimeFilterCRLFDirection direction;
+ CamelMimeFilterCRLFMode mode;
+ gboolean saw_cr;
+ gboolean saw_lf;
+ gboolean saw_dot;
+};
+
+G_DEFINE_TYPE (CamelMimeFilterCRLF, camel_mime_filter_crlf, CAMEL_TYPE_MIME_FILTER)
+
+static void
+mime_filter_crlf_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterCRLFPrivate *priv;
+ register const gchar *inptr;
+ const gchar *inend;
+ gboolean do_dots;
+ gchar *outptr;
+
+ priv = CAMEL_MIME_FILTER_CRLF_GET_PRIVATE (mime_filter);
+
+ do_dots = priv->mode == CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS;
+
+ inptr = in;
+ inend = in + len;
+
+ if (priv->direction == CAMEL_MIME_FILTER_CRLF_ENCODE) {
+ camel_mime_filter_set_size (mime_filter, 3 * len, FALSE);
+
+ outptr = mime_filter->outbuf;
+ while (inptr < inend) {
+ if (*inptr == '\r') {
+ priv->saw_cr = TRUE;
+ } else if (*inptr == '\n') {
+ priv->saw_lf = TRUE;
+ if (!priv->saw_cr)
+ *outptr++ = '\r';
+ priv->saw_cr = FALSE;
+ } else {
+ if (do_dots && *inptr == '.' && priv->saw_lf)
+ *outptr++ = '.';
+
+ priv->saw_cr = FALSE;
+ priv->saw_lf = FALSE;
+ }
+
+ *outptr++ = *inptr++;
+ }
+ } else {
+ /* Output can "grow" by one byte if priv->saw_cr was set as
+ * a carry-over from the previous invocation. This will happen
+ * in practice, as the input is processed in arbitrarily-sized
+ * blocks. */
+ camel_mime_filter_set_size (mime_filter, len + 1, FALSE);
+
+ outptr = mime_filter->outbuf;
+ while (inptr < inend) {
+ if (*inptr == '\r') {
+ priv->saw_cr = TRUE;
+ } else {
+ if (priv->saw_cr) {
+ priv->saw_cr = FALSE;
+
+ if (*inptr == '\n') {
+ priv->saw_lf = TRUE;
+ *outptr++ = *inptr++;
+ continue;
+ } else
+ *outptr++ = '\r';
+ }
+
+ *outptr++ = *inptr;
+ }
+
+ if (do_dots && *inptr == '.') {
+ if (priv->saw_lf) {
+ priv->saw_dot = TRUE;
+ priv->saw_lf = FALSE;
+ inptr++;
+ } else if (priv->saw_dot) {
+ priv->saw_dot = FALSE;
+ }
+ }
+
+ priv->saw_lf = FALSE;
+
+ inptr++;
+ }
+ }
+
+ *out = mime_filter->outbuf;
+ *outlen = outptr - mime_filter->outbuf;
+ *outprespace = mime_filter->outpre;
+}
+
+static void
+mime_filter_crlf_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ if (len)
+ mime_filter_crlf_filter (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace);
+}
+
+static void
+mime_filter_crlf_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterCRLFPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_CRLF_GET_PRIVATE (mime_filter);
+
+ priv->saw_cr = FALSE;
+ priv->saw_lf = TRUE;
+ priv->saw_dot = FALSE;
+}
+
+static void
+camel_mime_filter_crlf_class_init (CamelMimeFilterCRLFClass *class)
+{
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterCRLFPrivate));
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_crlf_filter;
+ mime_filter_class->complete = mime_filter_crlf_complete;
+ mime_filter_class->reset = mime_filter_crlf_reset;
+}
+
+static void
+camel_mime_filter_crlf_init (CamelMimeFilterCRLF *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_CRLF_GET_PRIVATE (filter);
+
+ filter->priv->saw_cr = FALSE;
+ filter->priv->saw_lf = TRUE;
+ filter->priv->saw_dot = FALSE;
+}
+
+/**
+ * camel_mime_filter_crlf_new:
+ * @direction: encode vs decode
+ * @mode: whether or not to perform SMTP dot-escaping
+ *
+ * Create a new #CamelMimeFiletrCRLF object.
+ *
+ * Returns: a new #CamelMimeFilterCRLF object
+ **/
+CamelMimeFilter *
+camel_mime_filter_crlf_new (CamelMimeFilterCRLFDirection direction,
+ CamelMimeFilterCRLFMode mode)
+{
+ CamelMimeFilter *filter;
+ CamelMimeFilterCRLFPrivate *priv;
+
+ filter = g_object_new (CAMEL_TYPE_MIME_FILTER_CRLF, NULL);
+ priv = CAMEL_MIME_FILTER_CRLF_GET_PRIVATE (filter);
+
+ priv->direction = direction;
+ priv->mode = mode;
+
+ return filter;
+}
diff --git a/src/camel/camel-mime-filter-crlf.h b/src/camel/camel-mime-filter-crlf.h
new file mode 100644
index 000000000..3c2ae6984
--- /dev/null
+++ b/src/camel/camel-mime-filter-crlf.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_CRLF_H
+#define CAMEL_MIME_FILTER_CRLF_H
+
+#include <camel/camel-enums.h>
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_CRLF \
+ (camel_mime_filter_crlf_get_type ())
+#define CAMEL_MIME_FILTER_CRLF(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_CRLF, CamelMimeFilterCRLF))
+#define CAMEL_MIME_FILTER_CRLF_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_CRLF, CamelMimeFilterCRLFClass))
+#define CAMEL_IS_MIME_FILTER_CRLF(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_CRLF))
+#define CAMEL_IS_MIME_FILTER_CRLF_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_CRLF))
+#define CAMEL_MIME_FILTER_CRLF_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_CRLF, CamelMimeFilterCRLFClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterCRLF CamelMimeFilterCRLF;
+typedef struct _CamelMimeFilterCRLFClass CamelMimeFilterCRLFClass;
+typedef struct _CamelMimeFilterCRLFPrivate CamelMimeFilterCRLFPrivate;
+
+struct _CamelMimeFilterCRLF {
+ CamelMimeFilter parent;
+ CamelMimeFilterCRLFPrivate *priv;
+};
+
+struct _CamelMimeFilterCRLFClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_crlf_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_crlf_new (CamelMimeFilterCRLFDirection direction,
+ CamelMimeFilterCRLFMode mode);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_CRLF_H */
diff --git a/src/camel/camel-mime-filter-enriched.c b/src/camel/camel-mime-filter-enriched.c
new file mode 100644
index 000000000..d09b0f98d
--- /dev/null
+++ b/src/camel/camel-mime-filter-enriched.c
@@ -0,0 +1,612 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "camel-mime-filter-enriched.h"
+#include "camel-string-utils.h"
+
+#define CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_ENRICHED, CamelMimeFilterEnrichedPrivate))
+
+struct _CamelMimeFilterEnrichedPrivate {
+ guint32 flags;
+ gint nofill;
+};
+
+/* text/enriched is rfc1896 */
+
+typedef gchar * (*EnrichedParamParser) (const gchar *inptr, gint inlen);
+
+static gchar *param_parse_color (const gchar *inptr, gint inlen);
+static gchar *param_parse_font (const gchar *inptr, gint inlen);
+static gchar *param_parse_lang (const gchar *inptr, gint inlen);
+
+static struct {
+ const gchar *enriched;
+ const gchar *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_color },
+ { "/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 },
+};
+
+static GHashTable *enriched_hash = NULL;
+
+G_DEFINE_TYPE (CamelMimeFilterEnriched, camel_mime_filter_enriched, CAMEL_TYPE_MIME_FILTER)
+
+#if 0
+static gboolean
+enriched_tag_needs_param (const gchar *tag)
+{
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (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 gchar *tag)
+{
+ return strstr (tag, "%s") != NULL;
+}
+
+static const gchar *valid_colors[] = {
+ "red", "green", "blue", "yellow", "cyan", "magenta", "black", "white"
+};
+
+static gchar *
+param_parse_color (const gchar *inptr,
+ gint inlen)
+{
+ const gchar *inend, *end;
+ guint32 rgb = 0;
+ guint v;
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (valid_colors); i++) {
+ if (!g_ascii_strncasecmp (inptr, valid_colors[i], inlen))
+ return g_strdup (valid_colors[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 color 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, (gchar **) &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 color? */
+ return g_strdup ("black");
+}
+
+static gchar *
+param_parse_font (const gchar *fontfamily,
+ gint inlen)
+{
+ register const gchar *inptr = fontfamily;
+ const gchar *inend = inptr + inlen;
+
+ /* don't allow any of '"', '<', nor '>' */
+ while (inptr < inend && *inptr != '"' && *inptr != '<' && *inptr != '>')
+ inptr++;
+
+ return g_strndup (fontfamily, inptr - fontfamily);
+}
+
+static gchar *
+param_parse_lang (const gchar *lang,
+ gint inlen)
+{
+ register const gchar *inptr = lang;
+ const gchar *inend = inptr + inlen;
+
+ /* don't allow any of '"', '<', nor '>' */
+ while (inptr < inend && *inptr != '"' && *inptr != '<' && *inptr != '>')
+ inptr++;
+
+ return g_strndup (lang, inptr - lang);
+}
+
+static gchar *
+param_parse (const gchar *enriched,
+ const gchar *inptr,
+ gint inlen)
+{
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (enriched_tags); i++) {
+ if (!g_ascii_strcasecmp (enriched, enriched_tags[i].enriched))
+ return enriched_tags[i].parse_param (inptr, inlen);
+ }
+
+ g_warn_if_reached ();
+
+ return NULL;
+}
+
+#define IS_RICHTEXT CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT
+
+static void
+enriched_to_html (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize inlen,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace,
+ gboolean flush)
+{
+ CamelMimeFilterEnrichedPrivate *priv;
+ const gchar *tag, *inend, *outend;
+ register const gchar *inptr;
+ register gchar *outptr;
+
+ priv = CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE (mime_filter);
+
+ camel_mime_filter_set_size (mime_filter, inlen * 2 + 6, FALSE);
+
+ inptr = in;
+ inend = in + inlen;
+ outptr = mime_filter->outbuf;
+ outend = mime_filter->outbuf + mime_filter->outsize;
+
+ retry:
+ do {
+ while (inptr < inend && outptr < outend && !strchr (" <>&\n", *inptr)) {
+ *outptr = *inptr;
+
+ outptr++;
+ inptr++;
+ }
+
+ if (outptr == outend)
+ goto backup;
+
+ if ((inptr + 1) >= inend)
+ break;
+
+ switch (*inptr++) {
+ case ' ':
+ while (inptr < inend && (outptr + 7) < outend && *inptr == ' ') {
+ memcpy (outptr, "&nbsp;", 6);
+ outptr += 6;
+ inptr++;
+ }
+
+ if (outptr < outend)
+ *outptr++ = ' ';
+
+ break;
+ case '\n':
+ if (!(priv->flags & IS_RICHTEXT)) {
+ /* text/enriched */
+ if (priv->nofill > 0) {
+ if ((outptr + 4) < outend) {
+ memcpy (outptr, "<br>", 4);
+ outptr += 4;
+ } else {
+ inptr--;
+ goto backup;
+ }
+ } else if (*inptr == '\n') {
+ if ((outptr + 4) >= outend) {
+ inptr--;
+ goto backup;
+ }
+
+ while (inptr < inend && (outptr + 4) < outend && *inptr == '\n') {
+ memcpy (outptr, "<br>", 4);
+ outptr += 4;
+ inptr++;
+ }
+ } else {
+ *outptr++ = ' ';
+ }
+ } else {
+ /* text/richtext */
+ *outptr++ = ' ';
+ }
+ break;
+ case '>':
+ if ((outptr + 4) < outend) {
+ memcpy (outptr, "&gt;", 4);
+ outptr += 4;
+ } else {
+ inptr--;
+ goto backup;
+ }
+ break;
+ case '&':
+ if ((outptr + 5) < outend) {
+ memcpy (outptr, "&amp;", 5);
+ outptr += 5;
+ } else {
+ inptr--;
+ goto backup;
+ }
+ break;
+ case '<':
+ if (!(priv->flags & IS_RICHTEXT)) {
+ /* text/enriched */
+ if (*inptr == '<') {
+ if ((outptr + 4) < outend) {
+ memcpy (outptr, "&lt;", 4);
+ outptr += 4;
+ inptr++;
+ break;
+ } else {
+ inptr--;
+ goto backup;
+ }
+ }
+ } else {
+ /* text/richtext */
+ if ((inend - inptr) >= 3 && (outptr + 4) < outend) {
+ if (strncmp (inptr, "lt>", 3) == 0) {
+ memcpy (outptr, "&lt;", 4);
+ outptr += 4;
+ inptr += 3;
+ break;
+ } else if (strncmp (inptr, "nl>", 3) == 0) {
+ memcpy (outptr, "<br>", 4);
+ outptr += 4;
+ inptr += 3;
+ break;
+ }
+ } else {
+ inptr--;
+ goto backup;
+ }
+ }
+
+ tag = inptr;
+ while (inptr < inend && *inptr != '>')
+ inptr++;
+
+ if (inptr == inend) {
+ inptr = tag - 1;
+ goto need_input;
+ }
+
+ if (!g_ascii_strncasecmp (tag, "nofill>", 7)) {
+ if ((outptr + 5) < outend) {
+ priv->nofill++;
+ } else {
+ inptr = tag - 1;
+ goto backup;
+ }
+ } else if (!g_ascii_strncasecmp (tag, "/nofill>", 8)) {
+ if ((outptr + 6) < outend) {
+ priv->nofill--;
+ } else {
+ inptr = tag - 1;
+ goto backup;
+ }
+ } else {
+ const gchar *html_tag;
+ gchar *enriched_tag;
+ gint 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 gchar *start;
+ gchar *param;
+
+ while (inptr < inend && *inptr != '<')
+ inptr++;
+
+ if (inptr == inend || (inend - inptr) <= 15) {
+ inptr = tag - 1;
+ goto need_input;
+ }
+
+ if (g_ascii_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 (g_ascii_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 (mime_filter, inptr, (unsigned) (inend - inptr));
+
+ *out = mime_filter->outbuf;
+ *outlen = outptr - mime_filter->outbuf;
+ *outprespace = mime_filter->outpre;
+
+ return;
+
+ backup:
+
+ if (flush) {
+ gsize offset, grow;
+
+ grow = (inend - inptr) * 2 + 20;
+ offset = outptr - mime_filter->outbuf;
+ camel_mime_filter_set_size (mime_filter, mime_filter->outsize + grow, TRUE);
+ outend = mime_filter->outbuf + mime_filter->outsize;
+ outptr = mime_filter->outbuf + offset;
+
+ goto retry;
+ } else {
+ camel_mime_filter_backup (mime_filter, inptr, (unsigned) (inend - inptr));
+ }
+
+ *out = mime_filter->outbuf;
+ *outlen = outptr - mime_filter->outbuf;
+ *outprespace = mime_filter->outpre;
+}
+
+static void
+mime_filter_enriched_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ enriched_to_html (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, FALSE);
+}
+
+static void
+mime_filter_enriched_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ enriched_to_html (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, TRUE);
+}
+
+static void
+mime_filter_enriched_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterEnrichedPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE (mime_filter);
+
+ priv->nofill = 0;
+}
+
+static void
+camel_mime_filter_enriched_class_init (CamelMimeFilterEnrichedClass *class)
+{
+ CamelMimeFilterClass *mime_filter_class;
+ gint i;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterEnrichedPrivate));
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_enriched_filter;
+ mime_filter_class->complete = mime_filter_enriched_complete;
+ mime_filter_class->reset = mime_filter_enriched_reset;
+
+ enriched_hash = g_hash_table_new (
+ camel_strcase_hash, camel_strcase_equal);
+ for (i = 0; i < G_N_ELEMENTS (enriched_tags); i++)
+ g_hash_table_insert (
+ enriched_hash,
+ (gpointer) enriched_tags[i].enriched,
+ (gpointer) enriched_tags[i].html);
+}
+
+static void
+camel_mime_filter_enriched_init (CamelMimeFilterEnriched *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE (filter);
+}
+
+/**
+ * camel_mime_filter_enriched_new:
+ * @flags: bitwise set of flags to specify filter behaviour
+ *
+ * Create a new #CamelMimeFilterEnriched object to convert input text
+ * streams from text/plain into text/enriched or text/richtext.
+ *
+ * Returns: a new #CamelMimeFilterEnriched object
+ **/
+CamelMimeFilter *
+camel_mime_filter_enriched_new (guint32 flags)
+{
+ CamelMimeFilter *new;
+ CamelMimeFilterEnrichedPrivate *priv;
+
+ new = g_object_new (CAMEL_TYPE_MIME_FILTER_ENRICHED, NULL);
+ priv = CAMEL_MIME_FILTER_ENRICHED_GET_PRIVATE (new);
+
+ priv->flags = flags;
+
+ return new;
+}
+
+/**
+ * camel_enriched_to_html:
+ * @in: input textual string
+ * @flags: flags specifying filter behaviour
+ *
+ * Convert @in from text/plain into text/enriched or text/richtext
+ * based on @flags.
+ *
+ * Returns: a newly allocated string containing the enriched or
+ * richtext version of @in.
+ **/
+gchar *
+camel_enriched_to_html (const gchar *in,
+ guint32 flags)
+{
+ CamelMimeFilter *filter;
+ gsize outlen, outpre;
+ gchar *outbuf;
+
+ if (in == NULL)
+ return NULL;
+
+ filter = camel_mime_filter_enriched_new (flags);
+
+ camel_mime_filter_complete (filter, (gchar *) in, strlen (in), 0, &outbuf, &outlen, &outpre);
+ outbuf = g_strndup (outbuf, outlen);
+ g_object_unref (filter);
+
+ return outbuf;
+}
diff --git a/src/camel/camel-mime-filter-enriched.h b/src/camel/camel-mime-filter-enriched.h
new file mode 100644
index 000000000..0ffad8e6e
--- /dev/null
+++ b/src/camel/camel-mime-filter-enriched.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_ENRICHED_H
+#define CAMEL_MIME_FILTER_ENRICHED_H
+
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_ENRICHED \
+ (camel_mime_filter_enriched_get_type ())
+#define CAMEL_MIME_FILTER_ENRICHED(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_ENRICHED, CamelMimeFilterEnriched))
+#define CAMEL_MIME_FILTER_ENRICHED_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_ENRICHED, CamelMimeFilterEnrichedClass))
+#define CAMEL_IS_MIME_FILTER_ENRICHED(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_ENRICHED))
+#define CAMEL_IS_MIME_FILTER_ENRICHED_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_ENRICHED))
+#define CAMEL_MIME_FILTER_ENRICHED_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_ENRICHED, CamelMimeFilterEnrichedClass))
+
+#define CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT (1 << 0)
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterEnriched CamelMimeFilterEnriched;
+typedef struct _CamelMimeFilterEnrichedClass CamelMimeFilterEnrichedClass;
+typedef struct _CamelMimeFilterEnrichedPrivate CamelMimeFilterEnrichedPrivate;
+
+struct _CamelMimeFilterEnriched {
+ CamelMimeFilter parent;
+ CamelMimeFilterEnrichedPrivate *priv;
+};
+
+struct _CamelMimeFilterEnrichedClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_enriched_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_enriched_new (guint32 flags);
+gchar * camel_enriched_to_html (const gchar *in,
+ guint32 flags);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_ENRICHED_H */
diff --git a/src/camel/camel-mime-filter-from.c b/src/camel/camel-mime-filter-from.c
new file mode 100644
index 000000000..fa99700f1
--- /dev/null
+++ b/src/camel/camel-mime-filter-from.c
@@ -0,0 +1,209 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include "camel-mime-filter-from.h"
+
+#define CAMEL_MIME_FILTER_FROM_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_FROM, CamelMimeFilterFromPrivate))
+
+#define d(x)
+
+struct _CamelMimeFilterFromPrivate {
+ gboolean midline; /* are we between lines? */
+};
+
+struct fromnode {
+ struct fromnode *next;
+ const gchar *pointer;
+};
+
+G_DEFINE_TYPE (CamelMimeFilterFrom, camel_mime_filter_from, CAMEL_TYPE_MIME_FILTER)
+
+/* Yes, it is complicated ... */
+static void
+mime_filter_from_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterFromPrivate *priv;
+ const gchar *inptr, *inend;
+ gint left;
+ gint fromcount = 0;
+ struct fromnode *head = NULL, *tail = (struct fromnode *) &head, *node;
+ gchar *outptr;
+
+ priv = CAMEL_MIME_FILTER_FROM_GET_PRIVATE (mime_filter);
+
+ inptr = in;
+ inend = inptr + len;
+
+ d (printf ("Filtering '%.*s'\n", len, in));
+
+ /* first, see if we need to escape any from's */
+ while (inptr < inend) {
+ register gint c = -1;
+
+ if (priv->midline)
+ while (inptr < inend && (c = *inptr++) != '\n')
+ ;
+
+ if (c == '\n' || !priv->midline) {
+ left = inend - inptr;
+ if (left > 0) {
+ priv->midline = TRUE;
+ if (left < 5) {
+ if (inptr[0] == 'F') {
+ camel_mime_filter_backup (mime_filter, inptr, left);
+ priv->midline = FALSE;
+ inend = inptr;
+ break;
+ }
+ } else {
+ if (!strncmp (inptr, "From ", 5)) {
+ fromcount++;
+ /* yes, we do alloc them on the stack ... at most we're going to get
+ * len / 7 of them anyway */
+ node = alloca (sizeof (*node));
+ node->pointer = inptr;
+ node->next = NULL;
+ tail->next = node;
+ tail = node;
+ inptr += 5;
+ }
+ }
+ } else {
+ /* \n is at end of line, check next buffer */
+ priv->midline = FALSE;
+ }
+ }
+ }
+
+ if (fromcount > 0) {
+ camel_mime_filter_set_size (mime_filter, len + fromcount, FALSE);
+ node = head;
+ inptr = in;
+ outptr = mime_filter->outbuf;
+ while (node) {
+ memcpy (outptr, inptr, node->pointer - inptr);
+ outptr += node->pointer - inptr;
+ *outptr++ = '>';
+ inptr = node->pointer;
+ node = node->next;
+ }
+ memcpy (outptr, inptr, inend - inptr);
+ outptr += inend - inptr;
+ *out = mime_filter->outbuf;
+ *outlen = outptr - mime_filter->outbuf;
+ *outprespace = mime_filter->outbuf - mime_filter->outreal;
+
+ d (printf ("Filtered '%.*s'\n", *outlen, *out));
+ } else {
+ *out = (gchar *) in;
+ *outlen = inend - in;
+ *outprespace = prespace;
+
+ d (printf ("Filtered '%.*s'\n", *outlen, *out));
+ }
+}
+
+static void
+mime_filter_from_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ *out = (gchar *) in;
+ *outlen = len;
+ *outprespace = prespace;
+}
+
+static void
+camel_mime_filter_from_class_init (CamelMimeFilterFromClass *class)
+{
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterFromPrivate));
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_from_filter;
+ mime_filter_class->complete = mime_filter_from_complete;
+}
+
+static void
+camel_mime_filter_from_init (CamelMimeFilterFrom *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_FROM_GET_PRIVATE (filter);
+}
+
+/**
+ * camel_mime_filter_from_new:
+ *
+ * Create a new #CamelMimeFilterFrom object.
+ *
+ * Returns: a new #CamelMimeFilterFrom object
+ **/
+CamelMimeFilter *
+camel_mime_filter_from_new (void)
+{
+ return g_object_new (CAMEL_TYPE_MIME_FILTER_FROM, NULL);
+}
+
+#if 0
+
+#include <stdio.h>
+
+gint main (gint argc, gchar **argv)
+{
+ CamelMimeFilterFrom *f;
+ gchar *buffer;
+ gint len, prespace;
+
+ g_tk_init (&argc, &argv);
+
+ f = camel_mime_filter_from_new ();
+
+ buffer = "This is a test\nFrom Someone\nTo someone. From Someone else, From\n From blah\nFromblah\nBye! \nFrom ";
+ len = strlen (buffer);
+ prespace = 0;
+
+ printf ("input = '%.*s'\n", len, buffer);
+ camel_mime_filter_filter (f, buffer, len, prespace, &buffer, &len, &prespace);
+ printf ("output = '%.*s'\n", len, buffer);
+ buffer = "";
+ len = 0;
+ prespace = 0;
+ camel_mime_filter_complete (f, buffer, len, prespace, &buffer, &len, &prespace);
+ printf ("complete = '%.*s'\n", len, buffer);
+
+ return 0;
+}
+
+#endif
diff --git a/src/camel/camel-mime-filter-from.h b/src/camel/camel-mime-filter-from.h
new file mode 100644
index 000000000..86b3d2078
--- /dev/null
+++ b/src/camel/camel-mime-filter-from.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_FROM_H
+#define CAMEL_MIME_FILTER_FROM_H
+
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_FROM \
+ (camel_mime_filter_from_get_type ())
+#define CAMEL_MIME_FILTER_FROM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_FROM, CamelMimeFilterFrom))
+#define CAMEL_MIME_FILTER_FROM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_FROM, CamelMimeFilterFromClass))
+#define CAMEL_IS_MIME_FILTER_FROM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_FROM))
+#define CAMEL_IS_MIME_FILTER_FROM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_FROM))
+#define CAMEL_MIME_FILTER_FROM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_FROM, CamelMimeFilterFromClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterFrom CamelMimeFilterFrom;
+typedef struct _CamelMimeFilterFromClass CamelMimeFilterFromClass;
+typedef struct _CamelMimeFilterFromPrivate CamelMimeFilterFromPrivate;
+
+struct _CamelMimeFilterFrom {
+ CamelMimeFilter parent;
+ CamelMimeFilterFromPrivate *priv;
+};
+
+struct _CamelMimeFilterFromClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_from_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_from_new (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_FROM_H */
diff --git a/src/camel/camel-mime-filter-gzip.c b/src/camel/camel-mime-filter-gzip.c
new file mode 100644
index 000000000..8e8c9e1fe
--- /dev/null
+++ b/src/camel/camel-mime-filter-gzip.c
@@ -0,0 +1,482 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <zlib.h>
+
+#include "camel-mime-filter-gzip.h"
+
+#define CAMEL_MIME_FILTER_GZIP_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_GZIP, CamelMimeFilterGZipPrivate))
+
+/* rfc1952 */
+
+enum {
+ GZIP_FLAG_FTEXT = (1 << 0),
+ GZIP_FLAG_FHCRC = (1 << 1),
+ GZIP_FLAG_FEXTRA = (1 << 2),
+ GZIP_FLAG_FNAME = (1 << 3),
+ GZIP_FLAG_FCOMMENT = (1 << 4),
+ GZIP_FLAG_RESERVED0 = (1 << 5),
+ GZIP_FLAG_RESERVED1 = (1 << 6),
+ GZIP_FLAG_RESERVED2 = (1 << 7)
+};
+
+#define GZIP_FLAG_RESERVED (GZIP_FLAG_RESERVED0 | GZIP_FLAG_RESERVED1 | GZIP_FLAG_RESERVED2)
+
+typedef union {
+ guchar buf[10];
+ struct {
+ guint8 id1;
+ guint8 id2;
+ guint8 cm;
+ guint8 flg;
+ guint32 mtime;
+ guint8 xfl;
+ guint8 os;
+ } v;
+} gzip_hdr_t;
+
+typedef union {
+ struct {
+ guint16 xlen;
+ guint16 xlen_nread;
+ guint16 crc16;
+
+ guint8 got_hdr : 1;
+ guint8 is_valid : 1;
+ guint8 got_xlen : 1;
+ guint8 got_fname : 1;
+ guint8 got_fcomment : 1;
+ guint8 got_crc16 : 1;
+ } unzip;
+ struct {
+ guint32 wrote_hdr : 1;
+ } zip;
+} gzip_state_t;
+
+struct _CamelMimeFilterGZipPrivate {
+
+ CamelMimeFilterGZipMode mode;
+ gint level;
+
+ z_stream *stream;
+
+ gzip_state_t state;
+ gzip_hdr_t hdr;
+
+ guint32 crc32;
+ guint32 isize;
+};
+
+G_DEFINE_TYPE (CamelMimeFilterGZip, camel_mime_filter_gzip, CAMEL_TYPE_MIME_FILTER)
+
+static void
+gzip_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace,
+ gint flush)
+{
+ CamelMimeFilterGZipPrivate *priv;
+ gint retval;
+
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (mime_filter);
+
+ if (!priv->state.zip.wrote_hdr) {
+ priv->hdr.v.id1 = 31;
+ priv->hdr.v.id2 = 139;
+ priv->hdr.v.cm = Z_DEFLATED;
+ priv->hdr.v.mtime = 0;
+ priv->hdr.v.flg = 0;
+ if (priv->level == Z_BEST_COMPRESSION)
+ priv->hdr.v.xfl = 2;
+ else if (priv->level == Z_BEST_SPEED)
+ priv->hdr.v.xfl = 4;
+ else
+ priv->hdr.v.xfl = 0;
+ priv->hdr.v.os = 255;
+
+ camel_mime_filter_set_size (mime_filter, (len * 2) + 22, FALSE);
+
+ memcpy (mime_filter->outbuf, priv->hdr.buf, 10);
+
+ priv->stream->next_out = (Bytef *) mime_filter->outbuf + 10;
+ priv->stream->avail_out = mime_filter->outsize - 10;
+
+ priv->state.zip.wrote_hdr = TRUE;
+ } else {
+ camel_mime_filter_set_size (mime_filter, (len * 2) + 12, FALSE);
+
+ priv->stream->next_out = (Bytef *) mime_filter->outbuf;
+ priv->stream->avail_out = mime_filter->outsize;
+ }
+
+ priv->stream->next_in = (Bytef *) in;
+ priv->stream->avail_in = len;
+
+ do {
+ /* FIXME: handle error cases? */
+ if ((retval = deflate (priv->stream, flush)) != Z_OK)
+ fprintf (stderr, "gzip: %d: %s\n", retval, priv->stream->msg);
+
+ if (flush == Z_FULL_FLUSH) {
+ gsize n;
+
+ n = mime_filter->outsize - priv->stream->avail_out;
+ camel_mime_filter_set_size (mime_filter, n + (priv->stream->avail_in * 2) + 12, TRUE);
+ priv->stream->avail_out = mime_filter->outsize - n;
+ priv->stream->next_out = (Bytef *) mime_filter->outbuf + n;
+
+ if (priv->stream->avail_in == 0) {
+ guint32 val;
+
+ val = GUINT32_TO_LE (priv->crc32);
+ memcpy (priv->stream->next_out, &val, 4);
+ priv->stream->avail_out -= 4;
+ priv->stream->next_out += 4;
+
+ val = GUINT32_TO_LE (priv->isize);
+ memcpy (priv->stream->next_out, &val, 4);
+ priv->stream->avail_out -= 4;
+ priv->stream->next_out += 4;
+
+ break;
+ }
+ } else {
+ if (priv->stream->avail_in > 0)
+ camel_mime_filter_backup (mime_filter, (const gchar *) priv->stream->next_in, priv->stream->avail_in);
+
+ break;
+ }
+ } while (1);
+
+ priv->crc32 = crc32 (priv->crc32, (guchar *) in, len - priv->stream->avail_in);
+ priv->isize += len - priv->stream->avail_in;
+
+ *out = mime_filter->outbuf;
+ *outlen = mime_filter->outsize - priv->stream->avail_out;
+ *outprespace = mime_filter->outpre;
+}
+
+static void
+gunzip_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace,
+ gint flush)
+{
+ CamelMimeFilterGZipPrivate *priv;
+ guint16 need, val;
+ gint retval;
+
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (mime_filter);
+
+ if (!priv->state.unzip.got_hdr) {
+ if (len < 10) {
+ camel_mime_filter_backup (mime_filter, in, len);
+ return;
+ }
+
+ memcpy (priv->hdr.buf, in, 10);
+ priv->state.unzip.got_hdr = TRUE;
+ len -= 10;
+ in += 10;
+
+ priv->state.unzip.is_valid = (priv->hdr.v.id1 == 31 &&
+ priv->hdr.v.id2 == 139 &&
+ priv->hdr.v.cm == Z_DEFLATED);
+ }
+
+ if (!priv->state.unzip.is_valid)
+ return;
+
+ if (priv->hdr.v.flg & GZIP_FLAG_FEXTRA) {
+ if (!priv->state.unzip.got_xlen) {
+ if (len < 2) {
+ camel_mime_filter_backup (mime_filter, in, len);
+ return;
+ }
+
+ memcpy (&val, in, 2);
+ priv->state.unzip.xlen = GUINT16_FROM_LE (val);
+ priv->state.unzip.got_xlen = TRUE;
+ len -= 2;
+ in += 2;
+ }
+
+ if (priv->state.unzip.xlen_nread < priv->state.unzip.xlen) {
+ need = priv->state.unzip.xlen - priv->state.unzip.xlen_nread;
+
+ if (need < len) {
+ priv->state.unzip.xlen_nread += need;
+ len -= need;
+ in += need;
+ } else {
+ priv->state.unzip.xlen_nread += len;
+ return;
+ }
+ }
+ }
+
+ if ((priv->hdr.v.flg & GZIP_FLAG_FNAME) && !priv->state.unzip.got_fname) {
+ while (*in && len > 0) {
+ len--;
+ in++;
+ }
+
+ if (*in == '\0' && len > 0) {
+ priv->state.unzip.got_fname = TRUE;
+ len--;
+ in++;
+ } else {
+ return;
+ }
+ }
+
+ if ((priv->hdr.v.flg & GZIP_FLAG_FCOMMENT) && !priv->state.unzip.got_fcomment) {
+ while (*in && len > 0) {
+ len--;
+ in++;
+ }
+
+ if (*in == '\0' && len > 0) {
+ priv->state.unzip.got_fcomment = TRUE;
+ len--;
+ in++;
+ } else {
+ return;
+ }
+ }
+
+ if ((priv->hdr.v.flg & GZIP_FLAG_FHCRC) && !priv->state.unzip.got_crc16) {
+ if (len < 2) {
+ camel_mime_filter_backup (mime_filter, in, len);
+ return;
+ }
+
+ memcpy (&val, in, 2);
+ priv->state.unzip.crc16 = GUINT16_FROM_LE (val);
+ len -= 2;
+ in += 2;
+ }
+
+ if (len == 0)
+ return;
+
+ camel_mime_filter_set_size (mime_filter, (len * 2) + 12, FALSE);
+
+ priv->stream->next_in = (Bytef *) in;
+ priv->stream->avail_in = len - 8;
+
+ priv->stream->next_out = (Bytef *) mime_filter->outbuf;
+ priv->stream->avail_out = mime_filter->outsize;
+
+ do {
+ /* FIXME: handle error cases? */
+ if ((retval = inflate (priv->stream, flush)) != Z_OK)
+ fprintf (stderr, "gunzip: %d: %s\n", retval, priv->stream->msg);
+
+ if (flush == Z_FULL_FLUSH) {
+ gsize n;
+
+ if (priv->stream->avail_in == 0) {
+ /* FIXME: extract & compare calculated crc32 and isize values? */
+ break;
+ }
+
+ n = mime_filter->outsize - priv->stream->avail_out;
+ camel_mime_filter_set_size (mime_filter, n + (priv->stream->avail_in * 2) + 12, TRUE);
+ priv->stream->avail_out = mime_filter->outsize - n;
+ priv->stream->next_out = (Bytef *) mime_filter->outbuf + n;
+ } else {
+ priv->stream->avail_in += 8;
+
+ if (priv->stream->avail_in > 0)
+ camel_mime_filter_backup (mime_filter, (gchar *) priv->stream->next_in, priv->stream->avail_in);
+
+ break;
+ }
+ } while (1);
+
+ /* FIXME: if we keep this, we could check that the gzip'd
+ * stream is sane, but how would we tell our consumer if it
+ * was/wasn't? */
+ /*priv->crc32 = crc32 (priv->crc32, in, len - priv->stream->avail_in - 8);
+ priv->isize += len - priv->stream->avail_in - 8;*/
+
+ *out = mime_filter->outbuf;
+ *outlen = mime_filter->outsize - priv->stream->avail_out;
+ *outprespace = mime_filter->outpre;
+}
+
+static void
+mime_filter_gzip_finalize (GObject *object)
+{
+ CamelMimeFilterGZipPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (object);
+
+ if (priv->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
+ deflateEnd (priv->stream);
+ else
+ inflateEnd (priv->stream);
+
+ g_free (priv->stream);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_mime_filter_gzip_parent_class)->finalize (object);
+}
+
+static void
+mime_filter_gzip_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterGZipPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (mime_filter);
+
+ if (priv->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
+ gzip_filter (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, Z_SYNC_FLUSH);
+ else
+ gunzip_filter (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, Z_SYNC_FLUSH);
+}
+
+static void
+mime_filter_gzip_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterGZipPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (mime_filter);
+
+ if (priv->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
+ gzip_filter (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, Z_FULL_FLUSH);
+ else
+ gunzip_filter (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, Z_FULL_FLUSH);
+}
+
+/* should this 'flush' outstanding state/data bytes? */
+static void
+mime_filter_gzip_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterGZipPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (mime_filter);
+
+ memset (&priv->state, 0, sizeof (priv->state));
+
+ if (priv->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
+ deflateReset (priv->stream);
+ else
+ inflateReset (priv->stream);
+
+ priv->crc32 = crc32 (0, Z_NULL, 0);
+ priv->isize = 0;
+}
+
+static void
+camel_mime_filter_gzip_class_init (CamelMimeFilterGZipClass *class)
+{
+ GObjectClass *object_class;
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterGZipPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = mime_filter_gzip_finalize;
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_gzip_filter;
+ mime_filter_class->complete = mime_filter_gzip_complete;
+ mime_filter_class->reset = mime_filter_gzip_reset;
+}
+
+static void
+camel_mime_filter_gzip_init (CamelMimeFilterGZip *mime_filter)
+{
+ mime_filter->priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (mime_filter);
+ mime_filter->priv->stream = g_new0 (z_stream, 1);
+ mime_filter->priv->crc32 = crc32 (0, Z_NULL, 0);
+}
+
+/**
+ * camel_mime_filter_gzip_new:
+ * @mode: zip or unzip
+ * @level: compression level
+ *
+ * Creates a new gzip (or gunzip) filter.
+ *
+ * Returns: a new gzip (or gunzip) filter.
+ **/
+CamelMimeFilter *
+camel_mime_filter_gzip_new (CamelMimeFilterGZipMode mode,
+ gint level)
+{
+ CamelMimeFilter *new;
+ CamelMimeFilterGZipPrivate *priv;
+ gint retval;
+
+ new = g_object_new (CAMEL_TYPE_MIME_FILTER_GZIP, NULL);
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (new);
+
+ priv->mode = mode;
+ priv->level = level;
+
+ if (mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
+ retval = deflateInit2 (priv->stream, level, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+ else
+ retval = inflateInit2 (priv->stream, -MAX_WBITS);
+
+ if (retval != Z_OK) {
+ g_object_unref (new);
+ return NULL;
+ }
+
+ return new;
+}
diff --git a/src/camel/camel-mime-filter-gzip.h b/src/camel/camel-mime-filter-gzip.h
new file mode 100644
index 000000000..ac2ca5f89
--- /dev/null
+++ b/src/camel/camel-mime-filter-gzip.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_GZIP_H
+#define CAMEL_MIME_FILTER_GZIP_H
+
+#include <camel/camel-enums.h>
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_GZIP \
+ (camel_mime_filter_gzip_get_type ())
+#define CAMEL_MIME_FILTER_GZIP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_GZIP, CamelMimeFilterGZip))
+#define CAMEL_MIME_FILTER_GZIP_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_GZIP, CamelMimeFilterGZipClass))
+#define CAMEL_IS_MIME_FILTER_GZIP(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_GZIP))
+#define CAMEL_IS_MIME_FILTER_GZIP_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_GZIP))
+#define CAMEL_MIME_FILTER_GZIP_GET_CLASS(obj) \
+ (CAMEL_CHECK_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_GZIP, CamelMimeFilterGZipClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterGZip CamelMimeFilterGZip;
+typedef struct _CamelMimeFilterGZipClass CamelMimeFilterGZipClass;
+typedef struct _CamelMimeFilterGZipPrivate CamelMimeFilterGZipPrivate;
+
+struct _CamelMimeFilterGZip {
+ CamelMimeFilter parent;
+ CamelMimeFilterGZipPrivate *priv;
+};
+
+struct _CamelMimeFilterGZipClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_gzip_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_gzip_new (CamelMimeFilterGZipMode mode,
+ gint level);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_GZIP_H */
diff --git a/src/camel/camel-mime-filter-html.c b/src/camel/camel-mime-filter-html.c
new file mode 100644
index 000000000..8bf93770e
--- /dev/null
+++ b/src/camel/camel-mime-filter-html.c
@@ -0,0 +1,210 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "camel-html-parser.h"
+#include "camel-mime-filter-html.h"
+
+#define d(x)
+
+#define CAMEL_MIME_FILTER_HTML_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_HTML, CamelMimeFilterHTMLPrivate))
+
+struct _CamelMimeFilterHTMLPrivate {
+ CamelHTMLParser *ctxt;
+};
+
+G_DEFINE_TYPE (CamelMimeFilterHTML, camel_mime_filter_html, CAMEL_TYPE_MIME_FILTER)
+
+/* ********************************************************************** */
+
+#if 0
+
+/* well we odnt use this stuff yet */
+
+static struct {
+ gchar *element;
+ gchar *remap;
+} map_start[] = {
+ { "p", "\n\n" },
+ { "br", "\n" },
+ { "h1", "\n" }, { "h2", "\n" }, { "h3", "\n" }, { "h4", "\n" }, { "h5", "\n" }, { "h6", "\n" },
+};
+
+static struct {
+ gchar *element;
+ gchar *remap;
+} map_end[] = {
+ { "h1", "\n" }, { "h2", "\n" }, { "h3", "\n" }, { "h4", "\n" }, { "h5", "\n" }, { "h6", "\n" },
+};
+#endif
+
+/* ********************************************************************** */
+
+static void
+mime_filter_html_run (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize inlen,
+ gsize prespace,
+ gchar **out,
+ gsize *outlenptr,
+ gsize *outprespace,
+ gint last)
+{
+ CamelMimeFilterHTMLPrivate *priv;
+ CamelHTMLParserState state;
+ gchar *outp;
+
+ priv = CAMEL_MIME_FILTER_HTML_GET_PRIVATE (mime_filter);
+
+ d (printf ("converting html:\n%.*s\n", (gint) inlen, in));
+
+ /* We should generally shrink the data, but this'll do */
+ camel_mime_filter_set_size (mime_filter, inlen * 2 + 256, FALSE);
+ outp = mime_filter->outbuf;
+
+ camel_html_parser_set_data (priv->ctxt, in, inlen, last);
+ do {
+ const gchar *data;
+ gint len;
+
+ state = camel_html_parser_step (priv->ctxt, &data, &len);
+
+ switch (state) {
+ case CAMEL_HTML_PARSER_DATA:
+ case CAMEL_HTML_PARSER_ENT:
+ memcpy (outp, data, len);
+ outp += len;
+ break;
+ case CAMEL_HTML_PARSER_ELEMENT:
+ /* FIXME: do some whitespace processing here */
+ break;
+ default:
+ /* ignore everything else */
+ break;
+ }
+ } while (state != CAMEL_HTML_PARSER_EOF && state != CAMEL_HTML_PARSER_EOD);
+
+ *out = mime_filter->outbuf;
+ *outlenptr = outp - mime_filter->outbuf;
+ *outprespace = mime_filter->outbuf - mime_filter->outreal;
+
+ d (printf ("converted html end:\n%.*s\n", (gint) * outlenptr, *out));
+}
+
+static void
+mime_filter_html_dispose (GObject *object)
+{
+ CamelMimeFilterHTMLPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_HTML_GET_PRIVATE (object);
+
+ if (priv->ctxt != NULL) {
+ g_object_unref (priv->ctxt);
+ priv->ctxt = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_mime_filter_html_parent_class)->dispose (object);
+}
+
+static void
+mime_filter_html_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlenptr,
+ gsize *outprespace)
+{
+ mime_filter_html_run (
+ mime_filter, in, len, prespace,
+ out, outlenptr, outprespace, FALSE);
+}
+
+static void
+mime_filter_html_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlenptr,
+ gsize *outprespace)
+{
+ mime_filter_html_run (
+ mime_filter, in, len, prespace,
+ out, outlenptr, outprespace, TRUE);
+}
+
+static void
+mime_filter_html_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterHTMLPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_HTML_GET_PRIVATE (mime_filter);
+
+ g_object_unref (priv->ctxt);
+ priv->ctxt = camel_html_parser_new ();
+}
+
+static void
+camel_mime_filter_html_class_init (CamelMimeFilterHTMLClass *class)
+{
+ GObjectClass *object_class;
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterHTMLPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = mime_filter_html_dispose;
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_html_filter;
+ mime_filter_class->complete = mime_filter_html_complete;
+ mime_filter_class->reset = mime_filter_html_reset;
+}
+
+static void
+camel_mime_filter_html_init (CamelMimeFilterHTML *mime_filter)
+{
+ mime_filter->priv = CAMEL_MIME_FILTER_HTML_GET_PRIVATE (mime_filter);
+ mime_filter->priv->ctxt = camel_html_parser_new ();
+}
+
+/**
+ * camel_mime_filter_html_new:
+ *
+ * Create a new #CamelMimeFilterHTML object.
+ *
+ * Returns: a new #CamelMimeFilterHTML object
+ **/
+CamelMimeFilter *
+camel_mime_filter_html_new (void)
+{
+ return g_object_new (CAMEL_TYPE_MIME_FILTER_HTML, NULL);
+}
diff --git a/src/camel/camel-mime-filter-html.h b/src/camel/camel-mime-filter-html.h
new file mode 100644
index 000000000..71e9a9e91
--- /dev/null
+++ b/src/camel/camel-mime-filter-html.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_HTML_H
+#define CAMEL_MIME_FILTER_HTML_H
+
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_HTML \
+ (camel_mime_filter_html_get_type ())
+#define CAMEL_MIME_FILTER_HTML(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_HTML, CamelMimeFilterHTML))
+#define CAMEL_MIME_FILTER_HTML_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_HTML, CamelMimeFilterHTMLClass))
+#define CAMEL_IS_MIME_FILTER_HTML(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_HTML))
+#define CAMEL_IS_MIME_FILTER_HTML_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_HTML))
+#define CAMEL_MIME_FILTER_HTML_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_HTML, CamelMimeFilterHTMLClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterHTML CamelMimeFilterHTML;
+typedef struct _CamelMimeFilterHTMLClass CamelMimeFilterHTMLClass;
+typedef struct _CamelMimeFilterHTMLPrivate CamelMimeFilterHTMLPrivate;
+
+struct _CamelMimeFilterHTML {
+ CamelMimeFilter parent;
+ CamelMimeFilterHTMLPrivate *priv;
+};
+
+struct _CamelMimeFilterHTMLClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_html_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_html_new (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_HTML_H */
diff --git a/src/camel/camel-mime-filter-index.c b/src/camel/camel-mime-filter-index.c
new file mode 100644
index 000000000..2801fc578
--- /dev/null
+++ b/src/camel/camel-mime-filter-index.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "camel-mime-filter-index.h"
+#include "camel-text-index.h"
+
+#define CAMEL_MIME_FILTER_INDEX_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_INDEX, CamelMimeFilterIndexPrivate))
+
+struct _CamelMimeFilterIndexPrivate {
+ CamelIndex *index;
+ CamelIndexName *name;
+};
+
+G_DEFINE_TYPE (CamelMimeFilterIndex, camel_mime_filter_index, CAMEL_TYPE_MIME_FILTER)
+
+static void
+mime_filter_index_dispose (GObject *object)
+{
+ CamelMimeFilterIndexPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_INDEX_GET_PRIVATE (object);
+
+ if (priv->name != NULL) {
+ g_object_unref (priv->name);
+ priv->name = NULL;
+ }
+
+ if (priv->index != NULL) {
+ g_object_unref (priv->index);
+ priv->index = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_mime_filter_index_parent_class)->dispose (object);
+}
+
+static void
+mime_filter_index_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlenptr,
+ gsize *outprespace)
+{
+ CamelMimeFilterIndexPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_INDEX_GET_PRIVATE (mime_filter);
+
+ if (priv->index == NULL || priv->name == NULL) {
+ goto donothing;
+ }
+
+ camel_index_name_add_buffer (priv->name, in, len);
+
+donothing:
+ *out = (gchar *) in;
+ *outlenptr = len;
+ *outprespace = prespace;
+}
+
+static void
+mime_filter_index_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlenptr,
+ gsize *outprespace)
+{
+ CamelMimeFilterIndexPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_INDEX_GET_PRIVATE (mime_filter);
+
+ if (priv->index == NULL || priv->name == NULL) {
+ goto donothing;
+ }
+
+ camel_index_name_add_buffer (priv->name, in, len);
+ camel_index_name_add_buffer (priv->name, NULL, 0);
+
+donothing:
+ *out = (gchar *) in;
+ *outlenptr = len;
+ *outprespace = prespace;
+}
+
+static void
+camel_mime_filter_index_class_init (CamelMimeFilterIndexClass *class)
+{
+ GObjectClass *object_class;
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterIndexPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = mime_filter_index_dispose;
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_index_filter;
+ mime_filter_class->complete = mime_filter_index_complete;
+}
+
+static void
+camel_mime_filter_index_init (CamelMimeFilterIndex *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_INDEX_GET_PRIVATE (filter);
+}
+
+/**
+ * camel_mime_filter_index_new:
+ * @index: a #CamelIndex object
+ *
+ * Create a new #CamelMimeFilterIndex based on @index.
+ *
+ * Returns: a new #CamelMimeFilterIndex object
+ **/
+CamelMimeFilter *
+camel_mime_filter_index_new (CamelIndex *index)
+{
+ CamelMimeFilter *new;
+ CamelMimeFilterIndexPrivate *priv;
+
+ new = g_object_new (CAMEL_TYPE_MIME_FILTER_INDEX, NULL);
+
+ priv = CAMEL_MIME_FILTER_INDEX_GET_PRIVATE (new);
+
+ if (index != NULL)
+ priv->index = g_object_ref (index);
+
+ return new;
+}
+
+/* Set the match name for any indexed words */
+
+/**
+ * camel_mime_filter_index_set_name:
+ * @filter: a #CamelMimeFilterIndex object
+ * @name: a #CamelIndexName object
+ *
+ * Set the match name for any indexed words.
+ **/
+void
+camel_mime_filter_index_set_name (CamelMimeFilterIndex *filter,
+ CamelIndexName *name)
+{
+ g_return_if_fail (CAMEL_IS_MIME_FILTER_INDEX (filter));
+
+ if (name != NULL) {
+ g_return_if_fail (CAMEL_IS_INDEX_NAME (name));
+ g_object_ref (name);
+ }
+
+ if (filter->priv->name != NULL)
+ g_object_unref (filter->priv->name);
+
+ filter->priv->name = name;
+}
+
+/**
+ * camel_mime_filter_index_set_index:
+ * @filter: a #CamelMimeFilterIndex object
+ * @index: a #CamelIndex object
+ *
+ * Set @index on @filter.
+ **/
+void
+camel_mime_filter_index_set_index (CamelMimeFilterIndex *filter,
+ CamelIndex *index)
+{
+ g_return_if_fail (CAMEL_IS_MIME_FILTER_INDEX (filter));
+
+ if (index != NULL) {
+ g_return_if_fail (CAMEL_IS_INDEX (index));
+ g_object_ref (index);
+ }
+
+ if (filter->priv->index) {
+ gchar *out;
+ gsize outlen, outspace;
+
+ camel_mime_filter_complete (
+ CAMEL_MIME_FILTER (filter),
+ "", 0, 0, &out, &outlen, &outspace);
+ g_object_unref (filter->priv->index);
+ }
+
+ filter->priv->index = index;
+}
diff --git a/src/camel/camel-mime-filter-index.h b/src/camel/camel-mime-filter-index.h
new file mode 100644
index 000000000..3ca388121
--- /dev/null
+++ b/src/camel/camel-mime-filter-index.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_INDEX_H
+#define CAMEL_MIME_FILTER_INDEX_H
+
+#include <camel/camel-index.h>
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_INDEX \
+ (camel_mime_filter_index_get_type ())
+#define CAMEL_MIME_FILTER_INDEX(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_INDEX, CamelMimeFilterIndex))
+#define CAMEL_MIME_FILTER_INDEX_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_INDEX, CamelMimeFilterIndexClass))
+#define CAMEL_IS_MIME_FILTER_INDEX(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_INDEX))
+#define CAMEL_IS_MIME_FILTER_INDEX_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_INDEX))
+#define CAMEL_MIME_FILTER_INDEX_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_INDEX, CamelMimeFilterIndexClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterIndex CamelMimeFilterIndex;
+typedef struct _CamelMimeFilterIndexClass CamelMimeFilterIndexClass;
+typedef struct _CamelMimeFilterIndexPrivate CamelMimeFilterIndexPrivate;
+
+struct _CamelMimeFilterIndex {
+ CamelMimeFilter parent;
+ CamelMimeFilterIndexPrivate *priv;
+};
+
+struct _CamelMimeFilterIndexClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_index_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_index_new (CamelIndex *index);
+
+/* Set the match name for any indexed words */
+void camel_mime_filter_index_set_name (CamelMimeFilterIndex *filter,
+ CamelIndexName *name);
+void camel_mime_filter_index_set_index
+ (CamelMimeFilterIndex *filter,
+ CamelIndex *index);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_INDEX_H */
diff --git a/src/camel/camel-mime-filter-linewrap.c b/src/camel/camel-mime-filter-linewrap.c
new file mode 100644
index 000000000..790b9fbb5
--- /dev/null
+++ b/src/camel/camel-mime-filter-linewrap.c
@@ -0,0 +1,202 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+
+#include "camel-mime-filter-linewrap.h"
+
+#define CAMEL_MIME_FILTER_LINEWRAP_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_LINEWRAP, CamelMimeFilterLinewrapPrivate))
+
+struct _CamelMimeFilterLinewrapPrivate {
+ guint wrap_len;
+ guint max_len;
+ gchar indent;
+ gint nchars;
+ guint32 flags;
+};
+
+G_DEFINE_TYPE (CamelMimeFilterLinewrap, camel_mime_filter_linewrap, CAMEL_TYPE_MIME_FILTER)
+
+static void
+mime_filter_linewrap_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterLinewrapPrivate *priv;
+ gchar *q;
+ const gchar *inend, *p;
+ gint nchars;
+
+ priv = CAMEL_MIME_FILTER_LINEWRAP_GET_PRIVATE (mime_filter);
+
+ nchars = priv->nchars;
+
+ /* we'll be adding chars here so we need a bigger buffer */
+ camel_mime_filter_set_size (mime_filter, 3 * len, FALSE);
+
+ p = in;
+ q = mime_filter->outbuf;
+ inend = in + len;
+
+ while (p < inend) {
+ if (*p == '\n') {
+ *q++ = *p++;
+ nchars = 0;
+ } else if (isspace (*p)) {
+ if (nchars >= priv->wrap_len) {
+ *q++ = '\n';
+ while (p < inend && isspace (*p))
+ p++;
+ nchars = 0;
+ } else {
+ *q++ = *p++;
+ nchars++;
+ }
+ } else {
+ *q++ = *p++;
+ nchars++;
+ }
+
+ /* line is getting way too long, we must force a wrap here */
+ if (nchars >= priv->max_len && *p != '\n') {
+ gboolean wrapped = FALSE;
+
+ if (isspace (*p)) {
+ while (p < inend && isspace (*p) && *p != '\n')
+ p++;
+ } else if ((priv->flags & CAMEL_MIME_FILTER_LINEWRAP_WORD) != 0) {
+ gchar *r = q - 1;
+
+ /* find the first space backward */
+ while (r > mime_filter->outbuf && !isspace (*r))
+ r--;
+
+ if (r > mime_filter->outbuf && *r != '\n') {
+ /* found some valid */
+ *r = '\n';
+ wrapped = TRUE;
+
+ if ((priv->flags & CAMEL_MIME_FILTER_LINEWRAP_NOINDENT) == 0) {
+ gchar *s = q + 1;
+
+ while (s > r) {
+ *s = *(s - 1);
+ s--;
+ }
+
+ *r = priv->indent;
+ q++;
+ }
+
+ nchars = q - r - 1;
+ }
+ }
+
+ if (!wrapped) {
+ *q++ = '\n';
+ if ((priv->flags & CAMEL_MIME_FILTER_LINEWRAP_NOINDENT) == 0) {
+ *q++ = priv->indent;
+ nchars = 1;
+ } else
+ nchars = 0;
+ }
+ }
+ }
+
+ priv->nchars = nchars;
+
+ *out = mime_filter->outbuf;
+ *outlen = q - mime_filter->outbuf;
+ *outprespace = mime_filter->outpre;
+}
+
+static void
+mime_filter_linewrap_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ if (len)
+ mime_filter_linewrap_filter (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace);
+}
+
+static void
+mime_filter_linewrap_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterLinewrapPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_LINEWRAP_GET_PRIVATE (mime_filter);
+
+ priv->nchars = 0;
+}
+
+static void
+camel_mime_filter_linewrap_class_init (CamelMimeFilterLinewrapClass *class)
+{
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterLinewrapPrivate));
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_linewrap_filter;
+ mime_filter_class->complete = mime_filter_linewrap_complete;
+ mime_filter_class->reset = mime_filter_linewrap_reset;
+}
+
+static void
+camel_mime_filter_linewrap_init (CamelMimeFilterLinewrap *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_LINEWRAP_GET_PRIVATE (filter);
+}
+
+CamelMimeFilter *
+camel_mime_filter_linewrap_new (guint preferred_len,
+ guint max_len,
+ gchar indent_char,
+ guint32 flags)
+{
+ CamelMimeFilter *filter;
+ CamelMimeFilterLinewrapPrivate *priv;
+
+ filter = g_object_new (CAMEL_TYPE_MIME_FILTER_LINEWRAP, NULL);
+ priv = CAMEL_MIME_FILTER_LINEWRAP_GET_PRIVATE (filter);
+
+ priv->indent = indent_char;
+ priv->wrap_len = preferred_len;
+ priv->max_len = max_len;
+ priv->nchars = 0;
+
+ if (indent_char == 0)
+ priv->flags |= CAMEL_MIME_FILTER_LINEWRAP_NOINDENT;
+
+ return filter;
+}
diff --git a/src/camel/camel-mime-filter-linewrap.h b/src/camel/camel-mime-filter-linewrap.h
new file mode 100644
index 000000000..51bf6b79c
--- /dev/null
+++ b/src/camel/camel-mime-filter-linewrap.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_LINEWRAP_H
+#define CAMEL_MIME_FILTER_LINEWRAP_H
+
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_LINEWRAP \
+ (camel_mime_filter_linewrap_get_type ())
+#define CAMEL_MIME_FILTER_LINEWRAP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_LINEWRAP, CamelMimeFilterLinewrap))
+#define CAMEL_MIME_FILTER_LINEWRAP_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_LINEWRAP, CamelMimeFilterLinewrapClass))
+#define CAMEL_IS_MIME_FILTER_LINEWRAP(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_LINEWRAP))
+#define CAMEL_IS_MIME_FILTER_LINEWRAP_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_LINEWRAP))
+#define CAMEL_MIME_FILTER_LINEWRAP_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_LINEWRAP, CamelMimeFilterLinewrapClass))
+
+G_BEGIN_DECLS
+
+enum {
+ CAMEL_MIME_FILTER_LINEWRAP_NOINDENT = (1 << 0), /* does not indent; it's forced for indent_char = 0 */
+ CAMEL_MIME_FILTER_LINEWRAP_WORD = (1 << 1), /* indents on word boundary */
+};
+
+typedef struct _CamelMimeFilterLinewrap CamelMimeFilterLinewrap;
+typedef struct _CamelMimeFilterLinewrapClass CamelMimeFilterLinewrapClass;
+typedef struct _CamelMimeFilterLinewrapPrivate CamelMimeFilterLinewrapPrivate;
+
+struct _CamelMimeFilterLinewrap {
+ CamelMimeFilter parent;
+ CamelMimeFilterLinewrapPrivate *priv;
+};
+
+struct _CamelMimeFilterLinewrapClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_linewrap_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_linewrap_new (guint preferred_len,
+ guint max_len,
+ gchar indent_char,
+ guint32 flags);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_LINEWRAP_H */
diff --git a/src/camel/camel-mime-filter-pgp.c b/src/camel/camel-mime-filter-pgp.c
new file mode 100644
index 000000000..ac29e26ed
--- /dev/null
+++ b/src/camel/camel-mime-filter-pgp.c
@@ -0,0 +1,214 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matt Brown <matt@mattb.net.nz>
+ * Jeffrey Stedfast <fejj@novell.com>
+ */
+
+/* Strips PGP message headers from the input stream and also performs
+ * pgp decoding as described in section 7.1 of RFC2440 */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <string.h>
+
+#include "camel-mime-filter-pgp.h"
+#include "camel-mime-utils.h"
+
+#define CAMEL_MIME_FILTER_PGP_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_PGP, CamelMimeFilterPgpPrivate))
+
+#define BEGIN_PGP_SIGNED_MESSAGE "-----BEGIN PGP SIGNED MESSAGE-----"
+#define BEGIN_PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+#define END_PGP_SIGNATURE "-----END PGP SIGNATURE-----"
+
+#define BEGIN_PGP_SIGNED_MESSAGE_LEN (sizeof (BEGIN_PGP_SIGNED_MESSAGE) - 1)
+#define BEGIN_PGP_SIGNATURE_LEN (sizeof (BEGIN_PGP_SIGNATURE) - 1)
+#define END_PGP_SIGNATURE_LEN (sizeof (END_PGP_SIGNATURE) - 1)
+
+struct _CamelMimeFilterPgpPrivate {
+ gint state;
+};
+
+enum {
+ PGP_PREFACE,
+ PGP_HEADER,
+ PGP_MESSAGE,
+ PGP_FOOTER
+};
+
+G_DEFINE_TYPE (CamelMimeFilterPgp, camel_mime_filter_pgp, CAMEL_TYPE_MIME_FILTER)
+
+static void
+mime_filter_pgp_run (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize inlen,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace,
+ gint last)
+{
+ CamelMimeFilterPgpPrivate *priv;
+ const gchar *start, *inend = in + inlen;
+ register const gchar *inptr = in;
+ register gchar *o;
+ gboolean blank;
+ gsize len;
+
+ priv = CAMEL_MIME_FILTER_PGP_GET_PRIVATE (mime_filter);
+
+ /* only need as much space as the input, we're stripping chars */
+ camel_mime_filter_set_size (mime_filter, inlen, FALSE);
+
+ o = mime_filter->outbuf;
+
+ while (inptr < inend) {
+ start = inptr;
+
+ blank = TRUE;
+ while (inptr < inend && *inptr != '\n') {
+ if (blank && !strchr (" \t\r", *inptr))
+ blank = FALSE;
+ inptr++;
+ }
+
+ if (inptr == inend) {
+ if (!last) {
+ camel_mime_filter_backup (mime_filter, start, inend - start);
+ inend = start;
+ }
+ break;
+ }
+
+ len = inptr - start;
+ if (len > 0 && inptr[-1] == '\r')
+ len--;
+
+ while (len > 0 && camel_mime_is_lwsp (start[len - 1]))
+ len--;
+
+ inptr++;
+
+ switch (priv->state) {
+ case PGP_PREFACE:
+ /* check for the beginning of the pgp block */
+ if (len == BEGIN_PGP_SIGNED_MESSAGE_LEN && !strncmp (start, BEGIN_PGP_SIGNED_MESSAGE, len)) {
+ priv->state++;
+ break;
+ }
+
+ memcpy (o, start, inptr - start);
+ o += (inptr - start);
+ break;
+ case PGP_HEADER:
+ /* pgp headers (Hash: SHA1, etc) end with a blank (zero-length,
+ * or containing only whitespace) line; see RFC2440 */
+ if (blank)
+ priv->state++;
+ break;
+ case PGP_MESSAGE:
+ /* check for beginning of the pgp signature block */
+ if (len == BEGIN_PGP_SIGNATURE_LEN && !strncmp (start, BEGIN_PGP_SIGNATURE, len)) {
+ priv->state++;
+ break;
+ }
+
+ /* do dash decoding */
+ if (!strncmp (start, "- ", 2)) {
+ /* Dash encoded line found, skip encoding */
+ start += 2;
+ }
+
+ memcpy (o, start, inptr - start);
+ o += (inptr - start);
+ break;
+ case PGP_FOOTER:
+ if (len == END_PGP_SIGNATURE_LEN && !strncmp (start, END_PGP_SIGNATURE, len))
+ priv->state = PGP_PREFACE;
+ break;
+ }
+ }
+
+ *out = mime_filter->outbuf;
+ *outlen = o - mime_filter->outbuf;
+ *outprespace = mime_filter->outpre;
+}
+
+static void
+mime_filter_pgp_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ mime_filter_pgp_run (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, FALSE);
+}
+
+static void
+mime_filter_pgp_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ mime_filter_pgp_run (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, TRUE);
+}
+
+static void
+mime_filter_pgp_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterPgpPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_PGP_GET_PRIVATE (mime_filter);
+
+ priv->state = PGP_PREFACE;
+}
+
+static void
+camel_mime_filter_pgp_class_init (CamelMimeFilterPgpClass *class)
+{
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterPgpPrivate));
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_pgp_filter;
+ mime_filter_class->complete = mime_filter_pgp_complete;
+ mime_filter_class->reset = mime_filter_pgp_reset;
+}
+
+static void
+camel_mime_filter_pgp_init (CamelMimeFilterPgp *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_PGP_GET_PRIVATE (filter);
+}
+
+CamelMimeFilter *
+camel_mime_filter_pgp_new (void)
+{
+ return g_object_new (CAMEL_TYPE_MIME_FILTER_PGP, NULL);
+}
diff --git a/src/camel/camel-mime-filter-pgp.h b/src/camel/camel-mime-filter-pgp.h
new file mode 100644
index 000000000..a2b915587
--- /dev/null
+++ b/src/camel/camel-mime-filter-pgp.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matt Brown <matt@mattb.net.nz>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_PGP_H
+#define CAMEL_MIME_FILTER_PGP_H
+
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_PGP \
+ (camel_mime_filter_pgp_get_type ())
+#define CAMEL_MIME_FILTER_PGP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_PGP, CamelMimeFilterPgp))
+#define CAMEL_MIME_FILTER_PGP_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_PGP, CamelMimeFilterPgpClass))
+#define CAMEL_IS_MIME_FILTER_PGP(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_PGP))
+#define CAMEL_IS_MIME_FILTER_PGP_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_PGP))
+#define CAMEL_MIME_FILTER_PGP_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_PGP, CamelMimeFilterPgpClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterPgp CamelMimeFilterPgp;
+typedef struct _CamelMimeFilterPgpClass CamelMimeFilterPgpClass;
+typedef struct _CamelMimeFilterPgpPrivate CamelMimeFilterPgpPrivate;
+
+struct _CamelMimeFilterPgp {
+ CamelMimeFilter parent;
+ CamelMimeFilterPgpPrivate *priv;
+};
+
+struct _CamelMimeFilterPgpClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_pgp_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_pgp_new (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_PGP_H */
diff --git a/src/camel/camel-mime-filter-progress.c b/src/camel/camel-mime-filter-progress.c
new file mode 100644
index 000000000..08921f328
--- /dev/null
+++ b/src/camel/camel-mime-filter-progress.c
@@ -0,0 +1,163 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-mime-filter-progress.h"
+#include "camel-operation.h"
+
+#define CAMEL_MIME_FILTER_PROGRESS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_PROGRESS, CamelMimeFilterProgressPrivate))
+
+#define d(x)
+#define w(x)
+
+struct _CamelMimeFilterProgressPrivate {
+ GCancellable *cancellable;
+ gsize total;
+ gsize count;
+};
+
+G_DEFINE_TYPE (CamelMimeFilterProgress, camel_mime_filter_progress, CAMEL_TYPE_MIME_FILTER)
+
+static void
+mime_filter_progress_dispose (GObject *object)
+{
+ CamelMimeFilterProgressPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_PROGRESS_GET_PRIVATE (object);
+
+ if (priv->cancellable != NULL) {
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_mime_filter_progress_parent_class)->dispose (object);
+}
+
+static void
+mime_filter_progress_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterProgressPrivate *priv;
+ gdouble percent;
+
+ priv = CAMEL_MIME_FILTER_PROGRESS_GET_PRIVATE (mime_filter);
+ priv->count += len;
+
+ if (priv->count < priv->total)
+ percent = ((gdouble) priv->count * 100.0) / ((gdouble) priv->total);
+ else
+ percent = 100.0;
+
+ camel_operation_progress (priv->cancellable, (gint) percent);
+
+ *outprespace = prespace;
+ *outlen = len;
+ *out = (gchar *) in;
+}
+
+static void
+mime_filter_progress_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ mime_filter_progress_filter (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace);
+}
+
+static void
+mime_filter_progress_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterProgressPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_PROGRESS_GET_PRIVATE (mime_filter);
+
+ priv->count = 0;
+}
+
+static void
+camel_mime_filter_progress_class_init (CamelMimeFilterProgressClass *class)
+{
+ GObjectClass *object_class;
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterProgressPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = mime_filter_progress_dispose;
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_progress_filter;
+ mime_filter_class->complete = mime_filter_progress_complete;
+ mime_filter_class->reset = mime_filter_progress_reset;
+}
+
+static void
+camel_mime_filter_progress_init (CamelMimeFilterProgress *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_PROGRESS_GET_PRIVATE (filter);
+}
+
+/**
+ * camel_mime_filter_progress_new:
+ * @cancellable: a #CamelOperation cast as a #GCancellable
+ * @total: total number of bytes to report progress on
+ *
+ * Create a new #CamelMimeFilterProgress object that will report streaming
+ * progress. While the function will silently accept @cancellable being
+ * %NULL or a plain #GCancellable for convenience sake, no progress will be
+ * reported unless @cancellable is a #CamelOperation.
+ *
+ * Returns: a new #CamelMimeFilter object
+ *
+ * Since: 2.24
+ **/
+CamelMimeFilter *
+camel_mime_filter_progress_new (GCancellable *cancellable,
+ gsize total)
+{
+ CamelMimeFilter *filter;
+ CamelMimeFilterProgressPrivate *priv;
+
+ filter = g_object_new (CAMEL_TYPE_MIME_FILTER_PROGRESS, NULL);
+ priv = CAMEL_MIME_FILTER_PROGRESS_GET_PRIVATE (filter);
+
+ if (CAMEL_IS_OPERATION (cancellable))
+ priv->cancellable = g_object_ref (cancellable);
+
+ priv->total = total;
+
+ return filter;
+}
diff --git a/src/camel/camel-mime-filter-progress.h b/src/camel/camel-mime-filter-progress.h
new file mode 100644
index 000000000..9f8a19cf2
--- /dev/null
+++ b/src/camel/camel-mime-filter-progress.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_PROGRESS_H
+#define CAMEL_MIME_FILTER_PROGRESS_H
+
+#include <gio/gio.h>
+
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_PROGRESS \
+ (camel_mime_filter_progress_get_type ())
+#define CAMEL_MIME_FILTER_PROGRESS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_PROGRESS, CamelMimeFilterProgress))
+#define CAMEL_MIME_FILTER_PROGRESS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_PROGRESS, CamelMimeFilterProgressClass))
+#define CAMEL_IS_MIME_FILTER_PROGRESS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_PROGRESS))
+#define CAMEL_IS_MIME_FILTER_PROGRESS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_PROGRESS))
+#define CAMEL_MIME_FILTER_PROGRESS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_PROGRESS, CamelMimeFilterProgressClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterProgress CamelMimeFilterProgress;
+typedef struct _CamelMimeFilterProgressClass CamelMimeFilterProgressClass;
+typedef struct _CamelMimeFilterProgressPrivate CamelMimeFilterProgressPrivate;
+
+/**
+ * CamelMimeFilterProgress:
+ *
+ * Since: 2.24
+ **/
+struct _CamelMimeFilterProgress {
+ CamelMimeFilter parent;
+ CamelMimeFilterProgressPrivate *priv;
+};
+
+struct _CamelMimeFilterProgressClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_progress_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_progress_new (GCancellable *cancellable,
+ gsize total);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_PROGRESS_H */
diff --git a/src/camel/camel-mime-filter-tohtml.c b/src/camel/camel-mime-filter-tohtml.c
new file mode 100644
index 000000000..07024aa1a
--- /dev/null
+++ b/src/camel/camel-mime-filter-tohtml.c
@@ -0,0 +1,630 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-mime-filter-tohtml.h"
+#include "camel-url-scanner.h"
+#include "camel-utf8.h"
+
+#define CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_TOHTML, CamelMimeFilterToHTMLPrivate))
+
+struct _CamelMimeFilterToHTMLPrivate {
+
+ CamelUrlScanner *scanner;
+
+ CamelMimeFilterToHTMLFlags flags;
+ guint32 color;
+
+ guint blockquote_depth;
+
+ guint32 column : 31;
+ guint32 pre_open : 1;
+};
+
+/*
+ * TODO: convert common text/plain 'markup' to html. eg.:
+ *
+ * _word_ -> <u>_word_</u>
+ * *word* -> <b>*word*</b>
+ * /word/ -> <i>/word/</i>
+ */
+
+#define d(x)
+
+#define FOOLISHLY_UNMUNGE_FROM 0
+
+#define CONVERT_WEB_URLS CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS
+#define CONVERT_ADDRSPEC CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES
+
+static struct {
+ CamelMimeFilterToHTMLFlags mask;
+ CamelUrlPattern pattern;
+} patterns[] = {
+ { CONVERT_WEB_URLS, { "file://", "", camel_url_file_start, camel_url_file_end } },
+ { CONVERT_WEB_URLS, { "ftp://", "", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_WEB_URLS, { "sftp://", "", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_WEB_URLS, { "http://", "", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_WEB_URLS, { "https://", "", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_WEB_URLS, { "news://", "", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_WEB_URLS, { "nntp://", "", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_WEB_URLS, { "telnet://", "", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_WEB_URLS, { "webcal://", "", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_WEB_URLS, { "mailto:", "", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_WEB_URLS, { "callto:", "", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_WEB_URLS, { "h323:", "", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_WEB_URLS, { "sip:", "", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_WEB_URLS, { "www.", "http://", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_WEB_URLS, { "ftp.", "ftp://", camel_url_web_start, camel_url_web_end } },
+ { CONVERT_ADDRSPEC, { "@", "mailto:", camel_url_addrspec_start, camel_url_addrspec_end } },
+};
+
+G_DEFINE_TYPE (CamelMimeFilterToHTML, camel_mime_filter_tohtml, CAMEL_TYPE_MIME_FILTER)
+
+static gchar *
+check_size (CamelMimeFilter *mime_filter,
+ gchar *outptr,
+ gchar **outend,
+ gsize len)
+{
+ gsize offset;
+
+ if (*outend - outptr >= len)
+ return outptr;
+
+ offset = outptr - mime_filter->outbuf;
+
+ camel_mime_filter_set_size (
+ mime_filter, mime_filter->outsize + len, TRUE);
+
+ *outend = mime_filter->outbuf + mime_filter->outsize;
+
+ return mime_filter->outbuf + offset;
+}
+
+static gchar *
+append_string_verbatim (CamelMimeFilter *mime_filter,
+ const gchar *str,
+ gchar *outptr,
+ gchar **outend)
+{
+ gsize len = strlen (str);
+
+ outptr = check_size (mime_filter, outptr, outend, len);
+ memcpy (outptr, str, len);
+ outptr += len;
+
+ return outptr;
+}
+
+static gint
+citation_depth (const gchar *in,
+ const gchar *inend,
+ goffset *out_skip)
+{
+ register const gchar *inptr = in;
+ gint depth = 0;
+ goffset skip = 0;
+
+ if (out_skip != NULL)
+ *out_skip = 0;
+
+ if (!strchr (">|", *inptr++))
+ goto exit;
+
+#if FOOLISHLY_UNMUNGE_FROM
+ /* check that it isn't an escaped From line */
+ if (!strncmp (inptr, "From", 4)) {
+ goto exit;
+#endif
+
+ depth = 1;
+ skip = 1;
+
+ while (inptr < inend && *inptr != '\n') {
+ if (*inptr == ' ') {
+ inptr++;
+ skip++;
+ }
+
+ if (inptr >= inend || !strchr (">|", *inptr++))
+ break;
+
+ depth++;
+ skip++;
+ }
+
+exit:
+ if (out_skip != NULL)
+ *out_skip = (depth > 0) ? skip : 0;
+
+ return depth;
+}
+
+static gchar *
+writeln (CamelMimeFilter *mime_filter,
+ const guchar *in,
+ const guchar *inend,
+ gchar *outptr,
+ gchar **outend)
+{
+ CamelMimeFilterToHTMLPrivate *priv;
+ const guchar *inptr = in;
+
+ priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (mime_filter);
+
+ while (inptr < inend) {
+ guint32 u;
+
+ outptr = check_size (mime_filter, outptr, outend, 16);
+
+ u = camel_utf8_getc_limit (&inptr, inend);
+ switch (u) {
+ case 0xffff:
+ g_warning (
+ "Truncated UTF-8 buffer (The cause might "
+ "be missing character encoding information "
+ "in the message header. Try a different "
+ "character encoding.)");
+ return outptr;
+ case '<':
+ outptr = g_stpcpy (outptr, "&lt;");
+ priv->column++;
+ break;
+ case '>':
+ outptr = g_stpcpy (outptr, "&gt;");
+ priv->column++;
+ break;
+ case '&':
+ outptr = g_stpcpy (outptr, "&amp;");
+ priv->column++;
+ break;
+ case '"':
+ outptr = g_stpcpy (outptr, "&quot;");
+ priv->column++;
+ break;
+ case '\t':
+ if (priv->flags & (CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES)) {
+ do {
+ outptr = check_size (mime_filter, outptr, outend, 7);
+ outptr = g_stpcpy (outptr, "&nbsp;");
+ priv->column++;
+ } while (priv->column % 8);
+ break;
+ }
+ /* otherwise, FALL THROUGH */
+ case ' ':
+ if (priv->flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES
+ && ((inptr == (in + 1) || (inptr < inend && (*inptr == ' ' || *inptr == '\t'))))) {
+ outptr = g_stpcpy (outptr, "&nbsp;");
+ priv->column++;
+ break;
+ }
+ /* otherwise, FALL THROUGH */
+ default:
+ if (u == '\r' && inptr >= inend) {
+ /* This constructs \r\n sequence at the end of the line, thus pass it in
+ only if not converting the new-line breaks */
+ if (!(priv->flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_NL))
+ *outptr++ = u;
+ } else if (u >= 20 && u <0x80) {
+ *outptr++ = u;
+ } else {
+ if (priv->flags & CAMEL_MIME_FILTER_TOHTML_ESCAPE_8BIT)
+ *outptr++ = '?';
+ else
+ outptr += sprintf (outptr, "&#%u;", u);
+ }
+ priv->column++;
+ break;
+ }
+ }
+
+ return outptr;
+}
+
+static void
+html_convert (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize inlen,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace,
+ gboolean flush)
+{
+ CamelMimeFilterToHTMLPrivate *priv;
+ const gchar *inptr;
+ gchar *outptr, *outend;
+ const gchar *start;
+ const gchar *inend;
+ gint depth;
+
+ priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (mime_filter);
+
+ if (inlen == 0) {
+ if (!priv->pre_open && priv->blockquote_depth == 0) {
+ /* No closing tags needed. */
+ *out = (gchar *) in;
+ *outlen = 0;
+ *outprespace = 0;
+ return;
+ }
+
+ outptr = mime_filter->outbuf;
+ outend = mime_filter->outbuf + mime_filter->outsize;
+
+ while (priv->blockquote_depth > 0) {
+ outptr = check_size (mime_filter, outptr, &outend, 15);
+ outptr = g_stpcpy (outptr, "</blockquote>");
+ priv->blockquote_depth--;
+ }
+
+ if (priv->pre_open) {
+ /* close the pre-tag */
+ outptr = check_size (mime_filter, outptr, &outend, 10);
+ outptr = g_stpcpy (outptr, "</pre>");
+ priv->pre_open = FALSE;
+ }
+
+ *out = mime_filter->outbuf;
+ *outlen = outptr - mime_filter->outbuf;
+ *outprespace = mime_filter->outpre;
+
+ return;
+ }
+
+ camel_mime_filter_set_size (mime_filter, inlen * 2 + 6, FALSE);
+
+ inptr = in;
+ inend = in + inlen;
+ outptr = mime_filter->outbuf;
+ outend = mime_filter->outbuf + mime_filter->outsize;
+
+ if (priv->flags & CAMEL_MIME_FILTER_TOHTML_PRE && !priv->pre_open) {
+ outptr = g_stpcpy (outptr, "<pre>");
+ priv->pre_open = TRUE;
+ }
+
+ start = inptr;
+ do {
+ while (inptr < inend && *inptr != '\n')
+ inptr++;
+
+ if (inptr >= inend && !flush)
+ break;
+
+ priv->column = 0;
+ depth = 0;
+
+ if (priv->flags & CAMEL_MIME_FILTER_TOHTML_MARK_CITATION) {
+ depth = citation_depth (start, inend, NULL);
+
+ if (depth > 0) {
+ /* FIXME: we could easily support multiple color depths here */
+
+ outptr = check_size (mime_filter, outptr, &outend, 25);
+ outptr += sprintf (outptr, "<font color=\"#%06x\">", (priv->color & 0xffffff));
+ }
+#if FOOLISHLY_UNMUNGE_FROM
+ else if (*start == '>') {
+ /* >From line */
+ start++;
+ }
+#endif
+
+ } else if (priv->flags & CAMEL_MIME_FILTER_TOHTML_QUOTE_CITATION) {
+ goffset skip = 0;
+
+ depth = citation_depth (start, inend, &skip);
+ while (priv->blockquote_depth < depth) {
+ outptr = check_size (mime_filter, outptr, &outend, 30);
+ outptr = g_stpcpy (outptr, "<blockquote type=\"cite\">\n");
+ priv->blockquote_depth++;
+ }
+ while (priv->blockquote_depth > depth) {
+ outptr = check_size (mime_filter, outptr, &outend, 15);
+ outptr = g_stpcpy (outptr, "</blockquote>\n");
+ priv->blockquote_depth--;
+ }
+#if FOOLISHLY_UNMUNGE_FROM
+ if (depth == 0 && *start == '>') {
+ /* >From line */
+ skip = 1;
+ }
+#endif
+ start += skip;
+
+ } else if (priv->flags & CAMEL_MIME_FILTER_TOHTML_CITE) {
+ outptr = check_size (mime_filter, outptr, &outend, 6);
+ outptr = g_stpcpy (outptr, "&gt; ");
+ priv->column += 2;
+ }
+
+#define CONVERT_URLS (CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES)
+ if (priv->flags & CONVERT_URLS) {
+ gsize matchlen, len;
+ CamelUrlMatch match;
+
+ len = inptr - start;
+
+ do {
+ if (camel_url_scanner_scan (priv->scanner, start, len - (len > 0 && start[len - 1] == 0 ? 1 : 0), &match)) {
+ /* write out anything before the first regex match */
+ outptr = writeln (
+ mime_filter,
+ (const guchar *) start,
+ (const guchar *) start +
+ match.um_so,
+ outptr, &outend);
+
+ start += match.um_so;
+ len -= match.um_so;
+
+ matchlen = match.um_eo - match.um_so;
+
+ /* write out the href tag */
+ outptr = append_string_verbatim (mime_filter, "<a href=\"", outptr, &outend);
+ /* prefix shouldn't need escaping, but let's be safe */
+ outptr = writeln (
+ mime_filter,
+ (const guchar *) match.prefix,
+ (const guchar *) match.prefix +
+ strlen (match.prefix),
+ outptr, &outend);
+ outptr = writeln (
+ mime_filter,
+ (const guchar *) start,
+ (const guchar *) start +
+ matchlen,
+ outptr, &outend);
+ outptr = append_string_verbatim (
+ mime_filter, "\">",
+ outptr, &outend);
+
+ /* now write the matched string */
+ outptr = writeln (
+ mime_filter,
+ (const guchar *) start,
+ (const guchar *) start +
+ matchlen,
+ outptr, &outend);
+ priv->column += matchlen;
+ start += matchlen;
+ len -= matchlen;
+
+ /* close the href tag */
+ outptr = append_string_verbatim (
+ mime_filter, "</a>",
+ outptr, &outend);
+ } else {
+ /* nothing matched so write out the remainder of this line buffer */
+ outptr = writeln (
+ mime_filter,
+ (const guchar *) start,
+ (const guchar *) start + len,
+ outptr, &outend);
+ break;
+ }
+ } while (len > 0);
+ } else {
+ outptr = writeln (
+ mime_filter,
+ (const guchar *) start,
+ (const guchar *) inptr,
+ outptr, &outend);
+ }
+
+ if ((priv->flags & CAMEL_MIME_FILTER_TOHTML_MARK_CITATION) && depth > 0) {
+ outptr = check_size (mime_filter, outptr, &outend, 8);
+ outptr = g_stpcpy (outptr, "</font>");
+ }
+
+ if (inptr < inend) {
+ if (priv->flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_NL) {
+ outptr = check_size (mime_filter, outptr, &outend, 5);
+ outptr = g_stpcpy (outptr, "<br>");
+ }
+
+ outptr = append_string_verbatim (mime_filter, "\n", outptr, &outend);
+ }
+
+ start = ++inptr;
+ } while (inptr < inend);
+
+ if (flush) {
+ /* flush the rest of our input buffer */
+ if (start < inend)
+ outptr = writeln (
+ mime_filter,
+ (const guchar *) start,
+ (const guchar *) inend,
+ outptr, &outend);
+
+ while (priv->blockquote_depth > 0) {
+ outptr = check_size (mime_filter, outptr, &outend, 15);
+ outptr = g_stpcpy (outptr, "</blockquote>");
+ priv->blockquote_depth--;
+ }
+
+ if (priv->pre_open) {
+ /* close the pre-tag */
+ outptr = check_size (mime_filter, outptr, &outend, 10);
+ outptr = g_stpcpy (outptr, "</pre>");
+ priv->pre_open = FALSE;
+ }
+ } else if (start < inend) {
+ /* backup */
+ camel_mime_filter_backup (
+ mime_filter, start, (gsize) (inend - start));
+ }
+
+ *out = mime_filter->outbuf;
+ *outlen = outptr - mime_filter->outbuf;
+ *outprespace = mime_filter->outpre;
+}
+
+static void
+mime_filter_tohtml_finalize (GObject *object)
+{
+ CamelMimeFilterToHTMLPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (object);
+
+ camel_url_scanner_free (priv->scanner);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_mime_filter_tohtml_parent_class)->finalize (object);
+}
+
+static void
+mime_filter_tohtml_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ html_convert (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, FALSE);
+}
+
+static void
+mime_filter_tohtml_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ html_convert (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, TRUE);
+}
+
+static void
+mime_filter_tohtml_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterToHTMLPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (mime_filter);
+
+ priv->column = 0;
+ priv->pre_open = FALSE;
+}
+
+static void
+camel_mime_filter_tohtml_class_init (CamelMimeFilterToHTMLClass *class)
+{
+ GObjectClass *object_class;
+ CamelMimeFilterClass *filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterToHTMLPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = mime_filter_tohtml_finalize;
+
+ filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ filter_class->filter = mime_filter_tohtml_filter;
+ filter_class->complete = mime_filter_tohtml_complete;
+ filter_class->reset = mime_filter_tohtml_reset;
+}
+
+static void
+camel_mime_filter_tohtml_init (CamelMimeFilterToHTML *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (filter);
+ filter->priv->scanner = camel_url_scanner_new ();
+}
+
+/**
+ * camel_mime_filter_tohtml_new:
+ * @flags: bitwise flags defining the behaviour
+ * @color: color to use when highlighting quoted text
+ *
+ * Create a new #CamelMimeFilterToHTML object to convert plain text
+ * into HTML.
+ *
+ * Returns: a new #CamelMimeFilterToHTML object
+ **/
+CamelMimeFilter *
+camel_mime_filter_tohtml_new (CamelMimeFilterToHTMLFlags flags,
+ guint32 color)
+{
+ CamelMimeFilter *filter;
+ CamelMimeFilterToHTMLPrivate *priv;
+ gint i;
+
+ filter = g_object_new (CAMEL_TYPE_MIME_FILTER_TOHTML, NULL);
+ priv = CAMEL_MIME_FILTER_TOHTML_GET_PRIVATE (filter);
+
+ priv->flags = flags;
+ priv->color = color;
+
+ for (i = 0; i < G_N_ELEMENTS (patterns); i++) {
+ if (patterns[i].mask & flags)
+ camel_url_scanner_add (
+ priv->scanner, &patterns[i].pattern);
+ }
+
+ return filter;
+}
+
+/**
+ * camel_text_to_html:
+ * @in: input text
+ * @flags: bitwise flags defining the html conversion behaviour
+ * @color: color to use when syntax highlighting
+ *
+ * Convert @in from plain text into HTML.
+ *
+ * Returns: a newly allocated string containing the HTMLified version
+ * of @in
+ **/
+gchar *
+camel_text_to_html (const gchar *in,
+ CamelMimeFilterToHTMLFlags flags,
+ guint32 color)
+{
+ CamelMimeFilter *filter;
+ gsize outlen, outpre;
+ gchar *outbuf;
+
+ g_return_val_if_fail (in != NULL, NULL);
+
+ filter = camel_mime_filter_tohtml_new (flags, color);
+
+ camel_mime_filter_complete (
+ filter, (gchar *) in, strlen (in), 0,
+ &outbuf, &outlen, &outpre);
+
+ outbuf = g_strndup (outbuf, outlen);
+
+ g_object_unref (filter);
+
+ return outbuf;
+}
diff --git a/src/camel/camel-mime-filter-tohtml.h b/src/camel/camel-mime-filter-tohtml.h
new file mode 100644
index 000000000..a0b9d2a90
--- /dev/null
+++ b/src/camel/camel-mime-filter-tohtml.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_TOHTML_H
+#define CAMEL_MIME_FILTER_TOHTML_H
+
+#include <camel/camel-enums.h>
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_TOHTML \
+ (camel_mime_filter_tohtml_get_type ())
+#define CAMEL_MIME_FILTER_TOHTML(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_TOHTML, CamelMimeFilterToHTML))
+#define CAMEL_MIME_FILTER_TOHTML_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_TOHTML, CamelMimeFilterToHTMLClass))
+#define CAMEL_IS_MIME_FILTER_TOHTML(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_TOHTML))
+#define CAMEL_IS_MIME_FILTER_TOHTML_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_TOHTML))
+#define CAMEL_MIME_FILTER_TOHTML_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_TOHTML, CamelMimeFilterToHTMLClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterToHTML CamelMimeFilterToHTML;
+typedef struct _CamelMimeFilterToHTMLClass CamelMimeFilterToHTMLClass;
+typedef struct _CamelMimeFilterToHTMLPrivate CamelMimeFilterToHTMLPrivate;
+
+struct _CamelMimeFilterToHTML {
+ CamelMimeFilter parent;
+ CamelMimeFilterToHTMLPrivate *priv;
+};
+
+struct _CamelMimeFilterToHTMLClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_tohtml_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_tohtml_new (CamelMimeFilterToHTMLFlags flags,
+ guint32 color);
+
+/* utility functions to replace e_text_to_html */
+
+gchar * camel_text_to_html (const gchar *in,
+ CamelMimeFilterToHTMLFlags flags,
+ guint32 color);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_TOHTML_H */
diff --git a/src/camel/camel-mime-filter-windows.c b/src/camel/camel-mime-filter-windows.c
new file mode 100644
index 000000000..d2358a11a
--- /dev/null
+++ b/src/camel/camel-mime-filter-windows.c
@@ -0,0 +1,209 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-charset-map.h"
+#include "camel-mime-filter-windows.h"
+
+#define CAMEL_MIME_FILTER_WINDOWS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_WINDOWS, CamelMimeFilterWindowsPrivate))
+
+#define d(x)
+#define w(x)
+
+struct _CamelMimeFilterWindowsPrivate {
+ gboolean is_windows;
+ gchar *claimed_charset;
+};
+
+G_DEFINE_TYPE (CamelMimeFilterWindows, camel_mime_filter_windows, CAMEL_TYPE_MIME_FILTER)
+
+static void
+mime_filter_windows_finalize (GObject *object)
+{
+ CamelMimeFilterWindowsPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_WINDOWS_GET_PRIVATE (object);
+
+ g_free (priv->claimed_charset);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_mime_filter_windows_parent_class)->finalize (object);
+}
+
+static void
+mime_filter_windows_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterWindowsPrivate *priv;
+ register guchar *inptr;
+ guchar *inend;
+
+ priv = CAMEL_MIME_FILTER_WINDOWS_GET_PRIVATE (mime_filter);
+
+ if (!priv->is_windows) {
+ inptr = (guchar *) in;
+ inend = inptr + len;
+
+ while (inptr < inend) {
+ register guchar c = *inptr++;
+
+ if (c >= 128 && c <= 159) {
+ w (g_warning (
+ "Encountered Windows "
+ "charset masquerading as %s",
+ priv->claimed_charset));
+ priv->is_windows = TRUE;
+ break;
+ }
+ }
+ }
+
+ *out = (gchar *) in;
+ *outlen = len;
+ *outprespace = prespace;
+}
+
+static void
+mime_filter_windows_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ mime_filter_windows_filter (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace);
+}
+
+static void
+mime_filter_windows_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterWindowsPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_WINDOWS_GET_PRIVATE (mime_filter);
+
+ priv->is_windows = FALSE;
+}
+
+static void
+camel_mime_filter_windows_class_init (CamelMimeFilterWindowsClass *class)
+{
+ GObjectClass *object_class;
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterWindowsPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = mime_filter_windows_finalize;
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_windows_filter;
+ mime_filter_class->complete = mime_filter_windows_complete;
+ mime_filter_class->reset = mime_filter_windows_reset;
+}
+
+static void
+camel_mime_filter_windows_init (CamelMimeFilterWindows *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_WINDOWS_GET_PRIVATE (filter);
+}
+
+/**
+ * camel_mime_filter_windows_new:
+ * @claimed_charset: ISO charset name
+ *
+ * Create a new #CamelMimeFilterWindows object that will analyse
+ * whether or not the text is really encoded in @claimed_charset.
+ *
+ * Returns: a new #CamelMimeFilter object
+ **/
+CamelMimeFilter *
+camel_mime_filter_windows_new (const gchar *claimed_charset)
+{
+ CamelMimeFilter *filter;
+ CamelMimeFilterWindowsPrivate *priv;
+
+ g_return_val_if_fail (claimed_charset != NULL, NULL);
+
+ filter = g_object_new (CAMEL_TYPE_MIME_FILTER_WINDOWS, NULL);
+ priv = CAMEL_MIME_FILTER_WINDOWS_GET_PRIVATE (filter);
+
+ priv->claimed_charset = g_strdup (claimed_charset);
+
+ return filter;
+}
+
+/**
+ * camel_mime_filter_windows_is_windows_charset:
+ * @filter: a #CamelMimeFilterWindows object
+ *
+ * Get whether or not the textual content filtered by @filter is
+ * really in a Microsoft Windows charset rather than the claimed ISO
+ * charset.
+ *
+ * Returns: %TRUE if the text was found to be in a Microsoft Windows
+ * CP125x charset or %FALSE otherwise.
+ **/
+gboolean
+camel_mime_filter_windows_is_windows_charset (CamelMimeFilterWindows *filter)
+{
+ g_return_val_if_fail (CAMEL_IS_MIME_FILTER_WINDOWS (filter), FALSE);
+
+ return filter->priv->is_windows;
+}
+
+/**
+ * camel_mime_filter_windows_real_charset:
+ * @filter: a #CamelMimeFilterWindows object
+ *
+ * Get the name of the actual charset used to encode the textual
+ * content filtered by @filter (it will either be the original
+ * claimed_charset passed in at creation time or the Windows-CP125x
+ * equivalent).
+ *
+ * Returns: the name of the actual charset
+ **/
+const gchar *
+camel_mime_filter_windows_real_charset (CamelMimeFilterWindows *filter)
+{
+ const gchar *charset;
+
+ g_return_val_if_fail (CAMEL_IS_MIME_FILTER_WINDOWS (filter), NULL);
+
+ charset = filter->priv->claimed_charset;
+
+ if (filter->priv->is_windows)
+ charset = camel_charset_iso_to_windows (charset);
+
+ return charset;
+}
diff --git a/src/camel/camel-mime-filter-windows.h b/src/camel/camel-mime-filter-windows.h
new file mode 100644
index 000000000..17a3d2aea
--- /dev/null
+++ b/src/camel/camel-mime-filter-windows.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_WINDOWS_H
+#define CAMEL_MIME_FILTER_WINDOWS_H
+
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_WINDOWS \
+ (camel_mime_filter_windows_get_type ())
+#define CAMEL_MIME_FILTER_WINDOWS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_WINDOWS, CamelMimeFilterWindows))
+#define CAMEL_MIME_FILTER_WINDOWS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_WINDOWS, CamelMimeFilterWindowsClass))
+#define CAMEL_IS_MIME_FILTER_WINDOWS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_WINDOWS))
+#define CAMEL_IS_MIME_FILTER_WINDOWS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_WINDOWS))
+#define CAMEL_MIME_FILTER_WINDOWS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_WINDOWS, CamelMimeFilterWindowsClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterWindows CamelMimeFilterWindows;
+typedef struct _CamelMimeFilterWindowsClass CamelMimeFilterWindowsClass;
+typedef struct _CamelMimeFilterWindowsPrivate CamelMimeFilterWindowsPrivate;
+
+struct _CamelMimeFilterWindows {
+ CamelMimeFilter parent;
+ CamelMimeFilterWindowsPrivate *priv;
+};
+
+struct _CamelMimeFilterWindowsClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_windows_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_windows_new
+ (const gchar *claimed_charset);
+gboolean camel_mime_filter_windows_is_windows_charset
+ (CamelMimeFilterWindows *filter);
+const gchar * camel_mime_filter_windows_real_charset
+ (CamelMimeFilterWindows *filter);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_WINDOWS_H */
diff --git a/src/camel/camel-mime-filter-yenc.c b/src/camel/camel-mime-filter-yenc.c
new file mode 100644
index 000000000..ba87c9ee1
--- /dev/null
+++ b/src/camel/camel-mime-filter-yenc.c
@@ -0,0 +1,574 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include "camel-mime-filter-yenc.h"
+
+#define CAMEL_MIME_FILTER_YENC_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_YENC, CamelMimeFilterYencPrivate))
+
+struct _CamelMimeFilterYencPrivate {
+
+ CamelMimeFilterYencDirection direction;
+
+ gint part;
+
+ gint state;
+ guint32 pcrc;
+ guint32 crc;
+};
+
+G_DEFINE_TYPE (CamelMimeFilterYenc, camel_mime_filter_yenc, CAMEL_TYPE_MIME_FILTER)
+
+/* here we do all of the basic yEnc filtering */
+static void
+mime_filter_yenc_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterYencPrivate *priv;
+ gsize newlen = 0;
+
+ priv = CAMEL_MIME_FILTER_YENC_GET_PRIVATE (mime_filter);
+
+ switch (priv->direction) {
+ case CAMEL_MIME_FILTER_YENC_DIRECTION_ENCODE:
+ /* won't go to more than 2 * (x + 2) + 62 */
+ camel_mime_filter_set_size (
+ mime_filter, (len + 2) * 2 + 62, FALSE);
+ newlen = camel_yencode_step (
+ (const guchar *) in, len,
+ (guchar *) mime_filter->outbuf, &priv->state,
+ &priv->pcrc, &priv->crc);
+ g_return_if_fail (newlen <= (len + 2) * 2 + 62);
+ break;
+ case CAMEL_MIME_FILTER_YENC_DIRECTION_DECODE:
+ if (!(priv->state & CAMEL_MIME_YDECODE_STATE_DECODE)) {
+ const gchar *inptr, *inend;
+ gsize left;
+
+ inptr = in;
+ inend = inptr + len;
+
+ /* we cannot start decoding until we have found an =ybegin line */
+ if (!(priv->state & CAMEL_MIME_YDECODE_STATE_BEGIN)) {
+ while (inptr < inend) {
+ left = inend - inptr;
+ if (left < 8) {
+ if (!strncmp (inptr, "=ybegin ", left))
+ camel_mime_filter_backup (mime_filter, inptr, left);
+ break;
+ } else if (!strncmp (inptr, "=ybegin ", 8)) {
+ for (in = inptr; inptr < inend && *inptr != '\n'; inptr++);
+ if (inptr < inend) {
+ inptr++;
+ priv->state |= CAMEL_MIME_YDECODE_STATE_BEGIN;
+ /* we can start ydecoding if the next line isn't
+ * a ypart... */
+ in = inptr;
+ len = inend - in;
+ } else {
+ /* we don't have enough... */
+ camel_mime_filter_backup (mime_filter, in, left);
+ }
+ break;
+ }
+
+ /* go to the next line */
+ while (inptr < inend && *inptr != '\n')
+ inptr++;
+
+ if (inptr < inend)
+ inptr++;
+ }
+ }
+
+ left = inend - inptr;
+ if ((priv->state & CAMEL_MIME_YDECODE_STATE_BEGIN) && left > 0) {
+ /* we have found an '=ybegin' line but we may yet have an "=ypart" line to
+ * yield before decoding the content */
+ if (left < 7 && !strncmp (inptr, "=ypart ", left)) {
+ camel_mime_filter_backup (mime_filter, inptr, left);
+ } else if (!strncmp (inptr, "=ypart ", 7)) {
+ for (in = inptr; inptr < inend && *inptr != '\n'; inptr++);
+ if (inptr < inend) {
+ inptr++;
+ priv->state |= CAMEL_MIME_YDECODE_STATE_PART | CAMEL_MIME_YDECODE_STATE_DECODE;
+ in = inptr;
+ len = inend - in;
+ } else {
+ camel_mime_filter_backup (mime_filter, in, left);
+ }
+ } else {
+ /* guess it doesn't have a =ypart line */
+ priv->state |= CAMEL_MIME_YDECODE_STATE_DECODE;
+ }
+ }
+ }
+
+ if ((priv->state & CAMEL_MIME_YDECODE_STATE_DECODE) && !(priv->state & CAMEL_MIME_YDECODE_STATE_END)) {
+ /* all yEnc headers have been found so we can now start decoding */
+ camel_mime_filter_set_size (
+ mime_filter, len + 3, FALSE);
+ newlen = camel_ydecode_step (
+ (const guchar *) in, len,
+ (guchar *) mime_filter->outbuf,
+ &priv->state, &priv->pcrc, &priv->crc);
+ g_return_if_fail (newlen <= len + 3);
+ } else {
+ newlen = 0;
+ }
+ break;
+ }
+
+ *out = mime_filter->outbuf;
+ *outlen = newlen;
+ *outprespace = mime_filter->outpre;
+}
+
+static void
+mime_filter_yenc_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterYencPrivate *priv;
+ gsize newlen = 0;
+
+ priv = CAMEL_MIME_FILTER_YENC_GET_PRIVATE (mime_filter);
+
+ switch (priv->direction) {
+ case CAMEL_MIME_FILTER_YENC_DIRECTION_ENCODE:
+ /* won't go to more than 2 * (x + 2) + 62 */
+ camel_mime_filter_set_size (
+ mime_filter, (len + 2) * 2 + 62, FALSE);
+ newlen = camel_yencode_close (
+ (const guchar *) in, len,
+ (guchar *) mime_filter->outbuf,
+ &priv->state, &priv->pcrc, &priv->crc);
+ g_return_if_fail (newlen <= (len + 2) * 2 + 62);
+ break;
+ case CAMEL_MIME_FILTER_YENC_DIRECTION_DECODE:
+ if ((priv->state & CAMEL_MIME_YDECODE_STATE_DECODE) &&
+ !(priv->state & CAMEL_MIME_YDECODE_STATE_END)) {
+ /* all yEnc headers have been found so we
+ * can now start decoding */
+ camel_mime_filter_set_size (
+ mime_filter, len + 3, FALSE);
+ newlen = camel_ydecode_step (
+ (const guchar *) in, len,
+ (guchar *) mime_filter->outbuf,
+ &priv->state, &priv->pcrc, &priv->crc);
+ g_return_if_fail (newlen <= len + 3);
+ } else {
+ newlen = 0;
+ }
+ break;
+ }
+
+ *out = mime_filter->outbuf;
+ *outlen = newlen;
+ *outprespace = mime_filter->outpre;
+}
+
+/* should this 'flush' outstanding state/data bytes? */
+static void
+mime_filter_yenc_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterYencPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_YENC_GET_PRIVATE (mime_filter);
+
+ switch (priv->direction) {
+ case CAMEL_MIME_FILTER_YENC_DIRECTION_ENCODE:
+ priv->state = CAMEL_MIME_YENCODE_STATE_INIT;
+ break;
+ case CAMEL_MIME_FILTER_YENC_DIRECTION_DECODE:
+ priv->state = CAMEL_MIME_YDECODE_STATE_INIT;
+ break;
+ }
+
+ priv->pcrc = CAMEL_MIME_YENCODE_CRC_INIT;
+ priv->crc = CAMEL_MIME_YENCODE_CRC_INIT;
+}
+
+static void
+camel_mime_filter_yenc_class_init (CamelMimeFilterYencClass *class)
+{
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterYencPrivate));
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_yenc_filter;
+ mime_filter_class->complete = mime_filter_yenc_complete;
+ mime_filter_class->reset = mime_filter_yenc_reset;
+}
+
+static void
+camel_mime_filter_yenc_init (CamelMimeFilterYenc *filter)
+{
+ filter->priv = CAMEL_MIME_FILTER_YENC_GET_PRIVATE (filter);
+
+ filter->priv->part = 0;
+ filter->priv->pcrc = CAMEL_MIME_YENCODE_CRC_INIT;
+ filter->priv->crc = CAMEL_MIME_YENCODE_CRC_INIT;
+}
+
+/**
+ * camel_mime_filter_yenc_new:
+ * @direction: encode direction
+ *
+ * Create a new #CamelMimeFilterYenc filter object.
+ *
+ * Returns: a new #CamelMimeFilterYenc object
+ **/
+CamelMimeFilter *
+camel_mime_filter_yenc_new (CamelMimeFilterYencDirection direction)
+{
+ CamelMimeFilter *filter;
+ CamelMimeFilterYencPrivate *priv;
+
+ filter = g_object_new (CAMEL_TYPE_MIME_FILTER_YENC, NULL);
+ priv = CAMEL_MIME_FILTER_YENC_GET_PRIVATE (filter);
+
+ priv->direction = direction;
+
+ switch (direction) {
+ case CAMEL_MIME_FILTER_YENC_DIRECTION_ENCODE:
+ priv->state = CAMEL_MIME_YENCODE_STATE_INIT;
+ break;
+ case CAMEL_MIME_FILTER_YENC_DIRECTION_DECODE:
+ priv->state = CAMEL_MIME_YDECODE_STATE_INIT;
+ break;
+ default:
+ g_warn_if_reached ();
+ }
+
+ return filter;
+}
+
+/**
+ * camel_mime_filter_yenc_set_state:
+ * @yenc: a #CamelMimeFilterYenc object
+ * @state: encode/decode state
+ *
+ * Sets the current state of the yencoder/ydecoder
+ **/
+void
+camel_mime_filter_yenc_set_state (CamelMimeFilterYenc *yenc,
+ gint state)
+{
+ g_return_if_fail (CAMEL_IS_MIME_FILTER_YENC (yenc));
+
+ yenc->priv->state = state;
+}
+
+/**
+ * camel_mime_filter_yenc_set_crc:
+ * @yenc: a #CamelMimeFilterYenc object
+ * @crc: crc32 value
+ *
+ * Sets the current crc32 value on the yEnc filter @yenc to @crc.
+ **/
+void
+camel_mime_filter_yenc_set_crc (CamelMimeFilterYenc *yenc,
+ guint32 crc)
+{
+ g_return_if_fail (CAMEL_IS_MIME_FILTER_YENC (yenc));
+
+ yenc->priv->crc = crc;
+}
+
+/**
+ * camel_mime_filter_yenc_get_pcrc:
+ * @yenc: a #CamelMimeFilterYenc object
+ *
+ * Get the computed part crc or (#guint32) %-1 on fail.
+ *
+ * Returns: the computed part crc or (#guint32) %-1 on fail.
+ **/
+guint32
+camel_mime_filter_yenc_get_pcrc (CamelMimeFilterYenc *yenc)
+{
+ g_return_val_if_fail (CAMEL_IS_MIME_FILTER_YENC (yenc), -1);
+
+ return CAMEL_MIME_YENCODE_CRC_FINAL (yenc->priv->pcrc);
+}
+
+/**
+ * camel_mime_filter_yenc_get_crc:
+ * @yenc: a #CamelMimeFiletrYenc object
+ *
+ * Get the computed crc or (#guint32) -1 on fail.
+ *
+ * Returns: the computed crc or (#guint32) -1 on fail.
+ **/
+guint32
+camel_mime_filter_yenc_get_crc (CamelMimeFilterYenc *yenc)
+{
+ g_return_val_if_fail (CAMEL_IS_MIME_FILTER_YENC (yenc), -1);
+
+ return CAMEL_MIME_YENCODE_CRC_FINAL (yenc->priv->crc);
+}
+
+static const gint yenc_crc_table[256] = {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+#define yenc_crc_add(crc, c) (yenc_crc_table[(((gint) (crc)) ^ ((guchar) (c))) & 0xff] ^ ((((gint) (crc)) >> 8) & 0x00ffffff))
+
+#define YENC_NEWLINE_ESCAPE (CAMEL_MIME_YDECODE_STATE_EOLN | CAMEL_MIME_YDECODE_STATE_ESCAPE)
+
+/**
+ * camel_ydecode_step:
+ * @in: input buffer
+ * @inlen: input buffer length
+ * @out: output buffer
+ * @state: ydecode state
+ * @pcrc: part crc state
+ * @crc: crc state
+ *
+ * Performs a 'decode step' on a chunk of yEncoded data of length
+ * @inlen pointed to by @in and writes to @out. Assumes the =ybegin
+ * and =ypart lines have already been stripped off.
+ *
+ * To get the crc32 value of the part, use #CAMEL_MIME_YENCODE_CRC_FINAL
+ * (@pcrc). If there are more parts, you should reuse @crc without
+ * re-initializing. Once all parts have been decoded, you may get the
+ * combined crc32 value of all the parts using #CAMEL_MIME_YENCODE_CRC_FINAL
+ * (@crc).
+ *
+ * Returns: the number of bytes decoded
+ **/
+gsize
+camel_ydecode_step (const guchar *in,
+ gsize inlen,
+ guchar *out,
+ gint *state,
+ guint32 *pcrc,
+ guint32 *crc)
+{
+ register const guchar *inptr;
+ register guchar *outptr;
+ const guchar *inend;
+ guchar ch;
+ gint ystate;
+
+ if (*state & CAMEL_MIME_YDECODE_STATE_END)
+ return 0;
+
+ ystate = *state;
+
+ inend = in + inlen;
+ outptr = out;
+
+ inptr = in;
+ while (inptr < inend) {
+ ch = *inptr++;
+
+ if ((ystate & YENC_NEWLINE_ESCAPE) == YENC_NEWLINE_ESCAPE) {
+ ystate &= ~CAMEL_MIME_YDECODE_STATE_EOLN;
+
+ if (ch == 'y') {
+ /* we probably have a =yend here */
+ ystate |= CAMEL_MIME_YDECODE_STATE_END;
+ break;
+ }
+ }
+
+ if (ch == '\n') {
+ ystate |= CAMEL_MIME_YDECODE_STATE_EOLN;
+ continue;
+ }
+
+ if (ystate & CAMEL_MIME_YDECODE_STATE_ESCAPE) {
+ ystate &= ~CAMEL_MIME_YDECODE_STATE_ESCAPE;
+ ch -= 64;
+ } else if (ch == '=') {
+ ystate |= CAMEL_MIME_YDECODE_STATE_ESCAPE;
+ continue;
+ }
+
+ ystate &= ~CAMEL_MIME_YDECODE_STATE_EOLN;
+
+ *outptr++ = ch -= 42;
+
+ *pcrc = yenc_crc_add (*pcrc, ch);
+ *crc = yenc_crc_add (*crc, ch);
+ }
+
+ *state = ystate;
+
+ return outptr - out;
+}
+
+/**
+ * camel_yencode_step:
+ * @in: input buffer
+ * @inlen: input buffer length
+ * @out: output buffer
+ * @state: yencode state
+ * @pcrc: part crc state
+ * @crc: crc state
+ *
+ * Performs an yEncode 'encode step' on a chunk of raw data of length
+ * @inlen pointed to by @in and writes to @out.
+ *
+ * @state should be initialized to #CAMEL_MIME_YENCODE_STATE_INIT before
+ * beginning making the first call to this function. Subsequent calls
+ * should reuse @state.
+ *
+ * Along the same lines, @pcrc and @crc should be initialized to
+ * #CAMEL_MIME_YENCODE_CRC_INIT before using.
+ *
+ * Returns: the number of bytes encoded
+ **/
+gsize
+camel_yencode_step (const guchar *in,
+ gsize inlen,
+ guchar *out,
+ gint *state,
+ guint32 *pcrc,
+ guint32 *crc)
+{
+ register const guchar *inptr;
+ register guchar *outptr;
+ const guchar *inend;
+ register gint already;
+ guchar ch;
+
+ inend = in + inlen;
+ outptr = out;
+
+ already = *state;
+
+ inptr = in;
+ while (inptr < inend) {
+ ch = (*inptr++);
+
+ *pcrc = yenc_crc_add (*pcrc, ch);
+ *crc = yenc_crc_add (*crc, ch);
+
+ ch += 42;
+
+ if (ch == '\0' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '=') {
+ *outptr++ = '=';
+ *outptr++ = ch + 64;
+ already += 2;
+ } else {
+ *outptr++ = ch;
+ already++;
+ }
+
+ if (already >= 128) {
+ *outptr++ = '\n';
+ already = 0;
+ }
+ }
+
+ *state = already;
+
+ return outptr - out;
+}
+
+/**
+ * camel_yencode_close:
+ * @in: input buffer
+ * @inlen: input buffer length
+ * @out: output buffer
+ * @state: yencode state
+ * @pcrc: part crc state
+ * @crc: crc state
+ *
+ * Call this function when finished encoding data with
+ * camel_yencode_step() to flush off the remaining state.
+ *
+ * #CAMEL_MIME_YENCODE_CRC_FINAL (@pcrc) will give you the crc32 of the
+ * encoded "part". If there are more "parts" to encode, you should
+ * re-use @crc when encoding the next "parts" and then use
+ * #CAMEL_MIME_YENCODE_CRC_FINAL (@crc) to get the combined crc32 value of
+ * all the parts.
+ *
+ * Returns: the number of bytes encoded.
+ **/
+gsize
+camel_yencode_close (const guchar *in,
+ gsize inlen,
+ guchar *out,
+ gint *state,
+ guint32 *pcrc,
+ guint32 *crc)
+{
+ register guchar *outptr;
+
+ outptr = out;
+
+ if (inlen)
+ outptr += camel_yencode_step (in, inlen, out, state, pcrc, crc);
+
+ if (*state)
+ *outptr++ = '\n';
+
+ *state = CAMEL_MIME_YENCODE_STATE_INIT;
+
+ return outptr - out;
+}
diff --git a/src/camel/camel-mime-filter-yenc.h b/src/camel/camel-mime-filter-yenc.h
new file mode 100644
index 000000000..fa9d6d70b
--- /dev/null
+++ b/src/camel/camel-mime-filter-yenc.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_YENC_H
+#define CAMEL_MIME_FILTER_YENC_H
+
+#include <camel/camel-enums.h>
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER_YENC \
+ (camel_mime_filter_yenc_get_type ())
+#define CAMEL_MIME_FILTER_YENC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER_YENC, CamelMimeFilterYenc))
+#define CAMEL_MIME_FILTER_YENC_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER_YENC, CamelMimeFilterYencClass))
+#define CAMEL_IS_MIME_FILTER_YENC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_YENC))
+#define CAMEL_IS_MIME_FILTER_YENC_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER_YENC))
+#define CAMEL_MIME_FILTER_YENC_GET_CLASS(obj) \
+ (CAMEL_CHECK_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER_YENC, CamelMimeFilterYencClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilterYenc CamelMimeFilterYenc;
+typedef struct _CamelMimeFilterYencClass CamelMimeFilterYencClass;
+typedef struct _CamelMimeFilterYencPrivate CamelMimeFilterYencPrivate;
+
+#define CAMEL_MIME_YDECODE_STATE_INIT (0)
+#define CAMEL_MIME_YENCODE_STATE_INIT (0)
+
+/* first 8 bits are reserved for saving a byte */
+
+/* reserved for use only within camel_mime_ydecode_step */
+#define CAMEL_MIME_YDECODE_STATE_EOLN (1 << 8)
+#define CAMEL_MIME_YDECODE_STATE_ESCAPE (1 << 9)
+
+/* bits 10 and 11 reserved for later uses? */
+
+#define CAMEL_MIME_YDECODE_STATE_BEGIN (1 << 12)
+#define CAMEL_MIME_YDECODE_STATE_PART (1 << 13)
+#define CAMEL_MIME_YDECODE_STATE_DECODE (1 << 14)
+#define CAMEL_MIME_YDECODE_STATE_END (1 << 15)
+
+#define CAMEL_MIME_YENCODE_CRC_INIT (~0)
+#define CAMEL_MIME_YENCODE_CRC_FINAL(crc) (~crc)
+
+struct _CamelMimeFilterYenc {
+ CamelMimeFilter parent;
+ CamelMimeFilterYencPrivate *priv;
+};
+
+struct _CamelMimeFilterYencClass {
+ CamelMimeFilterClass parent_class;
+};
+
+GType camel_mime_filter_yenc_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_yenc_new (CamelMimeFilterYencDirection direction);
+void camel_mime_filter_yenc_set_state (CamelMimeFilterYenc *yenc,
+ gint state);
+void camel_mime_filter_yenc_set_crc (CamelMimeFilterYenc *yenc,
+ guint32 crc);
+guint32 camel_mime_filter_yenc_get_pcrc (CamelMimeFilterYenc *yenc);
+guint32 camel_mime_filter_yenc_get_crc (CamelMimeFilterYenc *yenc);
+
+gsize camel_ydecode_step (const guchar *in,
+ gsize inlen,
+ guchar *out,
+ gint *state,
+ guint32 *pcrc,
+ guint32 *crc);
+gsize camel_yencode_step (const guchar *in,
+ gsize inlen,
+ guchar *out,
+ gint *state,
+ guint32 *pcrc,
+ guint32 *crc);
+gsize camel_yencode_close (const guchar *in,
+ gsize inlen,
+ guchar *out,
+ gint *state,
+ guint32 *pcrc,
+ guint32 *crc);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_YENC_H */
diff --git a/src/camel/camel-mime-filter.c b/src/camel/camel-mime-filter.c
new file mode 100644
index 000000000..0141c9e29
--- /dev/null
+++ b/src/camel/camel-mime-filter.c
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include <string.h>
+
+#include "camel-mime-filter.h"
+
+/*#define MALLOC_CHECK */ /* for some malloc checking, requires mcheck enabled */
+
+/* only suitable for glibc */
+#ifdef MALLOC_CHECK
+#include <mcheck.h>
+#endif
+
+#define CAMEL_MIME_FILTER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER, CamelMimeFilterPrivate))
+
+struct _CamelMimeFilterPrivate {
+ gchar *inbuf;
+ gsize inlen;
+};
+
+/* Compatible with filter() and complete() methods. */
+typedef void (*FilterMethod) (CamelMimeFilter *filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace);
+
+#define PRE_HEAD (64)
+#define BACK_HEAD (64)
+
+G_DEFINE_ABSTRACT_TYPE (CamelMimeFilter, camel_mime_filter, G_TYPE_OBJECT)
+
+static void
+mime_filter_finalize (GObject *object)
+{
+ CamelMimeFilter *mime_filter;
+
+ mime_filter = CAMEL_MIME_FILTER (object);
+
+ g_free (mime_filter->outreal);
+ g_free (mime_filter->backbuf);
+ g_free (mime_filter->priv->inbuf);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_mime_filter_parent_class)->finalize (object);
+}
+
+static void
+mime_filter_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ /* default - do nothing */
+}
+
+static void
+camel_mime_filter_class_init (CamelMimeFilterClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = mime_filter_finalize;
+
+ class->complete = mime_filter_complete;
+}
+
+static void
+camel_mime_filter_init (CamelMimeFilter *mime_filter)
+{
+ mime_filter->priv = CAMEL_MIME_FILTER_GET_PRIVATE (mime_filter);
+
+ mime_filter->outreal = NULL;
+ mime_filter->outbuf = NULL;
+ mime_filter->outsize = 0;
+
+ mime_filter->backbuf = NULL;
+ mime_filter->backsize = 0;
+ mime_filter->backlen = 0;
+}
+
+/**
+ * camel_mime_filter_new:
+ *
+ * Create a new #CamelMimeFilter object.
+ *
+ * Returns: a new #CamelMimeFilter
+ **/
+CamelMimeFilter *
+camel_mime_filter_new (void)
+{
+ return g_object_new (CAMEL_TYPE_MIME_FILTER, NULL);
+}
+
+#ifdef MALLOC_CHECK
+static void
+checkmem (gpointer p)
+{
+ if (p) {
+ gint status = mprobe (p);
+
+ switch (status) {
+ case MCHECK_HEAD:
+ printf ("Memory underrun at %p\n", p);
+ abort ();
+ case MCHECK_TAIL:
+ printf ("Memory overrun at %p\n", p);
+ abort ();
+ case MCHECK_FREE:
+ printf ("Double free %p\n", p);
+ abort ();
+ }
+ }
+}
+#endif
+
+static void
+filter_run (CamelMimeFilter *f,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace,
+ FilterMethod method)
+{
+#ifdef MALLOC_CHECK
+ checkmem (f->outreal);
+ checkmem (f->backbuf);
+#endif
+ /*
+ * here we take a performance hit, if the input buffer doesn't
+ * have the pre-space required. We make a buffer that does ...
+ */
+ if (f->backlen > 0) {
+ struct _CamelMimeFilterPrivate *p;
+ gint newlen;
+
+ p = CAMEL_MIME_FILTER_GET_PRIVATE (f);
+
+ newlen = len + prespace + f->backlen;
+ if (p->inlen < newlen) {
+ /* NOTE: g_realloc copies data, we dont need that (slower) */
+ g_free (p->inbuf);
+ p->inbuf = g_malloc (newlen + PRE_HEAD);
+ p->inlen = newlen + PRE_HEAD;
+ }
+
+ /* copy to end of structure */
+ memcpy (p->inbuf + p->inlen - len, in, len);
+ in = p->inbuf + p->inlen - len;
+ prespace = p->inlen - len;
+
+ /* preload any backed up data */
+ memcpy ((gchar *) in - f->backlen, f->backbuf, f->backlen);
+ in -= f->backlen;
+ len += f->backlen;
+ prespace -= f->backlen;
+ f->backlen = 0;
+ }
+
+#ifdef MALLOC_CHECK
+ checkmem (f->outreal);
+ checkmem (f->backbuf);
+#endif
+
+ method (f, in, len, prespace, out, outlen, outprespace);
+
+#ifdef MALLOC_CHECK
+ checkmem (f->outreal);
+ checkmem (f->backbuf);
+#endif
+
+}
+
+/**
+ * camel_mime_filter_filter:
+ * @filter: a #CamelMimeFilter object
+ * @in: (array length=len): input buffer
+ * @len: length of @in
+ * @prespace: amount of prespace
+ * @out: (out) (array length=outlen): pointer to the output buffer (to be set)
+ * @outlen: (out): pointer to the length of the output buffer (to be set)
+ * @outprespace: (out): pointer to the output prespace length (to be set)
+ *
+ * Passes the input buffer, @in, through @filter and generates an
+ * output buffer, @out.
+ **/
+void
+camel_mime_filter_filter (CamelMimeFilter *filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterClass *class;
+
+ g_return_if_fail (CAMEL_IS_MIME_FILTER (filter));
+ g_return_if_fail (in != NULL);
+
+ class = CAMEL_MIME_FILTER_GET_CLASS (filter);
+ g_return_if_fail (class->filter != NULL);
+
+ filter_run (
+ filter, in, len, prespace, out,
+ outlen, outprespace, class->filter);
+}
+
+/**
+ * camel_mime_filter_complete:
+ * @filter: a #CamelMimeFilter object
+ * @in: (array length=len): input buffer
+ * @len: length of @in
+ * @prespace: amount of prespace
+ * @out: (out) (array length=outlen): pointer to the output buffer (to be set)
+ * @outlen: (out): pointer to the length of the output buffer (to be set)
+ * @outprespace: (out): pointer to the output prespace length (to be set)
+ *
+ * Passes the input buffer, @in, through @filter and generates an
+ * output buffer, @out and makes sure that all data is flushed to the
+ * output buffer. This must be the last filtering call made, no
+ * further calls to camel_mime_filter_filter() may be called on @filter
+ * until @filter has been reset using camel_mime_filter_reset().
+ **/
+void
+camel_mime_filter_complete (CamelMimeFilter *filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterClass *class;
+
+ g_return_if_fail (CAMEL_IS_MIME_FILTER (filter));
+ g_return_if_fail (in != NULL);
+
+ class = CAMEL_MIME_FILTER_GET_CLASS (filter);
+ g_return_if_fail (class->complete != NULL);
+
+ filter_run (
+ filter, in, len, prespace, out,
+ outlen, outprespace, class->complete);
+}
+
+/**
+ * camel_mime_filter_reset:
+ * @filter: a #CamelMimeFilter object
+ *
+ * Resets the state on @filter so that it may be used again.
+ **/
+void
+camel_mime_filter_reset (CamelMimeFilter *filter)
+{
+ CamelMimeFilterClass *class;
+
+ g_return_if_fail (CAMEL_IS_MIME_FILTER (filter));
+
+ class = CAMEL_MIME_FILTER_GET_CLASS (filter);
+
+ if (class->reset != NULL)
+ class->reset (filter);
+
+ /* could free some buffers, if they are really big? */
+ filter->backlen = 0;
+}
+
+/**
+ * camel_mime_filter_backup:
+ * @filter: a #CamelMimeFilter object
+ * @data (array length=length): data buffer to backup
+ * @length: length of @data
+ *
+ * Saves @data to be used as prespace input data to the next call to
+ * camel_mime_filter_filter() or camel_mime_filter_complete().
+ *
+ * Note: New calls replace old data.
+ **/
+void
+camel_mime_filter_backup (CamelMimeFilter *filter,
+ const gchar *data,
+ gsize length)
+{
+ if (filter->backsize < length) {
+ /* g_realloc copies data, unnecessary overhead */
+ g_free (filter->backbuf);
+ filter->backbuf = g_malloc (length + BACK_HEAD);
+ filter->backsize = length + BACK_HEAD;
+ }
+ filter->backlen = length;
+ memcpy (filter->backbuf, data, length);
+}
+
+/**
+ * camel_mime_filter_set_size:
+ * @filter: a #CamelMimeFilter object
+ * @size: requested amount of storage space
+ * @keep: %TRUE to keep existing buffered data or %FALSE otherwise
+ *
+ * Ensure that @filter has enough storage space to store @size bytes
+ * for filter output.
+ **/
+void
+camel_mime_filter_set_size (CamelMimeFilter *filter,
+ gsize size,
+ gint keep)
+{
+ if (filter->outsize < size) {
+ gint offset = filter->outptr - filter->outreal;
+ if (keep) {
+ filter->outreal = g_realloc (filter->outreal, size + PRE_HEAD * 4);
+ } else {
+ g_free (filter->outreal);
+ filter->outreal = g_malloc (size + PRE_HEAD * 4);
+ }
+ filter->outptr = filter->outreal + offset;
+ filter->outbuf = filter->outreal + PRE_HEAD * 4;
+ filter->outsize = size;
+ /* this could be offset from the end of the structure, but
+ * this should be good enough */
+ filter->outpre = PRE_HEAD * 4;
+ }
+}
diff --git a/src/camel/camel-mime-filter.h b/src/camel/camel-mime-filter.h
new file mode 100644
index 000000000..a064aed51
--- /dev/null
+++ b/src/camel/camel-mime-filter.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/* Abstract class for non-copying filters */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_FILTER_H
+#define CAMEL_MIME_FILTER_H
+
+#include <sys/types.h>
+
+#include <glib-object.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_FILTER \
+ (camel_mime_filter_get_type ())
+#define CAMEL_MIME_FILTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_FILTER, CamelMimeFilter))
+#define CAMEL_MIME_FILTER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_FILTER, CamelMimeFilterClass))
+#define CAMEL_IS_MIME_FILTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_FILTER))
+#define CAMEL_IS_MIME_FILTER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_FILTER))
+#define CAMEL_MIME_FILTER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_FILTER, CamelMimeFilterClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeFilter CamelMimeFilter;
+typedef struct _CamelMimeFilterClass CamelMimeFilterClass;
+typedef struct _CamelMimeFilterPrivate CamelMimeFilterPrivate;
+
+struct _CamelMimeFilter {
+ GObject parent;
+ CamelMimeFilterPrivate *priv;
+
+ gchar *outreal; /* real malloc'd buffer */
+ gchar *outbuf; /* first 'writable' position allowed (outreal + outpre) */
+ gchar *outptr;
+ gsize outsize;
+ gsize outpre; /* prespace of this buffer */
+
+ gchar *backbuf;
+ gsize backsize;
+ gsize backlen; /* significant data there */
+};
+
+struct _CamelMimeFilterClass {
+ GObjectClass parent_class;
+
+ void (*filter) (CamelMimeFilter *filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace);
+ void (*complete) (CamelMimeFilter *filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace);
+ void (*reset) (CamelMimeFilter *filter);
+};
+
+GType camel_mime_filter_get_type (void);
+CamelMimeFilter *
+ camel_mime_filter_new (void);
+void camel_mime_filter_filter (CamelMimeFilter *filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace);
+void camel_mime_filter_complete (CamelMimeFilter *filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace);
+void camel_mime_filter_reset (CamelMimeFilter *filter);
+
+/* sets/returns number of bytes backed up on the input */
+void camel_mime_filter_backup (CamelMimeFilter *filter,
+ const gchar *data,
+ gsize length);
+
+/* ensure this much size available for filter output */
+void camel_mime_filter_set_size (CamelMimeFilter *filter,
+ gsize size,
+ gint keep);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_FILTER_H */
diff --git a/src/camel/camel-mime-message.c b/src/camel/camel-mime-message.c
new file mode 100644
index 000000000..d41b030b6
--- /dev/null
+++ b/src/camel/camel-mime-message.c
@@ -0,0 +1,1434 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/* camel-mime-message.c : class for a mime_message
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-iconv.h"
+#include "camel-mime-filter-bestenc.h"
+#include "camel-mime-filter-charset.h"
+#include "camel-mime-message.h"
+#include "camel-multipart.h"
+#include "camel-stream-filter.h"
+#include "camel-stream-mem.h"
+#include "camel-stream-null.h"
+#include "camel-string-utils.h"
+#include "camel-url.h"
+
+#ifdef G_OS_WIN32
+#ifdef gmtime_r
+#undef gmtime_r
+#endif
+
+/* The gmtime() in Microsoft's C library is MT-safe */
+#define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
+#endif
+#define d(x)
+
+extern gint camel_verbose_debug;
+
+/* these 2 below should be kept in sync */
+typedef enum {
+ HEADER_UNKNOWN,
+ HEADER_FROM,
+ HEADER_REPLY_TO,
+ HEADER_SUBJECT,
+ HEADER_TO,
+ HEADER_RESENT_TO,
+ HEADER_CC,
+ HEADER_RESENT_CC,
+ HEADER_BCC,
+ HEADER_RESENT_BCC,
+ HEADER_DATE,
+ HEADER_MESSAGE_ID
+} CamelHeaderType;
+
+static const gchar *header_names[] = {
+ /* dont include HEADER_UNKNOWN string */
+ "From", "Reply-To", "Subject", "To", "Resent-To", "Cc", "Resent-Cc",
+ "Bcc", "Resent-Bcc", "Date", "Message-ID", NULL
+};
+
+static const gchar *recipient_names[] = {
+ "To", "Cc", "Bcc", "Resent-To", "Resent-Cc", "Resent-Bcc", NULL
+};
+
+static GHashTable *header_name_table;
+
+G_DEFINE_TYPE (CamelMimeMessage, camel_mime_message, CAMEL_TYPE_MIME_PART)
+
+/* FIXME: check format of fields. */
+static gboolean
+process_header (CamelMedium *medium,
+ const gchar *name,
+ const gchar *value)
+{
+ CamelHeaderType header_type;
+ CamelMimeMessage *message = CAMEL_MIME_MESSAGE (medium);
+ CamelInternetAddress *addr;
+ const gchar *charset;
+ gchar *unfolded;
+
+ header_type = (CamelHeaderType) g_hash_table_lookup (header_name_table, name);
+ switch (header_type) {
+ case HEADER_FROM:
+ addr = camel_internet_address_new ();
+ unfolded = camel_header_unfold (value);
+ if (camel_address_decode ((CamelAddress *) addr, unfolded) <= 0) {
+ g_object_unref (addr);
+ } else {
+ if (message->from)
+ g_object_unref (message->from);
+ message->from = addr;
+ }
+ g_free (unfolded);
+ break;
+ case HEADER_REPLY_TO:
+ addr = camel_internet_address_new ();
+ unfolded = camel_header_unfold (value);
+ if (camel_address_decode ((CamelAddress *) addr, unfolded) <= 0) {
+ g_object_unref (addr);
+ } else {
+ if (message->reply_to)
+ g_object_unref (message->reply_to);
+ message->reply_to = addr;
+ }
+ g_free (unfolded);
+ break;
+ case HEADER_SUBJECT:
+ g_free (message->subject);
+ if (((CamelDataWrapper *) message)->mime_type) {
+ charset = camel_content_type_param (((CamelDataWrapper *) message)->mime_type, "charset");
+ charset = camel_iconv_charset_name (charset);
+ } else
+ charset = NULL;
+
+ unfolded = camel_header_unfold (value);
+ message->subject = g_strstrip (camel_header_decode_string (unfolded, charset));
+ g_free (unfolded);
+ break;
+ case HEADER_TO:
+ case HEADER_CC:
+ case HEADER_BCC:
+ case HEADER_RESENT_TO:
+ case HEADER_RESENT_CC:
+ case HEADER_RESENT_BCC:
+ addr = g_hash_table_lookup (message->recipients, name);
+ if (value) {
+ unfolded = camel_header_unfold (value);
+ camel_address_decode (CAMEL_ADDRESS (addr), unfolded);
+ g_free (unfolded);
+ } else {
+ camel_address_remove (CAMEL_ADDRESS (addr), -1);
+ }
+ return FALSE;
+ case HEADER_DATE:
+ if (value) {
+ message->date = camel_header_decode_date (value, &message->date_offset);
+ } else {
+ message->date = CAMEL_MESSAGE_DATE_CURRENT;
+ message->date_offset = 0;
+ }
+ break;
+ case HEADER_MESSAGE_ID:
+ g_free (message->message_id);
+ if (value)
+ message->message_id = camel_header_msgid_decode (value);
+ else
+ message->message_id = NULL;
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+unref_recipient (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ g_object_unref (value);
+}
+
+static void
+mime_message_ensure_required_headers (CamelMimeMessage *message)
+{
+ CamelMedium *medium = CAMEL_MEDIUM (message);
+
+ if (message->from == NULL) {
+ camel_medium_set_header (medium, "From", "");
+ }
+ if (!camel_medium_get_header (medium, "Date"))
+ camel_mime_message_set_date (
+ message, CAMEL_MESSAGE_DATE_CURRENT, 0);
+
+ if (message->subject == NULL)
+ camel_mime_message_set_subject (message, "No Subject");
+
+ if (message->message_id == NULL)
+ camel_mime_message_set_message_id (message, NULL);
+
+ /* FIXME: "To" header needs to be set explicitly as well ... */
+
+ if (!camel_medium_get_header (medium, "Mime-Version"))
+ camel_medium_set_header (medium, "Mime-Version", "1.0");
+}
+
+static void
+mime_message_dispose (GObject *object)
+{
+ CamelMimeMessage *message = CAMEL_MIME_MESSAGE (object);
+
+ if (message->reply_to != NULL) {
+ g_object_unref (message->reply_to);
+ message->reply_to = NULL;
+ }
+
+ if (message->from != NULL) {
+ g_object_unref (message->from);
+ message->from = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_mime_message_parent_class)->dispose (object);
+}
+
+static void
+mime_message_finalize (GObject *object)
+{
+ CamelMimeMessage *message = CAMEL_MIME_MESSAGE (object);
+
+ g_free (message->subject);
+
+ g_free (message->message_id);
+
+ g_hash_table_foreach (message->recipients, unref_recipient, NULL);
+ g_hash_table_destroy (message->recipients);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_mime_message_parent_class)->finalize (object);
+}
+
+static gssize
+mime_message_write_to_stream_sync (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimeMessage *message;
+
+ message = CAMEL_MIME_MESSAGE (data_wrapper);
+ mime_message_ensure_required_headers (message);
+
+ /* Chain up to parent's write_to_stream_sync() method. */
+ return CAMEL_DATA_WRAPPER_CLASS (camel_mime_message_parent_class)->
+ write_to_stream_sync (
+ data_wrapper, stream, cancellable, error);
+}
+
+static gssize
+mime_message_write_to_output_stream_sync (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimeMessage *message;
+
+ message = CAMEL_MIME_MESSAGE (data_wrapper);
+ mime_message_ensure_required_headers (message);
+
+ /* Chain up to parent's write_to_output_stream_sync() method. */
+ return CAMEL_DATA_WRAPPER_CLASS (camel_mime_message_parent_class)->
+ write_to_output_stream_sync (
+ data_wrapper, output_stream, cancellable, error);
+}
+
+static void
+mime_message_add_header (CamelMedium *medium,
+ const gchar *name,
+ gconstpointer value)
+{
+ CamelMediumClass *medium_class;
+
+ medium_class = CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class);
+
+ /* if we process it, then it must be forced unique as well ... */
+ if (process_header (medium, name, value))
+ medium_class->set_header (medium, name, value);
+ else
+ medium_class->add_header (medium, name, value);
+}
+
+static void
+mime_message_set_header (CamelMedium *medium,
+ const gchar *name,
+ gconstpointer value)
+{
+ process_header (medium, name, value);
+
+ /* Chain up to parent's set_header() method. */
+ CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (medium, name, value);
+}
+
+static void
+mime_message_remove_header (CamelMedium *medium,
+ const gchar *name)
+{
+ process_header (medium, name, NULL);
+
+ /* Chain up to parent's remove_header() method. */
+ CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->remove_header (medium, name);
+}
+
+static gboolean
+mime_message_construct_from_parser_sync (CamelMimePart *dw,
+ CamelMimeParser *mp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimePartClass *mime_part_class;
+ gchar *buf;
+ gsize len;
+ gint state;
+ gint err;
+ gboolean success;
+
+ /* let the mime-part construct the guts ... */
+ mime_part_class = CAMEL_MIME_PART_CLASS (camel_mime_message_parent_class);
+ success = mime_part_class->construct_from_parser_sync (
+ dw, mp, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ /* ... then clean up the follow-on state */
+ state = camel_mime_parser_step (mp, &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 (mp);
+ case CAMEL_MIME_PARSER_STATE_MESSAGE_END:
+ break;
+ default:
+ g_error ("Bad parser state: Expecing MESSAGE_END or EOF or EOM, got: %u", camel_mime_parser_state (mp));
+ camel_mime_parser_unstep (mp);
+ return FALSE;
+ }
+
+ err = camel_mime_parser_errno (mp);
+ if (err != 0) {
+ errno = err;
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ success = FALSE;
+ }
+
+ return success;
+}
+
+static void
+camel_mime_message_class_init (CamelMimeMessageClass *class)
+{
+ GObjectClass *object_class;
+ CamelDataWrapperClass *data_wrapper_class;
+ CamelMimePartClass *mime_part_class;
+ CamelMediumClass *medium_class;
+ gint ii;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = mime_message_dispose;
+ object_class->finalize = mime_message_finalize;
+
+ data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (class);
+ data_wrapper_class->write_to_stream_sync = mime_message_write_to_stream_sync;
+ data_wrapper_class->decode_to_stream_sync = mime_message_write_to_stream_sync;
+ data_wrapper_class->write_to_output_stream_sync = mime_message_write_to_output_stream_sync;
+ data_wrapper_class->decode_to_output_stream_sync = mime_message_write_to_output_stream_sync;
+
+ medium_class = CAMEL_MEDIUM_CLASS (class);
+ medium_class->add_header = mime_message_add_header;
+ medium_class->set_header = mime_message_set_header;
+ medium_class->remove_header = mime_message_remove_header;
+
+ mime_part_class = CAMEL_MIME_PART_CLASS (class);
+ mime_part_class->construct_from_parser_sync = mime_message_construct_from_parser_sync;
+
+ header_name_table = g_hash_table_new (
+ camel_strcase_hash, camel_strcase_equal);
+ for (ii = 0; header_names[ii] != NULL; ii++)
+ g_hash_table_insert (
+ header_name_table,
+ (gpointer) header_names[ii],
+ GINT_TO_POINTER (ii + 1));
+}
+
+static void
+camel_mime_message_init (CamelMimeMessage *mime_message)
+{
+ gint ii;
+
+ mime_message->recipients = g_hash_table_new (
+ camel_strcase_hash, camel_strcase_equal);
+ for (ii = 0; recipient_names[ii] != NULL; ii++) {
+ g_hash_table_insert (
+ mime_message->recipients,
+ (gpointer) recipient_names[ii],
+ camel_internet_address_new ());
+ }
+
+ mime_message->subject = NULL;
+ mime_message->reply_to = NULL;
+ mime_message->from = NULL;
+ mime_message->date = CAMEL_MESSAGE_DATE_CURRENT;
+ mime_message->date_offset = 0;
+ mime_message->date_received = CAMEL_MESSAGE_DATE_CURRENT;
+ mime_message->date_received_offset = 0;
+ mime_message->message_id = NULL;
+}
+
+/**
+ * camel_mime_message_new:
+ *
+ * Create a new #CamelMimeMessage object.
+ *
+ * Returns: a new #CamelMimeMessage object
+ **/
+CamelMimeMessage *
+camel_mime_message_new (void)
+{
+ return g_object_new (CAMEL_TYPE_MIME_MESSAGE, NULL);
+}
+
+/* **** Date: */
+
+/**
+ * camel_mime_message_set_date:
+ * @message: a #CamelMimeMessage object
+ * @date: a time_t date
+ * @offset: an offset from GMT
+ *
+ * Set the date on a message.
+ **/
+void
+camel_mime_message_set_date (CamelMimeMessage *message,
+ time_t date,
+ gint offset)
+{
+ gchar *datestr;
+
+ g_return_if_fail (message);
+
+ if (date == CAMEL_MESSAGE_DATE_CURRENT) {
+ struct tm local;
+ gint tz;
+
+ date = time (NULL);
+ camel_localtime_with_offset (date, &local, &tz);
+ offset = (((tz / 60 / 60) * 100) + (tz / 60 % 60));
+ }
+ message->date = date;
+ message->date_offset = offset;
+
+ datestr = camel_header_format_date (date, offset);
+ CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header ((CamelMedium *) message, "Date", datestr);
+ g_free (datestr);
+}
+
+/**
+ * camel_mime_message_get_date:
+ * @message: a #CamelMimeMessage object
+ * @offset: output for the GMT offset
+ *
+ * Get the date and GMT offset of a message.
+ *
+ * Returns: the date of the message
+ **/
+time_t
+camel_mime_message_get_date (CamelMimeMessage *msg,
+ gint *offset)
+{
+ if (offset)
+ *offset = msg->date_offset;
+
+ return msg->date;
+}
+
+/**
+ * camel_mime_message_get_date_received:
+ * @message: a #CamelMimeMessage object
+ * @offset: output for the GMT offset
+ *
+ * Get the received date and GMT offset of a message.
+ *
+ * Returns: the received date of the message
+ **/
+time_t
+camel_mime_message_get_date_received (CamelMimeMessage *msg,
+ gint *offset)
+{
+ if (msg->date_received == CAMEL_MESSAGE_DATE_CURRENT) {
+ const gchar *received;
+
+ received = camel_medium_get_header ((CamelMedium *) msg, "received");
+ if (received)
+ received = strrchr (received, ';');
+ if (received)
+ msg->date_received = camel_header_decode_date (received + 1, &msg->date_received_offset);
+ }
+
+ if (offset)
+ *offset = msg->date_received_offset;
+
+ return msg->date_received;
+}
+
+/* **** Message-ID: */
+
+/**
+ * camel_mime_message_set_message_id:
+ * @message: a #CamelMimeMessage object
+ * @message_id: id of the message
+ *
+ * Set the message-id on a message.
+ **/
+void
+camel_mime_message_set_message_id (CamelMimeMessage *mime_message,
+ const gchar *message_id)
+{
+ gchar *id;
+
+ g_return_if_fail (mime_message);
+
+ g_free (mime_message->message_id);
+
+ if (message_id) {
+ id = g_strstrip (g_strdup (message_id));
+ } else {
+ CamelInternetAddress *from;
+ const gchar *domain = NULL;
+
+ from = camel_mime_message_get_from (mime_message);
+ if (from && camel_internet_address_get (from, 0, NULL, &domain) && domain) {
+ const gchar *at = strchr (domain, '@');
+ if (at)
+ domain = at + 1;
+ else
+ domain = NULL;
+ }
+
+ id = camel_header_msgid_generate (domain);
+ }
+
+ mime_message->message_id = id;
+ id = g_strdup_printf ("<%s>", mime_message->message_id);
+ CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (mime_message), "Message-ID", id);
+ g_free (id);
+}
+
+/**
+ * camel_mime_message_get_message_id:
+ * @message: a #CamelMimeMessage object
+ *
+ * Get the message-id of a message.
+ *
+ * Returns: the message-id of a message
+ **/
+const gchar *
+camel_mime_message_get_message_id (CamelMimeMessage *mime_message)
+{
+ g_return_val_if_fail (mime_message, NULL);
+
+ return mime_message->message_id;
+}
+
+/* **** Reply-To: */
+
+/**
+ * camel_mime_message_set_reply_to:
+ * @message: a #CamelMimeMessage object
+ * @reply_to: a #CamelInternetAddress object
+ *
+ * Set the Reply-To of a message.
+ **/
+void
+camel_mime_message_set_reply_to (CamelMimeMessage *msg,
+ CamelInternetAddress *reply_to)
+{
+ gchar *addr;
+
+ g_return_if_fail (msg);
+
+ if (msg->reply_to) {
+ g_object_unref (msg->reply_to);
+ msg->reply_to = NULL;
+ }
+
+ if (reply_to == NULL) {
+ CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->remove_header (CAMEL_MEDIUM (msg), "Reply-To");
+ return;
+ }
+
+ msg->reply_to = (CamelInternetAddress *) camel_address_new_clone ((CamelAddress *) reply_to);
+ addr = camel_address_encode ((CamelAddress *) msg->reply_to);
+ CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (msg), "Reply-To", addr);
+ g_free (addr);
+}
+
+/**
+ * camel_mime_message_get_reply_to:
+ * @message: a #CamelMimeMessage object
+ *
+ * Get the Reply-To of a message.
+ *
+ * Returns: (transfer none): the Reply-To address of the message
+ **/
+CamelInternetAddress *
+camel_mime_message_get_reply_to (CamelMimeMessage *mime_message)
+{
+ g_return_val_if_fail (mime_message, NULL);
+
+ /* TODO: ref for threading? */
+
+ return mime_message->reply_to;
+}
+
+/* **** Subject: */
+
+/**
+ * camel_mime_message_set_subject:
+ * @message: a #CamelMimeMessage object
+ * @subject: UTF-8 message subject
+ *
+ * Set the subject text of a message.
+ **/
+void
+camel_mime_message_set_subject (CamelMimeMessage *message,
+ const gchar *subject)
+{
+ gchar *text;
+
+ g_return_if_fail (message);
+
+ g_free (message->subject);
+
+ if (subject) {
+ message->subject = g_strstrip (g_strdup (subject));
+ text = camel_header_encode_string ((guchar *) message->subject);
+ } else {
+ message->subject = NULL;
+ text = NULL;
+ }
+
+ CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (message), "Subject", text);
+ g_free (text);
+}
+
+/**
+ * camel_mime_message_get_subject:
+ * @message: a #CamelMimeMessage object
+ *
+ * Get the UTF-8 subject text of a message.
+ *
+ * Returns: the message subject
+ **/
+const gchar *
+camel_mime_message_get_subject (CamelMimeMessage *mime_message)
+{
+ g_return_val_if_fail (mime_message, NULL);
+
+ return mime_message->subject;
+}
+
+/* *** From: */
+
+/* Thought: Since get_from/set_from are so rarely called, it is probably not useful
+ * to cache the from (and reply_to) addresses as InternetAddresses internally, we
+ * could just get it from the headers and reprocess every time. */
+
+/**
+ * camel_mime_message_set_from:
+ * @message: a #CamelMimeMessage object
+ * @from: a #CamelInternetAddress object
+ *
+ * Set the from address of a message.
+ **/
+void
+camel_mime_message_set_from (CamelMimeMessage *msg,
+ CamelInternetAddress *from)
+{
+ gchar *addr;
+
+ g_return_if_fail (msg);
+
+ if (msg->from) {
+ g_object_unref (msg->from);
+ msg->from = NULL;
+ }
+
+ if (from == NULL || camel_address_length ((CamelAddress *) from) == 0) {
+ CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->remove_header (CAMEL_MEDIUM (msg), "From");
+ return;
+ }
+
+ msg->from = (CamelInternetAddress *) camel_address_new_clone ((CamelAddress *) from);
+ addr = camel_address_encode ((CamelAddress *) msg->from);
+ CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (msg), "From", addr);
+ g_free (addr);
+}
+
+/**
+ * camel_mime_message_get_from:
+ * @message: a #CamelMimeMessage object
+ *
+ * Get the from address of a message.
+ *
+ * Returns: (transfer none): the from address of the message
+ **/
+CamelInternetAddress *
+camel_mime_message_get_from (CamelMimeMessage *mime_message)
+{
+ g_return_val_if_fail (mime_message, NULL);
+
+ /* TODO: we should really ref this for multi-threading to work */
+
+ return mime_message->from;
+}
+
+/* **** To: Cc: Bcc: */
+
+/**
+ * camel_mime_message_set_recipients:
+ * @message: a #CamelMimeMessage object
+ * @type: recipient type (one of #CAMEL_RECIPIENT_TYPE_TO, #CAMEL_RECIPIENT_TYPE_CC, or #CAMEL_RECIPIENT_TYPE_BCC)
+ * @recipients: a #CamelInternetAddress with the recipient addresses set
+ *
+ * Set the recipients of a message.
+ **/
+void
+camel_mime_message_set_recipients (CamelMimeMessage *mime_message,
+ const gchar *type,
+ CamelInternetAddress *r)
+{
+ gchar *text;
+ CamelInternetAddress *addr;
+
+ g_return_if_fail (mime_message);
+
+ addr = g_hash_table_lookup (mime_message->recipients, type);
+ if (addr == NULL) {
+ g_warning ("trying to set a non-valid receipient type: %s", type);
+ return;
+ }
+
+ if (r == NULL || camel_address_length ((CamelAddress *) r) == 0) {
+ camel_address_remove ((CamelAddress *) addr, -1);
+ CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->remove_header (CAMEL_MEDIUM (mime_message), type);
+ return;
+ }
+
+ /* note this does copy, and not append (cat) */
+ camel_address_copy ((CamelAddress *) addr, (CamelAddress *) r);
+
+ /* and sync our headers */
+ text = camel_address_encode (CAMEL_ADDRESS (addr));
+ CAMEL_MEDIUM_CLASS (camel_mime_message_parent_class)->set_header (CAMEL_MEDIUM (mime_message), type, text);
+ g_free (text);
+}
+
+/**
+ * camel_mime_message_get_recipients:
+ * @message: a #CamelMimeMessage object
+ * @type: recipient type
+ *
+ * Get the message recipients of a specified type.
+ *
+ * Returns: (transfer none): the requested recipients
+ **/
+CamelInternetAddress *
+camel_mime_message_get_recipients (CamelMimeMessage *mime_message,
+ const gchar *type)
+{
+ g_return_val_if_fail (mime_message, NULL);
+
+ return g_hash_table_lookup (mime_message->recipients, type);
+}
+
+void
+camel_mime_message_set_source (CamelMimeMessage *mime_message,
+ const gchar *source_uid)
+{
+ CamelMedium *medium;
+ const gchar *name;
+
+ g_return_if_fail (CAMEL_IS_MIME_MESSAGE (mime_message));
+
+ /* FIXME The header name is Evolution-specific.
+ * "X" header prefix should be configurable
+ * somehow, perhaps through CamelSession. */
+
+ name = "X-Evolution-Source";
+ medium = CAMEL_MEDIUM (mime_message);
+
+ camel_medium_remove_header (medium, name);
+ if (source_uid != NULL)
+ camel_medium_add_header (medium, name, source_uid);
+}
+
+const gchar *
+camel_mime_message_get_source (CamelMimeMessage *mime_message)
+{
+ CamelMedium *medium;
+ const gchar *name;
+ const gchar *src;
+
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (mime_message), NULL);
+
+ /* FIXME The header name is Evolution-specific.
+ * "X" header prefix should be configurable
+ * somehow, perhaps through CamelSession. */
+
+ name = "X-Evolution-Source";
+ medium = CAMEL_MEDIUM (mime_message);
+
+ src = camel_medium_get_header (medium, name);
+ if (src != NULL) {
+ while (*src && isspace ((unsigned) *src))
+ ++src;
+ }
+
+ return src;
+}
+
+typedef gboolean (*CamelPartFunc)(CamelMimeMessage *, CamelMimePart *, gpointer data);
+
+static gboolean
+message_foreach_part_rec (CamelMimeMessage *msg,
+ CamelMimePart *part,
+ CamelPartFunc callback,
+ gpointer data)
+{
+ CamelDataWrapper *containee;
+ gint parts, i;
+ gint go = TRUE;
+
+ if (callback (msg, part, data) == FALSE)
+ return FALSE;
+
+ containee = camel_medium_get_content (CAMEL_MEDIUM (part));
+
+ if (containee == NULL)
+ return go;
+
+ /* 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; go && i < parts; i++) {
+ CamelMimePart *mpart = camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
+
+ go = message_foreach_part_rec (msg, mpart, callback, data);
+ }
+ } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
+ go = message_foreach_part_rec (msg, (CamelMimePart *) containee, callback, data);
+ }
+
+ return go;
+}
+
+/* dont make this public yet, it might need some more thinking ... */
+/* MPZ */
+static void
+camel_mime_message_foreach_part (CamelMimeMessage *msg,
+ CamelPartFunc callback,
+ gpointer data)
+{
+ message_foreach_part_rec (msg, (CamelMimePart *) msg, callback, data);
+}
+
+static gboolean
+check_8bit (CamelMimeMessage *msg,
+ CamelMimePart *part,
+ gpointer data)
+{
+ CamelTransferEncoding encoding;
+ gint *has8bit = data;
+
+ /* check this part, and stop as soon as we are done */
+ encoding = camel_mime_part_get_encoding (part);
+
+ *has8bit = encoding == CAMEL_TRANSFER_ENCODING_8BIT || encoding == CAMEL_TRANSFER_ENCODING_BINARY;
+
+ return !(*has8bit);
+}
+
+/**
+ * camel_mime_message_has_8bit_parts:
+ * @message: a #CamelMimeMessage object
+ *
+ * Find out if a message contains 8bit or binary encoded parts.
+ *
+ * Returns: %TRUE if the message contains 8bit parts or %FALSE otherwise
+ **/
+gboolean
+camel_mime_message_has_8bit_parts (CamelMimeMessage *msg)
+{
+ gint has8bit = FALSE;
+
+ camel_mime_message_foreach_part (msg, check_8bit, &has8bit);
+
+ return has8bit;
+}
+
+static gboolean
+mime_part_is_attachment (CamelMimePart *mp)
+{
+ const CamelContentDisposition *content_disposition;
+
+ content_disposition = camel_mime_part_get_content_disposition (mp);
+
+ return content_disposition &&
+ content_disposition->disposition &&
+ g_ascii_strcasecmp (content_disposition->disposition, "attachment") == 0;
+}
+
+/* finds the best charset and transfer encoding for a given part */
+static CamelTransferEncoding
+find_best_encoding (CamelMimePart *part,
+ CamelBestencRequired required,
+ CamelBestencEncoding enctype,
+ gchar **charsetp)
+{
+ CamelMimeFilter *charenc = NULL;
+ CamelTransferEncoding encoding;
+ CamelMimeFilter *bestenc;
+ guint flags, callerflags;
+ CamelDataWrapper *content;
+ CamelStream *filter;
+ const gchar *charsetin = NULL;
+ gchar *charset = NULL;
+ CamelStream *null;
+ gint idb, idc = -1;
+ gboolean istext;
+
+ /* we use all these weird stream things so we can do it with streams, and
+ * not have to read the whole lot into memory - although i have a feeling
+ * it would make things a fair bit simpler to do so ... */
+
+ d (printf ("starting to check part\n"));
+
+ content = camel_medium_get_content ((CamelMedium *) part);
+ if (content == NULL) {
+ /* charset might not be right here, but it'll get the right stuff
+ * if it is ever set */
+ *charsetp = NULL;
+ return CAMEL_TRANSFER_ENCODING_DEFAULT;
+ }
+
+ istext = camel_content_type_is (((CamelDataWrapper *) part)->mime_type, "text", "*");
+ if (istext) {
+ flags = CAMEL_BESTENC_GET_CHARSET | CAMEL_BESTENC_GET_ENCODING;
+ enctype |= CAMEL_BESTENC_TEXT;
+ } else {
+ flags = CAMEL_BESTENC_GET_ENCODING;
+ }
+
+ /* when building the message, any encoded parts are translated already */
+ flags |= CAMEL_BESTENC_LF_IS_CRLF;
+ /* and get any flags the caller passed in */
+ callerflags = (required & CAMEL_BESTENC_NO_FROM);
+ flags |= callerflags;
+
+ /* first a null stream, so any filtering is thrown away; we only want the sideeffects */
+ null = (CamelStream *) camel_stream_null_new ();
+ filter = camel_stream_filter_new (null);
+
+ /* if we're looking for the best charset, then we need to convert to UTF-8 */
+ if (istext && (required & CAMEL_BESTENC_GET_CHARSET) != 0
+ && (charsetin = camel_content_type_param (content->mime_type, "charset"))) {
+ charenc = camel_mime_filter_charset_new (charsetin, "UTF-8");
+ if (charenc != NULL)
+ idc = camel_stream_filter_add (
+ CAMEL_STREAM_FILTER (filter), charenc);
+ charsetin = NULL;
+ }
+
+ bestenc = camel_mime_filter_bestenc_new (flags);
+ idb = camel_stream_filter_add (
+ CAMEL_STREAM_FILTER (filter), bestenc);
+ d (printf ("writing to checking stream\n"));
+ camel_data_wrapper_decode_to_stream_sync (content, filter, NULL, NULL);
+ camel_stream_filter_remove (CAMEL_STREAM_FILTER (filter), idb);
+ if (idc != -1) {
+ camel_stream_filter_remove (CAMEL_STREAM_FILTER (filter), idc);
+ g_object_unref (charenc);
+ charenc = NULL;
+ }
+
+ if (istext && (required & CAMEL_BESTENC_GET_CHARSET) != 0) {
+ charsetin = camel_mime_filter_bestenc_get_best_charset (
+ CAMEL_MIME_FILTER_BESTENC (bestenc));
+ d (printf ("best charset = %s\n", charsetin ? charsetin : "(null)"));
+ charset = g_strdup (charsetin);
+
+ charsetin = camel_content_type_param (content->mime_type, "charset");
+ } else {
+ charset = NULL;
+ }
+
+ /* if we have US-ASCII, or we're not doing text, we dont need to bother with the rest */
+ if (istext && charsetin && charset && (required & CAMEL_BESTENC_GET_CHARSET) != 0) {
+ d (printf ("have charset, trying conversion/etc\n"));
+
+ /* now that 'bestenc' has told us what the best encoding is, we can use that to create
+ * a charset conversion filter as well, and then re-add the bestenc to filter the
+ * result to find the best encoding to use as well */
+
+ charenc = camel_mime_filter_charset_new (charsetin, charset);
+ if (charenc != NULL) {
+ /* otherwise, try another pass, converting to the real charset */
+
+ camel_mime_filter_reset (bestenc);
+ camel_mime_filter_bestenc_set_flags (
+ CAMEL_MIME_FILTER_BESTENC (bestenc),
+ CAMEL_BESTENC_GET_ENCODING |
+ CAMEL_BESTENC_LF_IS_CRLF | callerflags);
+
+ camel_stream_filter_add (
+ CAMEL_STREAM_FILTER (filter), charenc);
+ camel_stream_filter_add (
+ CAMEL_STREAM_FILTER (filter), bestenc);
+
+ /* and write it to the new stream */
+ camel_data_wrapper_write_to_stream_sync (
+ content, filter, NULL, NULL);
+
+ g_object_unref (charenc);
+ }
+ }
+
+ encoding = camel_mime_filter_bestenc_get_best_encoding (
+ CAMEL_MIME_FILTER_BESTENC (bestenc), enctype);
+
+ g_object_unref (filter);
+ g_object_unref (bestenc);
+ g_object_unref (null);
+
+ d (printf ("done, best encoding = %d\n", encoding));
+
+ if (charsetp)
+ *charsetp = charset;
+ else
+ g_free (charset);
+
+ return encoding;
+}
+
+struct _enc_data {
+ CamelBestencRequired required;
+ CamelBestencEncoding enctype;
+};
+
+static gboolean
+best_encoding (CamelMimeMessage *msg,
+ CamelMimePart *part,
+ gpointer datap)
+{
+ struct _enc_data *data = datap;
+ CamelTransferEncoding encoding;
+ CamelDataWrapper *wrapper;
+ gchar *charset;
+
+ /* Keep attachments untouched. */
+ if (mime_part_is_attachment (part))
+ return TRUE;
+
+ wrapper = camel_medium_get_content (CAMEL_MEDIUM (part));
+ if (!wrapper)
+ return FALSE;
+
+ /* we only care about actual content objects */
+ if (!CAMEL_IS_MULTIPART (wrapper) && !CAMEL_IS_MIME_MESSAGE (wrapper)) {
+ encoding = find_best_encoding (part, data->required, data->enctype, &charset);
+ /* we always set the encoding, if we got this far. GET_CHARSET implies
+ * also GET_ENCODING */
+ camel_mime_part_set_encoding (part, encoding);
+
+ if ((data->required & CAMEL_BESTENC_GET_CHARSET) != 0) {
+ if (camel_content_type_is (((CamelDataWrapper *) part)->mime_type, "text", "*")) {
+ gchar *newct;
+
+ /* FIXME: ick, the part content_type interface needs fixing bigtime */
+ camel_content_type_set_param (
+ ((CamelDataWrapper *) part)->mime_type, "charset",
+ charset ? charset : "us-ascii");
+ newct = camel_content_type_format (((CamelDataWrapper *) part)->mime_type);
+ if (newct) {
+ d (printf ("Setting content-type to %s\n", newct));
+
+ camel_mime_part_set_content_type (part, newct);
+ g_free (newct);
+ }
+ }
+ }
+
+ g_free (charset);
+ }
+
+ return TRUE;
+}
+
+/**
+ * camel_mime_message_set_best_encoding:
+ * @message: a #CamelMimeMessage object
+ * @required: a bitwise ORing of #CAMEL_BESTENC_GET_ENCODING and #CAMEL_BESTENC_GET_CHARSET
+ * @enctype: an encoding to enforce
+ *
+ * Re-encode all message parts to conform with the required encoding rules.
+ *
+ * If @enctype is #CAMEL_BESTENC_7BIT, then all parts will be re-encoded into
+ * one of the 7bit transfer encodings. If @enctype is #CAMEL_BESTENC_8bit, all
+ * parts will be re-encoded to either a 7bit encoding or, if the part is 8bit
+ * text, allowed to stay 8bit. If @enctype is #CAMEL_BESTENC_BINARY, then binary
+ * parts will be encoded as binary and 8bit textual parts will be encoded as 8bit.
+ **/
+void
+camel_mime_message_set_best_encoding (CamelMimeMessage *msg,
+ CamelBestencRequired required,
+ CamelBestencEncoding enctype)
+{
+ struct _enc_data data;
+
+ if ((required & (CAMEL_BESTENC_GET_ENCODING | CAMEL_BESTENC_GET_CHARSET)) == 0)
+ return;
+
+ data.required = required;
+ data.enctype = enctype;
+
+ camel_mime_message_foreach_part (msg, best_encoding, &data);
+}
+
+/**
+ * camel_mime_message_encode_8bit_parts:
+ * @message: a #CamelMimeMessage object
+ *
+ * Encode all message parts to a suitable transfer encoding for transport (7bit clean).
+ **/
+void
+camel_mime_message_encode_8bit_parts (CamelMimeMessage *mime_message)
+{
+ camel_mime_message_set_best_encoding (mime_message, CAMEL_BESTENC_GET_ENCODING, CAMEL_BESTENC_7BIT);
+}
+
+struct _check_content_id {
+ CamelMimePart *part;
+ const gchar *content_id;
+};
+
+static gboolean
+check_content_id (CamelMimeMessage *message,
+ CamelMimePart *part,
+ gpointer data)
+{
+ struct _check_content_id *check = (struct _check_content_id *) data;
+ const gchar *content_id;
+ gboolean found;
+
+ content_id = camel_mime_part_get_content_id (part);
+
+ found = content_id && !strcmp (content_id, check->content_id) ? TRUE : FALSE;
+ if (found)
+ check->part = g_object_ref (part);
+
+ return !found;
+}
+
+/**
+ * camel_mime_message_get_part_by_content_id:
+ * @message: a #CamelMimeMessage object
+ * @content_id: content-id to search for
+ *
+ * Get a MIME part by id from a message.
+ *
+ * Returns: (transfer none): the MIME part with the requested id or %NULL if not found
+ **/
+CamelMimePart *
+camel_mime_message_get_part_by_content_id (CamelMimeMessage *message,
+ const gchar *id)
+{
+ struct _check_content_id check;
+
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
+
+ if (id == NULL)
+ return NULL;
+
+ check.content_id = id;
+ check.part = NULL;
+
+ camel_mime_message_foreach_part (message, check_content_id, &check);
+
+ return check.part;
+}
+
+static const gchar tz_months[][4] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static const gchar tz_days[][4] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+/**
+ * camel_mime_message_build_mbox_from:
+ * @message: a #CamelMimeMessage object
+ *
+ * Build an MBox from-line from @message.
+ *
+ * Returns: an MBox from-line suitable for use in an mbox file
+ **/
+gchar *
+camel_mime_message_build_mbox_from (CamelMimeMessage *message)
+{
+ struct _camel_header_raw *header = ((CamelMimePart *) message)->headers;
+ GString *out = g_string_new ("From ");
+ gchar *ret;
+ const gchar *tmp;
+ time_t thetime;
+ gint offset;
+ struct tm tm;
+
+ tmp = camel_header_raw_find (&header, "Sender", NULL);
+ if (tmp == NULL)
+ tmp = camel_header_raw_find (&header, "From", NULL);
+ if (tmp != NULL) {
+ CamelHeaderAddress *addr = camel_header_address_decode (tmp, NULL);
+
+ tmp = NULL;
+ if (addr) {
+ if (addr->type == CAMEL_HEADER_ADDRESS_NAME) {
+ g_string_append (out, addr->v.addr);
+ tmp = "";
+ }
+ camel_header_address_unref (addr);
+ }
+ }
+
+ if (tmp == NULL)
+ g_string_append (out, "unknown@nodomain.now.au");
+
+ /* try use the received header to get the date */
+ tmp = camel_header_raw_find (&header, "Received", NULL);
+ if (tmp) {
+ tmp = strrchr (tmp, ';');
+ if (tmp)
+ tmp++;
+ }
+
+ /* if there isn't one, try the Date field */
+ if (tmp == NULL)
+ tmp = camel_header_raw_find (&header, "Date", NULL);
+
+ thetime = camel_header_decode_date (tmp, &offset);
+ thetime += ((offset / 100) * (60 * 60)) + (offset % 100) * 60;
+ gmtime_r (&thetime, &tm);
+ g_string_append_printf (
+ out, " %s %s %2d %02d:%02d:%02d %4d\n",
+ tz_days[tm.tm_wday],
+ tz_months[tm.tm_mon],
+ tm.tm_mday,
+ tm.tm_hour,
+ tm.tm_min,
+ tm.tm_sec,
+ tm.tm_year + 1900);
+
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ return ret;
+}
+
+static gboolean
+find_attachment (CamelMimeMessage *msg,
+ CamelMimePart *part,
+ gpointer data)
+{
+ const CamelContentDisposition *cd;
+ CamelContentType *ct;
+ gboolean *found = (gboolean *) data;
+
+ g_return_val_if_fail (part != NULL, FALSE);
+
+ ct = camel_mime_part_get_content_type (part);
+ if (ct && (
+ camel_content_type_is (ct, "application", "xpkcs7mime") ||
+ camel_content_type_is (ct, "application", "x-pkcs7-mime") ||
+ camel_content_type_is (ct, "application", "pkcs7-mime") ||
+ camel_content_type_is (ct, "application", "pkcs7-signature") ||
+ camel_content_type_is (ct, "application", "xpkcs7-signature") ||
+ camel_content_type_is (ct, "application", "x-pkcs7-signature") ||
+ camel_content_type_is (ct, "application", "pkcs7-signature") ||
+ camel_content_type_is (ct, "application", "pgp-signature") ||
+ camel_content_type_is (ct, "application", "pgp-encrypted")))
+ return !(*found);
+
+ cd = camel_mime_part_get_content_disposition (part);
+
+ if (cd) {
+ const struct _camel_header_param *param;
+
+ *found = (cd->disposition && g_ascii_strcasecmp (cd->disposition, "attachment") == 0);
+
+ if (!*found && (!cd->disposition || g_ascii_strcasecmp (cd->disposition, "inline") != 0)) {
+ for (param = cd->params; param && !(*found); param = param->next) {
+ if (param->name && param->value && *param->value && g_ascii_strcasecmp (param->name, "filename") == 0)
+ *found = TRUE;
+ }
+ }
+ }
+
+ return !(*found);
+}
+
+/**
+ * camel_mime_message_has_attachment:
+ * @message: a #CamelMimeMessage object
+ *
+ * Returns whether message contains at least one attachment part.
+ *
+ * Since: 2.28
+ **/
+gboolean
+camel_mime_message_has_attachment (CamelMimeMessage *message)
+{
+ gboolean found = FALSE;
+
+ g_return_val_if_fail (message != NULL, FALSE);
+
+ camel_mime_message_foreach_part (message, find_attachment, &found);
+
+ return found;
+}
+
+static void
+dumpline (const gchar *indent,
+ guint8 *data,
+ gsize data_len)
+{
+ gint j;
+ gchar *gutter;
+ guint gutter_size;
+
+ g_return_if_fail (data_len <= 16);
+
+ gutter_size = ((16 - data_len) * 3) + 4;
+ gutter = alloca (gutter_size + 1);
+ memset (gutter, ' ', gutter_size);
+ gutter[gutter_size] = 0;
+
+ printf ("%s ", indent);
+ /* Hex dump */
+ for (j = 0; j < data_len; j++)
+ printf ("%s%02x", j > 0 ? " " : "", data[j]);
+
+ /* ASCII dump */
+ printf ("%s", gutter);
+ for (j = 0; j < data_len; j++) {
+ printf ("%c", isprint (data[j]) ? data[j] : '.');
+ }
+ printf ("\n");
+}
+
+static void
+cmm_dump_rec (CamelMimeMessage *msg,
+ CamelMimePart *part,
+ gint body,
+ gint depth)
+{
+ CamelDataWrapper *containee;
+ gint parts, i;
+ gint go = TRUE;
+ gchar *s;
+ const GByteArray *data;
+
+ s = alloca (depth + 1);
+ memset (s, ' ', depth);
+ s[depth] = 0;
+ /* yes this leaks, so what its only debug stuff */
+ printf ("%sclass: %s\n", s, G_OBJECT_TYPE_NAME (part));
+ printf ("%smime-type: %s\n", s, camel_content_type_format (((CamelDataWrapper *) part)->mime_type));
+
+ containee = camel_medium_get_content ((CamelMedium *) part);
+
+ if (containee == NULL)
+ return;
+
+ printf ("%scontent class: %s\n", s, G_OBJECT_TYPE_NAME (containee));
+ printf ("%scontent mime-type: %s\n", s, camel_content_type_format (((CamelDataWrapper *) containee)->mime_type));
+
+ data = camel_data_wrapper_get_byte_array (containee);
+ if (body && data) {
+ guint t = 0;
+
+ printf ("%scontent len %d\n", s, data->len);
+ for (t = 0; t < data->len / 16; t++)
+ dumpline (s, &data->data[t * 16], 16);
+ if (data->len % 16)
+ dumpline (s, &data->data[t * 16], data->len % 16);
+ }
+
+ /* using the object types is more accurate than using the mime/types */
+ if (CAMEL_IS_MULTIPART (containee)) {
+ parts = camel_multipart_get_number ((CamelMultipart *) containee);
+ for (i = 0; go && i < parts; i++) {
+ CamelMimePart *mpart = camel_multipart_get_part ((CamelMultipart *) containee, i);
+
+ cmm_dump_rec (msg, mpart, body, depth + 2);
+ }
+ } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
+ cmm_dump_rec (msg, (CamelMimePart *) containee, body, depth + 2);
+ }
+}
+
+/**
+ * camel_mime_message_dump:
+ * @message:
+ * @body:
+ *
+ * Dump information about the mime message to stdout.
+ *
+ * If body is TRUE, then dump body content of the message as well.
+ **/
+void
+camel_mime_message_dump (CamelMimeMessage *message,
+ gint body)
+{
+ cmm_dump_rec (message, (CamelMimePart *) message, body, 0);
+}
diff --git a/src/camel/camel-mime-message.h b/src/camel/camel-mime-message.h
new file mode 100644
index 000000000..026de17bd
--- /dev/null
+++ b/src/camel/camel-mime-message.h
@@ -0,0 +1,154 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/* camelMimeMessage.h : class for a mime message
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_MESSAGE_H
+#define CAMEL_MIME_MESSAGE_H
+
+#include <camel/camel-mime-part.h>
+#include <camel/camel-mime-utils.h>
+#include <camel/camel-internet-address.h>
+#include <camel/camel-mime-filter-bestenc.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_MESSAGE \
+ (camel_mime_message_get_type ())
+#define CAMEL_MIME_MESSAGE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_MESSAGE, CamelMimeMessage))
+#define CAMEL_MIME_MESSAGE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_MESSAGE, CamelMimeMessageClass))
+#define CAMEL_IS_MIME_MESSAGE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_MESSAGE))
+#define CAMEL_IS_MIME_MESSAGE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_MESSAGE))
+#define CAMEL_MIME_MESSAGE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_MESSAGE, CamelMimeMessageClass))
+
+#define CAMEL_RECIPIENT_TYPE_TO "To"
+#define CAMEL_RECIPIENT_TYPE_CC "Cc"
+#define CAMEL_RECIPIENT_TYPE_BCC "Bcc"
+
+#define CAMEL_RECIPIENT_TYPE_RESENT_TO "Resent-To"
+#define CAMEL_RECIPIENT_TYPE_RESENT_CC "Resent-Cc"
+#define CAMEL_RECIPIENT_TYPE_RESENT_BCC "Resent-Bcc"
+
+/* specify local time */
+#define CAMEL_MESSAGE_DATE_CURRENT (~0)
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeMessage CamelMimeMessage;
+typedef struct _CamelMimeMessageClass CamelMimeMessageClass;
+
+struct _CamelMimeMessage {
+ CamelMimePart parent;
+
+ /* header fields */
+ time_t date;
+ gint date_offset; /* GMT offset */
+
+ /* cached internal copy */
+ time_t date_received;
+ gint date_received_offset; /* GMT offset */
+
+ gchar *subject;
+
+ gchar *message_id;
+
+ CamelInternetAddress *reply_to;
+ CamelInternetAddress *from;
+
+ GHashTable *recipients; /* hash table of CamelInternetAddress's */
+};
+
+struct _CamelMimeMessageClass {
+ CamelMimePartClass parent_class;
+};
+
+GType camel_mime_message_get_type (void);
+CamelMimeMessage *
+ camel_mime_message_new (void);
+void camel_mime_message_set_date (CamelMimeMessage *message,
+ time_t date,
+ gint offset);
+time_t camel_mime_message_get_date (CamelMimeMessage *message,
+ gint *offset);
+time_t camel_mime_message_get_date_received
+ (CamelMimeMessage *message,
+ gint *offset);
+void camel_mime_message_set_message_id
+ (CamelMimeMessage *message,
+ const gchar *message_id);
+const gchar * camel_mime_message_get_message_id
+ (CamelMimeMessage *message);
+void camel_mime_message_set_reply_to (CamelMimeMessage *message,
+ CamelInternetAddress *reply_to);
+CamelInternetAddress *
+ camel_mime_message_get_reply_to (CamelMimeMessage *message);
+void camel_mime_message_set_subject (CamelMimeMessage *message,
+ const gchar *subject);
+const gchar * camel_mime_message_get_subject (CamelMimeMessage *message);
+void camel_mime_message_set_from (CamelMimeMessage *message,
+ CamelInternetAddress *from);
+CamelInternetAddress *
+ camel_mime_message_get_from (CamelMimeMessage *message);
+CamelInternetAddress *
+ camel_mime_message_get_recipients
+ (CamelMimeMessage *message,
+ const gchar *type);
+void camel_mime_message_set_recipients
+ (CamelMimeMessage *message,
+ const gchar *type,
+ CamelInternetAddress *recipients);
+void camel_mime_message_set_source (CamelMimeMessage *message,
+ const gchar *source_uid);
+const gchar * camel_mime_message_get_source (CamelMimeMessage *message);
+
+/* utility functions */
+gboolean camel_mime_message_has_8bit_parts
+ (CamelMimeMessage *message);
+void camel_mime_message_set_best_encoding
+ (CamelMimeMessage *message,
+ CamelBestencRequired required,
+ CamelBestencEncoding enctype);
+void camel_mime_message_encode_8bit_parts
+ (CamelMimeMessage *message);
+CamelMimePart * camel_mime_message_get_part_by_content_id
+ (CamelMimeMessage *message,
+ const gchar *content_id);
+gchar * camel_mime_message_build_mbox_from
+ (CamelMimeMessage *message);
+gboolean camel_mime_message_has_attachment
+ (CamelMimeMessage *message);
+void camel_mime_message_dump (CamelMimeMessage *message,
+ gint body);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_MESSAGE_H */
diff --git a/src/camel/camel-mime-parser.c b/src/camel/camel-mime-parser.c
new file mode 100644
index 000000000..dccf0638c
--- /dev/null
+++ b/src/camel/camel-mime-parser.c
@@ -0,0 +1,2015 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/* 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 <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "camel-mempool.h"
+#include "camel-mime-filter.h"
+#include "camel-mime-parser.h"
+#include "camel-mime-utils.h"
+#include "camel-stream.h"
+
+#define r(x)
+#define h(x)
+#define c(x)
+#define d(x)
+
+#define PRESERVE_HEADERS
+
+/*#define PURIFY*/
+
+#define MEMPOOL
+
+#ifdef PURIFY
+gint 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(obj) (((CamelMimeParser *)(obj))->priv)
+
+struct _header_scan_state {
+
+ /* global state */
+
+ CamelMimeParserState state;
+
+ /* for building headers during scanning */
+ gchar *outbuf;
+ gchar *outptr;
+ gchar *outend;
+
+ gint fd; /* input for a fd input */
+ CamelStream *stream; /* or for a stream */
+ GInputStream *input_stream;
+
+ gint ioerrno; /* io error state */
+
+ /* for scanning input buffers */
+ gchar *realbuf; /* the real buffer, SCAN_HEAD *2 + SCAN_BUF bytes */
+ gchar *inbuf; /* points to a subset of the allocated memory, the underflow */
+ gchar *inptr; /* (upto SCAN_HEAD) is for use by filters so they dont copy all data */
+ gchar *inend;
+
+ gint atleast;
+
+ goffset seek; /* current offset to start of buffer */
+ gint unstep; /* how many states to 'unstep' (repeat the current state) */
+
+ guint midline:1; /* are we mid-line interrupted? */
+ guint scan_from:1; /* do we care about From lines? */
+ guint scan_pre_from:1; /* do we return pre-from data? */
+ guint eof:1; /* reached eof? */
+
+ goffset start_of_from; /* where from started */
+ goffset start_of_boundary; /* where the last boundary started */
+ goffset start_of_headers; /* where headers started from the last scan */
+
+ goffset header_start; /* start of last header, or -1 */
+
+ /* filters to apply to all content before output */
+ gint 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;
+
+ CamelMimeParserState savestate; /* state at invocation of this part */
+
+#ifdef MEMPOOL
+ CamelMemPool *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 */
+ gint prestage; /* used to determine if it is a pre-boundary or post-boundary data segment */
+
+ GByteArray *from_line; /* the from line */
+
+ gchar *boundary; /* for multipart/ * boundaries, including leading -- and trailing -- for the final part */
+ gint boundarylen; /* actual length of boundary, including leading -- if there is one */
+ gint boundarylenfinal; /* length of boundary, including trailing -- if there is one */
+ gint atleast; /* the biggest boundary from here to the parent */
+};
+
+struct _header_scan_filter {
+ struct _header_scan_filter *next;
+ gint id;
+ CamelMimeFilter *filter;
+};
+
+static void folder_scan_reset (struct _header_scan_state *s);
+static void folder_scan_step (struct _header_scan_state *s, gchar **databuffer, gsize *datalength);
+static void folder_scan_drop_step (struct _header_scan_state *s);
+static gint folder_scan_init_with_fd (struct _header_scan_state *s, gint fd);
+static gint folder_scan_init_with_stream (struct _header_scan_state *s, CamelStream *stream, GError **error);
+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, gint *lastone, gchar **data, gsize *length);
+static struct _header_scan_stack *folder_scan_header (struct _header_scan_state *s, gint *lastone);
+static gint folder_scan_skip_line (struct _header_scan_state *s, GByteArray *save);
+static goffset folder_seek (struct _header_scan_state *s, goffset offset, gint whence);
+static goffset folder_tell (struct _header_scan_state *s);
+static gint 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, gchar *header, gint offset);
+#endif
+
+#if d(!)0
+static gchar *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
+
+G_DEFINE_TYPE (CamelMimeParser, camel_mime_parser, G_TYPE_OBJECT)
+
+static void
+mime_parser_finalize (GObject *object)
+{
+ struct _header_scan_state *s = _PRIVATE (object);
+
+#ifdef PURIFY
+ purify_watch_remove_all ();
+#endif
+
+ folder_scan_close (s);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_mime_parser_parent_class)->finalize (object);
+}
+
+static void
+camel_mime_parser_class_init (CamelMimeParserClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = mime_parser_finalize;
+}
+
+static void
+camel_mime_parser_init (CamelMimeParser *parser)
+{
+ parser->priv = folder_scan_init ();
+}
+
+/**
+ * camel_mime_parser_new:
+ *
+ * Create a new CamelMimeParser object.
+ *
+ * Returns: A new CamelMimeParser widget.
+ **/
+CamelMimeParser *
+camel_mime_parser_new (void)
+{
+ return g_object_new (CAMEL_TYPE_MIME_PARSER, NULL);
+}
+
+/**
+ * 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.
+ *
+ * Returns: An id that may be passed to filter_remove() to remove
+ * the filter, or -1 if the operation failed.
+ *
+ * Since: 2.22
+ **/
+gint
+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 = NULL;
+ g_object_ref (G_OBJECT (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.
+ *
+ * Since: 2.22
+ **/
+void
+camel_mime_parser_filter_remove (CamelMimeParser *m,
+ gint 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) {
+ g_object_unref (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.
+ *
+ * Returns: The header value, or NULL if the header is not
+ * defined.
+ **/
+const gchar *
+camel_mime_parser_header (CamelMimeParser *m,
+ const gchar *name,
+ gint *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().
+ *
+ * Returns: (transfer none): The raw headers, or NULL if there are no headers
+ * defined for the current part or state. These are READ ONLY.
+ *
+ * Since: 2.22
+ **/
+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 gchar *
+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, (guint8 *) "", 1);
+
+ return (const gchar *) 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.
+ *
+ * Returns: The preface text, or NULL if there wasn't any.
+ *
+ * Since: 2.22
+ **/
+const gchar *
+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.
+ *
+ * Returns: The postface text, or NULL if there wasn't any.
+ *
+ * Since: 2.22
+ **/
+const gchar *
+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.
+ *
+ * Returns: The From line, or NULL if called out of context.
+ *
+ * Since: 2.22
+ **/
+const gchar *
+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.
+ *
+ * Returns: Returns -1 on error.
+ **/
+gint
+camel_mime_parser_init_with_fd (CamelMimeParser *m,
+ gint fd)
+{
+ struct _header_scan_state *s = _PRIVATE (m);
+
+ return folder_scan_init_with_fd (s, fd);
+}
+
+/**
+ * camel_mime_parser_init_with_stream:
+ * @m:
+ * @stream:
+ * @error: return location for a #GError, or %NULL
+ *
+ * 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.
+ *
+ * Returns: -1 on error.
+ **/
+gint
+camel_mime_parser_init_with_stream (CamelMimeParser *parser,
+ CamelStream *stream,
+ GError **error)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ return folder_scan_init_with_stream (s, stream, error);
+}
+
+/**
+ * camel_mime_parser_init_with_input_stream:
+ * @parser: a #CamelMimeParser
+ * @input_stream: a #GInputStream
+ *
+ * Initialize the scanner with @input_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.
+ *
+ * Since: 3.12
+ **/
+void
+camel_mime_parser_init_with_input_stream (CamelMimeParser *parser,
+ GInputStream *input_stream)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ folder_scan_reset (s);
+ s->input_stream = g_object_ref (input_stream);
+}
+
+/**
+ * camel_mime_parser_init_with_bytes:
+ * @parser: a #CamelMimeParser
+ * @bytes: a #GBytes containing the message content
+ *
+ * Convenience function creates a #GMemoryInputStream from @bytes and hands
+ * it off to camel_mime_parser_init_with_input_stream().
+ *
+ * Since: 3.12
+ **/
+void
+camel_mime_parser_init_with_bytes (CamelMimeParser *parser,
+ GBytes *bytes)
+{
+ GInputStream *input_stream;
+
+ g_return_if_fail (CAMEL_IS_MIME_PARSER (parser));
+ g_return_if_fail (bytes != NULL);
+
+ input_stream = g_memory_input_stream_new_from_bytes (bytes);
+ camel_mime_parser_init_with_input_stream (parser, input_stream);
+ g_object_unref (input_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.
+ *
+ * Returns: 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: (inout) (array length=datalength) (nullable): 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: (inout) (nullable): 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.
+ *
+ * Returns: The current new state of the parser
+ * is returned.
+ **/
+CamelMimeParserState
+camel_mime_parser_step (CamelMimeParser *parser,
+ gchar **databuffer,
+ gsize *datalength)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ d (printf ("OLD STATE: '%s' :\n", states[s->state]));
+
+ if (s->unstep <= 0) {
+ gchar *dummy;
+ gsize 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: (out) (array): The data buffer
+ * @len: The length of data to read
+ * @error: return location for a #GError, or %NULL
+ *
+ * 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 %G_MAXSSIZE, 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.
+ *
+ * Returns: The number of bytes available, or -1 on error.
+ **/
+gssize
+camel_mime_parser_read (CamelMimeParser *parser,
+ const gchar **databuffer,
+ gssize len,
+ GError **error)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+ gintptr 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) {
+ gint err = camel_mime_parser_errno (parser);
+
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (err),
+ "%s", g_strerror (err));
+ 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).
+ *
+ * Returns: See above.
+ *
+ * Since: 2.22
+ **/
+goffset
+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.
+ *
+ * Returns: The header start position, or -1 if
+ * no headers were scanned in the current state.
+ *
+ * Since: 2.22
+ **/
+goffset
+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.
+ *
+ * Returns: The start of the from line, or -1 if there
+ * was no From line, or From lines are not being scanned.
+ *
+ * Since: 2.22
+ **/
+goffset
+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.
+ *
+ * Returns: The start of the boundary, or -1 if there
+ * was no boundary encountered yet.
+ *
+ * Since: 2.22
+ **/
+goffset
+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.
+ *
+ * Returns: The new seek offset, or -1 on
+ * an error (for example, trying to seek on a non-seekable
+ * stream or file descriptor).
+ *
+ * Since: 2.22
+ **/
+goffset
+camel_mime_parser_seek (CamelMimeParser *parser,
+ goffset offset,
+ gint 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.
+ *
+ * Returns: The current parser state.
+ **/
+CamelMimeParserState
+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,
+ CamelMimeParserState newstate,
+ const gchar *boundary)
+{
+ struct _header_scan_stack *h;
+ struct _header_scan_state *s = _PRIVATE (mp);
+ gsize boundary_len;
+
+ h = g_malloc0 (sizeof (*h));
+ h->boundarylen = strlen (boundary) + 2;
+ h->boundarylenfinal = h->boundarylen + 2;
+ boundary_len = h->boundarylen + 3;
+ h->boundary = g_malloc (boundary_len);
+ g_snprintf (h->boundary, boundary_len, "--%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).
+ *
+ * Returns: (transfer none) (nullable): 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;
+}
+
+/* Return errno of the parser, incase any error occurred during processing */
+gint
+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 gint
+folder_read (struct _header_scan_state *s)
+{
+ gint len;
+ gint 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) {
+ memmove (s->inbuf, s->inptr, inoffset);
+ }
+ if (s->stream) {
+ len = camel_stream_read (
+ s->stream, s->inbuf + inoffset, SCAN_BUF - inoffset, NULL, NULL);
+ } else if (s->input_stream != NULL) {
+ len = g_input_stream_read (
+ s->input_stream, s->inbuf + inoffset,
+ SCAN_BUF - inoffset, NULL, NULL);
+ } 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_return_val_if_fail (s->inptr <= s->inend, 0);
+#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 goffset
+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 goffset
+folder_seek (struct _header_scan_state *s,
+ goffset offset,
+ gint whence)
+{
+ goffset newoffset;
+
+ if (s->stream) {
+ if (G_IS_SEEKABLE (s->stream)) {
+ /* NOTE: assumes whence seekable stream == whence libc, which is probably
+ * the case (or bloody well should've been) */
+ g_seekable_seek (
+ G_SEEKABLE (s->stream),
+ offset, whence, NULL, NULL);
+ newoffset = g_seekable_tell (G_SEEKABLE (s->stream));
+ } else {
+ newoffset = -1;
+ errno = EINVAL;
+ }
+ } else if (s->input_stream != NULL) {
+ if (G_IS_SEEKABLE (s->input_stream)) {
+ /* NOTE: assumes whence seekable stream == whence libc, which is probably
+ * the case (or bloody well should've been) */
+ g_seekable_seek (
+ G_SEEKABLE (s->input_stream),
+ offset, whence, NULL, NULL);
+ newoffset = g_seekable_tell (G_SEEKABLE (s->input_stream));
+ } 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
+ camel_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 gint
+folder_scan_skip_line (struct _header_scan_state *s,
+ GByteArray *save)
+{
+ gint atleast = s->atleast;
+ register gchar *inptr, *inend, c;
+ gint 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, (guint8 *) 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 gchar *boundary,
+ gint *lastone)
+{
+ struct _header_scan_stack *part;
+ gint 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) {
+ gint 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,
+ gchar *header,
+ gint offset)
+{
+ struct _camel_header_raw *l, *n;
+ gchar *content;
+
+ content = strchr (header, ':');
+ if (content) {
+ register gint len;
+ n = camel_mempool_alloc (h->pool, sizeof (*n));
+ n->next = NULL;
+
+ len = content - header;
+ n->name = camel_mempool_alloc (h->pool, len + 1);
+ memcpy (n->name, header, len);
+ n->name[len] = 0;
+
+ content++;
+
+ len = s->outptr - content;
+ n->value = camel_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 gintptr headerlen = inptr - start; \
+ \
+ if (headerlen > 0) { \
+ if (headerlen >= (s->outend - s->outptr)) { \
+ register gchar *outnew; \
+ register gintptr olen = ((s->outend - s->outbuf) + headerlen) * 2 + 1; \
+ outnew = g_realloc (s->outbuf, olen); \
+ s->outptr = s->outptr - s->outbuf + outnew; \
+ s->outbuf = outnew; \
+ s->outend = outnew + olen; \
+ } \
+ 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,
+ gint *lastone)
+{
+ gint atleast = s->atleast, newatleast;
+ gchar *start = NULL;
+ gint len;
+ struct _header_scan_stack *h;
+ gchar *inend;
+ register gchar *inptr;
+
+ h (printf ("scanning first bit\n"));
+
+ h = g_malloc0 (sizeof (*h));
+#ifdef MEMPOOL
+ h->pool = camel_mempool_new (8192, 4096, CAMEL_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) {
+ start = inptr;
+ 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;
+ }
+ }
+
+ /* goto next line/sentinal */
+ while ((*inptr++) != '\n')
+ ;
+
+ g_return_val_if_fail (inptr <= s->inend + 1, NULL);
+
+ /* 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;
+ header_append (s, start, inptr - 1);
+
+ /* check for end of headers */
+ if (s->outbuf == s->outptr)
+ goto header_done;
+
+ /* check for continuation/compress headers, we have atleast 1 gchar here to work with */
+ if (inptr[0] == ' ' || inptr[0] == '\t') {
+ h (printf ("continuation\n"));
+
+#ifdef PRESERVE_HEADERS
+ if (inptr - 1 >= start) {
+ start = inptr - 1;
+ header_append (s, start, inptr);
+ }
+#endif
+#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 */
+ s->outptr[0] = 0;
+
+ h (printf ("header '%s' at %d\n", s->outbuf, (gint) 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,
+ gint *lastone,
+ gchar **data,
+ gsize *length)
+{
+ gint atleast = s->atleast, newatleast;
+ register gchar *inptr;
+ gchar *inend;
+ gchar *start;
+ gint len;
+ struct _header_scan_stack *part;
+ gint 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 gchar 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;
+ }
+ }
+
+ 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;
+ *data = s->inptr;
+ 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);
+ g_clear_object (&s->stream);
+ g_clear_object (&s->input_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->input_stream = NULL;
+ s->ioerrno = 0;
+
+ s->outbuf = g_malloc (1024);
+ s->outptr = s->outbuf;
+ s->outend = s->outbuf + 1024;
+
+ s->realbuf = g_malloc0 (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;
+ }
+ g_clear_object (&s->stream);
+ g_clear_object (&s->input_stream);
+ s->ioerrno = 0;
+ s->eof = FALSE;
+}
+
+static gint
+folder_scan_init_with_fd (struct _header_scan_state *s,
+ gint fd)
+{
+ folder_scan_reset (s);
+ s->fd = fd;
+
+ return 0;
+}
+
+static gint
+folder_scan_init_with_stream (struct _header_scan_state *s,
+ CamelStream *stream,
+ GError **error)
+{
+ folder_scan_reset (s);
+ s->stream = g_object_ref (stream);
+
+ return 0;
+}
+
+#define USE_FROM
+
+static void
+folder_scan_step (struct _header_scan_state *s,
+ gchar **databuffer,
+ gsize *datalength)
+{
+ struct _header_scan_stack *h, *hb;
+ const gchar *content;
+ const gchar *bound;
+ gint type, state, seenlast;
+ CamelContentType *ct = NULL;
+ struct _header_scan_filter *f;
+ gsize presize;
+ gulong boundary_len;
+
+/* 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;
+ goto scan_pre_from;
+ } else {
+ s->start_of_from = -1;
+ goto scan_header;
+ }
+
+ case CAMEL_MIME_PARSER_STATE_PRE_FROM:
+
+ scan_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;
+ boundary_len = h->boundarylen + 3;
+ h->boundary = g_malloc (boundary_len);
+ g_snprintf (h->boundary, boundary_len, "--%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 (!g_ascii_strcasecmp (ct->type, "message")) {
+ if (!g_ascii_strcasecmp (ct->subtype, "rfc822")
+ || !g_ascii_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;
+ /* coverity[fallthrough] */
+
+ 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 (gchar), *datalength, stdout));
+ d (printf ("'\n"));
+
+ if (*datalength > 0) {
+ while (f) {
+ camel_mime_filter_filter (
+ f->filter,
+ *databuffer, *datalength, presize,
+ databuffer, datalength, &presize);
+ d (fwrite (*databuffer, sizeof (gchar), *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, (guint8 *) *databuffer, *datalength);
+ } else {
+ if (h->pretext == NULL)
+ h->pretext = g_byte_array_new ();
+ g_byte_array_append (h->pretext, (guint8 *) *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: %u", 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
+gint main (gint argc, gchar **argv)
+{
+ gint fd;
+ struct _header_scan_state *s;
+ gchar *data;
+ gsize len;
+ gint state;
+ gchar *name = "/tmp/evmail/Inbox";
+ struct _header_scan_stack *h;
+ gint i;
+ gint attach = 0;
+
+ if (argc == 2)
+ name = argv[1];
+
+ printf ("opening: %s", name);
+
+ for (i = 1; i < argc; i++) {
+ const gchar *encoding = NULL, *charset = NULL;
+ gchar *attachname;
+
+ name = argv[i];
+ printf ("opening: %s", name);
+
+ fd = g_open (name, O_RDONLY | O_BINARY, 0);
+ 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 && !g_ascii_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 && !g_ascii_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 && !g_ascii_strncasecmp (encoding, " base64", 7)) {
+ printf ("removing filters\n");
+#if 0
+ folder_filter_pull (s);
+ folder_filter_pull (s);
+#endif
+ }
+ if (encoding && !g_ascii_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/src/camel/camel-mime-parser.h b/src/camel/camel-mime-parser.h
new file mode 100644
index 000000000..ce5c098f2
--- /dev/null
+++ b/src/camel/camel-mime-parser.h
@@ -0,0 +1,157 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_PARSER_H
+#define CAMEL_MIME_PARSER_H
+
+#include <camel/camel-mime-utils.h>
+#include <camel/camel-mime-filter.h>
+#include <camel/camel-stream.h>
+
+/* Stardard GObject macros */
+#define CAMEL_TYPE_MIME_PARSER \
+ (camel_mime_parser_get_type ())
+#define CAMEL_MIME_PARSER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_PARSER, CamelMimeParser))
+#define CAMEL_MIME_PARSER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_PARSER, CamelMimeParserClass))
+#define CAMEL_IS_MIME_PARSER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_PARSER))
+#define CAMEL_IS_MIME_PARSER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_PARSER))
+#define CAMEL_MIME_PARSER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_PARSER, CamelMimeParserClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimeParser CamelMimeParser;
+typedef struct _CamelMimeParserClass CamelMimeParserClass;
+typedef struct _CamelMimeParserPrivate CamelMimeParserPrivate;
+
+/* 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 */
+typedef enum _camel_mime_parser_state_t {
+ 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 */
+} CamelMimeParserState;
+
+struct _CamelMimeParser {
+ GObject parent;
+ CamelMimeParserPrivate *priv;
+};
+
+struct _CamelMimeParserClass {
+ GObjectClass parent_class;
+
+ void (*message) (CamelMimeParser *parser, gpointer headers);
+ void (*part) (CamelMimeParser *parser);
+ void (*content) (CamelMimeParser *parser);
+};
+
+GType 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 */
+gint camel_mime_parser_errno (CamelMimeParser *parser);
+
+gint camel_mime_parser_init_with_fd (CamelMimeParser *m, gint fd);
+gint camel_mime_parser_init_with_stream (CamelMimeParser *m, CamelStream *stream, GError **error);
+void camel_mime_parser_init_with_input_stream (CamelMimeParser *parser, GInputStream *input_stream);
+void camel_mime_parser_init_with_bytes (CamelMimeParser *parser, GBytes *bytes);
+
+CamelStream *camel_mime_parser_stream (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: */
+gint camel_mime_parser_set_header_regex (CamelMimeParser *parser, gchar *matchstr);
+
+/* normal interface */
+CamelMimeParserState camel_mime_parser_step (CamelMimeParser *parser, gchar **databuffer, gsize *datalength);
+void camel_mime_parser_unstep (CamelMimeParser *parser);
+void camel_mime_parser_drop_step (CamelMimeParser *parser);
+CamelMimeParserState camel_mime_parser_state (CamelMimeParser *parser);
+void camel_mime_parser_push_state (CamelMimeParser *mp, CamelMimeParserState newstate, const gchar *boundary);
+
+/* read through the parser */
+gssize camel_mime_parser_read (CamelMimeParser *parser, const gchar **databuffer, gssize len, GError **error);
+
+/* get content type for the current part/header */
+CamelContentType *camel_mime_parser_content_type (CamelMimeParser *parser);
+
+/* get/change raw header by name */
+const gchar *camel_mime_parser_header (CamelMimeParser *m, const gchar *name, gint *offset);
+
+/* get all raw headers. READ ONLY! */
+struct _camel_header_raw *camel_mime_parser_headers_raw (CamelMimeParser *m);
+
+/* get multipart pre/postface */
+const gchar *camel_mime_parser_preface (CamelMimeParser *m);
+const gchar *camel_mime_parser_postface (CamelMimeParser *m);
+
+/* return the from line content */
+const gchar *camel_mime_parser_from_line (CamelMimeParser *m);
+
+/* add a processing filter for body contents */
+gint camel_mime_parser_filter_add (CamelMimeParser *m, CamelMimeFilter *mf);
+void camel_mime_parser_filter_remove (CamelMimeParser *m, gint id);
+
+/* these should be used with caution, because the state will not
+ * track the seeked position */
+/* FIXME: something to bootstrap the state? */
+goffset camel_mime_parser_tell (CamelMimeParser *parser);
+goffset camel_mime_parser_seek (CamelMimeParser *parser, goffset offset, gint whence);
+
+goffset camel_mime_parser_tell_start_headers (CamelMimeParser *parser);
+goffset camel_mime_parser_tell_start_from (CamelMimeParser *parser);
+goffset camel_mime_parser_tell_start_boundary (CamelMimeParser *parser);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_PARSER_H */
diff --git a/src/camel/camel-mime-part-utils.c b/src/camel/camel-mime-part-utils.c
new file mode 100644
index 000000000..9fe0f9e25
--- /dev/null
+++ b/src/camel/camel-mime-part-utils.c
@@ -0,0 +1,240 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/* camel-mime-part-utils : Utility for mime parsing and so on
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "camel-charset-map.h"
+#include "camel-html-parser.h"
+#include "camel-mime-filter-basic.h"
+#include "camel-mime-filter-charset.h"
+#include "camel-mime-filter-crlf.h"
+#include "camel-mime-message.h"
+#include "camel-mime-part-utils.h"
+#include "camel-multipart-encrypted.h"
+#include "camel-multipart-signed.h"
+#include "camel-multipart.h"
+#include "camel-stream-filter.h"
+#include "camel-stream-fs.h"
+#include "camel-stream-mem.h"
+#include "camel-stream-buffer.h"
+#include "camel-utf8.h"
+
+#define d(x) /* (printf("%s(%d): ", __FILE__, __LINE__),(x)) */
+
+/* simple data wrapper */
+static gboolean
+simple_data_wrapper_construct_from_parser (CamelDataWrapper *dw,
+ CamelMimeParser *mp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar *buf;
+ GByteArray *buffer;
+ CamelStream *mem;
+ gsize len;
+ gboolean success;
+
+ d (printf ("simple_data_wrapper_construct_from_parser()\n"));
+
+ /* read in the entire content */
+ buffer = g_byte_array_new ();
+ while (camel_mime_parser_step (mp, &buf, &len) != CAMEL_MIME_PARSER_STATE_BODY_END) {
+ d (printf ("appending o/p data: %d: %.*s\n", len, len, buf));
+ g_byte_array_append (buffer, (guint8 *) buf, len);
+ }
+
+ d (printf ("message part kept in memory!\n"));
+
+ mem = camel_stream_mem_new_with_byte_array (buffer);
+ success = camel_data_wrapper_construct_from_stream_sync (
+ dw, mem, cancellable, error);
+ g_object_unref (mem);
+
+ return success;
+}
+
+/**
+ * camel_mime_part_construct_content_from_parser:
+ *
+ * Since: 2.24
+ **/
+gboolean
+camel_mime_part_construct_content_from_parser (CamelMimePart *dw,
+ CamelMimeParser *mp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelDataWrapper *content = NULL;
+ CamelContentType *ct;
+ gchar *encoding;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (dw), FALSE);
+
+ ct = camel_mime_parser_content_type (mp);
+
+ encoding = camel_content_transfer_encoding_decode (camel_mime_parser_header (mp, "Content-Transfer-Encoding", NULL));
+
+ switch (camel_mime_parser_state (mp)) {
+ case CAMEL_MIME_PARSER_STATE_HEADER:
+ d (printf ("Creating body part\n"));
+ /* multipart/signed is some type that we must treat as binary data. */
+ if (camel_content_type_is (ct, "multipart", "signed")) {
+ content = (CamelDataWrapper *) camel_multipart_signed_new ();
+ camel_multipart_construct_from_parser ((CamelMultipart *) content, mp);
+ } else {
+ content = camel_data_wrapper_new ();
+ success = simple_data_wrapper_construct_from_parser (
+ content, mp, cancellable, error);
+ }
+ break;
+ case CAMEL_MIME_PARSER_STATE_MESSAGE:
+ d (printf ("Creating message part\n"));
+ content = (CamelDataWrapper *) camel_mime_message_new ();
+ success = camel_mime_part_construct_from_parser_sync (
+ (CamelMimePart *) content, mp, cancellable, error);
+ break;
+ case CAMEL_MIME_PARSER_STATE_MULTIPART:
+ d (printf ("Creating multi-part\n"));
+ if (camel_content_type_is (ct, "multipart", "encrypted"))
+ content = (CamelDataWrapper *) camel_multipart_encrypted_new ();
+ else if (camel_content_type_is (ct, "multipart", "signed"))
+ content = (CamelDataWrapper *) camel_multipart_signed_new ();
+ else
+ content = (CamelDataWrapper *) camel_multipart_new ();
+
+ camel_multipart_construct_from_parser ((CamelMultipart *) content, mp);
+ d (printf ("Created multi-part\n"));
+ break;
+ default:
+ g_warning ("Invalid state encountered???: %u", camel_mime_parser_state (mp));
+ }
+
+ if (content) {
+ if (encoding)
+ content->encoding = camel_transfer_encoding_from_string (encoding);
+
+ /* would you believe you have to set this BEFORE you set the content object??? oh my god !!!! */
+ camel_data_wrapper_set_mime_type_field (content, camel_mime_part_get_content_type (dw));
+ camel_medium_set_content ((CamelMedium *) dw, content);
+ g_object_unref (content);
+ }
+
+ g_free (encoding);
+
+ return success;
+}
+
+/**
+ * camel_mime_message_build_preview:
+ *
+ * <note>
+ * <para>
+ * This function blocks like crazy.
+ * </para>
+ * </note>
+ *
+ * Since: 2.28
+ **/
+gboolean
+camel_mime_message_build_preview (CamelMimePart *msg,
+ CamelMessageInfo *info)
+{
+ CamelDataWrapper *dw;
+ gboolean got_plain = FALSE;
+
+ dw = camel_medium_get_content ((CamelMedium *) msg);
+ if (camel_content_type_is (dw->mime_type, "multipart", "*")) {
+ gint i, nparts;
+ CamelMultipart *mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) msg);
+
+ g_warn_if_fail (CAMEL_IS_MULTIPART (mp));
+
+ nparts = camel_multipart_get_number (mp);
+ for (i = 0; i < nparts && !got_plain; i++) {
+ CamelMimePart *part = camel_multipart_get_part (mp, i);
+ got_plain = camel_mime_message_build_preview (part, info);
+ }
+
+ } else if (camel_content_type_is (dw->mime_type, "text", "*") &&
+ /* !camel_content_type_is (dw->mime_type, "text", "html") && */
+ !camel_content_type_is (dw->mime_type, "text", "calendar")) {
+ CamelStream *mstream, *bstream;
+
+ /* FIXME Pass a GCancellable and GError here. */
+ mstream = camel_stream_mem_new ();
+ if (camel_data_wrapper_decode_to_stream_sync (dw, mstream, NULL, NULL) > 0) {
+ gchar *line = NULL;
+ GString *str = g_string_new (NULL);
+
+ g_seekable_seek (
+ G_SEEKABLE (mstream), 0,
+ G_SEEK_SET, NULL, NULL);
+
+ bstream = camel_stream_buffer_new (mstream, CAMEL_STREAM_BUFFER_READ | CAMEL_STREAM_BUFFER_BUFFER);
+
+ /* We should fetch just 200 unquoted lines. */
+ while ((line = camel_stream_buffer_read_line ((CamelStreamBuffer *) bstream, NULL, NULL)) && str->len < 200) {
+ gchar *tmp = line;
+
+ if (*line == '>' || strstr (line, "wrote:")) {
+ g_free (tmp);
+ continue;
+ }
+ if (g_str_has_prefix (line, "--")) {
+ g_free (tmp);
+ line = NULL;
+ break;
+ }
+ while (*line && ((*line == ' ') || *line == '\t'))
+ line++;
+ if (*line == '\0' || *line == '\n') {
+ g_free (tmp);
+ continue;
+ }
+
+ g_string_append (str, " ");
+ g_string_append (str, line);
+ g_free (tmp);
+ line = NULL;
+ }
+ if (str->len > 100) {
+ g_string_insert (str, 100, "\n");
+ }
+ /* We don't mark dirty, as we don't store these */
+ ((CamelMessageInfoBase *) info)->preview = camel_utf8_make_valid (str->str);
+ g_string_free (str, TRUE);
+
+ g_object_unref (bstream);
+ }
+ g_object_unref (mstream);
+ return TRUE;
+ }
+
+ return got_plain;
+}
diff --git a/src/camel/camel-mime-part-utils.h b/src/camel/camel-mime-part-utils.h
new file mode 100644
index 000000000..916cc334a
--- /dev/null
+++ b/src/camel/camel-mime-part-utils.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-mime-part-utils : Utility for mime parsing and so on
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_PART_UTILS_H
+#define CAMEL_MIME_PART_UTILS_H
+
+#include <camel/camel-mime-part.h>
+#include <camel/camel-folder-summary.h>
+
+G_BEGIN_DECLS
+
+gboolean camel_mime_part_construct_content_from_parser
+ (CamelMimePart *mime_part,
+ CamelMimeParser *mp,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_mime_message_build_preview (CamelMimePart *mime_part,
+ CamelMessageInfo *info);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_PART_UTILS_H */
diff --git a/src/camel/camel-mime-part.c b/src/camel/camel-mime-part.c
new file mode 100644
index 000000000..70b842bc8
--- /dev/null
+++ b/src/camel/camel-mime-part.c
@@ -0,0 +1,1715 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/* camelMimePart.c : Abstract class for a mime_part
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-charset-map.h"
+#include "camel-debug.h"
+#include "camel-iconv.h"
+#include "camel-filter-output-stream.h"
+#include "camel-mime-filter-basic.h"
+#include "camel-mime-filter-charset.h"
+#include "camel-mime-filter-crlf.h"
+#include "camel-mime-parser.h"
+#include "camel-mime-part-utils.h"
+#include "camel-mime-part.h"
+#include "camel-mime-utils.h"
+#include "camel-stream-filter.h"
+#include "camel-stream-mem.h"
+#include "camel-stream-null.h"
+#include "camel-string-utils.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+#define CAMEL_MIME_PART_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_PART, CamelMimePartPrivate))
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _CamelMimePartPrivate {
+ gchar *description;
+ CamelContentDisposition *disposition;
+ gchar *content_id;
+ gchar *content_md5;
+ gchar *content_location;
+ GList *content_languages;
+ CamelTransferEncoding encoding;
+};
+
+struct _AsyncContext {
+ CamelMimeParser *parser;
+};
+
+enum {
+ PROP_0,
+ PROP_CONTENT_ID,
+ PROP_CONTENT_LOCATION,
+ PROP_CONTENT_MD5,
+ PROP_DESCRIPTION,
+ PROP_DISPOSITION,
+ PROP_FILENAME
+};
+
+typedef enum {
+ HEADER_UNKNOWN,
+ HEADER_DESCRIPTION,
+ HEADER_DISPOSITION,
+ HEADER_CONTENT_ID,
+ HEADER_ENCODING,
+ HEADER_CONTENT_MD5,
+ HEADER_CONTENT_LOCATION,
+ HEADER_CONTENT_LANGUAGES,
+ HEADER_CONTENT_TYPE
+} CamelHeaderType;
+
+static GHashTable *header_name_table;
+static GHashTable *header_formatted_table;
+
+G_DEFINE_TYPE (CamelMimePart, camel_mime_part, CAMEL_TYPE_MEDIUM)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+ if (async_context->parser != NULL)
+ g_object_unref (async_context->parser);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static gssize
+write_header (gpointer stream,
+ const gchar *name,
+ const gchar *value,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GString *buffer;
+ gssize n_written = 0;
+
+ buffer = g_string_new (name);
+ g_string_append_c (buffer, ':');
+ if (!isspace (value[0]))
+ g_string_append_c (buffer, ' ');
+ g_string_append (buffer, value);
+ g_string_append_c (buffer, '\n');
+
+ /* XXX For now we handle both types of streams. */
+
+ if (CAMEL_IS_STREAM (stream)) {
+ n_written = camel_stream_write (
+ CAMEL_STREAM (stream),
+ buffer->str, buffer->len,
+ cancellable, error);
+ } else if (G_IS_OUTPUT_STREAM (stream)) {
+ gboolean success;
+ gsize bytes_written = 0;
+
+ success = g_output_stream_write_all (
+ G_OUTPUT_STREAM (stream),
+ buffer->str, buffer->len,
+ &bytes_written, cancellable, error);
+ if (success)
+ n_written = (gssize) bytes_written;
+ else
+ n_written = -1;
+ } else {
+ g_warn_if_reached ();
+ }
+
+ g_string_free (buffer, TRUE);
+
+ return n_written;
+}
+
+static gssize
+write_references (gpointer stream,
+ const gchar *name,
+ const gchar *value,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GString *buffer;
+ const gchar *ids, *ide;
+ gssize n_written = 0;
+ gsize len;
+
+ /* this is only approximate, based on the next >, this way it retains
+ * any content from the original which may not be properly formatted,
+ * etc. It also doesn't handle the case where an individual messageid
+ * is too long, however thats a bad mail to start with ... */
+
+ buffer = g_string_new (name);
+ g_string_append_c (buffer, ':');
+ if (!isspace (value[0]))
+ g_string_append_c (buffer, ' ');
+
+ /* Fold only when not folded already */
+ if (!strchr (value, '\n')) {
+ len = buffer->len;
+
+ while (*value) {
+ ids = value;
+ ide = strchr (ids + 1, '>');
+ if (ide)
+ value = ++ide;
+ else
+ ide = value = strlen (ids) + ids;
+
+ if (len > 0 && len + (ide - ids) >= CAMEL_FOLD_SIZE) {
+ g_string_append_len (buffer, "\n\t", 2);
+ len = 0;
+ }
+
+ g_string_append_len (buffer, ids, ide - ids);
+ len += (ide - ids);
+ }
+ } else {
+ g_string_append (buffer, value);
+ }
+
+ if (buffer->len > 0 && buffer->str[buffer->len - 1] != '\n')
+ g_string_append_c (buffer, '\n');
+
+ /* XXX For now we handle both types of streams. */
+
+ if (CAMEL_IS_STREAM (stream)) {
+ n_written = camel_stream_write (
+ CAMEL_STREAM (stream),
+ buffer->str, buffer->len,
+ cancellable, error);
+ } else if (G_IS_OUTPUT_STREAM (stream)) {
+ gboolean success;
+ gsize bytes_written = 0;
+
+ success = g_output_stream_write_all (
+ G_OUTPUT_STREAM (stream),
+ buffer->str, buffer->len,
+ &bytes_written, cancellable, error);
+ if (success)
+ n_written = (gssize) bytes_written;
+ else
+ n_written = -1;
+ } else {
+ g_warn_if_reached ();
+ }
+
+ g_string_free (buffer, TRUE);
+
+ return n_written;
+}
+
+/* loads in a hash table the set of header names we */
+/* recognize and associate them with a unique enum */
+/* identifier (see CamelHeaderType above) */
+static void
+init_header_name_table (void)
+{
+ if (header_name_table)
+ return;
+
+ header_name_table = g_hash_table_new (
+ camel_strcase_hash, camel_strcase_equal);
+ g_hash_table_insert (
+ header_name_table,
+ (gpointer) "Content-Description",
+ (gpointer) HEADER_DESCRIPTION);
+ g_hash_table_insert (
+ header_name_table,
+ (gpointer) "Content-Disposition",
+ (gpointer) HEADER_DISPOSITION);
+ g_hash_table_insert (
+ header_name_table,
+ (gpointer) "Content-id",
+ (gpointer) HEADER_CONTENT_ID);
+ g_hash_table_insert (
+ header_name_table,
+ (gpointer) "Content-Transfer-Encoding",
+ (gpointer) HEADER_ENCODING);
+ g_hash_table_insert (
+ header_name_table,
+ (gpointer) "Content-MD5",
+ (gpointer) HEADER_CONTENT_MD5);
+ g_hash_table_insert (
+ header_name_table,
+ (gpointer) "Content-Location",
+ (gpointer) HEADER_CONTENT_LOCATION);
+ g_hash_table_insert (
+ header_name_table,
+ (gpointer) "Content-Type",
+ (gpointer) HEADER_CONTENT_TYPE);
+
+ header_formatted_table = g_hash_table_new (
+ camel_strcase_hash, camel_strcase_equal);
+ g_hash_table_insert (
+ header_formatted_table,
+ (gpointer) "Content-Type", write_header);
+ g_hash_table_insert (
+ header_formatted_table,
+ (gpointer) "Content-Disposition", write_header);
+ g_hash_table_insert (
+ header_formatted_table,
+ (gpointer) "From", write_header);
+ g_hash_table_insert (
+ header_formatted_table,
+ (gpointer) "Reply-To", write_header);
+ g_hash_table_insert (
+ header_formatted_table,
+ (gpointer) "Message-ID", write_header);
+ g_hash_table_insert (
+ header_formatted_table,
+ (gpointer) "In-Reply-To", write_header);
+ g_hash_table_insert (
+ header_formatted_table,
+ (gpointer) "References", write_references);
+}
+
+static void
+mime_part_set_disposition (CamelMimePart *mime_part,
+ const gchar *disposition)
+{
+ camel_content_disposition_unref (mime_part->priv->disposition);
+ if (disposition)
+ mime_part->priv->disposition =
+ camel_content_disposition_decode (disposition);
+ else
+ mime_part->priv->disposition = NULL;
+}
+
+static gboolean
+mime_part_process_header (CamelMedium *medium,
+ const gchar *name,
+ const gchar *value)
+{
+ CamelMimePart *mime_part = CAMEL_MIME_PART (medium);
+ CamelHeaderType header_type;
+ const gchar *charset;
+ gchar *text;
+
+ /* Try to parse the header pair. If it corresponds to something */
+ /* known, the job is done in the parsing routine. If not, */
+ /* we simply add the header in a raw fashion */
+
+ header_type = (CamelHeaderType) g_hash_table_lookup (header_name_table, name);
+ switch (header_type) {
+ case HEADER_DESCRIPTION: /* raw header->utf8 conversion */
+ g_free (mime_part->priv->description);
+ if (((CamelDataWrapper *) mime_part)->mime_type) {
+ charset = camel_content_type_param (((CamelDataWrapper *) mime_part)->mime_type, "charset");
+ charset = camel_iconv_charset_name (charset);
+ } else
+ charset = NULL;
+ mime_part->priv->description = g_strstrip (camel_header_decode_string (value, charset));
+ break;
+ case HEADER_DISPOSITION:
+ mime_part_set_disposition (mime_part, value);
+ break;
+ case HEADER_CONTENT_ID:
+ g_free (mime_part->priv->content_id);
+ mime_part->priv->content_id = camel_header_contentid_decode (value);
+ break;
+ case HEADER_ENCODING:
+ text = camel_header_token_decode (value);
+ mime_part->priv->encoding = camel_transfer_encoding_from_string (text);
+ g_free (text);
+ break;
+ case HEADER_CONTENT_MD5:
+ g_free (mime_part->priv->content_md5);
+ mime_part->priv->content_md5 = g_strdup (value);
+ break;
+ case HEADER_CONTENT_LOCATION:
+ g_free (mime_part->priv->content_location);
+ mime_part->priv->content_location = camel_header_location_decode (value);
+ break;
+ case HEADER_CONTENT_TYPE:
+ if (((CamelDataWrapper *) mime_part)->mime_type)
+ camel_content_type_unref (((CamelDataWrapper *) mime_part)->mime_type);
+ ((CamelDataWrapper *) mime_part)->mime_type = camel_content_type_decode (value);
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+mime_part_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONTENT_ID:
+ camel_mime_part_set_content_id (
+ CAMEL_MIME_PART (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_CONTENT_MD5:
+ camel_mime_part_set_content_md5 (
+ CAMEL_MIME_PART (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_CONTENT_LOCATION:
+ camel_mime_part_set_content_location (
+ CAMEL_MIME_PART (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_DESCRIPTION:
+ camel_mime_part_set_description (
+ CAMEL_MIME_PART (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_DISPOSITION:
+ camel_mime_part_set_disposition (
+ CAMEL_MIME_PART (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mime_part_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONTENT_ID:
+ g_value_set_string (
+ value, camel_mime_part_get_content_id (
+ CAMEL_MIME_PART (object)));
+ return;
+
+ case PROP_CONTENT_MD5:
+ g_value_set_string (
+ value, camel_mime_part_get_content_md5 (
+ CAMEL_MIME_PART (object)));
+ return;
+
+ case PROP_CONTENT_LOCATION:
+ g_value_set_string (
+ value, camel_mime_part_get_content_location (
+ CAMEL_MIME_PART (object)));
+ return;
+
+ case PROP_DESCRIPTION:
+ g_value_set_string (
+ value, camel_mime_part_get_description (
+ CAMEL_MIME_PART (object)));
+ return;
+
+ case PROP_DISPOSITION:
+ g_value_set_string (
+ value, camel_mime_part_get_disposition (
+ CAMEL_MIME_PART (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mime_part_finalize (GObject *object)
+{
+ CamelMimePartPrivate *priv;
+
+ priv = CAMEL_MIME_PART_GET_PRIVATE (object);
+
+ g_free (priv->description);
+ g_free (priv->content_id);
+ g_free (priv->content_md5);
+ g_free (priv->content_location);
+
+ g_list_free_full (priv->content_languages, (GDestroyNotify) g_free);
+ camel_content_disposition_unref (priv->disposition);
+
+ camel_header_raw_clear (&CAMEL_MIME_PART (object)->headers);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_mime_part_parent_class)->finalize (object);
+}
+
+static void
+mime_part_add_header (CamelMedium *medium,
+ const gchar *name,
+ gconstpointer value)
+{
+ CamelMimePart *part = CAMEL_MIME_PART (medium);
+
+ /* Try to parse the header pair. If it corresponds to something */
+ /* known, the job is done in the parsing routine. If not, */
+ /* we simply add the header in a raw fashion */
+
+ /* If it was one of the headers we handled, it must be unique, set it instead of add */
+ if (mime_part_process_header (medium, name, value))
+ camel_header_raw_replace (&part->headers, name, value, -1);
+ else
+ camel_header_raw_append (&part->headers, name, value, -1);
+}
+
+static void
+mime_part_set_header (CamelMedium *medium,
+ const gchar *name,
+ gconstpointer value)
+{
+ CamelMimePart *part = CAMEL_MIME_PART (medium);
+
+ mime_part_process_header (medium, name, value);
+ camel_header_raw_replace (&part->headers, name, value, -1);
+}
+
+static void
+mime_part_remove_header (CamelMedium *medium,
+ const gchar *name)
+{
+ CamelMimePart *part = (CamelMimePart *) medium;
+
+ mime_part_process_header (medium, name, NULL);
+ camel_header_raw_remove (&part->headers, name);
+}
+
+static gconstpointer
+mime_part_get_header (CamelMedium *medium,
+ const gchar *name)
+{
+ CamelMimePart *part = (CamelMimePart *) medium;
+ const gchar *value;
+
+ value = camel_header_raw_find (&part->headers, name, NULL);
+
+ /* Skip leading whitespace. */
+ while (value != NULL && g_ascii_isspace (*value))
+ value++;
+
+ return value;
+}
+
+static GArray *
+mime_part_get_headers (CamelMedium *medium)
+{
+ CamelMimePart *part = (CamelMimePart *) medium;
+ GArray *headers;
+ CamelMediumHeader header;
+ struct _camel_header_raw *h;
+
+ headers = g_array_new (FALSE, FALSE, sizeof (CamelMediumHeader));
+ for (h = part->headers; h; h = h->next) {
+ header.name = h->name;
+ header.value = h->value;
+ g_array_append_val (headers, header);
+ }
+
+ return headers;
+}
+
+static void
+mime_part_free_headers (CamelMedium *medium,
+ GArray *headers)
+{
+ g_array_free (headers, TRUE);
+}
+
+static void
+mime_part_set_content (CamelMedium *medium,
+ CamelDataWrapper *content)
+{
+ CamelDataWrapper *mime_part = CAMEL_DATA_WRAPPER (medium);
+ CamelMediumClass *medium_class;
+ CamelContentType *content_type;
+
+ /* Chain up to parent's set_content() method. */
+ medium_class = CAMEL_MEDIUM_CLASS (camel_mime_part_parent_class);
+ medium_class->set_content (medium, content);
+
+ content_type = camel_data_wrapper_get_mime_type_field (content);
+ if (mime_part->mime_type != content_type) {
+ gchar *txt;
+
+ txt = camel_content_type_format (content_type);
+ camel_medium_set_header (medium, "Content-Type", txt);
+ g_free (txt);
+ }
+}
+
+static gssize
+mime_part_write_to_stream_sync (CamelDataWrapper *dw,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimePart *mp = CAMEL_MIME_PART (dw);
+ CamelMedium *medium = CAMEL_MEDIUM (dw);
+ CamelStream *ostream = stream;
+ CamelDataWrapper *content;
+ gssize total = 0;
+ gssize count;
+ gint errnosav;
+
+ d (printf ("mime_part::write_to_stream\n"));
+
+ /* FIXME: something needs to be done about this ... */
+ /* TODO: content-languages header? */
+
+ if (mp->headers) {
+ struct _camel_header_raw *h = mp->headers;
+ gchar *val;
+ gssize (*writefn) (
+ gpointer stream,
+ const gchar *name,
+ const gchar *value,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* fold/write the headers. But dont fold headers that are already formatted
+ * (e.g. ones with parameter-lists, that we know about, and have created) */
+ while (h) {
+ val = h->value;
+ if (val == NULL) {
+ g_warning ("h->value is NULL here for %s", h->name);
+ count = 0;
+ } else if ((writefn = g_hash_table_lookup (header_formatted_table, h->name)) == NULL) {
+ val = camel_header_fold (val, strlen (h->name));
+ count = write_header (
+ stream, h->name, val,
+ cancellable, error);
+ g_free (val);
+ } else {
+ count = writefn (
+ stream, h->name, h->value,
+ cancellable, error);
+ }
+ if (count == -1)
+ return -1;
+ total += count;
+ h = h->next;
+ }
+ }
+
+ count = camel_stream_write (stream, "\n", 1, cancellable, error);
+ if (count == -1)
+ return -1;
+ total += count;
+
+ content = camel_medium_get_content (medium);
+ if (content) {
+ CamelMimeFilter *filter = NULL;
+ CamelStream *filter_stream = NULL;
+ CamelMimeFilter *charenc = NULL;
+ const gchar *content_charset = NULL;
+ const gchar *part_charset = NULL;
+ gboolean reencode = FALSE;
+ const gchar *filename;
+
+ if (camel_content_type_is (dw->mime_type, "text", "*")) {
+ content_charset = camel_content_type_param (content->mime_type, "charset");
+ part_charset = camel_content_type_param (dw->mime_type, "charset");
+
+ if (content_charset && part_charset) {
+ content_charset = camel_iconv_charset_name (content_charset);
+ part_charset = camel_iconv_charset_name (part_charset);
+ }
+ }
+
+ if (mp->priv->encoding != content->encoding) {
+ gchar *content;
+
+ switch (mp->priv->encoding) {
+ case CAMEL_TRANSFER_ENCODING_BASE64:
+ filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_BASE64_ENC);
+ break;
+ case CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE:
+ filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_QP_ENC);
+ break;
+ case CAMEL_TRANSFER_ENCODING_UUENCODE:
+ filename = camel_mime_part_get_filename (mp);
+ if (filename == NULL)
+ filename = "untitled";
+
+ content = g_strdup_printf (
+ "begin 644 %s\n", filename);
+ count = camel_stream_write_string (
+ ostream, content, cancellable, error);
+ g_free (content);
+
+ if (count == -1)
+ return -1;
+
+ total += count;
+ filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_UU_ENC);
+ break;
+ default:
+ /* content is encoded but the part doesn't want to be... */
+ reencode = TRUE;
+ break;
+ }
+ }
+
+ if (content_charset && part_charset && part_charset != content_charset)
+ charenc = camel_mime_filter_charset_new (content_charset, part_charset);
+
+ if (filter || charenc) {
+ filter_stream = camel_stream_filter_new (stream);
+
+ /* if we have a character encoder, add that always */
+ if (charenc) {
+ camel_stream_filter_add (
+ CAMEL_STREAM_FILTER (filter_stream), charenc);
+ g_object_unref (charenc);
+ }
+
+ if (filter) {
+ camel_stream_filter_add (
+ CAMEL_STREAM_FILTER (filter_stream), filter);
+ g_object_unref (filter);
+ }
+
+ stream = filter_stream;
+
+ reencode = TRUE;
+ }
+
+ if (reencode)
+ count = camel_data_wrapper_decode_to_stream_sync (
+ content, stream, cancellable, error);
+ else
+ count = camel_data_wrapper_write_to_stream_sync (
+ content, stream, cancellable, error);
+
+ if (filter_stream) {
+ errnosav = errno;
+ camel_stream_flush (stream, NULL, NULL);
+ g_object_unref (filter_stream);
+ errno = errnosav;
+ }
+
+ if (count == -1)
+ return -1;
+
+ total += count;
+
+ if (reencode && mp->priv->encoding == CAMEL_TRANSFER_ENCODING_UUENCODE) {
+ count = camel_stream_write (
+ ostream, "end\n", 4, cancellable, error);
+ if (count == -1)
+ return -1;
+ total += count;
+ }
+ } else {
+ g_warning ("No content for medium, nothing to write");
+ }
+
+ return total;
+}
+
+static gboolean
+mime_part_construct_from_stream_sync (CamelDataWrapper *dw,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimeParser *parser;
+ gboolean success;
+
+ d (printf ("mime_part::construct_from_stream()\n"));
+
+ parser = camel_mime_parser_new ();
+ if (camel_mime_parser_init_with_stream (parser, stream, error) == -1) {
+ success = FALSE;
+ } else {
+ success = camel_mime_part_construct_from_parser_sync (
+ CAMEL_MIME_PART (dw), parser, cancellable, error);
+ }
+ g_object_unref (parser);
+
+ return success;
+}
+
+static gssize
+mime_part_write_to_output_stream_sync (CamelDataWrapper *dw,
+ GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimePart *mp = CAMEL_MIME_PART (dw);
+ CamelMedium *medium = CAMEL_MEDIUM (dw);
+ CamelDataWrapper *content;
+ gsize bytes_written;
+ gssize total = 0;
+ gssize result;
+ gboolean success;
+
+ d (printf ("mime_part::write_to_stream\n"));
+
+ /* FIXME: something needs to be done about this ... */
+ /* TODO: content-languages header? */
+
+ if (mp->headers) {
+ struct _camel_header_raw *h = mp->headers;
+ gchar *val;
+ gssize (*writefn) (
+ gpointer stream,
+ const gchar *name,
+ const gchar *value,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* fold/write the headers. But dont fold headers that are already formatted
+ * (e.g. ones with parameter-lists, that we know about, and have created) */
+ while (h) {
+ val = h->value;
+ if (val == NULL) {
+ g_warning ("h->value is NULL here for %s", h->name);
+ bytes_written = 0;
+ result = 0;
+ } else if ((writefn = g_hash_table_lookup (header_formatted_table, h->name)) == NULL) {
+ val = camel_header_fold (val, strlen (h->name));
+ result = write_header (
+ output_stream, h->name, val,
+ cancellable, error);
+ g_free (val);
+ } else {
+ result = writefn (
+ output_stream, h->name, h->value,
+ cancellable, error);
+ }
+ if (result == -1)
+ return -1;
+ total += result;
+ h = h->next;
+ }
+ }
+
+ success = g_output_stream_write_all (
+ output_stream, "\n", 1,
+ &bytes_written, cancellable, error);
+ if (!success)
+ return -1;
+ total += (gssize) bytes_written;
+
+ content = camel_medium_get_content (medium);
+ if (content) {
+ CamelMimeFilter *filter = NULL;
+ GOutputStream *filter_stream;
+ const gchar *content_charset = NULL;
+ const gchar *part_charset = NULL;
+ gboolean content_type_is_text;
+ gboolean uuencoded = FALSE;
+ gboolean reencode = FALSE;
+ const gchar *filename;
+
+ content_type_is_text =
+ camel_content_type_is (dw->mime_type, "text", "*");
+
+ if (content_type_is_text) {
+ content_charset = camel_content_type_param (content->mime_type, "charset");
+ part_charset = camel_content_type_param (dw->mime_type, "charset");
+
+ if (content_charset && part_charset) {
+ content_charset = camel_iconv_charset_name (content_charset);
+ part_charset = camel_iconv_charset_name (part_charset);
+ }
+ }
+
+ if (mp->priv->encoding != content->encoding) {
+ gchar *content;
+
+ switch (mp->priv->encoding) {
+ case CAMEL_TRANSFER_ENCODING_BASE64:
+ filter = camel_mime_filter_basic_new (
+ CAMEL_MIME_FILTER_BASIC_BASE64_ENC);
+ break;
+ case CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE:
+ filter = camel_mime_filter_basic_new (
+ CAMEL_MIME_FILTER_BASIC_QP_ENC);
+ break;
+ case CAMEL_TRANSFER_ENCODING_UUENCODE:
+ filename = camel_mime_part_get_filename (mp);
+ if (filename == NULL)
+ filename = "untitled";
+
+ content = g_strdup_printf (
+ "begin 644 %s\n", filename);
+ success = g_output_stream_write_all (
+ output_stream,
+ content, strlen (content),
+ &bytes_written, cancellable, error);
+ g_free (content);
+
+ if (!success)
+ return -1;
+
+ uuencoded = TRUE;
+
+ total += bytes_written;
+ filter = camel_mime_filter_basic_new (
+ CAMEL_MIME_FILTER_BASIC_UU_ENC);
+ break;
+ default:
+ /* content is encoded but the part doesn't want to be... */
+ reencode = TRUE;
+ break;
+ }
+ }
+
+ filter_stream = g_object_ref (output_stream);
+
+ if (content_charset && part_charset && part_charset != content_charset) {
+ CamelMimeFilter *charenc;
+ GOutputStream *temp_stream;
+
+ charenc = camel_mime_filter_charset_new (
+ content_charset, part_charset);
+ temp_stream = camel_filter_output_stream_new (
+ filter_stream, charenc);
+ g_filter_output_stream_set_close_base_stream (
+ G_FILTER_OUTPUT_STREAM (temp_stream), FALSE);
+ g_object_unref (charenc);
+
+ g_object_unref (filter_stream);
+ filter_stream = temp_stream;
+
+ reencode = TRUE;
+ }
+
+ if (filter != NULL) {
+ GOutputStream *temp_stream;
+
+ temp_stream = camel_filter_output_stream_new (
+ filter_stream, filter);
+ g_filter_output_stream_set_close_base_stream (
+ G_FILTER_OUTPUT_STREAM (temp_stream), FALSE);
+ g_object_unref (filter);
+
+ g_object_unref (filter_stream);
+ filter_stream = temp_stream;
+
+ reencode = TRUE;
+ }
+
+ if (reencode)
+ result = camel_data_wrapper_decode_to_output_stream_sync (
+ content, filter_stream, cancellable, error);
+ else
+ result = camel_data_wrapper_write_to_output_stream_sync (
+ content, filter_stream, cancellable, error);
+
+ g_object_unref (filter_stream);
+
+ if (result == -1)
+ return -1;
+
+ total += result;
+
+ if (uuencoded) {
+ success = g_output_stream_write_all (
+ output_stream, "end\n", 4,
+ &bytes_written, cancellable, error);
+ if (!success)
+ return -1;
+ total += (gssize) bytes_written;
+ }
+ } else {
+ g_warning ("No content for medium, nothing to write");
+ }
+
+ return total;
+}
+
+static gboolean
+mime_part_construct_from_input_stream_sync (CamelDataWrapper *dw,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimeParser *parser;
+ gboolean success;
+
+ parser = camel_mime_parser_new ();
+ camel_mime_parser_init_with_input_stream (parser, input_stream);
+
+ success = camel_mime_part_construct_from_parser_sync (
+ CAMEL_MIME_PART (dw), parser, cancellable, error);
+
+ g_object_unref (parser);
+
+ return success;
+}
+
+static gboolean
+mime_part_construct_from_parser_sync (CamelMimePart *mime_part,
+ CamelMimeParser *parser,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelDataWrapper *dw = (CamelDataWrapper *) mime_part;
+ struct _camel_header_raw *headers;
+ const gchar *content;
+ gchar *buf;
+ gsize len;
+ gint err;
+ gboolean success = TRUE;
+
+ switch (camel_mime_parser_step (parser, &buf, &len)) {
+ case CAMEL_MIME_PARSER_STATE_MESSAGE:
+ /* set the default type of a message always */
+ if (dw->mime_type)
+ camel_content_type_unref (dw->mime_type);
+ dw->mime_type = camel_content_type_decode ("message/rfc822");
+ /* coverity[fallthrough] */
+
+ case CAMEL_MIME_PARSER_STATE_HEADER:
+ case CAMEL_MIME_PARSER_STATE_MULTIPART:
+ /* we have the headers, build them into 'us' */
+ headers = camel_mime_parser_headers_raw (parser);
+
+ /* if content-type exists, process it first, set for fallback charset in headers */
+ content = camel_header_raw_find (&headers, "content-type", NULL);
+ if (content)
+ mime_part_process_header ((CamelMedium *) dw, "content-type", content);
+
+ while (headers) {
+ if (g_ascii_strcasecmp (headers->name, "content-type") == 0
+ && headers->value != content)
+ camel_medium_add_header ((CamelMedium *) dw, "X-Invalid-Content-Type", headers->value);
+ else
+ camel_medium_add_header ((CamelMedium *) dw, headers->name, headers->value);
+ headers = headers->next;
+ }
+
+ success = camel_mime_part_construct_content_from_parser (
+ mime_part, parser, cancellable, error);
+ break;
+ default:
+ g_warning ("Invalid state encountered???: %u", camel_mime_parser_state (parser));
+ }
+
+ err = camel_mime_parser_errno (parser);
+ if (err != 0) {
+ errno = err;
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ success = FALSE;
+ }
+
+ return success;
+}
+
+static void
+camel_mime_part_class_init (CamelMimePartClass *class)
+{
+ GObjectClass *object_class;
+ CamelMediumClass *medium_class;
+ CamelDataWrapperClass *data_wrapper_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimePartPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = mime_part_set_property;
+ object_class->get_property = mime_part_get_property;
+ object_class->finalize = mime_part_finalize;
+
+ medium_class = CAMEL_MEDIUM_CLASS (class);
+ medium_class->add_header = mime_part_add_header;
+ medium_class->set_header = mime_part_set_header;
+ medium_class->remove_header = mime_part_remove_header;
+ medium_class->get_header = mime_part_get_header;
+ medium_class->get_headers = mime_part_get_headers;
+ medium_class->free_headers = mime_part_free_headers;
+ medium_class->set_content = mime_part_set_content;
+
+ data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (class);
+ data_wrapper_class->write_to_stream_sync = mime_part_write_to_stream_sync;
+ data_wrapper_class->construct_from_stream_sync = mime_part_construct_from_stream_sync;
+ data_wrapper_class->write_to_output_stream_sync = mime_part_write_to_output_stream_sync;
+ data_wrapper_class->construct_from_input_stream_sync = mime_part_construct_from_input_stream_sync;
+
+ class->construct_from_parser_sync = mime_part_construct_from_parser_sync;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CONTENT_ID,
+ g_param_spec_string (
+ "content-id",
+ "Content ID",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CONTENT_MD5,
+ g_param_spec_string (
+ "content-md5",
+ "Content MD5",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_DESCRIPTION,
+ g_param_spec_string (
+ "description",
+ "Description",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_DISPOSITION,
+ g_param_spec_string (
+ "disposition",
+ "Disposition",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+
+ init_header_name_table ();
+}
+
+static void
+camel_mime_part_init (CamelMimePart *mime_part)
+{
+ CamelDataWrapper *data_wrapper;
+
+ mime_part->priv = CAMEL_MIME_PART_GET_PRIVATE (mime_part);
+ mime_part->priv->encoding = CAMEL_TRANSFER_ENCODING_DEFAULT;
+
+ data_wrapper = CAMEL_DATA_WRAPPER (mime_part);
+
+ if (data_wrapper->mime_type != NULL)
+ camel_content_type_unref (data_wrapper->mime_type);
+
+ data_wrapper->mime_type = camel_content_type_new ("text", "plain");
+}
+
+/**
+ * camel_mime_part_new:
+ *
+ * Create a new MIME part.
+ *
+ * Returns: a new #CamelMimePart
+ **/
+CamelMimePart *
+camel_mime_part_new (void)
+{
+ return g_object_new (CAMEL_TYPE_MIME_PART, NULL);
+}
+
+/**
+ * camel_mime_part_set_content:
+ * @mime_part: a #CamelMimePart
+ * @data: (array length=length) (nullable): data to put into the part
+ * @length: length of @data
+ * @type: (nullable): Content-Type of the data
+ *
+ * Utility function used to set the content of a mime part object to
+ * be the provided data. If @length is 0, this routine can be used as
+ * a way to remove old content (in which case @data and @type are
+ * ignored and may be %NULL).
+ **/
+void
+camel_mime_part_set_content (CamelMimePart *mime_part,
+ const gchar *data,
+ gint length,
+ const gchar *type) /* why on earth is the type last? */
+{
+ CamelMedium *medium = CAMEL_MEDIUM (mime_part);
+
+ if (length) {
+ CamelDataWrapper *dw;
+ CamelStream *stream;
+
+ dw = camel_data_wrapper_new ();
+ camel_data_wrapper_set_mime_type (dw, type);
+ stream = camel_stream_mem_new_with_buffer (data, length);
+ camel_data_wrapper_construct_from_stream_sync (
+ dw, stream, NULL, NULL);
+ g_object_unref (stream);
+ camel_medium_set_content (medium, dw);
+ g_object_unref (dw);
+ } else
+ camel_medium_set_content (medium, NULL);
+}
+
+/**
+ * camel_mime_part_get_content_disposition:
+ * @mime_part: a #CamelMimePart
+ *
+ * Get the disposition of the MIME part as a structure.
+ * Returned pointer is owned by @mime_part.
+ *
+ * Returns: the disposition structure
+ *
+ * Since: 2.30
+ **/
+const CamelContentDisposition *
+camel_mime_part_get_content_disposition (CamelMimePart *mime_part)
+{
+ g_return_val_if_fail (mime_part != NULL, NULL);
+
+ return mime_part->priv->disposition;
+}
+
+/**
+ * camel_mime_part_get_content_id:
+ * @mime_part: a #CamelMimePart
+ *
+ * Get the content-id field of a MIME part.
+ *
+ * Returns: the content-id field of the MIME part
+ **/
+const gchar *
+camel_mime_part_get_content_id (CamelMimePart *mime_part)
+{
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (mime_part), NULL);
+
+ return mime_part->priv->content_id;
+}
+
+/**
+ * camel_mime_part_set_content_id:
+ * @mime_part: a #CamelMimePart
+ * @contentid: content id
+ *
+ * Set the content-id field on a MIME part.
+ **/
+void
+camel_mime_part_set_content_id (CamelMimePart *mime_part,
+ const gchar *contentid)
+{
+ CamelMedium *medium;
+ gchar *cid, *id;
+
+ g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
+
+ medium = CAMEL_MEDIUM (mime_part);
+
+ if (contentid)
+ id = g_strstrip (g_strdup (contentid));
+ else
+ id = camel_header_msgid_generate (NULL);
+
+ cid = g_strdup_printf ("<%s>", id);
+ camel_medium_set_header (medium, "Content-ID", cid);
+ g_free (cid);
+
+ g_free (id);
+
+ g_object_notify (G_OBJECT (mime_part), "content-id");
+}
+
+/**
+ * camel_mime_part_get_content_location:
+ * @mime_part: a #CamelMimePart
+ *
+ * Get the content-location field of a MIME part.
+ *
+ * Returns: the content-location field of a MIME part
+ **/
+const gchar *
+camel_mime_part_get_content_location (CamelMimePart *mime_part)
+{
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (mime_part), NULL);
+
+ return mime_part->priv->content_location;
+}
+
+/**
+ * camel_mime_part_set_content_location:
+ * @mime_part: a #CamelMimePart
+ * @location: the content-location value of the MIME part
+ *
+ * Set the content-location field of the MIME part.
+ **/
+void
+camel_mime_part_set_content_location (CamelMimePart *mime_part,
+ const gchar *location)
+{
+ CamelMedium *medium;
+
+ g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
+
+ medium = CAMEL_MEDIUM (mime_part);
+
+ /* FIXME: this should perform content-location folding */
+ camel_medium_set_header (medium, "Content-Location", location);
+
+ g_object_notify (G_OBJECT (mime_part), "content-location");
+}
+
+/**
+ * camel_mime_part_get_content_md5:
+ * @mime_part: a #CamelMimePart
+ *
+ * Get the content-md5 field of the MIME part.
+ *
+ * Returns: the content-md5 field of the MIME part
+ **/
+const gchar *
+camel_mime_part_get_content_md5 (CamelMimePart *mime_part)
+{
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (mime_part), NULL);
+
+ return mime_part->priv->content_md5;
+}
+
+/**
+ * camel_mime_part_set_content_md5:
+ * @mime_part: a #CamelMimePart
+ * @md5sum: the md5sum of the MIME part
+ *
+ * Set the content-md5 field of the MIME part.
+ **/
+void
+camel_mime_part_set_content_md5 (CamelMimePart *mime_part,
+ const gchar *content_md5)
+{
+ CamelMedium *medium;
+
+ g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
+
+ medium = CAMEL_MEDIUM (mime_part);
+
+ camel_medium_set_header (medium, "Content-MD5", content_md5);
+}
+
+/**
+ * camel_mime_part_get_content_languages:
+ * @mime_part: a #CamelMimePart
+ *
+ * Get the Content-Languages set on the MIME part.
+ *
+ * Returns: (element-type utf8) (transfer none): a #GList of languages
+ **/
+const GList *
+camel_mime_part_get_content_languages (CamelMimePart *mime_part)
+{
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (mime_part), NULL);
+
+ return mime_part->priv->content_languages;
+}
+
+/**
+ * camel_mime_part_set_content_languages:
+ * @mime_part: a #CamelMimePart
+ * @content_languages: (element-type utf8): list of languages
+ *
+ * Set the Content-Languages field of a MIME part.
+ **/
+void
+camel_mime_part_set_content_languages (CamelMimePart *mime_part,
+ GList *content_languages)
+{
+ g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
+
+ g_list_free_full (
+ mime_part->priv->content_languages,
+ (GDestroyNotify) g_free);
+
+ mime_part->priv->content_languages = content_languages;
+
+ /* FIXME: translate to a header and set it */
+}
+
+/**
+ * camel_mime_part_get_content_type:
+ * @mime_part: a #CamelMimePart
+ *
+ * Get the Content-Type of a MIME part.
+ *
+ * Returns: (transfer none): the parsed #CamelContentType of the MIME part
+ **/
+CamelContentType *
+camel_mime_part_get_content_type (CamelMimePart *mime_part)
+{
+ CamelDataWrapper *data_wrapper;
+
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (mime_part), NULL);
+
+ data_wrapper = CAMEL_DATA_WRAPPER (mime_part);
+
+ return camel_data_wrapper_get_mime_type_field (data_wrapper);
+}
+
+/**
+ * camel_mime_part_set_content_type:
+ * @mime_part: a #CamelMimePart
+ * @content_type: content-type string
+ *
+ * Set the content-type on a MIME part.
+ **/
+void
+camel_mime_part_set_content_type (CamelMimePart *mime_part,
+ const gchar *content_type)
+{
+ CamelMedium *medium;
+
+ g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
+
+ medium = CAMEL_MEDIUM (mime_part);
+
+ camel_medium_set_header (medium, "Content-Type", content_type);
+}
+
+/**
+ * camel_mime_part_get_description:
+ * @mime_part: a #CamelMimePart
+ *
+ * Get the description of the MIME part.
+ *
+ * Returns: the description
+ **/
+const gchar *
+camel_mime_part_get_description (CamelMimePart *mime_part)
+{
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (mime_part), NULL);
+
+ return mime_part->priv->description;
+}
+
+/**
+ * camel_mime_part_set_description:
+ * @mime_part: a #CamelMimePart
+ * @description: description of the MIME part
+ *
+ * Set a description on the MIME part.
+ **/
+void
+camel_mime_part_set_description (CamelMimePart *mime_part,
+ const gchar *description)
+{
+ CamelMedium *medium;
+ gchar *text;
+
+ g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
+ g_return_if_fail (description != NULL);
+
+ medium = CAMEL_MEDIUM (mime_part);
+
+ text = camel_header_encode_string ((guchar *) description);
+ camel_medium_set_header (medium, "Content-Description", text);
+ g_free (text);
+
+ g_object_notify (G_OBJECT (mime_part), "description");
+}
+
+/**
+ * camel_mime_part_get_disposition:
+ * @mime_part: a #CamelMimePart
+ *
+ * Get the disposition of the MIME part.
+ *
+ * Returns: the disposition
+ **/
+const gchar *
+camel_mime_part_get_disposition (CamelMimePart *mime_part)
+{
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (mime_part), NULL);
+
+ if (mime_part->priv->disposition)
+ return mime_part->priv->disposition->disposition;
+ else
+ return NULL;
+}
+
+/**
+ * camel_mime_part_set_disposition:
+ * @mime_part: a #CamelMimePart
+ * @disposition: disposition of the MIME part
+ *
+ * Set a disposition on the MIME part.
+ **/
+void
+camel_mime_part_set_disposition (CamelMimePart *mime_part,
+ const gchar *disposition)
+{
+ CamelMedium *medium;
+ gchar *text;
+
+ g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
+
+ medium = CAMEL_MEDIUM (mime_part);
+
+ /* we poke in a new disposition (so we dont lose 'filename', etc) */
+ if (mime_part->priv->disposition == NULL)
+ mime_part_set_disposition (mime_part, disposition);
+
+ if (mime_part->priv->disposition != NULL) {
+ g_free (mime_part->priv->disposition->disposition);
+ mime_part->priv->disposition->disposition = g_strdup (disposition);
+ }
+
+ text = camel_content_disposition_format (mime_part->priv->disposition);
+ camel_medium_set_header (medium, "Content-Disposition", text);
+ g_free (text);
+
+ g_object_notify (G_OBJECT (mime_part), "disposition");
+}
+
+/**
+ * camel_mime_part_get_encoding:
+ * @mime_part: a #CamelMimePart
+ *
+ * Get the Content-Transfer-Encoding of a MIME part.
+ *
+ * Returns: a #CamelTransferEncoding
+ **/
+CamelTransferEncoding
+camel_mime_part_get_encoding (CamelMimePart *mime_part)
+{
+ g_return_val_if_fail (
+ CAMEL_IS_MIME_PART (mime_part),
+ CAMEL_TRANSFER_ENCODING_DEFAULT);
+
+ return mime_part->priv->encoding;
+}
+
+/**
+ * camel_mime_part_set_encoding:
+ * @mime_part: a #CamelMimePart
+ * @encoding: a #CamelTransferEncoding
+ *
+ * Set the Content-Transfer-Encoding to use on a MIME part.
+ **/
+void
+camel_mime_part_set_encoding (CamelMimePart *mime_part,
+ CamelTransferEncoding encoding)
+{
+ CamelMedium *medium;
+ const gchar *text;
+
+ g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
+
+ medium = CAMEL_MEDIUM (mime_part);
+
+ text = camel_transfer_encoding_to_string (encoding);
+ camel_medium_set_header (medium, "Content-Transfer-Encoding", text);
+}
+
+/**
+ * camel_mime_part_get_filename:
+ * @mime_part: a #CamelMimePart
+ *
+ * Get the filename of a MIME part.
+ *
+ * Returns: the filename of the MIME part
+ **/
+const gchar *
+camel_mime_part_get_filename (CamelMimePart *mime_part)
+{
+ if (mime_part->priv->disposition) {
+ const gchar *name = camel_header_param (
+ mime_part->priv->disposition->params, "filename");
+ if (name)
+ return name;
+ }
+
+ return camel_content_type_param (
+ ((CamelDataWrapper *) mime_part)->mime_type, "name");
+}
+
+/**
+ * camel_mime_part_set_filename:
+ * @mime_part: a #CamelMimePart
+ * @filename: filename given to the MIME part
+ *
+ * Set the filename on a MIME part.
+ **/
+void
+camel_mime_part_set_filename (CamelMimePart *mime_part,
+ const gchar *filename)
+{
+ CamelDataWrapper *dw;
+ CamelMedium *medium;
+ gchar *str;
+
+ medium = CAMEL_MEDIUM (mime_part);
+
+ if (mime_part->priv->disposition == NULL)
+ mime_part->priv->disposition =
+ camel_content_disposition_decode ("attachment");
+
+ camel_header_set_param (
+ &mime_part->priv->disposition->params, "filename", filename);
+ str = camel_content_disposition_format (mime_part->priv->disposition);
+
+ camel_medium_set_header (medium, "Content-Disposition", str);
+ g_free (str);
+
+ dw = (CamelDataWrapper *) mime_part;
+ if (!dw->mime_type)
+ dw->mime_type = camel_content_type_new ("application", "octet-stream");
+ camel_content_type_set_param (dw->mime_type, "name", filename);
+ str = camel_content_type_format (dw->mime_type);
+ camel_medium_set_header (medium, "Content-Type", str);
+ g_free (str);
+}
+
+/**
+ * camel_mime_part_construct_from_parser_sync:
+ * @mime_part: a #CamelMimePart
+ * @parser: a #CamelMimeParser
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Constructs a MIME part from a parser.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_mime_part_construct_from_parser_sync (CamelMimePart *mime_part,
+ CamelMimeParser *parser,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimePartClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (mime_part), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_PARSER (parser), FALSE);
+
+ class = CAMEL_MIME_PART_GET_CLASS (mime_part);
+ g_return_val_if_fail (class->construct_from_parser_sync != NULL, FALSE);
+
+ success = class->construct_from_parser_sync (
+ mime_part, parser, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ mime_part, construct_from_parser_sync, success, error);
+
+ return success;
+}
+
+/* Helper for camel_mime_part_construct_from_parser() */
+static void
+mime_part_construct_from_parser_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_mime_part_construct_from_parser_sync (
+ CAMEL_MIME_PART (source_object),
+ async_context->parser,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_mime_part_construct_from_parser:
+ * @mime_part: a #CamelMimePart
+ * @parser: a #CamelMimeParser
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously constructs a MIME part from a parser.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_mime_part_construct_from_parser_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_mime_part_construct_from_parser (CamelMimePart *mime_part,
+ CamelMimeParser *parser,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
+ g_return_if_fail (CAMEL_IS_MIME_PARSER (parser));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->parser = g_object_ref (parser);
+
+ task = g_task_new (mime_part, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_mime_part_construct_from_parser);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, mime_part_construct_from_parser_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_mime_part_construct_from_parser_finish:
+ * @mime_part: a #CamelMimePart
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_mime_part_construct_from_parser().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_mime_part_construct_from_parser_finish (CamelMimePart *mime_part,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (mime_part), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, mime_part), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_mime_part_construct_from_parser), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/src/camel/camel-mime-part.h b/src/camel/camel-mime-part.h
new file mode 100644
index 000000000..bcacdcbda
--- /dev/null
+++ b/src/camel/camel-mime-part.h
@@ -0,0 +1,143 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/* camel-mime-part.h : class for a mime part
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_PART_H
+#define CAMEL_MIME_PART_H
+
+#include <camel/camel-medium.h>
+#include <camel/camel-mime-utils.h>
+#include <camel/camel-mime-parser.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MIME_PART \
+ (camel_mime_part_get_type ())
+#define CAMEL_MIME_PART(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MIME_PART, CamelMimePart))
+#define CAMEL_MIME_PART_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MIME_PART, CamelMimePartClass))
+#define CAMEL_IS_MIME_PART(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MIME_PART))
+#define CAMEL_IS_MIME_PART_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MIME_PART))
+#define CAMEL_MIME_PART_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MIME_PART, CamelMimePartClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMimePart CamelMimePart;
+typedef struct _CamelMimePartClass CamelMimePartClass;
+typedef struct _CamelMimePartPrivate CamelMimePartPrivate;
+
+struct _CamelMimePart {
+ CamelMedium parent;
+ CamelMimePartPrivate *priv;
+
+ struct _camel_header_raw *headers; /* mime headers */
+};
+
+struct _CamelMimePartClass {
+ CamelMediumClass parent_class;
+
+ /* Synchronous I/O Methods */
+ gboolean (*construct_from_parser_sync)
+ (CamelMimePart *mime_part,
+ CamelMimeParser *parser,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* Reserved slots. */
+ gpointer reserved[2];
+};
+
+GType camel_mime_part_get_type (void);
+CamelMimePart * camel_mime_part_new (void);
+void camel_mime_part_set_description (CamelMimePart *mime_part,
+ const gchar *description);
+const gchar * camel_mime_part_get_description (CamelMimePart *mime_part);
+void camel_mime_part_set_disposition (CamelMimePart *mime_part,
+ const gchar *disposition);
+const gchar * camel_mime_part_get_disposition (CamelMimePart *mime_part);
+const CamelContentDisposition *
+ camel_mime_part_get_content_disposition
+ (CamelMimePart *mime_part);
+void camel_mime_part_set_filename (CamelMimePart *mime_part,
+ const gchar *filename);
+const gchar * camel_mime_part_get_filename (CamelMimePart *mime_part);
+void camel_mime_part_set_content_id (CamelMimePart *mime_part,
+ const gchar *contentid);
+const gchar * camel_mime_part_get_content_id (CamelMimePart *mime_part);
+void camel_mime_part_set_content_md5 (CamelMimePart *mime_part,
+ const gchar *md5sum);
+const gchar * camel_mime_part_get_content_md5 (CamelMimePart *mime_part);
+void camel_mime_part_set_content_location
+ (CamelMimePart *mime_part,
+ const gchar *location);
+const gchar * camel_mime_part_get_content_location
+ (CamelMimePart *mime_part);
+void camel_mime_part_set_encoding (CamelMimePart *mime_part,
+ CamelTransferEncoding encoding);
+CamelTransferEncoding
+ camel_mime_part_get_encoding (CamelMimePart *mime_part);
+void camel_mime_part_set_content_languages
+ (CamelMimePart *mime_part,
+ GList *content_languages);
+const GList * camel_mime_part_get_content_languages
+ (CamelMimePart *mime_part);
+void camel_mime_part_set_content_type
+ (CamelMimePart *mime_part,
+ const gchar *content_type);
+CamelContentType *
+ camel_mime_part_get_content_type
+ (CamelMimePart *mime_part);
+void camel_mime_part_set_content (CamelMimePart *mime_part,
+ const gchar *data,
+ gint length,
+ const gchar *type);
+
+gboolean camel_mime_part_construct_from_parser_sync
+ (CamelMimePart *mime_part,
+ CamelMimeParser *parser,
+ GCancellable *cancellable,
+ GError **error);
+void camel_mime_part_construct_from_parser
+ (CamelMimePart *mime_part,
+ CamelMimeParser *parser,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_mime_part_construct_from_parser_finish
+ (CamelMimePart *mime_part,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_PART_H */
diff --git a/src/camel/camel-mime-utils.c b/src/camel/camel-mime-utils.c
new file mode 100644
index 000000000..7c33fb80b
--- /dev/null
+++ b/src/camel/camel-mime-utils.c
@@ -0,0 +1,5243 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/param.h> /* for MAXHOSTNAMELEN */
+#include <sys/stat.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 "camel-charset-map.h"
+#include "camel-iconv.h"
+#include "camel-mime-utils.h"
+#include "camel-net-utils.h"
+#ifdef G_OS_WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#ifdef HAVE_WSPIAPI_H
+#include <wspiapi.h>
+#endif
+#endif
+#include "camel-utf8.h"
+
+#ifdef G_OS_WIN32
+#ifdef gmtime_r
+#undef gmtime_r
+#endif
+
+/* The gmtime() in Microsoft's C library is MT-safe */
+#define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
+#endif
+
+#if !defined HAVE_LOCALTIME_R && !defined localtime_r
+# ifdef _LIBC
+# define localtime_r __localtime_r
+# else
+/* Approximate localtime_r as best we can in its absence. */
+# define localtime_r my_localtime_r
+static struct tm *localtime_r (const time_t *, struct tm *);
+static struct tm *
+localtime_r (t,
+ tp)
+ const time_t *t;
+ struct tm *tp;
+{
+ struct tm *l = localtime (t);
+ if (!l)
+ return 0;
+ *tp = *l;
+ return tp;
+}
+# endif /* !_LIBC */
+#endif /* HAVE_LOCALTIME_R && !defined (localtime_r) */
+
+/* for all non-essential warnings ... */
+#define w(x)
+
+#define d(x)
+#define d2(x)
+
+G_DEFINE_BOXED_TYPE (CamelContentType,
+ camel_content_type,
+ camel_content_type_ref,
+ camel_content_type_unref)
+
+G_DEFINE_BOXED_TYPE (CamelContentDisposition,
+ camel_content_disposition,
+ camel_content_disposition_ref,
+ camel_content_disposition_unref)
+
+G_DEFINE_BOXED_TYPE (CamelHeaderAddress,
+ camel_header_address,
+ camel_header_address_ref,
+ camel_header_address_unref)
+
+/**
+ * camel_mktime_utc:
+ * @tm: the #tm to convert to a calendar time representation
+ *
+ * Like mktime(3), but assumes UTC instead of local timezone.
+ *
+ * Returns: the calendar time representation of @tm
+ *
+ * Since: 3.4
+ **/
+time_t
+camel_mktime_utc (struct tm *tm)
+{
+ time_t tt;
+
+ tm->tm_isdst = -1;
+ tt = mktime (tm);
+
+#if defined (HAVE_TM_GMTOFF)
+ tt += tm->tm_gmtoff;
+#elif defined (HAVE_TIMEZONE)
+ if (tm->tm_isdst > 0) {
+#if defined (HAVE_ALTZONE)
+ tt -= altzone;
+#else
+ tt -= (timezone - 3600);
+#endif
+ } else
+ tt -= timezone;
+#endif
+
+ return tt;
+}
+
+/**
+ * camel_localtime_with_offset:
+ * @tt: the #time_t to convert
+ * @tm: the #tm to store the result in
+ * @offset: the #gint to store the offset in
+ *
+ * Converts the calendar time representation @tt to a broken-down
+ * time representation, stored in @tm, and provides the offset in
+ * seconds from UTC time, stored in @offset.
+ **/
+void
+camel_localtime_with_offset (time_t tt,
+ struct tm *tm,
+ gint *offset)
+{
+ localtime_r (&tt, tm);
+
+#if defined (HAVE_TM_GMTOFF)
+ *offset = tm->tm_gmtoff;
+#elif defined (HAVE_TIMEZONE)
+ if (tm->tm_isdst > 0) {
+#if defined (HAVE_ALTZONE)
+ *offset = -altzone;
+#else
+ *offset = -(timezone - 3600);
+#endif
+ } else
+ *offset = -timezone;
+#endif
+}
+
+#define CAMEL_UUENCODE_CHAR(c) ((c) ? (c) + ' ' : '`')
+#define CAMEL_UUDECODE_CHAR(c) (((c) - ' ') & 077)
+
+static const guchar tohex[16] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+};
+
+/**
+ * camel_uuencode_close:
+ * @in: (array length=len): input stream
+ * @len: input stream length
+ * @out: (inout) (array): output stream
+ * @uubuf: (inout) (array fixed-size=60): temporary buffer of 60 bytes
+ * @state: (inout): holds the number of bits that are stored in @save
+ * @save: (inout) (array length=state): leftover bits that have not yet been encoded
+ *
+ * Uuencodes a chunk of data. Call this when finished encoding data
+ * with camel_uuencode_step() to flush off the last little bit.
+ *
+ * Returns: the number of bytes encoded
+ **/
+gsize
+camel_uuencode_close (guchar *in,
+ gsize len,
+ guchar *out,
+ guchar *uubuf,
+ gint *state,
+ guint32 *save)
+{
+ register guchar *outptr, *bufptr;
+ register guint32 saved;
+ gint 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;
+ uufill++;
+ i++;
+ }
+
+ if (i == 3) {
+ /* convert 3 normal bytes into 4 uuencoded bytes */
+ guchar b0, b1, b2;
+
+ b0 = (saved >> 16) & 0xff;
+ 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) {
+ gint 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:
+ * @in: (array length=len): input stream
+ * @len: input stream length
+ * @out: (inout) (array): output stream
+ * @uubuf: (inout) (array fixed-size=60): temporary buffer of 60 bytes
+ * @state: (inout): holds the number of bits that are stored in @save
+ * @save: (inout) (array length=state): leftover bits that have not yet been encoded
+ *
+ * Uuencodes a chunk of data. 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).
+ *
+ * Returns: the number of bytes encoded
+ **/
+gsize
+camel_uuencode_step (guchar *in,
+ gsize len,
+ guchar *out,
+ guchar *uubuf,
+ gint *state,
+ guint32 *save)
+{
+ register guchar *inptr, *outptr, *bufptr;
+ guchar b0, b1, b2, *inend;
+ register guint32 saved;
+ gint uulen, i;
+
+ if (len == 0)
+ return 0;
+
+ inend = in + len;
+ outptr = out;
+ inptr = in;
+
+ saved = *save;
+ i = *state & 0xff;
+ uulen = (*state >> 8) & 0xff;
+
+ if ((len + uulen) < 45) {
+ /* not enough input to write a full uuencoded line */
+ bufptr = uubuf + ((uulen / 3) * 4);
+ } else {
+ bufptr = outptr + 1;
+
+ if (uulen > 0) {
+ /* copy the previous call's tmpbuf to outbuf */
+ memcpy (bufptr, uubuf, ((uulen / 3) * 4));
+ bufptr += ((uulen / 3) * 4);
+ }
+ }
+
+ if (i == 2) {
+ b0 = (saved >> 8) & 0xff;
+ b1 = saved & 0xff;
+ saved = 0;
+ i = 0;
+
+ goto skip2;
+ } else if (i == 1) {
+ if ((inptr + 2) < inend) {
+ b0 = saved & 0xff;
+ saved = 0;
+ i = 0;
+
+ goto skip1;
+ }
+
+ while (inptr < inend) {
+ saved = (saved << 8) | *inptr++;
+ i++;
+ }
+ }
+
+ while (inptr < inend) {
+ while (uulen < 45 && (inptr + 3) <= inend) {
+ b0 = *inptr++;
+ skip1:
+ b1 = *inptr++;
+ skip2:
+ b2 = *inptr++;
+
+ /* convert 3 normal bytes into 4 uuencoded bytes */
+ *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);
+
+ uulen += 3;
+ }
+
+ if (uulen >= 45) {
+ *outptr++ = CAMEL_UUENCODE_CHAR (uulen & 0xff);
+ outptr += ((45 / 3) * 4) + 1;
+
+ *outptr++ = '\n';
+ uulen = 0;
+
+ if ((inptr + 45) <= inend) {
+ /* we have enough input to output another full line */
+ bufptr = outptr + 1;
+ } else {
+ bufptr = uubuf;
+ }
+ } else {
+ /* not enough input to continue... */
+ for (i = 0, saved = 0; inptr < inend; i++)
+ saved = (saved << 8) | *inptr++;
+ }
+ }
+
+ *save = saved;
+ *state = ((uulen & 0xff) << 8) | (i & 0xff);
+
+ return outptr - out;
+}
+
+/**
+ * camel_uudecode_step:
+ * @in: (array length=inlen): input stream
+ * @inlen: max length of data to decode
+ * @out: (inout) (array): output stream
+ * @state: (inout): holds the number of bits that are stored in @save
+ * @save: (inout) (array length=state): leftover bits that have not yet been decoded
+ *
+ * Uudecodes a chunk of data. Performs a 'decode step' on a chunk of
+ * uuencoded data. Assumes the "begin mode filename" line has
+ * been stripped off.
+ *
+ * Returns: the number of bytes decoded
+ **/
+gsize
+camel_uudecode_step (guchar *in,
+ gsize len,
+ guchar *out,
+ gint *state,
+ guint32 *save)
+{
+ register guchar *inptr, *outptr;
+ guchar *inend, ch;
+ register guint32 saved;
+ gboolean last_was_eoln;
+ gint 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 = TRUE;
+
+ inptr++;
+ continue;
+ } else if (!uulen || last_was_eoln) {
+ /* first octet on a line is the uulen octet */
+ uulen = CAMEL_UUDECODE_CHAR (*inptr);
+ last_was_eoln = FALSE;
+ if (uulen == 0) {
+ *state |= CAMEL_UUDECODE_STATE_END;
+ break;
+ }
+
+ 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 */
+ guchar 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);
+ uulen -= 3;
+ } else {
+ gint orig_uulen = uulen;
+
+ if (orig_uulen >= 1) {
+ *outptr++ = CAMEL_UUDECODE_CHAR (b0) << 2 | CAMEL_UUDECODE_CHAR (b1) >> 4;
+ uulen--;
+ }
+
+ if (orig_uulen >= 2) {
+ *outptr++ = CAMEL_UUDECODE_CHAR (b1) << 4 | CAMEL_UUDECODE_CHAR (b2) >> 2;
+ uulen--;
+ }
+ }
+
+ i = 0;
+ saved = 0;
+ }
+ } else {
+ break;
+ }
+ }
+
+ *save = saved;
+ *state = (*state & CAMEL_UUDECODE_STATE_MASK) | ((uulen & 0xff) << 8) | (i & 0xff);
+
+ return outptr - out;
+}
+
+/**
+ * camel_quoted_encode_close:
+ * @in: (array length=len): input stream
+ * @len: length of the input
+ * @out: (inout) (array): output string
+ * @state: (inout): holds the number of bits that are stored in @save
+ * @save: (inout) (array length=state): leftover bits that have not yet been encoded
+ *
+ * Quoted-printable encodes a block of text. Call this when finished
+ * encoding data with camel_quoted_encode_step() to flush off
+ * the last little bit.
+ *
+ * Returns: the number of bytes encoded
+ **/
+gsize
+camel_quoted_encode_close (guchar *in,
+ gsize len,
+ guchar *out,
+ gint *state,
+ gint *save)
+{
+ register guchar *outptr = out;
+ gint 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;
+}
+
+/**
+ * camel_quoted_encode_step:
+ * @in: (array length=len): input stream
+ * @len: length of the input
+ * @out: (inout) (array): output string
+ * @state: (inout): holds the number of bits that are stored in @save
+ * @save: (inout) (array length=state): leftover bits that have not yet been encoded
+ *
+ * Quoted-printable encodes a block of text. Performs an 'encode
+ * step', saves left-over state in state and save (initialise to -1 on
+ * first invocation).
+ *
+ * Returns: the number of bytes encoded
+ **/
+gsize
+camel_quoted_encode_step (guchar *in,
+ gsize len,
+ guchar *out,
+ gint *statep,
+ gint *save)
+{
+ register guchar *inptr, *outptr, *inend;
+ guchar c;
+ register gint sofar = *save; /* keeps track of how many chars on a line */
+ register gint last = *statep; /* keeps track if last gchar to end was a space cr etc */
+
+ #define output_last() \
+ if (sofar + 3 > 74) { \
+ *outptr++ = '='; \
+ *outptr++ = '\n'; \
+ sofar = 0; \
+ } \
+ *outptr++ = '='; \
+ *outptr++ = tohex[(last >> 4) & 0xf]; \
+ *outptr++ = tohex[last & 0xf]; \
+ sofar += 3;
+
+ inptr = in;
+ inend = in + len;
+ outptr = out;
+ while (inptr < inend) {
+ c = *inptr++;
+ if (c == '\r') {
+ if (last != -1) {
+ output_last ();
+ }
+ last = c;
+ } else if (c == '\n') {
+ if (last != -1 && last != '\r') {
+ output_last ();
+ }
+ *outptr++ = '\n';
+ sofar = 0;
+ last = -1;
+ } else {
+ if (last != -1) {
+ if (camel_mime_is_qpsafe (last)) {
+ *outptr++ = last;
+ sofar++;
+ } else {
+ output_last ();
+ }
+ }
+
+ if (camel_mime_is_qpsafe (c)) {
+ if (sofar > 74) {
+ *outptr++ = '=';
+ *outptr++ = '\n';
+ sofar = 0;
+ }
+
+ /* delay output of space gchar */
+ 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;
+
+ #undef output_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.
+ */
+
+/**
+ * camel_quoted_decode_step:
+ * @in: (array length=len): input stream
+ * @len: max length of data to decode
+ * @out: (inout) (array): output stream
+ * @savestate: (inout): holds the number of bits that are stored in @saveme
+ * @saveme: (inout) (array length=savestate): leftover bits that have not yet been decoded
+ *
+ * Decodes a block of quoted-printable encoded data. Performs a
+ * 'decode step' on a chunk of QP encoded data.
+ *
+ * Returns: the number of bytes decoded
+ **/
+gsize
+camel_quoted_decode_step (guchar *in,
+ gsize len,
+ guchar *out,
+ gint *savestate,
+ gint *saveme)
+{
+ register guchar *inptr, *outptr;
+ guchar *inend, c;
+ gint 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 gsize
+quoted_decode (const guchar *in,
+ gsize len,
+ guchar *out)
+{
+ register const guchar *inptr;
+ register guchar *outptr;
+ const guchar *inend;
+ guchar c, c1;
+ gint 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 {
+ *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 gsize
+quoted_encode (const guchar *in,
+ gsize len,
+ guchar *out,
+ gushort safemask)
+{
+ register const guchar *inptr, *inend;
+ guchar *outptr;
+ guchar 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 gchar **in)
+{
+ const gchar *inptr = *in;
+ gchar 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 == '(') {
+ gint 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;
+}
+
+static gchar *
+camel_iconv_strndup (GIConv cd,
+ const gchar *string,
+ gsize n)
+{
+ gsize inleft, outleft, converted = 0;
+ gchar *out, *outbuf;
+ const gchar *inbuf;
+ gsize outlen;
+ gint errnosav;
+
+ if (cd == (GIConv) -1)
+ return g_strndup (string, n);
+
+ outlen = n * 2 + 16;
+ out = g_malloc (outlen + 4);
+
+ inbuf = string;
+ inleft = n;
+
+ do {
+ errno = 0;
+ outbuf = out + converted;
+ outleft = outlen - converted;
+
+ converted = g_iconv (cd, (gchar **) &inbuf, &inleft, &outbuf, &outleft);
+ if (converted == (gsize) -1) {
+ if (errno != E2BIG && errno != EINVAL)
+ goto fail;
+ }
+
+ /*
+ * E2BIG There is not sufficient room at *outbuf.
+ *
+ * We just need to grow our outbuffer and try again.
+ */
+
+ converted = outbuf - out;
+ if (errno == E2BIG) {
+ outlen += inleft * 2 + 16;
+ out = g_realloc (out, outlen + 4);
+ outbuf = out + converted;
+ }
+ } while (errno == E2BIG && inleft > 0);
+
+ /*
+ * EINVAL An incomplete multibyte sequence has been encoun­
+ * tered in the input.
+ *
+ * We'll just have to ignore it...
+ */
+
+ /* flush the iconv conversion */
+ while (g_iconv (cd, NULL, NULL, &outbuf, &outleft) == (gsize) -1) {
+ if (errno != E2BIG)
+ break;
+
+ outlen += 16;
+ converted = outbuf - out;
+ out = g_realloc (out, outlen + 4);
+ outleft = outlen - converted;
+ outbuf = out + converted;
+ }
+
+ /* Note: not all charsets can be nul-terminated with a single
+ * nul byte. UCS2, for example, needs 2 nul bytes and UCS4
+ * needs 4. I hope that 4 nul bytes is enough to terminate all
+ * multibyte charsets? */
+
+ /* nul-terminate the string */
+ memset (outbuf, 0, 4);
+
+ /* reset the cd */
+ g_iconv (cd, NULL, NULL, NULL, NULL);
+
+ return out;
+
+ fail:
+
+ errnosav = errno;
+
+ w (g_warning ("camel_iconv_strndup: %s at byte %lu", g_strerror (errno), n - inleft));
+
+ g_free (out);
+
+ /* reset the cd */
+ g_iconv (cd, NULL, NULL, NULL, NULL);
+
+ errno = errnosav;
+
+ return NULL;
+}
+
+#define is_ascii(c) isascii ((gint) ((guchar) (c)))
+
+static gchar *
+decode_8bit (const gchar *text,
+ gsize len,
+ const gchar *default_charset)
+{
+ const gchar *charsets[4] = { "UTF-8", NULL, NULL, NULL };
+ gsize inleft, outleft, outlen, rc, min, n;
+ const gchar *locale_charset, *best;
+ gchar *out, *outbuf;
+ const gchar *inbuf;
+ GIConv cd;
+ gint i = 1;
+
+ if (default_charset && g_ascii_strcasecmp (default_charset, "UTF-8") != 0)
+ charsets[i++] = default_charset;
+
+ locale_charset = camel_iconv_locale_charset ();
+ if (locale_charset && g_ascii_strcasecmp (locale_charset, "UTF-8") != 0)
+ charsets[i++] = locale_charset;
+
+ min = len;
+ best = charsets[0];
+
+ outlen = (len * 2) + 16;
+ out = g_malloc (outlen + 1);
+
+ for (i = 0; charsets[i]; i++) {
+ if ((cd = camel_iconv_open ("UTF-8", charsets[i])) == (GIConv) -1)
+ continue;
+
+ outleft = outlen;
+ outbuf = out;
+ inleft = len;
+ inbuf = text;
+ n = 0;
+
+ do {
+ rc = g_iconv (cd, (gchar **) &inbuf, &inleft, &outbuf, &outleft);
+ if (rc == (gsize) -1) {
+ if (errno == EINVAL) {
+ /* incomplete sequence at the end of the input buffer */
+ n += inleft;
+ break;
+ }
+
+ if (errno == E2BIG) {
+ outlen += (inleft * 2) + 16;
+ rc = (gsize) (outbuf - out);
+ out = g_realloc (out, outlen + 1);
+ outleft = outlen - rc;
+ outbuf = out + rc;
+ } else {
+ inleft--;
+ inbuf++;
+ n++;
+ }
+ }
+ } while (inleft > 0);
+
+ while ((rc = g_iconv (cd, NULL, NULL, &outbuf, &outleft)) == (gsize) -1) {
+ if (errno != E2BIG)
+ break;
+
+ outlen += 16;
+ rc = (gsize) (outbuf - out);
+ out = g_realloc (out, outlen + 1);
+ outleft = outlen - rc;
+ outbuf = out + rc;
+ }
+
+ *outbuf = '\0';
+
+ camel_iconv_close (cd);
+
+ if (rc != (gsize) -1 && n == 0)
+ return out;
+
+ if (n < min) {
+ best = charsets[i];
+ min = n;
+ }
+ }
+
+ /* if we get here, then none of the charsets fit the 8bit text flawlessly...
+ * try to find the one that fit the best and use that to convert what we can,
+ * replacing any byte we can't convert with a '?' */
+
+ if ((cd = camel_iconv_open ("UTF-8", best)) == (GIConv) -1) {
+ /* this shouldn't happen... but if we are here, then
+ * it did... the only thing we can do at this point
+ * is replace the 8bit garbage and pray */
+ register const gchar *inptr = text;
+ const gchar *inend = inptr + len;
+
+ outbuf = out;
+
+ while (inptr < inend) {
+ if (is_ascii (*inptr))
+ *outbuf++ = *inptr++;
+ else
+ *outbuf++ = '?';
+ }
+
+ *outbuf = '\0';
+
+ return out;
+ }
+
+ outleft = outlen;
+ outbuf = out;
+ inleft = len;
+ inbuf = text;
+
+ do {
+ rc = g_iconv (cd, (gchar **) &inbuf, &inleft, &outbuf, &outleft);
+ if (rc == (gsize) -1) {
+ if (errno == EINVAL) {
+ /* incomplete sequence at the end of the input buffer */
+ break;
+ }
+
+ if (errno == E2BIG) {
+ rc = outbuf - out;
+ outlen += inleft * 2 + 16;
+ out = g_realloc (out, outlen + 1);
+ outleft = outlen - rc;
+ outbuf = out + rc;
+ } else {
+ *outbuf++ = '?';
+ outleft--;
+ inleft--;
+ inbuf++;
+ }
+ }
+ } while (inleft > 0);
+
+ while ((rc = g_iconv (cd, NULL, NULL, &outbuf, &outleft)) == (gsize) -1) {
+ if (errno != E2BIG)
+ break;
+
+ outlen += 16;
+ rc = (gsize) (outbuf - out);
+ out = g_realloc (out, outlen + 1);
+ outleft = outlen - rc;
+ outbuf = out + rc;
+ }
+
+ *outbuf = '\0';
+
+ camel_iconv_close (cd);
+
+ return out;
+}
+
+#define is_rfc2047_encoded_word(atom, len) (len >= 7 && !strncmp (atom, "=?", 2) && !strncmp (atom + len - 2, "?=", 2))
+
+static void
+make_string_utf8_valid (gchar *text,
+ gsize textlen)
+{
+ gchar *p;
+ gsize len;
+
+ p = text;
+ len = textlen;
+
+ while (!g_utf8_validate (p, len, (const gchar **) &p)) {
+ len = textlen - (p - text);
+ *p = '?';
+ }
+}
+
+/* decode an rfc2047 encoded-word token */
+static gchar *
+rfc2047_decode_word (const gchar *in,
+ gsize inlen,
+ const gchar *default_charset)
+{
+ const guchar *instart = (const guchar *) in;
+ const guchar *inptr = instart + 2;
+ const guchar *inend = instart + inlen - 2;
+ guchar *decoded;
+ const gchar *charset;
+ gchar *charenc, *p;
+ guint32 save = 0;
+ gssize declen;
+ gint state = 0;
+ gsize len;
+ GIConv cd;
+ gchar *buf;
+
+ /* skip over the charset */
+ if (inlen < 8 || !(inptr = memchr (inptr, '?', inend - inptr)) || inptr[2] != '?')
+ return NULL;
+
+ inptr++;
+
+ switch (*inptr) {
+ case 'B':
+ case 'b':
+ inptr += 2;
+ decoded = g_alloca (inend - inptr);
+ declen = g_base64_decode_step ((gchar *) inptr, inend - inptr, decoded, &state, &save);
+ break;
+ case 'Q':
+ case 'q':
+ inptr += 2;
+ decoded = g_alloca (inend - inptr);
+ declen = quoted_decode (inptr, inend - inptr, decoded);
+
+ if (declen == -1) {
+ d (fprintf (stderr, "encountered broken 'Q' encoding\n"));
+ return NULL;
+ }
+ break;
+ default:
+ d (fprintf (stderr, "unknown encoding\n"));
+ return NULL;
+ }
+
+ /* never return empty string, return rather NULL */
+ if (!declen)
+ return NULL;
+
+ len = (inptr - 3) - (instart + 2);
+ charenc = g_alloca (len + 1);
+ memcpy (charenc, in + 2, len);
+ charenc[len] = '\0';
+ charset = charenc;
+
+ /* 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... */
+ if ((p = strchr (charset, '*')))
+ *p = '\0';
+
+ /* slight optimization? */
+ if (!g_ascii_strcasecmp (charset, "UTF-8"))
+ return g_strndup ((gchar *) decoded, declen);
+
+ if (charset[0])
+ charset = camel_iconv_charset_name (charset);
+
+ if (!charset[0] || (cd = camel_iconv_open ("UTF-8", charset)) == (GIConv) -1) {
+ w (g_warning (
+ "Cannot convert from %s to UTF-8, "
+ "header display may be corrupt: %s",
+ charset[0] ? charset : "unspecified charset",
+ g_strerror (errno)));
+
+ return decode_8bit ((gchar *) decoded, declen, default_charset);
+ }
+
+ buf = camel_iconv_strndup (cd, (gchar *) decoded, declen);
+ camel_iconv_close (cd);
+
+ if (buf != NULL)
+ return buf;
+
+ w (g_warning (
+ "Failed to convert \"%.*s\" to UTF-8, display may be "
+ "corrupt: %s", declen, decoded, g_strerror (errno)));
+
+ return decode_8bit ((gchar *) decoded, declen, charset);
+}
+
+/* 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 gchar *in,
+ gsize len)
+{
+ guint c;
+
+ while (len) {
+ c = (guint) * 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 gint
+append_8bit (GString *out,
+ const gchar *inbuf,
+ gsize inlen,
+ const gchar *charset)
+{
+ gchar *outbase, *outbuf;
+ gsize outlen;
+ GIConv ic;
+
+ ic = camel_iconv_open ("UTF-8", charset);
+ if (ic == (GIConv) -1)
+ return FALSE;
+
+ outlen = inlen * 6 + 16;
+ outbuf = outbase = g_malloc (outlen);
+
+ if (camel_iconv (ic, &inbuf, &inlen, &outbuf, &outlen) == (gsize) -1) {
+ w (g_warning ("Conversion to '%s' failed: %s", charset, g_strerror (errno)));
+ g_free (outbase);
+ camel_iconv_close (ic);
+ return FALSE;
+ }
+
+ camel_iconv (ic, NULL, NULL, &outbuf, &outlen);
+
+ *outbuf = 0;
+ g_string_append (out, outbase);
+ g_free (outbase);
+ camel_iconv_close (ic);
+
+ return TRUE;
+
+}
+
+static GString *
+append_quoted_pair (GString *str,
+ const gchar *in,
+ gsize inlen)
+{
+ register const gchar *inptr = in;
+ const gchar *inend = in + inlen;
+ gchar 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 gchar *
+header_decode_text (const gchar *in,
+ gint ctext,
+ const gchar *default_charset)
+{
+ register const gchar *inptr = in;
+ gboolean encoded = FALSE;
+ const gchar *lwsp, *text;
+ gsize nlwsp, n;
+ gboolean ascii;
+ gchar *decoded;
+ GString *out;
+
+ if (in == NULL)
+ return g_strdup ("");
+
+ out = g_string_sized_new (strlen (in) + 1);
+
+ while (*inptr != '\0') {
+ lwsp = inptr;
+ while (camel_mime_is_lwsp (*inptr))
+ inptr++;
+
+ nlwsp = (gsize) (inptr - lwsp);
+
+ if (*inptr != '\0') {
+ text = inptr;
+ ascii = TRUE;
+
+ if (!strncmp (inptr, "=?", 2)) {
+ inptr += 2;
+
+ /* skip past the charset (if one is even declared, sigh) */
+ while (*inptr && *inptr != '?') {
+ ascii = ascii && is_ascii (*inptr);
+ inptr++;
+ }
+
+ /* sanity check encoding type */
+ if (inptr[0] != '?' || !strchr ("BbQq", inptr[1]) || !inptr[1] || inptr[2] != '?')
+ goto non_rfc2047;
+
+ inptr += 3;
+
+ /* find the end of the rfc2047 encoded word token */
+ while (*inptr && strncmp (inptr, "?=", 2) != 0) {
+ ascii = ascii && is_ascii (*inptr);
+ inptr++;
+ }
+
+ if (!strncmp (inptr, "?=", 2))
+ inptr += 2;
+ } else {
+ non_rfc2047:
+ /* stop if we encounter a possible rfc2047 encoded
+ * token even if it's inside another word, sigh. */
+ while (*inptr && !camel_mime_is_lwsp (*inptr) &&
+ strncmp (inptr, "=?", 2) != 0) {
+ ascii = ascii && is_ascii (*inptr);
+ inptr++;
+ }
+ }
+
+ n = (gsize) (inptr - text);
+ if (is_rfc2047_encoded_word (text, n)) {
+ if ((decoded = rfc2047_decode_word (text, n, default_charset))) {
+ /* rfc2047 states that you must ignore all
+ * whitespace between encoded words */
+ if (!encoded)
+ g_string_append_len (out, lwsp, nlwsp);
+
+ g_string_append (out, decoded);
+ g_free (decoded);
+
+ encoded = TRUE;
+ } else {
+ /* append lwsp and invalid rfc2047 encoded-word token */
+ g_string_append_len (out, lwsp, nlwsp + n);
+ encoded = FALSE;
+ }
+ } else {
+ /* append lwsp */
+ g_string_append_len (out, lwsp, nlwsp);
+
+ /* append word token */
+ if (!ascii) {
+ /* *sigh* I hate broken mailers... */
+ decoded = decode_8bit (text, n, default_charset);
+ n = strlen (decoded);
+ text = decoded;
+ } else {
+ decoded = NULL;
+ }
+
+ if (!ctext)
+ g_string_append_len (out, text, n);
+ else
+ append_quoted_pair (out, text, n);
+
+ g_free (decoded);
+
+ encoded = FALSE;
+ }
+ } else {
+ /* appending trailing lwsp */
+ g_string_append_len (out, lwsp, nlwsp);
+ break;
+ }
+ }
+
+ decoded = g_string_free (out, FALSE);
+
+ return decoded;
+}
+
+/**
+ * camel_header_decode_string:
+ * @in: input header value string
+ * @default_charset: default charset to use if improperly encoded
+ *
+ * Decodes rfc2047 encoded-word tokens
+ *
+ * Returns: a string containing the UTF-8 version of the decoded header
+ * value
+ **/
+gchar *
+camel_header_decode_string (const gchar *in,
+ const gchar *default_charset)
+{
+ gchar *res;
+
+ if (in == NULL)
+ return NULL;
+
+ res = header_decode_text (in, FALSE, default_charset);
+
+ if (res)
+ make_string_utf8_valid (res, strlen (res));
+
+ return res;
+}
+
+/**
+ * camel_header_format_ctext:
+ * @in: input header value string
+ * @default_charset: default charset to use if improperly encoded
+ *
+ * Decodes a header which contains rfc2047 encoded-word tokens that
+ * may or may not be within a comment.
+ *
+ * Returns: a string containing the UTF-8 version of the decoded header
+ * value
+ **/
+gchar *
+camel_header_format_ctext (const gchar *in,
+ const gchar *default_charset)
+{
+ if (in == NULL)
+ return NULL;
+
+ return header_decode_text (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 gchar *in,
+ gsize len,
+ const gchar *type,
+ gushort safemask)
+{
+ GIConv ic = (GIConv) -1;
+ gchar *buffer, *out, *ascii;
+ gsize inlen, outlen, enclen, bufflen;
+ const gchar *inptr, *p;
+ gint 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 (g_ascii_strcasecmp (type, "UTF-8") != 0)
+ ic = camel_iconv_open (type, "UTF-8");
+
+ while (inlen) {
+ gssize convlen, proclen;
+ gint 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 == (GIConv) -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 gchar */
+ convlen = 0;
+ proclen = -1;
+ p = inptr;
+ i = 0;
+ while (p < (in + len) && convlen < (75 - strlen ("=?utf-8?q?\?="))) {
+ guchar 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;
+ }
+
+ if (proclen >= 0 && proclen < i && convlen < (75 - strlen ("=?utf-8?q?\?=")))
+ proclen = i;
+
+ /* well, we probably have broken utf8, just copy it anyway what the heck */
+ if (proclen == -1) {
+ 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 (camel_iconv (ic, &inptr, (gsize *) &convlen, &out, &outlen) == (gsize) -1 && errno != EINVAL) {
+ w (g_warning ("Conversion problem: conversion truncated: %s", g_strerror (errno)));
+ /* blah, we include it anyway, better than infinite loop ... */
+ inptr += convlen;
+ } else {
+ /* make sure we flush out any shift state */
+ camel_iconv (ic, NULL, NULL, &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 ((guchar *) buffer, enclen, (guchar *) out, safemask);
+ sprintf (out, "?=");
+
+ d (printf ("converted part = %s\n", ascii));
+
+ g_string_append (outstring, ascii);
+ }
+ }
+
+ if (ic != (GIConv) -1)
+ camel_iconv_close (ic);
+}
+
+static gchar *
+header_encode_string_rfc2047 (const guchar *in,
+ gboolean include_lwsp)
+{
+ const guchar *inptr = in, *start, *word;
+ gboolean last_was_encoded = FALSE;
+ gboolean last_was_space = FALSE;
+ const gchar *charset;
+ gint encoding;
+ GString *out;
+ gchar *outstr;
+
+ g_return_val_if_fail (g_utf8_validate ((const gchar *) 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 ((gchar *) 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 gchar *newinptr;
+
+ newinptr = g_utf8_next_char (inptr);
+ c = g_utf8_get_char ((gchar *) inptr);
+ if (newinptr == NULL || !g_unichar_validate (c)) {
+ w (g_warning (
+ "Invalid UTF-8 sequence encountered "
+ "(pos %d, gchar '%c'): %s",
+ (inptr - in), inptr[0], in));
+ inptr++;
+ continue;
+ }
+
+ if (c < 256 && !include_lwsp && 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, (const gchar *) start, word - start);
+ start = word;
+ }
+
+ switch (encoding) {
+ case 0:
+ g_string_append_len (out, (const gchar *) start, inptr - start);
+ last_was_encoded = FALSE;
+ break;
+ case 1:
+ if (last_was_encoded)
+ g_string_append_c (out, ' ');
+
+ rfc2047_encode_word (out, (const gchar *) 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, ' ');
+
+ if (!(charset = camel_charset_best ((const gchar *) start, inptr - start)))
+ charset = "UTF-8";
+ rfc2047_encode_word (out, (const gchar *) start, inptr - start, charset, 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 (include_lwsp || !camel_mime_is_lwsp (c)) {
+ last_was_space = FALSE;
+ }
+
+ if (!(c < 256 && !include_lwsp && camel_mime_is_lwsp (c)) && !word)
+ word = inptr;
+
+ inptr = (const guchar *) newinptr;
+ }
+
+ if (inptr - start) {
+ if (word && !(last_was_encoded && encoding)) {
+ g_string_append_len (out, (const gchar *) start, word - start);
+ start = word;
+ }
+
+ switch (encoding) {
+ case 0:
+ g_string_append_len (out, (const gchar *) start, inptr - start);
+ break;
+ case 1:
+ if (last_was_encoded)
+ g_string_append_c (out, ' ');
+
+ rfc2047_encode_word (out, (const gchar *) start, inptr - start, "ISO-8859-1", CAMEL_MIME_IS_ESAFE);
+ break;
+ case 2:
+ if (last_was_encoded)
+ g_string_append_c (out, ' ');
+
+ if (!(charset = camel_charset_best ((const gchar *) start, inptr - start)))
+ charset = "UTF-8";
+ rfc2047_encode_word (out, (const gchar *) start, inptr - start, charset, CAMEL_MIME_IS_ESAFE);
+ break;
+ }
+ }
+
+ outstr = out->str;
+ g_string_free (out, FALSE);
+
+ return outstr;
+}
+
+/* TODO: Should this worry about quotes?? */
+/**
+ * camel_header_encode_string:
+ * @in: input string
+ *
+ * Encodes a 'text' header according to the rules of rfc2047.
+ *
+ * Returns: the rfc2047 encoded header
+ **/
+gchar *
+camel_header_encode_string (const guchar *in)
+{
+ return header_encode_string_rfc2047 (in, FALSE);
+}
+
+/* apply quoted-string rules to a string */
+static void
+quote_word (GString *out,
+ gboolean do_quotes,
+ const gchar *start,
+ gsize len)
+{
+ gint 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 guchar *start, *end;
+ enum _phrase_word_t type;
+ gint 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 guchar *in)
+{
+ const guchar *inptr = in, *start, *last;
+ struct _phrase_word *word;
+ enum _phrase_word_t type;
+ gint 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 gchar *newinptr;
+
+ newinptr = g_utf8_next_char (inptr);
+ c = g_utf8_get_char ((gchar *) inptr);
+
+ if (!g_unichar_validate (c)) {
+ w (g_warning (
+ "Invalid UTF-8 sequence encountered "
+ "(pos %d, gchar '%c'): %s",
+ (inptr - in), inptr[0], in));
+ inptr++;
+ continue;
+ }
+
+ inptr = (const guchar *) 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->encoding = MAX (word->encoding, next->encoding);
+ 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) */
+/**
+ * camel_header_encode_phrase:
+ * @in: header to encode
+ *
+ * Encodes a 'phrase' header according to the rules in rfc2047.
+ *
+ * Returns: the encoded 'phrase'
+ **/
+gchar *
+camel_header_encode_phrase (const guchar *in)
+{
+ struct _phrase_word *word = NULL, *last_word = NULL;
+ GList *words, *wordl;
+ const gchar *charset;
+ GString *out;
+ gchar *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 gchar *start;
+ gsize 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, (gchar *) last_word->end, len);
+ }
+
+ switch (word->type) {
+ case WORD_ATOM:
+ out = g_string_append_len (out, (gchar *) word->start, word->end - word->start);
+ break;
+ case WORD_QSTRING:
+ quote_word (out, TRUE, (gchar *) 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 = (const gchar *) last_word->end;
+
+ /* encoded words need to be separated by linear whitespace */
+ g_string_append_c (out, ' ');
+ } else {
+ len = word->end - word->start;
+ start = (const gchar *) word->start;
+ }
+
+ if (word->encoding == 1) {
+ rfc2047_encode_word (out, start, len, "ISO-8859-1", CAMEL_MIME_IS_PSAFE);
+ } else {
+ if (!(charset = camel_charset_best (start, len)))
+ charset = "UTF-8";
+ rfc2047_encode_word (out, start, len, charset, 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 gchar *
+decode_token (const gchar **in)
+{
+ const gchar *inptr = *in;
+ const gchar *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;
+ }
+}
+
+/**
+ * camel_header_token_decode:
+ * @in: input string
+ *
+ * Gets the first token in the string according to the rules of
+ * rfc0822.
+ *
+ * Returns: a new string containing the first token in @in
+ **/
+gchar *
+camel_header_token_decode (const gchar *in)
+{
+ if (in == NULL)
+ return NULL;
+
+ return decode_token (&in);
+}
+
+/*
+ * <"> * ( <any gchar except <"> \, cr / \ <any char> ) <">
+*/
+static gchar *
+header_decode_quoted_string (const gchar **in)
+{
+ const gchar *inptr = *in;
+ gchar *out = NULL, *outptr;
+ gsize outlen;
+ gint c;
+
+ header_decode_lwsp (&inptr);
+ if (*inptr == '"') {
+ const gchar *intmp;
+ gint 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!= '"') {
+ inptr++;
+ if (c == '\\' && *inptr) {
+ c = *inptr++;
+ }
+ *outptr++ = c;
+ }
+ if (c)
+ inptr++;
+ *outptr = '\0';
+ }
+ *in = inptr;
+ return out;
+}
+
+static gchar *
+header_decode_atom (const gchar **in)
+{
+ const gchar *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 gboolean
+extract_rfc2047_encoded_word (const gchar **in,
+ gchar **word)
+{
+ const gchar *inptr = *in, *start;
+
+ header_decode_lwsp (&inptr);
+ start = inptr;
+
+ if (!strncmp (inptr, "=?", 2)) {
+ inptr += 2;
+
+ /* skip past the charset (if one is even declared, sigh) */
+ while (*inptr && *inptr != '?') {
+ inptr++;
+ }
+
+ /* sanity check encoding type */
+ if (inptr[0] != '?' || !strchr ("BbQq", inptr[1]) || !inptr[1] || inptr[2] != '?')
+ return FALSE;
+
+ inptr += 3;
+
+ /* find the end of the rfc2047 encoded word token */
+ while (*inptr && strncmp (inptr, "?=", 2) != 0) {
+ inptr++;
+ }
+
+ if (!strncmp (inptr, "?=", 2)) {
+ inptr += 2;
+
+ *in = inptr;
+ *word = g_strndup (start, inptr - start);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gchar *
+header_decode_word (const gchar **in)
+{
+ const gchar *inptr = *in;
+ gchar *word = NULL;
+
+ header_decode_lwsp (&inptr);
+ *in = inptr;
+
+ if (*inptr == '"') {
+ return header_decode_quoted_string (in);
+ } else if (*inptr == '=' && inptr[1] == '?' && extract_rfc2047_encoded_word (in, &word) && word) {
+ return word;
+ } else {
+ return header_decode_atom (in);
+ }
+}
+
+static gchar *
+header_decode_value (const gchar **in)
+{
+ const gchar *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? */
+
+/**
+ * camel_header_decode_int:
+ * @in: pointer to input string
+ *
+ * Extracts an integer token from @in and updates the pointer to point
+ * to after the end of the integer token (sort of like strtol).
+ *
+ * Returns: the gint value
+ **/
+gint
+camel_header_decode_int (const gchar **in)
+{
+ const gchar *inptr = *in;
+ gint 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 gchar *
+hex_decode (const gchar *in,
+ gsize len)
+{
+ const guchar *inend = (const guchar *) (in + len);
+ guchar *inptr, *outptr;
+ gchar *outbuf;
+
+ outbuf = (gchar *) g_malloc (len + 1);
+ outptr = (guchar *) outbuf;
+
+ inptr = (guchar *) 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 gchar *
+header_convert (const gchar *to,
+ const gchar *from,
+ const gchar *in,
+ gsize inlen)
+{
+ GIConv ic;
+ gsize outlen, ret;
+ gchar *outbuf, *outbase, *result = NULL;
+
+ ic = camel_iconv_open (to, from);
+ if (ic == (GIConv) -1)
+ return NULL;
+
+ outlen = inlen * 6 + 16;
+ outbuf = outbase = g_malloc (outlen);
+
+ ret = camel_iconv (ic, &in, &inlen, &outbuf, &outlen);
+ if (ret != (gsize) -1) {
+ camel_iconv (ic, NULL, NULL, &outbuf, &outlen);
+ *outbuf = '\0';
+ result = g_strdup (outbase);
+ }
+ camel_iconv_close (ic);
+ g_free (outbase);
+
+ return result;
+}
+
+/* an rfc2184 encoded string looks something like:
+ * us-ascii'en'This%20is%20even%20more%20
+ */
+
+static gchar *
+rfc2184_decode (const gchar *in,
+ gsize len)
+{
+ const gchar *inptr = in;
+ const gchar *inend = in + len;
+ const gchar *charset;
+ gchar *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 = camel_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;
+}
+
+/**
+ * camel_header_param:
+ * @params: parameters
+ * @name: name of param to find
+ *
+ * Searches @params for a param named @name and gets the value.
+ *
+ * Returns: the value of the @name param
+ **/
+gchar *
+camel_header_param (struct _camel_header_param *params,
+ const gchar *name)
+{
+ while (params && params->name &&
+ g_ascii_strcasecmp (params->name, name) != 0)
+ params = params->next;
+ if (params)
+ return params->value;
+
+ return NULL;
+}
+
+/**
+ * camel_header_set_param:
+ * @paramsp: poinetr to a list of params
+ * @name: name of param to set
+ * @value: value to set
+ *
+ * Set a parameter in the list.
+ *
+ * Returns: (transfer none): the set param
+ **/
+struct _camel_header_param *
+camel_header_set_param (struct _camel_header_param **l,
+ const gchar *name,
+ const gchar *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 = NULL;
+ pn->name = g_strdup (name);
+ pn->value = g_strdup (value);
+ p->next = pn;
+
+ return pn;
+}
+
+/**
+ * camel_content_type_param:
+ * @content_type: a #CamelContentType
+ * @name: name of param to find
+ *
+ * Searches the params on s #CamelContentType for a param named @name
+ * and gets the value.
+ *
+ * Returns: the value of the @name param
+ **/
+const gchar *
+camel_content_type_param (CamelContentType *t,
+ const gchar *name)
+{
+ if (t == NULL)
+ return NULL;
+ return camel_header_param (t->params, name);
+}
+
+/**
+ * camel_content_type_set_param:
+ * @content_type: a #CamelContentType
+ * @name: name of param to set
+ * @value: value of param to set
+ *
+ * Set a parameter on @content_type.
+ **/
+void
+camel_content_type_set_param (CamelContentType *t,
+ const gchar *name,
+ const gchar *value)
+{
+ camel_header_set_param (&t->params, name, value);
+}
+
+/**
+ * camel_content_type_is:
+ * @content_type: A content type specifier, or %NULL.
+ * @type: A type to check against.
+ * @subtype: A subtype to check against, or "*" to match any subtype.
+ *
+ * The subtype of "*" will match any subtype. If @ct is %NULL, then
+ * it will match the type "text/plain".
+ *
+ * Returns: %TRUE if the content type @ct is of type @type/@subtype or
+ * %FALSE otherwise
+ **/
+gint
+camel_content_type_is (CamelContentType *ct,
+ const gchar *type,
+ const gchar *subtype)
+{
+ /* no type == text/plain or text/"*" */
+ if (ct == NULL || (ct->type == NULL && ct->subtype == NULL)) {
+ return (!g_ascii_strcasecmp (type, "text")
+ && (!g_ascii_strcasecmp (subtype, "plain")
+ || !strcmp (subtype, "*")));
+ }
+
+ return (ct->type != NULL
+ && (!g_ascii_strcasecmp (ct->type, type)
+ && ((ct->subtype != NULL
+ && !g_ascii_strcasecmp (ct->subtype, subtype))
+ || !strcmp ("*", subtype))));
+}
+
+/**
+ * camel_header_param_list_free:
+ * @params: a list of params
+ *
+ * Free the list of params.
+ **/
+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;
+ }
+}
+
+/**
+ * camel_content_type_new:
+ * @type: the major type of the new content-type
+ * @subtype: the subtype
+ *
+ * Create a new #CamelContentType.
+ *
+ * Returns: the new #CamelContentType
+ **/
+CamelContentType *
+camel_content_type_new (const gchar *type,
+ const gchar *subtype)
+{
+ CamelContentType *t;
+
+ t = g_slice_new (CamelContentType);
+ t->type = g_strdup (type);
+ t->subtype = g_strdup (subtype);
+ t->params = NULL;
+ t->refcount = 1;
+
+ return t;
+}
+
+/**
+ * camel_content_type_ref:
+ * @content_type: a #CamelContentType
+ *
+ * Refs the content type.
+ **/
+CamelContentType *
+camel_content_type_ref (CamelContentType *ct)
+{
+ if (ct)
+ ct->refcount++;
+
+ return ct;
+}
+
+/**
+ * camel_content_type_unref:
+ * @content_type: a #CamelContentType
+ *
+ * Unrefs, and potentially frees, the content type.
+ **/
+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_slice_free (CamelContentType, ct);
+ ct = NULL;
+ } else {
+ ct->refcount--;
+ }
+ }
+}
+
+/* for decoding email addresses, canonically */
+static gchar *
+header_decode_domain (const gchar **in)
+{
+ const gchar *inptr = *in;
+ gint go = TRUE;
+ gchar *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);
+ while (*inptr && 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 {
+ gchar *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 gchar *
+header_decode_addrspec (const gchar **in)
+{
+ const gchar *inptr = *in;
+ gchar *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"));
+ g_string_free (addr, TRUE);
+
+ return NULL;
+ }
+
+ /* 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 CamelHeaderAddress *
+header_decode_mailbox (const gchar **in,
+ const gchar *charset)
+{
+ const gchar *inptr = *in;
+ gchar *pre;
+ gint closeme = FALSE;
+ GString *addr;
+ GString *name = NULL;
+ CamelHeaderAddress *address = NULL;
+ const gchar *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) {
+ gchar *text, *last;
+
+ /* perform internationalised decoding, and append */
+ text = header_decode_text (pre, FALSE, charset);
+ g_string_append (name, text);
+ last = pre;
+ g_free (text);
+
+ pre = header_decode_word (&inptr);
+ if (pre) {
+ gsize l = strlen (last);
+ gsize 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 == '@') {
+ gchar *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 != ',') {
+ gchar *text;
+ const gchar *name_part;
+ gboolean in_quote;
+
+ 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);
+
+ name_part = *in;
+ in_quote = FALSE;
+ while (*name_part && *name_part != ',') {
+ if (*name_part == '\"')
+ in_quote = !in_quote;
+ else if (!in_quote && *name_part == '<')
+ break;
+ name_part++;
+ }
+
+ if (*name_part == '<' && ((!strchr (name_part, ',') && strchr (name_part, '>')) || (strchr (name_part, ',') > strchr (name_part, '>')))) {
+ /* it's of a form "display-name <addr-spec>" */
+ if (name)
+ g_string_free (name, TRUE);
+ name = NULL;
+ g_string_free (addr, TRUE);
+
+ if (name_part == *in)
+ addr = g_string_new ("");
+ else
+ addr = g_string_new_len (*in, name_part - *in - (camel_mime_is_lwsp (name_part[-1]) ? 1 : 0));
+ }
+
+ /* check for address is encoded word ... */
+ text = header_decode_text (addr->str, FALSE, 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 */
+ if ((text = rfc2047_decode_word (name->str, name->len, charset))) {
+ 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 */
+ gchar *text, *tmp;
+ const gchar *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 = header_decode_text (tmp, FALSE, 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 gchar *locale_charset;
+ GString *out;
+
+ locale_charset = camel_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;
+ }
+
+ if (!name) {
+ gchar *text;
+
+ text = rfc2047_decode_word (addr->str, addr->len, charset);
+ if (text) {
+ g_string_truncate (addr, 0);
+ g_string_append (addr, text);
+ g_free (text);
+
+ make_string_utf8_valid (addr->str, addr->len);
+ }
+
+ } else {
+ make_string_utf8_valid (name->str, name->len);
+ }
+
+ 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 CamelHeaderAddress *
+header_decode_address (const gchar **in,
+ const gchar *charset)
+{
+ const gchar *inptr = *in;
+ gchar *pre;
+ GString *group = g_string_new ("");
+ CamelHeaderAddress *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 != ';') {
+ gint 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 gchar *
+header_msgid_decode_internal (const gchar **in)
+{
+ const gchar *inptr = *in;
+ gchar *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;
+}
+
+/**
+ * camel_header_msgid_decode:
+ * @in: input string
+ *
+ * Extract a message-id token from @in.
+ *
+ * Returns: the msg-id
+ **/
+gchar *
+camel_header_msgid_decode (const gchar *in)
+{
+ if (in == NULL)
+ return NULL;
+
+ return header_msgid_decode_internal (&in);
+}
+
+/**
+ * camel_header_contentid_decode:
+ * @in: input string
+ *
+ * Extract a content-id from @in.
+ *
+ * Returns: the extracted content-id
+ **/
+gchar *
+camel_header_contentid_decode (const gchar *in)
+{
+ const gchar *inptr = in;
+ gboolean at = FALSE;
+ GString *addr;
+ gchar *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)) && (*inptr == '\0' || !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 (*inptr != '\0' && 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;
+}
+
+static void
+header_references_decode_single (const gchar **in, GSList **list)
+{
+ const gchar *inptr = *in;
+ gchar *id, *word;
+
+ while (*inptr) {
+ header_decode_lwsp (&inptr);
+ if (*inptr == '<') {
+ id = header_msgid_decode_internal (&inptr);
+ if (id) {
+ *list = g_slist_prepend (*list, id);
+ break;
+ }
+ } else {
+ word = header_decode_word (&inptr);
+ if (word)
+ g_free (word);
+ else if (*inptr != '\0')
+ inptr++; /* Stupid mailer tricks */
+ }
+ }
+
+ *in = inptr;
+}
+
+/**
+ * camel_header_references_decode:
+ * @in:
+ *
+ * Generate a list of references, from most recent up.
+ *
+ * Returns: (element-type utf8) (transfer full):
+ **/
+GSList *
+camel_header_references_decode (const gchar *in)
+{
+ GSList *refs = NULL;
+
+ if (in == NULL || in[0] == '\0')
+ return NULL;
+
+ while (*in)
+ header_references_decode_single (&in, &refs);
+
+ return refs;
+}
+
+CamelHeaderAddress *
+camel_header_mailbox_decode (const gchar *in,
+ const gchar *charset)
+{
+ if (in == NULL)
+ return NULL;
+
+ return header_decode_mailbox (&in, charset);
+}
+
+CamelHeaderAddress *
+camel_header_address_decode (const gchar *in,
+ const gchar *charset)
+{
+ const gchar *inptr = in, *last;
+ CamelHeaderAddress *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;
+}
+
+/**
+ * camel_header_newsgroups_decode:
+ * @in:
+ *
+ * Returns: (element-type utf8) (transfer full):
+ **/
+GSList *
+camel_header_newsgroups_decode (const gchar *in)
+{
+ const gchar *inptr = in;
+ register gchar c;
+ GSList *list = NULL;
+ const gchar *start;
+
+ do {
+ header_decode_lwsp (&inptr);
+ start = inptr;
+ while ((c = *inptr++) && !camel_mime_is_lwsp (c) && c != ',')
+ ;
+ if (start != inptr - 1) {
+ list = g_slist_prepend (list, g_strndup (start, inptr - start - 1));
+ }
+ } while (c);
+
+ return list;
+}
+
+/* this must be kept in sync with the header */
+static const gchar *encodings[] = {
+ "",
+ "7bit",
+ "8bit",
+ "base64",
+ "quoted-printable",
+ "binary",
+ "x-uuencode",
+};
+
+const gchar *
+camel_transfer_encoding_to_string (CamelTransferEncoding encoding)
+{
+ if (encoding >= G_N_ELEMENTS (encodings))
+ encoding = 0;
+
+ return encodings[encoding];
+}
+
+CamelTransferEncoding
+camel_transfer_encoding_from_string (const gchar *string)
+{
+ gint i;
+
+ if (string != NULL) {
+ for (i = 0; i < G_N_ELEMENTS (encodings); i++)
+ if (!g_ascii_strcasecmp (string, encodings[i]))
+ return i;
+ }
+
+ return CAMEL_TRANSFER_ENCODING_DEFAULT;
+}
+
+void
+camel_header_mime_decode (const gchar *in,
+ gint *maj,
+ gint *min)
+{
+ const gchar *inptr = in;
+ gint 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;
+ gint index;
+};
+
+static gint
+rfc2184_param_cmp (gconstpointer ap,
+ gconstpointer bp)
+{
+ const struct _rfc2184_param *a = *(gpointer *) ap;
+ const struct _rfc2184_param *b = *(gpointer *) bp;
+ gint 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,
+ gchar *name,
+ gchar *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, FALSE, NULL))) {
+ g_free (value);
+ } else if (g_ascii_strcasecmp (name, "boundary") != 0 && !g_utf8_validate (value, -1, NULL)) {
+ const gchar *charset = camel_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 ((guchar) * value))
+ *value = '_';
+ }
+ } else
+ node->value = value;
+
+ return node;
+}
+
+static struct _camel_header_param *
+header_decode_param_list (const gchar **in)
+{
+ struct _camel_header_param *head = NULL, *last = (struct _camel_header_param *) &head;
+ GPtrArray *split = NULL;
+ const gchar *inptr = *in;
+ struct _rfc2184_param *work;
+ gchar *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 == ';') {
+ gchar *name;
+ gchar *value = NULL;
+
+ inptr++;
+ name = decode_token (&inptr);
+ header_decode_lwsp (&inptr);
+ if (*inptr == '=') {
+ inptr++;
+ value = header_decode_value (&inptr);
+ }
+
+ if (name && value) {
+ gchar *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;
+ gint 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;
+}
+
+/**
+ * camel_header_param_list_decode:
+ *
+ * Returns: (transfer full):
+ **/
+struct _camel_header_param *
+camel_header_param_list_decode (const gchar *in)
+{
+ if (in == NULL)
+ return NULL;
+
+ return header_decode_param_list (&in);
+}
+
+static gchar *
+header_encode_param (const guchar *in,
+ gboolean *encoded,
+ gboolean is_filename)
+{
+ const guchar *inptr = in;
+ guchar *outbuf = NULL;
+ const gchar *charset;
+ GString *out;
+ guint32 c;
+ gchar *str;
+
+ *encoded = FALSE;
+
+ g_return_val_if_fail (in != NULL, NULL);
+
+ if (is_filename) {
+ if (!g_utf8_validate ((gchar *) inptr, -1, NULL)) {
+ GString *buff = g_string_new ("");
+
+ for (; inptr && *inptr; inptr++) {
+ if (*inptr < 32)
+ g_string_append_printf (buff, "%%%02X", (*inptr) & 0xFF);
+ else
+ g_string_append_c (buff, *inptr);
+ }
+
+ outbuf = (guchar *) g_string_free (buff, FALSE);
+ inptr = outbuf;
+ }
+
+ /* do not set encoded flag for file names */
+ str = header_encode_string_rfc2047 (inptr, TRUE);
+ g_free (outbuf);
+
+ return str;
+ }
+
+ /* if we have really broken utf8 passed in, we just treat it as binary data */
+
+ charset = camel_charset_best ((gchar *) in, strlen ((gchar *) in));
+ if (charset == NULL) {
+ return g_strdup ((gchar *) in);
+ }
+
+ if (g_ascii_strcasecmp (charset, "UTF-8") != 0) {
+ if ((outbuf = (guchar *) header_convert (charset, "UTF-8", (const gchar *) in, strlen ((gchar *) in))))
+ inptr = outbuf;
+ else
+ return g_strdup ((gchar *) 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);
+
+ str = out->str;
+ g_string_free (out, FALSE);
+ *encoded = TRUE;
+
+ return str;
+}
+
+/* HACK: Set to non-zero when you want the 'filename' and 'name' headers encoded in RFC 2047 way,
+ * otherwise they will be encoded in the correct RFC 2231 way. It's because Outlook and GMail
+ * do not understand the correct standard and refuse attachments with localized name sent
+ * from evolution. This seems to have been fixed in Exchange 2007 at least - not sure about
+ * standalone Outlook. */
+gint camel_header_param_encode_filenames_in_rfc_2047 = 0;
+
+void
+camel_header_param_list_format_append (GString *out,
+ struct _camel_header_param *p)
+{
+ gint used = out->len;
+
+ while (p) {
+ gboolean is_filename = camel_header_param_encode_filenames_in_rfc_2047 && (g_ascii_strcasecmp (p->name, "filename") == 0 || g_ascii_strcasecmp (p->name, "name") == 0);
+ gboolean encoded = FALSE;
+ gboolean quote = FALSE;
+ gint here = out->len;
+ gsize nlen, vlen;
+ gchar *value;
+
+ if (!p->value) {
+ p = p->next;
+ continue;
+ }
+
+ value = header_encode_param ((guchar *) p->value, &encoded, is_filename);
+ if (!value) {
+ w (g_warning ("appending parameter %s=%s violates rfc2184", p->name, p->value));
+ value = g_strdup (p->value);
+ }
+
+ if (!encoded) {
+ gchar *ch;
+
+ for (ch = value; ch && *ch; ch++) {
+ if (camel_mime_is_tspecial (*ch) || camel_mime_is_lwsp (*ch))
+ break;
+ }
+
+ quote = ch && *ch;
+ }
+
+ quote = quote || is_filename;
+ nlen = strlen (p->name);
+ vlen = strlen (value);
+
+ /* do not fold file names */
+ if (!is_filename && 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 (!is_filename && nlen + vlen > CAMEL_FOLD_SIZE - 8) {
+ /* we need to do special rfc2184 parameter wrapping */
+ gint maxlen = CAMEL_FOLD_SIZE - (nlen + 8);
+ gchar *inptr, *inend;
+ gint i = 0;
+
+ inptr = value;
+ inend = value + vlen;
+
+ while (inptr < inend) {
+ gchar *ptr = inptr + MIN (inend - inptr, maxlen);
+
+ if (encoded && ptr < inend) {
+ /* be careful not to break an encoded gchar (ie %20) */
+ gchar *q = ptr;
+ gint 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 ? "*" : "");
+
+ /* Quote even if we don't need to in order to
+ * work around broken mail software like the
+ * Jive Forums' NNTP gateway */
+ 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;
+ }
+}
+
+gchar *
+camel_header_param_list_format (struct _camel_header_param *p)
+{
+ GString *out = g_string_new ("");
+ gchar *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 gchar *in)
+{
+ const gchar *inptr = in;
+ gchar *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 && (!g_ascii_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");
+}
+
+gchar *
+camel_content_type_format (CamelContentType *ct)
+{
+ GString *out;
+ gchar *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;
+}
+
+gchar *
+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);
+}
+
+gchar *
+camel_content_transfer_encoding_decode (const gchar *in)
+{
+ if (in)
+ return decode_token (&in);
+
+ return NULL;
+}
+
+CamelContentDisposition *
+camel_content_disposition_decode (const gchar *in)
+{
+ CamelContentDisposition *d = NULL;
+ const gchar *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;
+}
+
+CamelContentDisposition *
+camel_content_disposition_ref (CamelContentDisposition *d)
+{
+ if (d)
+ d->refcount++;
+
+ return d;
+}
+
+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--;
+ }
+ }
+}
+
+gchar *
+camel_content_disposition_format (CamelContentDisposition *d)
+{
+ GString *out;
+ gchar *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;
+}
+
+/* date parser macros */
+#define NUMERIC_CHARS "1234567890"
+#define WEEKDAY_CHARS "SundayMondayTuesdayWednesdayThursdayFridaySaturday"
+#define MONTH_CHARS "JanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctoberNovemberDecember"
+#define TIMEZONE_ALPHA_CHARS "UTCGMTESTEDTCSTCDTMSTPSTPDTZAMNY()"
+#define TIMEZONE_NUMERIC_CHARS "-+1234567890"
+#define TIME_CHARS "1234567890:"
+
+#define DATE_TOKEN_NON_NUMERIC (1 << 0)
+#define DATE_TOKEN_NON_WEEKDAY (1 << 1)
+#define DATE_TOKEN_NON_MONTH (1 << 2)
+#define DATE_TOKEN_NON_TIME (1 << 3)
+#define DATE_TOKEN_HAS_COLON (1 << 4)
+#define DATE_TOKEN_NON_TIMEZONE_ALPHA (1 << 5)
+#define DATE_TOKEN_NON_TIMEZONE_NUMERIC (1 << 6)
+#define DATE_TOKEN_HAS_SIGN (1 << 7)
+
+static guchar camel_datetok_table[256] = {
+ 128,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
+ 111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
+ 111,111,111,111,111,111,111,111, 79, 79,111,175,111,175,111,111,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,119,111,111,111,111,111,
+ 111, 75,111, 79, 75, 79,105, 79,111,111,107,111,111, 73, 75,107,
+ 79,111,111, 73, 77, 79,111,109,111, 79, 79,111,111,111,111,111,
+ 111,105,107,107,109,105,111,107,105,105,111,111,107,107,105,105,
+ 107,111,105,105,105,105,107,111,111,105,111,111,111,111,111,111,
+ 111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
+ 111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
+ 111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
+ 111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
+ 111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
+ 111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
+ 111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
+ 111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
+};
+
+static struct {
+ const gchar *name;
+ gint 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 const gchar tm_months[][4] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static const gchar tm_days[][4] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+/**
+ * camel_header_format_date:
+ * @date: time_t date representation
+ * @tz_offset: Timezone offset
+ *
+ * Allocates a string buffer containing the rfc822 formatted date
+ * string represented by @time and @tz_offset.
+ *
+ * Returns: a valid string representation of the date.
+ **/
+gchar *
+camel_header_format_date (time_t date,
+ gint tz_offset)
+{
+ struct tm tm;
+
+ d (printf ("offset = %d\n", tz_offset));
+
+ d (printf ("converting date %s", ctime (&date)));
+
+ date += ((tz_offset / 100) * (60 * 60)) + (tz_offset % 100) * 60;
+
+ d (printf ("converting date %s", ctime (&date)));
+
+ gmtime_r (&date, &tm);
+
+ return g_strdup_printf (
+ "%s, %02d %s %04d %02d:%02d:%02d %+05d",
+ tm_days[tm.tm_wday],
+ tm.tm_mday,
+ tm_months[tm.tm_mon],
+ tm.tm_year + 1900,
+ tm.tm_hour,
+ tm.tm_min,
+ tm.tm_sec,
+ tz_offset);
+}
+
+/* This is where it gets ugly... */
+
+struct _date_token {
+ struct _date_token *next;
+ guchar mask;
+ const gchar *start;
+ gsize len;
+};
+
+static struct _date_token *
+datetok (const gchar *date)
+{
+ struct _date_token *tokens = NULL, *token, *tail = (struct _date_token *) &tokens;
+ const gchar *start, *end;
+ guchar mask;
+
+ start = date;
+ while (*start) {
+ /* kill leading whitespace */
+ while (*start == ' ' || *start == '\t')
+ start++;
+
+ if (*start == '\0')
+ break;
+
+ mask = camel_datetok_table[(guchar) *start];
+
+ /* find the end of this token */
+ end = start + 1;
+ while (*end && !strchr ("-/,\t\r\n ", *end))
+ mask |= camel_datetok_table[(guchar) *end++];
+
+ if (end != start) {
+ token = g_malloc (sizeof (struct _date_token));
+ token->next = NULL;
+ token->start = start;
+ token->len = end - start;
+ token->mask = mask;
+
+ tail->next = token;
+ tail = token;
+ }
+
+ if (*end)
+ start = end + 1;
+ else
+ break;
+ }
+
+ return tokens;
+}
+
+static gint
+decode_int (const gchar *in,
+ gsize inlen)
+{
+ register const gchar *inptr;
+ gint sign = 1, val = 0;
+ const gchar *inend;
+
+ inptr = in;
+ inend = in + inlen;
+
+ if (*inptr == '-') {
+ sign = -1;
+ inptr++;
+ } else if (*inptr == '+')
+ inptr++;
+
+ for (; inptr < inend; inptr++) {
+ if (!(*inptr >= '0' && *inptr <= '9'))
+ return -1;
+ else
+ val = (val * 10) + (*inptr - '0');
+ }
+
+ val *= sign;
+
+ return val;
+}
+
+#if 0
+static gint
+get_days_in_month (gint month,
+ gint year)
+{
+ switch (month) {
+ case 1:
+ case 3:
+ case 5:
+ case 7:
+ case 8:
+ case 10:
+ case 12:
+ return 31;
+ case 4:
+ case 6:
+ case 9:
+ case 11:
+ return 30;
+ case 2:
+ if (g_date_is_leap_year (year))
+ return 29;
+ else
+ return 28;
+ default:
+ return 0;
+ }
+}
+#endif
+
+static gint
+get_wday (const gchar *in,
+ gsize inlen)
+{
+ gint wday;
+
+ g_return_val_if_fail (in != NULL, -1);
+
+ if (inlen < 3)
+ return -1;
+
+ for (wday = 0; wday < 7; wday++) {
+ if (!g_ascii_strncasecmp (in, tm_days[wday], 3))
+ return wday;
+ }
+
+ return -1; /* unknown week day */
+}
+
+static gint
+get_mday (const gchar *in,
+ gsize inlen)
+{
+ gint mday;
+
+ g_return_val_if_fail (in != NULL, -1);
+
+ mday = decode_int (in, inlen);
+
+ if (mday < 0 || mday > 31)
+ mday = -1;
+
+ return mday;
+}
+
+static gint
+get_month (const gchar *in,
+ gsize inlen)
+{
+ gint i;
+
+ g_return_val_if_fail (in != NULL, -1);
+
+ if (inlen < 3)
+ return -1;
+
+ for (i = 0; i < 12; i++) {
+ if (!g_ascii_strncasecmp (in, tm_months[i], 3))
+ return i;
+ }
+
+ return -1; /* unknown month */
+}
+
+static gint
+get_year (const gchar *in,
+ gsize inlen)
+{
+ gint year;
+
+ g_return_val_if_fail (in != NULL, -1);
+
+ if ((year = decode_int (in, inlen)) == -1)
+ return -1;
+
+ if (year < 100)
+ year += (year < 70) ? 2000 : 1900;
+
+ if (year < 1969)
+ return -1;
+
+ return year;
+}
+
+static gboolean
+get_time (const gchar *in,
+ gsize inlen,
+ gint *hour,
+ gint *min,
+ gint *sec)
+{
+ register const gchar *inptr;
+ gint *val, colons = 0;
+ const gchar *inend;
+
+ *hour = *min = *sec = 0;
+
+ inend = in + inlen;
+ val = hour;
+ for (inptr = in; inptr < inend; inptr++) {
+ if (*inptr == ':') {
+ colons++;
+ switch (colons) {
+ case 1:
+ val = min;
+ break;
+ case 2:
+ val = sec;
+ break;
+ default:
+ return FALSE;
+ }
+ } else if (!(*inptr >= '0' && *inptr <= '9'))
+ return FALSE;
+ else
+ *val = (*val * 10) + (*inptr - '0');
+ }
+
+ return TRUE;
+}
+
+static gint
+get_tzone (struct _date_token **token)
+{
+ const gchar *inptr, *inend;
+ gsize inlen;
+ gint i, t;
+
+ for (i = 0; *token && i < 2; *token = (*token)->next, i++) {
+ inptr = (*token)->start;
+ inlen = (*token)->len;
+ inend = inptr + inlen;
+
+ if (*inptr == '+' || *inptr == '-') {
+ return decode_int (inptr, inlen);
+ } else {
+ if (*inptr == '(') {
+ inptr++;
+ if (*(inend - 1) == ')')
+ inlen -= 2;
+ else
+ inlen--;
+ }
+
+ for (t = 0; t < 15; t++) {
+ gsize len = strlen (tz_offsets[t].name);
+
+ if (len != inlen)
+ continue;
+
+ if (!strncmp (inptr, tz_offsets[t].name, len))
+ return tz_offsets[t].offset;
+ }
+ }
+ }
+
+ return -1;
+}
+
+static time_t
+parse_rfc822_date (struct _date_token *tokens,
+ gint *tzone)
+{
+ gint hour, min, sec, offset, n;
+ struct _date_token *token;
+ struct tm tm;
+ time_t t;
+
+ g_return_val_if_fail (tokens != NULL, (time_t) 0);
+
+ token = tokens;
+
+ memset ((gpointer) &tm, 0, sizeof (struct tm));
+
+ if ((n = get_wday (token->start, token->len)) != -1) {
+ /* not all dates may have this... */
+ tm.tm_wday = n;
+ token = token->next;
+ }
+
+ /* get the mday */
+ if (!token || (n = get_mday (token->start, token->len)) == -1)
+ return (time_t) 0;
+
+ tm.tm_mday = n;
+ token = token->next;
+
+ /* get the month */
+ if (!token || (n = get_month (token->start, token->len)) == -1)
+ return (time_t) 0;
+
+ tm.tm_mon = n;
+ token = token->next;
+
+ /* get the year */
+ if (!token || (n = get_year (token->start, token->len)) == -1)
+ return (time_t) 0;
+
+ tm.tm_year = n - 1900;
+ token = token->next;
+
+ /* get the hour/min/sec */
+ if (!token || !get_time (token->start, token->len, &hour, &min, &sec))
+ return (time_t) 0;
+
+ tm.tm_hour = hour;
+ tm.tm_min = min;
+ tm.tm_sec = sec;
+ token = token->next;
+
+ if (token && token->start && (
+ g_ascii_strncasecmp (token->start, "AM", 2) == 0 ||
+ g_ascii_strncasecmp (token->start, "PM", 2) == 0)) {
+ /* not a valid RFC 822 time representation */
+ return 0;
+ }
+
+ /* get the timezone */
+ if (!token || (n = get_tzone (&token)) == -1) {
+ /* I guess we assume tz is GMT? */
+ offset = 0;
+ } else {
+ offset = n;
+ }
+
+ t = camel_mktime_utc (&tm);
+
+ /* t is now GMT of the time we want, but not offset by the timezone ... */
+
+ /* this should convert the time to the GMT equiv time */
+ t -= ((offset / 100) * 60 * 60) + (offset % 100) * 60;
+
+ if (tzone)
+ *tzone = offset;
+
+ return t;
+}
+
+#define date_token_mask(t) (((struct _date_token *) t)->mask)
+#define is_numeric(t) ((date_token_mask (t) & DATE_TOKEN_NON_NUMERIC) == 0)
+#define is_weekday(t) ((date_token_mask (t) & DATE_TOKEN_NON_WEEKDAY) == 0)
+#define is_month(t) ((date_token_mask (t) & DATE_TOKEN_NON_MONTH) == 0)
+#define is_time(t) (((date_token_mask (t) & DATE_TOKEN_NON_TIME) == 0) && (date_token_mask (t) & DATE_TOKEN_HAS_COLON))
+#define is_tzone_alpha(t) ((date_token_mask (t) & DATE_TOKEN_NON_TIMEZONE_ALPHA) == 0)
+#define is_tzone_numeric(t) (((date_token_mask (t) & DATE_TOKEN_NON_TIMEZONE_NUMERIC) == 0) && (date_token_mask (t) & DATE_TOKEN_HAS_SIGN))
+#define is_tzone(t) (is_tzone_alpha (t) || is_tzone_numeric (t))
+
+static time_t
+parse_broken_date (struct _date_token *tokens,
+ gint *tzone)
+{
+ gboolean got_wday, got_month, got_tzone, is_pm;
+ gint hour, min, sec, offset, n;
+ struct _date_token *token;
+ struct tm tm;
+ time_t t;
+
+ memset ((gpointer) &tm, 0, sizeof (struct tm));
+ got_wday = got_month = got_tzone = FALSE;
+ is_pm = FALSE;
+ offset = 0;
+
+ token = tokens;
+ while (token) {
+ if (is_weekday (token) && !got_wday) {
+ if ((n = get_wday (token->start, token->len)) != -1) {
+ d (printf ("weekday; "));
+ got_wday = TRUE;
+ tm.tm_wday = n;
+ goto next;
+ }
+ }
+
+ if (is_month (token) && !got_month) {
+ if ((n = get_month (token->start, token->len)) != -1) {
+ d (printf ("month; "));
+ got_month = TRUE;
+ tm.tm_mon = n;
+ goto next;
+ }
+ }
+
+ if (is_time (token) && !tm.tm_hour && !tm.tm_min && !tm.tm_sec) {
+ if (get_time (token->start, token->len, &hour, &min, &sec)) {
+ d (printf ("time; "));
+ tm.tm_hour = hour;
+ tm.tm_min = min;
+ tm.tm_sec = sec;
+ goto next;
+ }
+ }
+
+ if (!got_tzone && token->start && (
+ g_ascii_strncasecmp (token->start, "AM", 2) == 0 ||
+ g_ascii_strncasecmp (token->start, "PM", 2) == 0)) {
+ is_pm = g_ascii_strncasecmp (token->start, "PM", 2) == 0;
+
+ goto next;
+ }
+
+ if (is_tzone (token) && !got_tzone) {
+ struct _date_token *t = token;
+
+ if ((n = get_tzone (&t)) != -1) {
+ d (printf ("tzone; "));
+ got_tzone = TRUE;
+ offset = n;
+ goto next;
+ }
+ }
+
+ if (is_numeric (token)) {
+ if (token->len == 4 && !tm.tm_year) {
+ if ((n = get_year (token->start, token->len)) != -1) {
+ d (printf ("year; "));
+ tm.tm_year = n - 1900;
+ goto next;
+ }
+ } else {
+ /* Note: assumes MM-DD-YY ordering if '0 < MM < 12' holds true */
+ if (!got_month && token->next && is_numeric (token->next)) {
+ if ((n = decode_int (token->start, token->len)) > 12) {
+ goto mday;
+ } else if (n > 0) {
+ d (printf ("mon; "));
+ got_month = TRUE;
+ tm.tm_mon = n - 1;
+ }
+ goto next;
+ } else if (!tm.tm_mday && (n = get_mday (token->start, token->len)) != -1) {
+ mday:
+ d (printf ("mday; "));
+ tm.tm_mday = n;
+ goto next;
+ } else if (!tm.tm_year) {
+ if ((n = get_year (token->start, token->len)) != -1) {
+ d (printf ("2-digit year; "));
+ tm.tm_year = n - 1900;
+ }
+ goto next;
+ }
+ }
+ }
+
+ d (printf ("???; "));
+
+ next:
+
+ token = token->next;
+ }
+
+ d (printf ("\n"));
+
+ t = camel_mktime_utc (&tm);
+
+ /* t is now GMT of the time we want, but not offset by the timezone ... */
+
+ /* this should convert the time to the GMT equiv time */
+ t -= ((offset / 100) * 60 * 60) + (offset % 100) * 60;
+
+ if (is_pm)
+ t += 12 * 60 * 60;
+
+ if (tzone)
+ *tzone = offset;
+
+ return t;
+}
+
+/**
+ * camel_header_decode_date:
+ * @str: input date string
+ * @tz_offset: timezone offset
+ *
+ * Decodes the rfc822 date string and saves the GMT offset into
+ * @tz_offset if non-NULL.
+ *
+ * Returns: the time_t representation of the date string specified by
+ * @str or (time_t) %0 on error. If @tz_offset is non-NULL, the value
+ * of the timezone offset will be stored.
+ **/
+time_t
+camel_header_decode_date (const gchar *str,
+ gint *tz_offset)
+{
+ struct _date_token *token, *tokens;
+ time_t date;
+
+ if (!str || !(tokens = datetok (str))) {
+ if (tz_offset)
+ *tz_offset = 0;
+
+ return (time_t) 0;
+ }
+
+ if (!(date = parse_rfc822_date (tokens, tz_offset)))
+ date = parse_broken_date (tokens, tz_offset);
+
+ /* cleanup */
+ while (tokens) {
+ token = tokens;
+ tokens = tokens->next;
+ g_free (token);
+ }
+
+ return date;
+}
+
+gchar *
+camel_header_location_decode (const gchar *in)
+{
+ gint quote = 0;
+ GString *out = g_string_new ("");
+ gchar 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 *header)
+{
+ guchar *cp;
+
+ cp = (guchar *) header->value;
+ while (cp && *cp) {
+ if (!isascii (*cp)) {
+ w (g_warning ("Appending header violates rfc: %s: %s", header->name, header->value));
+ return;
+ }
+ cp++;
+ }
+}
+#endif
+
+void
+camel_header_raw_append_parse (struct _camel_header_raw **list,
+ const gchar *header,
+ gint offset)
+{
+ register const gchar *in;
+ gsize fieldlen;
+ gchar *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 gchar *name,
+ const gchar *value,
+ gint 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 (!g_ascii_strcasecmp (name, "To")) {
+ printf ("- Decoding To\n");
+ camel_header_to_decode (value);
+ } else if (!g_ascii_strcasecmp (name, "Content-type")) {
+ printf ("- Decoding content-type\n");
+ camel_content_type_dump (camel_content_type_decode (value));
+ } else if (!g_ascii_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 gchar *name)
+{
+ struct _camel_header_raw *l;
+
+ l = *list;
+ while (l) {
+ if (!g_ascii_strcasecmp (l->name, name))
+ break;
+ l = l->next;
+ }
+ return l;
+}
+
+const gchar *
+camel_header_raw_find (struct _camel_header_raw **list,
+ const gchar *name,
+ gint *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 gchar *
+camel_header_raw_find_next (struct _camel_header_raw **list,
+ const gchar *name,
+ gint *offset,
+ const gchar *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 gchar *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 gchar *name,
+ const gchar *value,
+ gint 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;
+}
+
+/**
+ * camel_header_msgid_generate:
+ * @domain: domain to use (like "example.com") for the ID suffix; can be NULL
+ *
+ * Either the @domain is used, or the user's local hostname,
+ * in case it's NULL or empty.
+ *
+ * Returns: Unique message ID.
+ **/
+gchar *
+camel_header_msgid_generate (const gchar *domain)
+{
+ static GMutex count_lock;
+#define COUNT_LOCK() g_mutex_lock (&count_lock)
+#define COUNT_UNLOCK() g_mutex_unlock (&count_lock)
+ gchar host[MAXHOSTNAMELEN];
+ const gchar *name;
+ static gint count = 0;
+ gchar *msgid;
+ gint retval;
+ struct addrinfo *ai = NULL, hints = { 0 };
+ static gchar *cached_hostname = NULL;
+
+ COUNT_LOCK ();
+ if (!cached_hostname && (!domain || !*domain)) {
+ domain = NULL;
+
+ retval = gethostname (host, sizeof (host));
+ if (retval == 0 && *host) {
+ hints.ai_flags = AI_CANONNAME;
+ ai = camel_getaddrinfo (
+ host, NULL, &hints, NULL, NULL);
+ if (ai && ai->ai_canonname)
+ name = ai->ai_canonname;
+ else
+ name = host;
+ } else
+ name = "localhost.localdomain";
+
+ cached_hostname = g_strdup (name);
+ }
+
+ msgid = g_strdup_printf ("%d.%d.%d.camel@%s", (gint) time (NULL), getpid (), count++, domain ? domain : cached_hostname);
+ COUNT_UNLOCK ();
+
+ if (ai)
+ camel_freeaddrinfo (ai);
+
+ return msgid;
+}
+
+static struct {
+ const gchar *name;
+ const gchar *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>]*)" },
+};
+
+static gpointer
+mailing_list_init (gpointer param)
+{
+ gint i, errcode, failed = 0;
+
+ /* 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) {
+ gchar *errstr;
+ gsize 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);
+ failed++;
+ }
+ }
+
+ g_warn_if_fail (failed == 0);
+
+ return NULL;
+}
+
+gchar *
+camel_header_raw_check_mailing_list (struct _camel_header_raw **list)
+{
+ static GOnce once = G_ONCE_INIT;
+ const gchar *v;
+ regmatch_t match[3];
+ gint i, j;
+
+ g_once (&once, mailing_list_init, NULL);
+
+ for (i = 0; i < G_N_ELEMENTS (mail_list_magic); 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) {
+ gint len1, len2;
+ gchar *mlist;
+
+ len1 = match[1].rm_eo - match[1].rm_so;
+ len2 = match[2].rm_eo - match[2].rm_so;
+
+ mlist = g_malloc (len1 + len2 + 2);
+ memcpy (mlist, v + match[1].rm_so, len1);
+ if (len2) {
+ mlist[len1] = '@';
+ memcpy (mlist + len1 + 1, v + match[2].rm_so, len2);
+ mlist[len1 + len2 + 1] = '\0';
+ } else {
+ mlist[len1] = '\0';
+ }
+
+ return mlist;
+ }
+ }
+
+ return NULL;
+}
+
+/* ok, here's the address stuff, what a mess ... */
+CamelHeaderAddress *
+camel_header_address_new (void)
+{
+ CamelHeaderAddress *h;
+ h = g_malloc0 (sizeof (*h));
+ h->type = CAMEL_HEADER_ADDRESS_NONE;
+ h->refcount = 1;
+ return h;
+}
+
+CamelHeaderAddress *
+camel_header_address_new_name (const gchar *name,
+ const gchar *addr)
+{
+ CamelHeaderAddress *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;
+}
+
+CamelHeaderAddress *
+camel_header_address_new_group (const gchar *name)
+{
+ CamelHeaderAddress *h;
+
+ h = camel_header_address_new ();
+ h->type = CAMEL_HEADER_ADDRESS_GROUP;
+ h->name = g_strdup (name);
+ return h;
+}
+
+CamelHeaderAddress *
+camel_header_address_ref (CamelHeaderAddress *h)
+{
+ if (h)
+ h->refcount++;
+
+ return h;
+}
+
+void
+camel_header_address_unref (CamelHeaderAddress *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 (CamelHeaderAddress *h,
+ const gchar *name)
+{
+ if (h) {
+ g_free (h->name);
+ h->name = g_strdup (name);
+ }
+}
+
+void
+camel_header_address_set_addr (CamelHeaderAddress *h,
+ const gchar *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 (CamelHeaderAddress *h,
+ CamelHeaderAddress *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 (CamelHeaderAddress *h,
+ CamelHeaderAddress *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 (CamelHeaderAddress **l,
+ CamelHeaderAddress **h)
+{
+ if (l) {
+ CamelHeaderAddress *n = (CamelHeaderAddress *) l;
+
+ while (n->next)
+ n = n->next;
+ n->next = *h;
+ }
+}
+
+void
+camel_header_address_list_append (CamelHeaderAddress **l,
+ CamelHeaderAddress *h)
+{
+ if (h) {
+ camel_header_address_list_append_list (l, &h);
+ h->next = NULL;
+ }
+}
+
+void
+camel_header_address_list_clear (CamelHeaderAddress **l)
+{
+ CamelHeaderAddress *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,
+ gint encode,
+ CamelHeaderAddress *a)
+{
+ gchar *text;
+
+ while (a) {
+ switch (a->type) {
+ case CAMEL_HEADER_ADDRESS_NAME:
+ if (encode)
+ text = camel_header_encode_phrase ((guchar *) 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 ((guchar *) 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, ", ");
+ }
+}
+
+gchar *
+camel_header_address_list_encode (CamelHeaderAddress *a)
+{
+ GString *out;
+ gchar *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;
+}
+
+gchar *
+camel_header_address_list_format (CamelHeaderAddress *a)
+{
+ GString *out;
+ gchar *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;
+}
+
+gchar *
+camel_header_address_fold (const gchar *in,
+ gsize headerlen)
+{
+ gsize len, outlen;
+ const gchar *inptr = in, *space, *p, *n;
+ GString *out;
+ gchar *ret;
+ gint 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 ((gchar *) in);
+
+ return ret;
+}
+
+/* simple header folding */
+/* will work even if the header is already folded */
+gchar *
+camel_header_fold (const gchar *in,
+ gsize headerlen)
+{
+ gsize len, outlen, tmplen;
+ const gchar *inptr = in, *space, *p, *n;
+ GString *out;
+ gchar *ret;
+ gint needunfold = FALSE;
+ gchar spc;
+
+ 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 = inptr;
+ while (*space && *space != ' ' && *space != '\t')
+ space++;
+
+ if (*space)
+ len = space - inptr + 1;
+ else
+ len = space - 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] == ' ' || out->str[out->len - 1] == '\t')) {
+ spc = out->str[out->len - 1];
+ g_string_truncate (out, out->len - 1);
+ g_string_append_c (out, '\n');
+ g_string_append_c (out, spc);
+ outlen = 1;
+ }
+
+ /* check for very long words, just cut them up */
+ while (outlen + len > CAMEL_FOLD_MAX_SIZE) {
+ tmplen = CAMEL_FOLD_MAX_SIZE - outlen;
+ g_string_append_len (out, inptr, tmplen);
+ g_string_append (out, "\n\t");
+ inptr += tmplen;
+ len -= tmplen;
+ outlen = 1;
+ }
+ }
+
+ g_string_append_len (out, inptr, len);
+ outlen += len;
+ inptr += len;
+ }
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ if (needunfold)
+ g_free ((gchar *) in);
+
+ return ret;
+}
+
+gchar *
+camel_header_unfold (const gchar *in)
+{
+ const gchar *inptr = in;
+ gchar c, *o, *out;
+
+ if (in == NULL)
+ return NULL;
+
+ out = g_malloc (strlen (in) + 1);
+
+ 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;
+}
diff --git a/src/camel/camel-mime-utils.h b/src/camel/camel-mime-utils.h
new file mode 100644
index 000000000..2a51044bf
--- /dev/null
+++ b/src/camel/camel-mime-utils.h
@@ -0,0 +1,259 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MIME_UTILS_H
+#define CAMEL_MIME_UTILS_H
+
+#include <time.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <camel/camel-enums.h>
+
+/* maximum recommended size of a line from camel_header_fold() */
+#define CAMEL_FOLD_SIZE (77)
+/* maximum hard size of a line from camel_header_fold() */
+#define CAMEL_FOLD_MAX_SIZE (998)
+
+typedef enum {
+ CAMEL_UUDECODE_STATE_INIT = 0,
+ CAMEL_UUDECODE_STATE_BEGIN = (1 << 16),
+ CAMEL_UUDECODE_STATE_END = (1 << 17)
+} CamelUUDecodeState;
+
+#define CAMEL_UUDECODE_STATE_MASK (CAMEL_UUDECODE_STATE_BEGIN | CAMEL_UUDECODE_STATE_END)
+
+G_BEGIN_DECLS
+
+typedef struct _camel_header_param {
+ struct _camel_header_param *next;
+ gchar *name;
+ gchar *value;
+} CamelHeaderParam;
+
+/* describes a content-type */
+typedef struct {
+ gchar *type;
+ gchar *subtype;
+ struct _camel_header_param *params;
+ guint refcount;
+} CamelContentType;
+
+/* a raw rfc822 header */
+/* the value MUST be US-ASCII */
+struct _camel_header_raw {
+ struct _camel_header_raw *next;
+ gchar *name;
+ gchar *value;
+ gint offset; /* in file, if known */
+};
+
+typedef struct _CamelContentDisposition {
+ gchar *disposition;
+ struct _camel_header_param *params;
+ guint refcount;
+} CamelContentDisposition;
+
+typedef enum _camel_header_address_t {
+ CAMEL_HEADER_ADDRESS_NONE, /* uninitialised */
+ CAMEL_HEADER_ADDRESS_NAME,
+ CAMEL_HEADER_ADDRESS_GROUP
+} CamelHeaderAddressType;
+
+typedef struct _camel_header_address {
+ struct _camel_header_address *next;
+ CamelHeaderAddressType type;
+ gchar *name;
+ union {
+ gchar *addr;
+ struct _camel_header_address *members;
+ } v;
+ guint refcount;
+} CamelHeaderAddress;
+
+/* Time utilities */
+time_t camel_mktime_utc (struct tm *tm);
+void camel_localtime_with_offset (time_t tt,
+ struct tm *tm,
+ gint *offset);
+
+/* Address lists */
+GType camel_header_address_get_type (void) G_GNUC_CONST;
+CamelHeaderAddress *camel_header_address_new (void);
+CamelHeaderAddress *camel_header_address_new_name (const gchar *name, const gchar *addr);
+CamelHeaderAddress *camel_header_address_new_group (const gchar *name);
+CamelHeaderAddress *camel_header_address_ref (CamelHeaderAddress *addrlist);
+void camel_header_address_unref (CamelHeaderAddress *addrlist);
+void camel_header_address_set_name (CamelHeaderAddress *addrlist, const gchar *name);
+void camel_header_address_set_addr (CamelHeaderAddress *addrlist, const gchar *addr);
+void camel_header_address_set_members (CamelHeaderAddress *addrlist, CamelHeaderAddress *group);
+void camel_header_address_add_member (CamelHeaderAddress *addrlist, CamelHeaderAddress *member);
+void camel_header_address_list_append_list (CamelHeaderAddress **addrlistp, CamelHeaderAddress **addrs);
+void camel_header_address_list_append (CamelHeaderAddress **addrlistp, CamelHeaderAddress *addr);
+void camel_header_address_list_clear (CamelHeaderAddress **addrlistp);
+
+CamelHeaderAddress *camel_header_address_decode (const gchar *in, const gchar *charset);
+CamelHeaderAddress *camel_header_mailbox_decode (const gchar *in, const gchar *charset);
+/* for mailing */
+gchar *camel_header_address_list_encode (CamelHeaderAddress *addrlist);
+/* for display */
+gchar *camel_header_address_list_format (CamelHeaderAddress *addrlist);
+
+/* structured header prameters */
+struct _camel_header_param *camel_header_param_list_decode (const gchar *in);
+gchar *camel_header_param (struct _camel_header_param *params, const gchar *name);
+struct _camel_header_param *camel_header_set_param (struct _camel_header_param **paramsp, const gchar *name, const gchar *value);
+void camel_header_param_list_format_append (GString *out, struct _camel_header_param *params);
+gchar *camel_header_param_list_format (struct _camel_header_param *params);
+void camel_header_param_list_free (struct _camel_header_param *params);
+
+/* Content-Type header */
+GType camel_content_type_get_type (void) G_GNUC_CONST;
+CamelContentType *camel_content_type_new (const gchar *type, const gchar *subtype);
+CamelContentType *camel_content_type_decode (const gchar *in);
+void camel_content_type_unref (CamelContentType *content_type);
+CamelContentType *camel_content_type_ref (CamelContentType *content_type);
+const gchar *camel_content_type_param (CamelContentType *content_type, const gchar *name);
+void camel_content_type_set_param (CamelContentType *content_type, const gchar *name, const gchar *value);
+gint camel_content_type_is (CamelContentType *content_type, const gchar *type, const gchar *subtype);
+gchar *camel_content_type_format (CamelContentType *content_type);
+gchar *camel_content_type_simple (CamelContentType *content_type);
+
+/* DEBUGGING function */
+void camel_content_type_dump (CamelContentType *content_type);
+
+/* Content-Disposition header */
+GType camel_content_disposition_get_type (void) G_GNUC_CONST;
+CamelContentDisposition *camel_content_disposition_decode (const gchar *in);
+CamelContentDisposition *camel_content_disposition_ref (CamelContentDisposition *disposition);
+void camel_content_disposition_unref (CamelContentDisposition *disposition);
+gchar *camel_content_disposition_format (CamelContentDisposition *disposition);
+
+/* decode the contents of a content-encoding header */
+gchar *camel_content_transfer_encoding_decode (const gchar *in);
+
+/* raw headers */
+void camel_header_raw_append (struct _camel_header_raw **list, const gchar *name, const gchar *value, gint offset);
+void camel_header_raw_append_parse (struct _camel_header_raw **list, const gchar *header, gint offset);
+const gchar *camel_header_raw_find (struct _camel_header_raw **list, const gchar *name, gint *offset);
+const gchar *camel_header_raw_find_next (struct _camel_header_raw **list, const gchar *name, gint *offset, const gchar *last);
+void camel_header_raw_replace (struct _camel_header_raw **list, const gchar *name, const gchar *value, gint offset);
+void camel_header_raw_remove (struct _camel_header_raw **list, const gchar *name);
+void camel_header_raw_fold (struct _camel_header_raw **list);
+void camel_header_raw_clear (struct _camel_header_raw **list);
+
+gchar *camel_header_raw_check_mailing_list (struct _camel_header_raw **list);
+
+/* fold a header */
+gchar *camel_header_address_fold (const gchar *in, gsize headerlen);
+gchar *camel_header_fold (const gchar *in, gsize headerlen);
+gchar *camel_header_unfold (const gchar *in);
+
+/* decode a header which is a simple token */
+gchar *camel_header_token_decode (const gchar *in);
+
+gint camel_header_decode_int (const gchar **in);
+
+/* decode/encode a string type, like a subject line */
+gchar *camel_header_decode_string (const gchar *in, const gchar *default_charset);
+gchar *camel_header_encode_string (const guchar *in);
+
+/* decode (text | comment) - a one-way op */
+gchar *camel_header_format_ctext (const gchar *in, const gchar *default_charset);
+
+/* encode a phrase, like the real name of an address */
+gchar *camel_header_encode_phrase (const guchar *in);
+
+/* FIXME: these are the only 2 functions in this header which are ch_(action)_type
+ * rather than ch_type_(action) */
+
+/* decode an email date field into a GMT time, + optional offset */
+time_t camel_header_decode_date (const gchar *str, gint *tz_offset);
+gchar *camel_header_format_date (time_t date, gint tz_offset);
+
+/* decode a message id */
+gchar *camel_header_msgid_decode (const gchar *in);
+gchar *camel_header_contentid_decode (const gchar *in);
+
+/* generate msg id */
+gchar *camel_header_msgid_generate (const gchar *domain);
+
+/* decode a References or In-Reply-To header */
+GSList *camel_header_references_decode (const gchar *in);
+
+/* decode content-location */
+gchar *camel_header_location_decode (const gchar *in);
+
+/* nntp stuff */
+GSList *camel_header_newsgroups_decode (const gchar *in);
+
+const gchar *camel_transfer_encoding_to_string (CamelTransferEncoding encoding);
+CamelTransferEncoding camel_transfer_encoding_from_string (const gchar *string);
+
+/* decode the mime-type header */
+void camel_header_mime_decode (const gchar *in, gint *maj, gint *min);
+
+gsize camel_uudecode_step (guchar *in, gsize inlen, guchar *out, gint *state, guint32 *save);
+
+gsize camel_uuencode_step (guchar *in, gsize len, guchar *out, guchar *uubuf, gint *state,
+ guint32 *save);
+gsize camel_uuencode_close (guchar *in, gsize len, guchar *out, guchar *uubuf, gint *state,
+ guint32 *save);
+
+gsize camel_quoted_decode_step (guchar *in, gsize len, guchar *out, gint *savestate, gint *saveme);
+
+gsize camel_quoted_encode_step (guchar *in, gsize len, guchar *out, gint *state, gint *save);
+gsize camel_quoted_encode_close (guchar *in, gsize len, guchar *out, gint *state, gint *save);
+
+/* camel ctype type functions for rfc822/rfc2047/other, which are non-locale specific */
+enum {
+ CAMEL_MIME_IS_CTRL = 1 << 0,
+ CAMEL_MIME_IS_LWSP = 1 << 1,
+ CAMEL_MIME_IS_TSPECIAL = 1 << 2,
+ CAMEL_MIME_IS_SPECIAL = 1 << 3,
+ CAMEL_MIME_IS_SPACE = 1 << 4,
+ CAMEL_MIME_IS_DSPECIAL = 1 << 5,
+ CAMEL_MIME_IS_QPSAFE = 1 << 6,
+ CAMEL_MIME_IS_ESAFE = 1 << 7, /* encoded word safe */
+ CAMEL_MIME_IS_PSAFE = 1 << 8, /* encoded word in phrase safe */
+ CAMEL_MIME_IS_ATTRCHAR = 1 << 9 /* attribute-char safe (rfc2184) */
+};
+
+extern gushort camel_mime_special_table[256];
+
+#define camel_mime_is_ctrl(x) ((camel_mime_special_table[(guchar)(x)] & CAMEL_MIME_IS_CTRL) != 0)
+#define camel_mime_is_lwsp(x) ((camel_mime_special_table[(guchar)(x)] & CAMEL_MIME_IS_LWSP) != 0)
+#define camel_mime_is_tspecial(x) ((camel_mime_special_table[(guchar)(x)] & CAMEL_MIME_IS_TSPECIAL) != 0)
+#define camel_mime_is_type(x, t) ((camel_mime_special_table[(guchar)(x)] & (t)) != 0)
+#define camel_mime_is_ttoken(x) ((camel_mime_special_table[(guchar)(x)] & (CAMEL_MIME_IS_TSPECIAL|CAMEL_MIME_IS_LWSP|CAMEL_MIME_IS_CTRL)) == 0)
+#define camel_mime_is_atom(x) ((camel_mime_special_table[(guchar)(x)] & (CAMEL_MIME_IS_SPECIAL|CAMEL_MIME_IS_SPACE|CAMEL_MIME_IS_CTRL)) == 0)
+#define camel_mime_is_dtext(x) ((camel_mime_special_table[(guchar)(x)] & CAMEL_MIME_IS_DSPECIAL) == 0)
+#define camel_mime_is_fieldname(x) ((camel_mime_special_table[(guchar)(x)] & (CAMEL_MIME_IS_CTRL|CAMEL_MIME_IS_SPACE)) == 0)
+#define camel_mime_is_qpsafe(x) ((camel_mime_special_table[(guchar)(x)] & CAMEL_MIME_IS_QPSAFE) != 0)
+#define camel_mime_is_especial(x) ((camel_mime_special_table[(guchar)(x)] & CAMEL_MIME_IS_ESPECIAL) != 0)
+#define camel_mime_is_psafe(x) ((camel_mime_special_table[(guchar)(x)] & CAMEL_MIME_IS_PSAFE) != 0)
+#define camel_mime_is_attrchar(x) ((camel_mime_special_table[(guchar)(x)] & CAMEL_MIME_IS_ATTRCHAR) != 0)
+
+G_END_DECLS
+
+#endif /* CAMEL_MIME_UTILS_H */
diff --git a/src/camel/camel-movemail.c b/src/camel/camel-movemail.c
new file mode 100644
index 000000000..4e00e2ef6
--- /dev/null
+++ b/src/camel/camel-movemail.c
@@ -0,0 +1,576 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-movemail.c: mbox copying function
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-lock-client.h"
+#include "camel-mime-filter-from.h"
+#include "camel-mime-filter.h"
+#include "camel-mime-parser.h"
+#include "camel-movemail.h"
+
+#define d(x)
+
+#define CHECK_CALL(x) G_STMT_START { \
+ if ((x) == -1) { \
+ g_debug ("%s: Call of '" #x "' failed: %s", G_STRFUNC, g_strerror (errno)); \
+ } \
+ } G_STMT_END
+
+#ifdef MOVEMAIL_PATH
+#include <sys/wait.h>
+
+static void movemail_external (const gchar *source, const gchar *dest,
+ GError **error);
+#endif
+
+#ifdef ENABLE_BROKEN_SPOOL
+static gint camel_movemail_copy_filter (gint fromfd, gint tofd, goffset start, gsize bytes, CamelMimeFilter *filter);
+static gint camel_movemail_solaris (gint oldsfd, gint dfd, GError **error);
+#else
+/* these could probably be exposed as a utility? (but only mbox needs it) */
+static gint camel_movemail_copy_file (gint sfd, gint dfd, GError **error);
+#endif
+
+#if 0
+static gint camel_movemail_copy (gint fromfd, gint tofd, goffset start, gsize bytes);
+#endif
+
+/**
+ * camel_movemail:
+ * @source: source file
+ * @dest: destination file
+ * @error: return location for a #GError, or %NULL
+ *
+ * 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 or 0 on success.
+ **/
+gint
+camel_movemail (const gchar *source,
+ const gchar *dest,
+ GError **error)
+{
+ gint lockid = -1;
+ gint res = -1;
+ gint sfd, dfd;
+ struct stat st;
+
+ /* open files */
+ sfd = open (source, O_RDWR);
+ if (sfd == -1 && errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not open mail file %s: %s"),
+ source, g_strerror (errno));
+ return -1;
+ } else if (sfd == -1) {
+ /* No mail. */
+ return 0;
+ }
+
+ /* Stat 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 (fstat (sfd, &st) == -1) {
+ close (sfd);
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not check mail file %s: %s"),
+ source, g_strerror (errno));
+ return -1;
+ }
+
+ if (st.st_size == 0) {
+ close (sfd);
+ return 0;
+ }
+
+ dfd = open (dest, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
+ if (dfd == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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, error);
+ if (lockid == -1) {
+ close (sfd);
+ close (dfd);
+ return -1;
+ }
+
+#ifdef ENABLE_BROKEN_SPOOL
+ res = camel_movemail_solaris (sfd, dfd, ex);
+#else
+ res = camel_movemail_copy_file (sfd, dfd, error);
+#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) {
+ CHECK_CALL (ftruncate (sfd, 0));
+ } else {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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 gchar *source,
+ const gchar *dest,
+ GError **error)
+{
+ sigset_t mask, omask;
+ pid_t pid;
+ gint fd[2], len = 0, nread, status;
+ gchar 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);
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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);
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Movemail program failed: %s"),
+ output ? output : _("(Unknown error)"));
+ }
+ g_free (output);
+}
+#endif
+
+#ifndef ENABLE_BROKEN_SPOOL
+static gint
+camel_movemail_copy_file (gint sfd,
+ gint dfd,
+ GError **error)
+{
+ gint nread, nwrote;
+ gchar buf[4096];
+
+ while (1) {
+ gint written = 0;
+
+ nread = read (sfd, buf, sizeof (buf));
+ if (nread == 0)
+ break;
+ else if (nread == -1) {
+ if (errno == EINTR)
+ continue;
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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 */
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Error writing mail temp file: %s"),
+ g_strerror (errno));
+ return -1;
+ }
+ written += nwrote;
+ nread -= nwrote;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+#if 0
+static gint
+camel_movemail_copy (gint fromfd,
+ gint tofd,
+ goffset start,
+ gsize bytes)
+{
+ gchar buffer[4096];
+ gint written = 0;
+
+ d (printf ("writing %d bytes ... ", bytes));
+
+ if (lseek (fromfd, start, SEEK_SET) != start)
+ return -1;
+
+ while (bytes > 0) {
+ gint 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 ENABLE_BROKEN_SPOOL
+static gint
+camel_movemail_copy_filter (gint fromfd,
+ gint tofd,
+ goffset start,
+ gsize bytes,
+ CamelMimeFilter *filter)
+{
+ gchar buffer[4096 + PRE_SIZE];
+ gint written = 0;
+ gchar *filterbuffer;
+ gint 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) {
+ gint 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 gint
+solaris_header_write (gint fd,
+ struct _camel_header_raw *header)
+{
+ struct iovec iv[4];
+ gint 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 (g_ascii_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 gint
+camel_movemail_solaris (gint oldsfd,
+ gint dfd,
+ GError **error)
+{
+ CamelMimeParser *mp;
+ gchar *buffer;
+ gint len;
+ gint sfd;
+ CamelMimeFilter *ffrom;
+ gint ret = 1;
+ gchar *from = NULL;
+
+ /* need to dup as the mime parser will close on finish */
+ sfd = dup (oldsfd);
+ if (sfd == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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_return_val_if_fail (camel_mime_parser_from_line (mp), -1);
+ from = g_strdup (camel_mime_parser_from_line (mp));
+ if (camel_mime_parser_step (mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_FROM_END) {
+ const gchar *cl;
+ gint length;
+ gint start, body;
+ goffset 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", (gint) 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, 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);
+ }
+
+ g_object_unref (mp);
+ g_object_unref (ffrom);
+
+ return ret;
+
+fail:
+ g_free (from);
+
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Error copying mail temp file: %s"),
+ g_strerror (errno));
+
+ g_object_unref (mp);
+ g_object_unref (ffrom);
+
+ return -1;
+}
+#endif /* ENABLE_BROKEN_SPOOL */
+
diff --git a/src/camel/camel-movemail.h b/src/camel/camel-movemail.h
new file mode 100644
index 000000000..dbf1d7816
--- /dev/null
+++ b/src/camel/camel-movemail.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-movemail.h: mbox copy function
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MOVEMAIL_H
+#define CAMEL_MOVEMAIL_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gint camel_movemail (const gchar *source, const gchar *dest, GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_MOVEMAIL_H */
diff --git a/src/camel/camel-msgport.c b/src/camel/camel-msgport.c
new file mode 100644
index 000000000..26a9cd0ff
--- /dev/null
+++ b/src/camel/camel-msgport.c
@@ -0,0 +1,473 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <nspr.h>
+
+#ifdef G_OS_WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <winsock2.h>
+#endif
+
+#include "camel-msgport.h"
+
+#ifdef G_OS_WIN32
+#define MP_CLOSE(socket) closesocket (socket)
+#define MP_READ(socket, buf, nbytes) recv((socket), (buf), (nbytes), 0)
+#define MP_WRITE(socket, buf, nbytes) send((socket), (buf), (nbytes), 0)
+#define MP_IS_STATUS_INTR() 0 /* No WSAEINTR errors in WinSock2 */
+#else
+#define MP_CLOSE(socket) close (socket)
+#define MP_READ(socket, buf, nbytes) read((socket), (buf), (nbytes))
+#define MP_WRITE(socket, buf, nbytes) write((socket), (buf), (nbytes))
+#define MP_IS_STATUS_INTR() (errno == EINTR)
+#endif
+
+/* message flags */
+enum {
+ MSG_FLAG_SYNC_WITH_PIPE = 1 << 0,
+ MSG_FLAG_SYNC_WITH_PR_PIPE = 1 << 1
+};
+
+struct _CamelMsgPort {
+ GAsyncQueue *queue;
+ gint pipe[2]; /* on Win32, actually a pair of SOCKETs */
+ PRFileDesc *prpipe[2];
+};
+
+static gint
+msgport_pipe (gint *fds)
+{
+#ifndef G_OS_WIN32
+ if (pipe (fds) != -1)
+ return 0;
+
+ fds[0] = -1;
+ fds[1] = -1;
+
+ return -1;
+#else
+ SOCKET temp, socket1 = -1, socket2 = -1;
+ struct sockaddr_in saddr;
+ gint len;
+ u_long arg;
+ fd_set read_set, write_set;
+ struct timeval tv;
+
+ temp = socket (AF_INET, SOCK_STREAM, 0);
+
+ if (temp == INVALID_SOCKET) {
+ goto out0;
+ }
+
+ arg = 1;
+ if (ioctlsocket (temp, FIONBIO, &arg) == SOCKET_ERROR) {
+ goto out0;
+ }
+
+ memset (&saddr, 0, sizeof (saddr));
+ saddr.sin_family = AF_INET;
+ saddr.sin_port = 0;
+ saddr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+
+ if (bind (temp, (struct sockaddr *) &saddr, sizeof (saddr))) {
+ goto out0;
+ }
+
+ if (listen (temp, 1) == SOCKET_ERROR) {
+ goto out0;
+ }
+
+ len = sizeof (saddr);
+ if (getsockname (temp, (struct sockaddr *) &saddr, &len)) {
+ goto out0;
+ }
+
+ socket1 = socket (AF_INET, SOCK_STREAM, 0);
+
+ if (socket1 == INVALID_SOCKET) {
+ goto out0;
+ }
+
+ arg = 1;
+ if (ioctlsocket (socket1, FIONBIO, &arg) == SOCKET_ERROR) {
+ goto out1;
+ }
+
+ if (connect (socket1, (struct sockaddr *) &saddr, len) != SOCKET_ERROR ||
+ WSAGetLastError () != WSAEWOULDBLOCK) {
+ goto out1;
+ }
+
+ FD_ZERO (&read_set);
+ FD_SET (temp, &read_set);
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ if (select (0, &read_set, NULL, NULL, NULL) == SOCKET_ERROR) {
+ goto out1;
+ }
+
+ if (!FD_ISSET (temp, &read_set)) {
+ goto out1;
+ }
+
+ socket2 = accept (temp, (struct sockaddr *) &saddr, &len);
+ if (socket2 == INVALID_SOCKET) {
+ goto out1;
+ }
+
+ FD_ZERO (&write_set);
+ FD_SET (socket1, &write_set);
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ if (select (0, NULL, &write_set, NULL, NULL) == SOCKET_ERROR) {
+ goto out2;
+ }
+
+ if (!FD_ISSET (socket1, &write_set)) {
+ goto out2;
+ }
+
+ arg = 0;
+ if (ioctlsocket (socket1, FIONBIO, &arg) == SOCKET_ERROR) {
+ goto out2;
+ }
+
+ arg = 0;
+ if (ioctlsocket (socket2, FIONBIO, &arg) == SOCKET_ERROR) {
+ goto out2;
+ }
+
+ fds[0] = socket1;
+ fds[1] = socket2;
+
+ closesocket (temp);
+
+ return 0;
+
+out2:
+ closesocket (socket2);
+out1:
+ closesocket (socket1);
+out0:
+ closesocket (temp);
+ errno = EMFILE; /* FIXME: use the real syscall errno? */
+
+ fds[0] = -1;
+ fds[1] = -1;
+
+ return -1;
+
+#endif
+}
+
+static gint
+msgport_prpipe (PRFileDesc **fds)
+{
+#ifdef G_OS_WIN32
+ if (PR_NewTCPSocketPair (fds) != PR_FAILURE)
+ return 0;
+#else
+ if (PR_CreatePipe (&fds[0], &fds[1]) != PR_FAILURE)
+ return 0;
+#endif
+
+ fds[0] = NULL;
+ fds[1] = NULL;
+
+ return -1;
+}
+
+static void
+msgport_sync_with_pipe (gint fd)
+{
+ gchar buffer[1];
+
+ while (fd >= 0) {
+ if (MP_READ (fd, buffer, 1) > 0)
+ break;
+ else if (!MP_IS_STATUS_INTR ()) {
+ g_warning (
+ "%s: Failed to read from pipe: %s",
+ G_STRFUNC, g_strerror (errno));
+ break;
+ }
+ }
+}
+
+static void
+msgport_sync_with_prpipe (PRFileDesc *prfd)
+{
+ gchar buffer[1];
+
+ while (prfd != NULL) {
+ if (PR_Read (prfd, buffer, 1) > 0)
+ break;
+ else if (PR_GetError () != PR_PENDING_INTERRUPT_ERROR) {
+ gchar *text = g_alloca (PR_GetErrorTextLength ());
+ PR_GetErrorText (text);
+ g_warning (
+ "%s: Failed to read from NSPR pipe: %s",
+ G_STRFUNC, text);
+ break;
+ }
+ }
+}
+
+/**
+ * camel_msgport_new:
+ *
+ * Since: 2.24
+ **/
+CamelMsgPort *
+camel_msgport_new (void)
+{
+ CamelMsgPort *msgport;
+
+ msgport = g_slice_new (CamelMsgPort);
+ msgport->queue = g_async_queue_new ();
+ msgport->pipe[0] = -1;
+ msgport->pipe[1] = -1;
+ msgport->prpipe[0] = NULL;
+ msgport->prpipe[1] = NULL;
+
+ return msgport;
+}
+
+/**
+ * camel_msgport_destroy:
+ *
+ * Since: 2.24
+ **/
+void
+camel_msgport_destroy (CamelMsgPort *msgport)
+{
+ g_return_if_fail (msgport != NULL);
+
+ if (msgport->pipe[0] >= 0) {
+ MP_CLOSE (msgport->pipe[0]);
+ MP_CLOSE (msgport->pipe[1]);
+ }
+ if (msgport->prpipe[0] != NULL) {
+ PR_Close (msgport->prpipe[0]);
+ PR_Close (msgport->prpipe[1]);
+ }
+
+ g_async_queue_unref (msgport->queue);
+ g_slice_free (CamelMsgPort, msgport);
+}
+
+/**
+ * camel_msgport_fd:
+ *
+ * Since: 2.24
+ **/
+gint
+camel_msgport_fd (CamelMsgPort *msgport)
+{
+ gint fd;
+
+ g_return_val_if_fail (msgport != NULL, -1);
+
+ g_async_queue_lock (msgport->queue);
+ fd = msgport->pipe[0];
+ if (fd < 0 && msgport_pipe (msgport->pipe) == 0)
+ fd = msgport->pipe[0];
+ g_async_queue_unlock (msgport->queue);
+
+ return fd;
+}
+
+/**
+ * camel_msgport_prfd:
+ *
+ * Since: 2.24
+ **/
+PRFileDesc *
+camel_msgport_prfd (CamelMsgPort *msgport)
+{
+ PRFileDesc *prfd;
+
+ g_return_val_if_fail (msgport != NULL, NULL);
+
+ g_async_queue_lock (msgport->queue);
+ prfd = msgport->prpipe[0];
+ if (prfd == NULL && msgport_prpipe (msgport->prpipe) == 0)
+ prfd = msgport->prpipe[0];
+ g_async_queue_unlock (msgport->queue);
+
+ return prfd;
+}
+
+/**
+ * camel_msgport_push:
+ *
+ * Since: 2.24
+ **/
+void
+camel_msgport_push (CamelMsgPort *msgport,
+ CamelMsg *msg)
+{
+ gint fd;
+ PRFileDesc *prfd;
+
+ g_return_if_fail (msgport != NULL);
+ g_return_if_fail (msg != NULL);
+
+ g_async_queue_lock (msgport->queue);
+
+ msg->flags = 0;
+
+ fd = msgport->pipe[1];
+ while (fd >= 0) {
+ if (MP_WRITE (fd, "E", 1) > 0) {
+ msg->flags |= MSG_FLAG_SYNC_WITH_PIPE;
+ break;
+ } else if (!MP_IS_STATUS_INTR ()) {
+ g_warning (
+ "%s: Failed to write to pipe: %s",
+ G_STRFUNC, g_strerror (errno));
+ break;
+ }
+ }
+
+ prfd = msgport->prpipe[1];
+ while (prfd != NULL) {
+ if (PR_Write (prfd, "E", 1) > 0) {
+ msg->flags |= MSG_FLAG_SYNC_WITH_PR_PIPE;
+ break;
+ } else if (PR_GetError () != PR_PENDING_INTERRUPT_ERROR) {
+ gchar *text = g_alloca (PR_GetErrorTextLength ());
+ PR_GetErrorText (text);
+ g_warning (
+ "%s: Failed to write to NSPR pipe: %s",
+ G_STRFUNC, text);
+ break;
+ }
+ }
+
+ g_async_queue_push_unlocked (msgport->queue, msg);
+ g_async_queue_unlock (msgport->queue);
+}
+
+/**
+ * camel_msgport_pop:
+ *
+ * Since: 2.24
+ **/
+CamelMsg *
+camel_msgport_pop (CamelMsgPort *msgport)
+{
+ CamelMsg *msg;
+
+ g_return_val_if_fail (msgport != NULL, NULL);
+
+ g_async_queue_lock (msgport->queue);
+
+ msg = g_async_queue_pop_unlocked (msgport->queue);
+
+ g_return_val_if_fail (msg != NULL, NULL);
+
+ if (msg->flags & MSG_FLAG_SYNC_WITH_PIPE)
+ msgport_sync_with_pipe (msgport->pipe[0]);
+ if (msg->flags & MSG_FLAG_SYNC_WITH_PR_PIPE)
+ msgport_sync_with_prpipe (msgport->prpipe[0]);
+
+ g_async_queue_unlock (msgport->queue);
+
+ return msg;
+}
+
+/**
+ * camel_msgport_try_pop:
+ *
+ * Since: 2.24
+ **/
+CamelMsg *
+camel_msgport_try_pop (CamelMsgPort *msgport)
+{
+ CamelMsg *msg;
+
+ g_return_val_if_fail (msgport != NULL, NULL);
+
+ g_async_queue_lock (msgport->queue);
+
+ msg = g_async_queue_try_pop_unlocked (msgport->queue);
+
+ if (msg != NULL && msg->flags & MSG_FLAG_SYNC_WITH_PIPE)
+ msgport_sync_with_pipe (msgport->pipe[0]);
+ if (msg != NULL && msg->flags & MSG_FLAG_SYNC_WITH_PR_PIPE)
+ msgport_sync_with_prpipe (msgport->prpipe[0]);
+
+ g_async_queue_unlock (msgport->queue);
+
+ return msg;
+}
+
+/**
+ * camel_msgport_timeout_pop:
+ * @msgport: a #CamelMsgPort
+ * @timeout: number of microseconds to wait
+ *
+ * Since: 3.8
+ **/
+CamelMsg *
+camel_msgport_timeout_pop (CamelMsgPort *msgport,
+ guint64 timeout)
+{
+ CamelMsg *msg;
+
+ g_return_val_if_fail (msgport != NULL, NULL);
+
+ g_async_queue_lock (msgport->queue);
+
+ msg = g_async_queue_timeout_pop_unlocked (msgport->queue, timeout);
+
+ if (msg != NULL && msg->flags & MSG_FLAG_SYNC_WITH_PIPE)
+ msgport_sync_with_pipe (msgport->pipe[0]);
+ if (msg != NULL && msg->flags & MSG_FLAG_SYNC_WITH_PR_PIPE)
+ msgport_sync_with_prpipe (msgport->prpipe[0]);
+
+ g_async_queue_unlock (msgport->queue);
+
+ return msg;
+}
+
+/**
+ * camel_msgport_reply:
+ *
+ * Since: 2.24
+ **/
+void
+camel_msgport_reply (CamelMsg *msg)
+{
+ g_return_if_fail (msg != NULL);
+
+ if (msg->reply_port)
+ camel_msgport_push (msg->reply_port, msg);
+
+ /* else lost? */
+}
diff --git a/src/camel/camel-msgport.h b/src/camel/camel-msgport.h
new file mode 100644
index 000000000..6e740314c
--- /dev/null
+++ b/src/camel/camel-msgport.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MSGPORT_H
+#define CAMEL_MSGPORT_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/**
+ * CamelMsgPort:
+ *
+ * Since: 2.24
+ **/
+
+typedef struct _CamelMsg CamelMsg;
+typedef struct _CamelMsgPort CamelMsgPort;
+
+/**
+ * CamelMsg:
+ *
+ * Since: 2.24
+ **/
+struct _CamelMsg {
+ CamelMsgPort *reply_port;
+ gint flags;
+};
+
+CamelMsgPort * camel_msgport_new (void);
+void camel_msgport_destroy (CamelMsgPort *msgport);
+gint camel_msgport_fd (CamelMsgPort *msgport);
+void camel_msgport_push (CamelMsgPort *msgport,
+ CamelMsg *msg);
+CamelMsg * camel_msgport_pop (CamelMsgPort *msgport);
+CamelMsg * camel_msgport_try_pop (CamelMsgPort *msgport);
+CamelMsg * camel_msgport_timeout_pop (CamelMsgPort *msgport,
+ guint64 timeout);
+void camel_msgport_reply (CamelMsg *msg);
+
+struct PRFileDesc * camel_msgport_prfd (CamelMsgPort *msgport);
+
+G_END_DECLS
+
+#endif /* CAMEL_MSGPORT_H */
diff --git a/src/camel/camel-multipart-encrypted.c b/src/camel/camel-multipart-encrypted.c
new file mode 100644
index 000000000..5853b3e4b
--- /dev/null
+++ b/src/camel/camel-multipart-encrypted.c
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include "camel-multipart-encrypted.h"
+
+G_DEFINE_TYPE (
+ CamelMultipartEncrypted,
+ camel_multipart_encrypted,
+ CAMEL_TYPE_MULTIPART)
+
+static void
+camel_multipart_encrypted_class_init (CamelMultipartEncryptedClass *class)
+{
+}
+
+static void
+camel_multipart_encrypted_init (CamelMultipartEncrypted *multipart)
+{
+ camel_data_wrapper_set_mime_type (
+ CAMEL_DATA_WRAPPER (multipart), "multipart/encrypted");
+}
+
+/**
+ * camel_multipart_encrypted_new:
+ *
+ * Create a new #CamelMultipartEncrypted object.
+ *
+ * A MultipartEncrypted should be used to store and create parts of
+ * type "multipart/encrypted".
+ *
+ * Returns: a new #CamelMultipartEncrypted object
+ **/
+CamelMultipartEncrypted *
+camel_multipart_encrypted_new (void)
+{
+ return g_object_new (CAMEL_TYPE_MULTIPART_ENCRYPTED, NULL);
+}
diff --git a/src/camel/camel-multipart-encrypted.h b/src/camel/camel-multipart-encrypted.h
new file mode 100644
index 000000000..dedb469f0
--- /dev/null
+++ b/src/camel/camel-multipart-encrypted.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MULTIPART_ENCRYPTED_H
+#define CAMEL_MULTIPART_ENCRYPTED_H
+
+#include <camel/camel-multipart.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MULTIPART_ENCRYPTED \
+ (camel_multipart_encrypted_get_type ())
+#define CAMEL_MULTIPART_ENCRYPTED(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MULTIPART_ENCRYPTED, CamelMultipartEncrypted))
+#define CAMEL_MULTIPART_ENCRYPTED_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MULTIPART_ENCRYPTED, CamelMultipartEncryptedClass))
+#define CAMEL_IS_MULTIPART_ENCRYPTED(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MULTIPART_ENCRYPTED))
+#define CAMEL_IS_MULTIPART_ENCRYPTED_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MULTIPART_ENCRYPTED))
+#define CAMEL_MULTIPART_ENCRYPTED_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MULTIPART_ENCRYPTED, CamelMultipartEncryptedClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMultipartEncrypted CamelMultipartEncrypted;
+typedef struct _CamelMultipartEncryptedClass CamelMultipartEncryptedClass;
+typedef struct _CamelMultipartEncryptedPrivate CamelMultipartEncryptedPrivate;
+
+/* 'handy' enums for getting the internal parts of the multipart */
+enum {
+ CAMEL_MULTIPART_ENCRYPTED_VERSION,
+ CAMEL_MULTIPART_ENCRYPTED_CONTENT
+};
+
+struct _CamelMultipartEncrypted {
+ CamelMultipart parent;
+ CamelMultipartEncryptedPrivate *priv;
+};
+
+struct _CamelMultipartEncryptedClass {
+ CamelMultipartClass parent_class;
+
+};
+
+GType camel_multipart_encrypted_get_type (void) G_GNUC_CONST;
+CamelMultipartEncrypted *
+ camel_multipart_encrypted_new (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_MULTIPART_ENCRYPTED_H */
diff --git a/src/camel/camel-multipart-signed.c b/src/camel/camel-multipart-signed.c
new file mode 100644
index 000000000..737131e7c
--- /dev/null
+++ b/src/camel/camel-multipart-signed.c
@@ -0,0 +1,877 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-multipart.c : Abstract class for a multipart
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-mime-filter-canon.h"
+#include "camel-mime-filter-crlf.h"
+#include "camel-mime-message.h"
+#include "camel-mime-parser.h"
+#include "camel-mime-part.h"
+#include "camel-multipart-signed.h"
+#include "camel-object.h"
+#include "camel-stream-filter.h"
+#include "camel-stream-mem.h"
+
+#define CAMEL_MULTIPART_SIGNED_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MULTIPART_SIGNED, CamelMultipartSignedPrivate))
+
+struct _CamelMultipartSignedPrivate {
+ /* These are the client visible parts,
+ * decoded forms of our data wrapper content. */
+ CamelMimePart *content;
+ CamelMimePart *signature;
+
+ /* The raw content which must go over the wire, if we have
+ * generated it.
+ *
+ * XXX Perhaps this should just set data_wrapper->stream and
+ * update start1/end1 accordingly, as it is done for other
+ * parts, or visa versa? */
+ CamelStream *contentraw;
+
+ /* Offset pointers of start of boundary in content object. */
+ goffset start1, end1;
+ goffset start2, end2;
+};
+
+G_DEFINE_TYPE (
+ CamelMultipartSigned,
+ camel_multipart_signed,
+ CAMEL_TYPE_MULTIPART)
+
+static CamelStream *
+multipart_signed_clip_stream (CamelMultipartSigned *mps,
+ goffset start,
+ goffset end)
+{
+ CamelDataWrapper *data_wrapper;
+ GByteArray *src;
+ GByteArray *dst;
+
+ g_return_val_if_fail (start != -1, NULL);
+ g_return_val_if_fail (end != -1, NULL);
+ g_return_val_if_fail (end >= start, NULL);
+
+ data_wrapper = CAMEL_DATA_WRAPPER (mps);
+
+ src = camel_data_wrapper_get_byte_array (data_wrapper);
+ dst = g_byte_array_new ();
+
+ if (start >= 0 && end < src->len) {
+ const guint8 *data = &src->data[start];
+ g_byte_array_append (dst, data, end - start);
+ }
+
+ /* Stream takes ownership of the byte array. */
+ return camel_stream_mem_new_with_byte_array (dst);
+}
+
+static gint
+multipart_signed_skip_content (CamelMimeParser *cmp)
+{
+ gchar *buf;
+ gsize len;
+ gint 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 */
+ state = camel_mime_parser_step (cmp, &buf, &len);
+ if (state != CAMEL_MIME_PARSER_STATE_EOF &&
+ state != CAMEL_MIME_PARSER_STATE_FROM_END) {
+ multipart_signed_skip_content (cmp);
+ } else {
+ camel_mime_parser_unstep (cmp);
+ break;
+ }
+
+ /* 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: Expecting MESSAGE_END or EOF or EOM, got: %u", camel_mime_parser_state (cmp));
+ camel_mime_parser_unstep (cmp);
+ return -1;
+ }
+ break;
+ case CAMEL_MIME_PARSER_STATE_MULTIPART:
+ /* embedded multipart */
+ while (camel_mime_parser_step (cmp, &buf, &len) != CAMEL_MIME_PARSER_STATE_MULTIPART_END)
+ multipart_signed_skip_content (cmp);
+ break;
+ default:
+ g_warning ("Invalid state encountered???: %u", camel_mime_parser_state (cmp));
+ }
+
+ return 0;
+}
+
+static gint
+multipart_signed_parse_content (CamelMultipartSigned *mps)
+{
+ CamelMimeParser *cmp;
+ CamelMultipart *mp = (CamelMultipart *) mps;
+ CamelDataWrapper *data_wrapper;
+ GByteArray *byte_array;
+ CamelStream *stream;
+ const gchar *boundary;
+ gchar *buf;
+ gsize len;
+ gint state;
+
+ boundary = camel_multipart_get_boundary (mp);
+ g_return_val_if_fail (boundary != NULL, -1);
+
+ data_wrapper = CAMEL_DATA_WRAPPER (mps);
+ byte_array = camel_data_wrapper_get_byte_array (data_wrapper);
+
+ stream = camel_stream_mem_new ();
+
+ /* Do not give the stream ownership of the byte array. */
+ camel_stream_mem_set_byte_array (
+ CAMEL_STREAM_MEM (stream), byte_array);
+
+ /* 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. */
+
+ cmp = camel_mime_parser_new ();
+ camel_mime_parser_init_with_stream (cmp, stream, NULL);
+ camel_mime_parser_push_state (cmp, CAMEL_MIME_PARSER_STATE_MULTIPART, boundary);
+
+ mps->priv->start1 = -1;
+ mps->priv->end1 = -1;
+ mps->priv->start2 = -1;
+ mps->priv->end2 = -1;
+
+ while ((state = camel_mime_parser_step (cmp, &buf, &len)) != CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
+ if (mps->priv->start1 == -1) {
+ mps->priv->start1 = camel_mime_parser_tell_start_headers (cmp);
+ } else if (mps->priv->start2 == -1) {
+ mps->priv->start2 = camel_mime_parser_tell_start_headers (cmp);
+ mps->priv->end1 = camel_mime_parser_tell_start_boundary (cmp);
+ if (mps->priv->end1 > mps->priv->start1 && byte_array->data[mps->priv->end1 - 1] == '\n')
+ mps->priv->end1--;
+ if (mps->priv->end1 > mps->priv->start1 && byte_array->data[mps->priv->end1 - 1] == '\r')
+ mps->priv->end1--;
+ } else {
+ g_warning ("multipart/signed has more than 2 parts, remaining parts ignored");
+ state = CAMEL_MIME_PARSER_STATE_MULTIPART_END;
+ break;
+ }
+
+ if (multipart_signed_skip_content (cmp) == -1)
+ break;
+ }
+
+ if (state == CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
+ mps->priv->end2 = camel_mime_parser_tell_start_boundary (cmp);
+
+ camel_multipart_set_preface (mp, camel_mime_parser_preface (cmp));
+ camel_multipart_set_postface (mp, camel_mime_parser_postface (cmp));
+ }
+
+ g_object_unref (cmp);
+ g_object_unref (stream);
+
+ if (mps->priv->end2 == -1 || mps->priv->start2 == -1) {
+ if (mps->priv->end1 == -1)
+ mps->priv->start1 = -1;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+multipart_signed_dispose (GObject *object)
+{
+ CamelMultipartSignedPrivate *priv;
+
+ priv = CAMEL_MULTIPART_SIGNED_GET_PRIVATE (object);
+
+ g_clear_object (&priv->content);
+ g_clear_object (&priv->signature);
+ g_clear_object (&priv->contentraw);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_multipart_signed_parent_class)->dispose (object);
+}
+
+static gssize
+multipart_signed_write_to_stream_sync (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMultipartSignedPrivate *priv;
+ CamelMultipart *mp = (CamelMultipart *) data_wrapper;
+ GByteArray *byte_array;
+ const gchar *boundary;
+ const gchar *preface;
+ const gchar *postface;
+ gssize total = 0;
+ gssize count;
+ gchar *content;
+
+ priv = CAMEL_MULTIPART_SIGNED_GET_PRIVATE (data_wrapper);
+
+ byte_array = camel_data_wrapper_get_byte_array (data_wrapper);
+
+ /* 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 (byte_array->len > 0) {
+ return camel_stream_write (
+ stream, (gchar *) byte_array->data,
+ byte_array->len, cancellable, error);
+ }
+
+ /* 3 */
+ if (priv->contentraw == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("No content available"));
+ return -1;
+ }
+
+ /* 3 */
+ if (priv->signature == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("No signature available"));
+ return -1;
+ }
+
+ boundary = camel_multipart_get_boundary (mp);
+ preface = camel_multipart_get_preface (mp);
+ postface = camel_multipart_get_postface (mp);
+
+ /* 2 */
+ if (preface != NULL) {
+ count = camel_stream_write_string (
+ stream, preface, cancellable, error);
+ if (count == -1)
+ return -1;
+ total += count;
+ }
+
+ /* first boundary */
+ content = g_strdup_printf ("\n--%s\n", boundary);
+ count = camel_stream_write_string (
+ stream, content, cancellable, error);
+ g_free (content);
+ if (count == -1)
+ return -1;
+ total += count;
+
+ /* output content part */
+ /* XXX Both CamelGpgContext and CamelSMIMEContext set this
+ * to a memory stream, so assume it's always seekable. */
+ g_seekable_seek (
+ G_SEEKABLE (priv->contentraw), 0, G_SEEK_SET, NULL, NULL);
+ count = camel_stream_write_to_stream (
+ priv->contentraw, stream, cancellable, error);
+ if (count == -1)
+ return -1;
+ total += count;
+
+ /* boundary */
+ content = g_strdup_printf ("\n--%s\n", boundary);
+ count = camel_stream_write_string (
+ stream, content, cancellable, error);
+ g_free (content);
+ if (count == -1)
+ return -1;
+ total += count;
+
+ /* signature */
+ count = camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (priv->signature),
+ stream, cancellable, error);
+ if (count == -1)
+ return -1;
+ total += count;
+
+ /* write the terminating boudary delimiter */
+ content = g_strdup_printf ("\n--%s--\n", boundary);
+ count = camel_stream_write_string (
+ stream, content, cancellable, error);
+ g_free (content);
+ if (count == -1)
+ return -1;
+ total += count;
+
+ /* and finally the postface */
+ if (postface != NULL) {
+ count = camel_stream_write_string (
+ stream, postface, cancellable, error);
+ if (count == -1)
+ return -1;
+ total += count;
+ }
+
+ return total;
+}
+
+static gboolean
+multipart_signed_construct_from_stream_sync (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMultipartSignedPrivate *priv;
+ CamelDataWrapperClass *parent_class;
+ gboolean success;
+
+ priv = CAMEL_MULTIPART_SIGNED_GET_PRIVATE (data_wrapper);
+
+ /* Chain up to parent's construct_from_stream_sync() method. */
+ parent_class = CAMEL_DATA_WRAPPER_CLASS (
+ camel_multipart_signed_parent_class);
+ success = parent_class->construct_from_stream_sync (
+ data_wrapper, stream, cancellable, error);
+
+ if (success) {
+ priv->start1 = -1;
+ g_clear_object (&priv->content);
+ g_clear_object (&priv->signature);
+ g_clear_object (&priv->contentraw);
+ }
+
+ return success;
+}
+
+static gssize
+multipart_signed_write_to_output_stream_sync (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMultipartSignedPrivate *priv;
+ CamelMultipart *mp = (CamelMultipart *) data_wrapper;
+ GByteArray *byte_array;
+ const gchar *boundary;
+ const gchar *preface;
+ const gchar *postface;
+ gsize bytes_written;
+ gssize total = 0;
+ gssize result;
+ gchar *content;
+ gboolean success;
+
+ priv = CAMEL_MULTIPART_SIGNED_GET_PRIVATE (data_wrapper);
+
+ byte_array = camel_data_wrapper_get_byte_array (data_wrapper);
+
+ /* 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 (byte_array->len > 0) {
+ success = g_output_stream_write_all (
+ output_stream,
+ byte_array->data, byte_array->len,
+ &bytes_written, cancellable, error);
+ return success ? (gssize) bytes_written : -1;
+ }
+
+ /* 3 */
+ if (priv->contentraw == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("No content available"));
+ return -1;
+ }
+
+ /* 3 */
+ if (priv->signature == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("No signature available"));
+ return -1;
+ }
+
+ boundary = camel_multipart_get_boundary (mp);
+ preface = camel_multipart_get_preface (mp);
+ postface = camel_multipart_get_postface (mp);
+
+ /* 2 */
+ if (preface != NULL) {
+ success = g_output_stream_write_all (
+ output_stream,
+ preface, strlen (preface),
+ &bytes_written, cancellable, error);
+ if (!success)
+ return -1;
+ total += (gssize) bytes_written;
+ }
+
+ /* first boundary */
+ content = g_strdup_printf ("\n--%s\n", boundary);
+ success = g_output_stream_write_all (
+ output_stream,
+ content, strlen (content),
+ &bytes_written, cancellable, error);
+ g_free (content);
+ if (!success)
+ return -1;
+ total += (gssize) bytes_written;
+
+ /* output content part */
+ /* XXX Both CamelGpgContext and CamelSMIMEContext set this
+ * to a memory stream, so we'll assume it to be so and
+ * grab its GByteArray. Would be better to store the
+ * raw message content as a reference-counted GBytes
+ * rather than a stream. */
+ g_return_val_if_fail (CAMEL_IS_STREAM_MEM (priv->contentraw), -1);
+ byte_array = camel_stream_mem_get_byte_array (
+ CAMEL_STREAM_MEM (priv->contentraw));
+ success = g_output_stream_write_all (
+ output_stream,
+ byte_array->data, byte_array->len,
+ &bytes_written, cancellable, error);
+ if (!success)
+ return -1;
+ total += (gssize) bytes_written;
+
+ /* boundary */
+ content = g_strdup_printf ("\n--%s\n", boundary);
+ success = g_output_stream_write_all (
+ output_stream,
+ content, strlen (content),
+ &bytes_written, cancellable, error);
+ g_free (content);
+ if (!success)
+ return -1;
+ total += (gssize) bytes_written;
+
+ /* signature */
+ result = camel_data_wrapper_write_to_output_stream_sync (
+ CAMEL_DATA_WRAPPER (priv->signature),
+ output_stream, cancellable, error);
+ if (result == -1)
+ return -1;
+ total += result;
+
+ /* write the terminating boudary delimiter */
+ content = g_strdup_printf ("\n--%s--\n", boundary);
+ success = g_output_stream_write_all (
+ output_stream,
+ content, strlen (content),
+ &bytes_written, cancellable, error);
+ g_free (content);
+ if (!success)
+ return -1;
+ total += (gssize) bytes_written;
+
+ /* and finally the postface */
+ if (postface != NULL) {
+ success = g_output_stream_write_all (
+ output_stream,
+ postface, strlen (postface),
+ &bytes_written, cancellable, error);
+ if (!success)
+ return -1;
+ total += (gssize) bytes_written;
+ }
+
+ return total;
+}
+
+static gboolean
+multipart_signed_construct_from_input_stream_sync (CamelDataWrapper *data_wrapper,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{ CamelMultipartSignedPrivate *priv;
+ gboolean success;
+
+ priv = CAMEL_MULTIPART_SIGNED_GET_PRIVATE (data_wrapper);
+
+ /* Chain up to parent's construct_from_input_stream_sync() method. */
+ success = CAMEL_DATA_WRAPPER_CLASS (
+ camel_multipart_signed_parent_class)->
+ construct_from_input_stream_sync (
+ data_wrapper, input_stream, cancellable, error);
+
+ if (success) {
+ priv->start1 = -1;
+ g_clear_object (&priv->content);
+ g_clear_object (&priv->signature);
+ g_clear_object (&priv->contentraw);
+ }
+
+ return success;
+}
+
+static void
+multipart_signed_add_part (CamelMultipart *multipart,
+ CamelMimePart *part)
+{
+ g_warning ("Cannot add parts to a signed part using add_part");
+}
+
+static CamelMimePart *
+multipart_signed_get_part (CamelMultipart *multipart,
+ guint index)
+{
+ CamelMultipartSigned *mps;
+ CamelDataWrapper *data_wrapper;
+ CamelStream *stream;
+ GByteArray *byte_array;
+
+ mps = CAMEL_MULTIPART_SIGNED (multipart);
+
+ data_wrapper = CAMEL_DATA_WRAPPER (multipart);
+ byte_array = camel_data_wrapper_get_byte_array (data_wrapper);
+
+ switch (index) {
+ case CAMEL_MULTIPART_SIGNED_CONTENT:
+ if (mps->priv->content != NULL)
+ return mps->priv->content;
+ if (mps->priv->contentraw != NULL) {
+ g_return_val_if_fail (
+ G_IS_SEEKABLE (mps->priv->contentraw), NULL);
+ stream = g_object_ref (mps->priv->contentraw);
+ } else if (mps->priv->start1 == -1
+ && multipart_signed_parse_content (mps) == -1
+ && byte_array->len == 0) {
+ g_warning ("Trying to get content on an invalid multipart/signed");
+ return NULL;
+ } else if (byte_array->len == 0) {
+ return NULL;
+ } else if (mps->priv->start1 == -1) {
+ stream = camel_stream_mem_new ();
+ camel_stream_mem_set_byte_array (
+ CAMEL_STREAM_MEM (stream), byte_array);
+ } else {
+ stream = multipart_signed_clip_stream (
+ mps, mps->priv->start1, mps->priv->end1);
+ }
+ g_seekable_seek (
+ G_SEEKABLE (stream), 0, G_SEEK_SET, NULL, NULL);
+ mps->priv->content = camel_mime_part_new ();
+ camel_data_wrapper_construct_from_stream_sync (
+ CAMEL_DATA_WRAPPER (mps->priv->content),
+ stream, NULL, NULL);
+ g_object_unref (stream);
+ return mps->priv->content;
+ case CAMEL_MULTIPART_SIGNED_SIGNATURE:
+ if (mps->priv->signature != NULL)
+ return mps->priv->signature;
+ if (mps->priv->start1 == -1
+ && multipart_signed_parse_content (mps) == -1) {
+ g_warning ("Trying to get signature on invalid multipart/signed");
+ return NULL;
+ } else if (byte_array->len == 0) {
+ return NULL;
+ }
+ stream = multipart_signed_clip_stream (
+ mps, mps->priv->start2, mps->priv->end2);
+ g_seekable_seek (
+ G_SEEKABLE (stream), 0, G_SEEK_SET, NULL, NULL);
+ mps->priv->signature = camel_mime_part_new ();
+ camel_data_wrapper_construct_from_stream_sync (
+ CAMEL_DATA_WRAPPER (mps->priv->signature),
+ stream, NULL, NULL);
+ g_object_unref (stream);
+ return mps->priv->signature;
+ default:
+ g_warning ("trying to get object out of bounds for multipart");
+ }
+
+ return NULL;
+}
+
+static guint
+multipart_signed_get_number (CamelMultipart *multipart)
+{
+ CamelMultipartSigned *mps = (CamelMultipartSigned *) multipart;
+ CamelDataWrapper *data_wrapper;
+ GByteArray *byte_array;
+
+ data_wrapper = CAMEL_DATA_WRAPPER (multipart);
+ byte_array = camel_data_wrapper_get_byte_array (data_wrapper);
+
+ /* check what we have, so we return something reasonable */
+
+ if ((mps->priv->content || mps->priv->contentraw) && mps->priv->signature)
+ return 2;
+
+ if (mps->priv->start1 == -1 && multipart_signed_parse_content (mps) == -1) {
+ if (byte_array->len == 0)
+ return 0;
+ else
+ return 1;
+ } else {
+ return 2;
+ }
+}
+
+static gint
+multipart_signed_construct_from_parser (CamelMultipart *multipart,
+ CamelMimeParser *mp)
+{
+ CamelMultipartSignedPrivate *priv;
+ gint err;
+ CamelContentType *content_type;
+ CamelDataWrapper *data_wrapper;
+ GByteArray *byte_array;
+ gchar *buf;
+ gsize len;
+
+ priv = CAMEL_MULTIPART_SIGNED_GET_PRIVATE (multipart);
+
+ /* 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_return_val_if_fail (camel_mime_parser_state (mp) == CAMEL_MIME_PARSER_STATE_HEADER, -1);
+
+ /* 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"));
+
+ data_wrapper = CAMEL_DATA_WRAPPER (multipart);
+ byte_array = camel_data_wrapper_get_byte_array (data_wrapper);
+
+ /* Wipe any previous contents from the byte array. */
+ g_byte_array_set_size (byte_array, 0);
+
+ while (camel_mime_parser_step (mp, &buf, &len) != CAMEL_MIME_PARSER_STATE_BODY_END)
+ g_byte_array_append (byte_array, (guint8 *) buf, len);
+
+ priv->start1 = -1;
+ g_clear_object (&priv->content);
+ g_clear_object (&priv->signature);
+ g_clear_object (&priv->contentraw);
+
+ err = camel_mime_parser_errno (mp);
+ if (err != 0) {
+ errno = err;
+ return -1;
+ } else
+ return 0;
+}
+
+static void
+camel_multipart_signed_class_init (CamelMultipartSignedClass *class)
+{
+ GObjectClass *object_class;
+ CamelDataWrapperClass *data_wrapper_class;
+ CamelMultipartClass *multipart_class;
+
+ g_type_class_add_private (class, sizeof (CamelMultipartSignedPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = multipart_signed_dispose;
+
+ data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (class);
+ data_wrapper_class->write_to_stream_sync = multipart_signed_write_to_stream_sync;
+ data_wrapper_class->decode_to_stream_sync = multipart_signed_write_to_stream_sync;
+ data_wrapper_class->construct_from_stream_sync = multipart_signed_construct_from_stream_sync;
+ data_wrapper_class->write_to_output_stream_sync = multipart_signed_write_to_output_stream_sync;
+ data_wrapper_class->decode_to_output_stream_sync = multipart_signed_write_to_output_stream_sync;
+ data_wrapper_class->construct_from_input_stream_sync = multipart_signed_construct_from_input_stream_sync;
+
+ multipart_class = CAMEL_MULTIPART_CLASS (class);
+ multipart_class->add_part = multipart_signed_add_part;
+ multipart_class->get_part = multipart_signed_get_part;
+ multipart_class->get_number = multipart_signed_get_number;
+ multipart_class->construct_from_parser = multipart_signed_construct_from_parser;
+}
+
+static void
+camel_multipart_signed_init (CamelMultipartSigned *multipart)
+{
+ multipart->priv = CAMEL_MULTIPART_SIGNED_GET_PRIVATE (multipart);
+
+ camel_data_wrapper_set_mime_type (
+ CAMEL_DATA_WRAPPER (multipart), "multipart/signed");
+
+ multipart->priv->start1 = -1;
+}
+
+/**
+ * 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).
+ *
+ * Returns: a new #CamelMultipartSigned object
+ **/
+CamelMultipartSigned *
+camel_multipart_signed_new (void)
+{
+ return g_object_new (CAMEL_TYPE_MULTIPART_SIGNED, NULL);
+}
+
+/**
+ * camel_multipart_signed_get_content_stream:
+ * @mps: a #CamlMultipartSigned object
+ * @error: return location for a #GError, or %NULL
+ *
+ * Get the raw signed content stream of the multipart/signed MIME part
+ * suitable for use with verification of the signature.
+ *
+ * Returns: (transfer full): the signed content stream
+ **/
+CamelStream *
+camel_multipart_signed_get_content_stream (CamelMultipartSigned *mps,
+ GError **error)
+{
+ 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->priv->contentraw) {
+ constream = g_object_ref (mps->priv->contentraw);
+ } else {
+ CamelStream *base_stream;
+ CamelStream *filter_stream;
+ CamelMimeFilter *canon_filter;
+
+ if (mps->priv->start1 == -1 && multipart_signed_parse_content (mps) == -1) {
+ g_set_error (
+ error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC,
+ _("parse error"));
+ return NULL;
+ }
+
+ /* first, prepare our parts */
+
+ base_stream = multipart_signed_clip_stream (
+ mps, mps->priv->start1, mps->priv->end1);
+
+ filter_stream = camel_stream_filter_new (base_stream);
+
+ /* Note: see rfc2015 or rfc3156, section 5 */
+ canon_filter = camel_mime_filter_canon_new (
+ CAMEL_MIME_FILTER_CANON_CRLF);
+ camel_stream_filter_add (
+ CAMEL_STREAM_FILTER (filter_stream), canon_filter);
+ g_object_unref (canon_filter);
+
+ /* We want to return a pure memory stream,
+ * so apply the CRLF filter ahead of time. */
+ constream = camel_stream_mem_new ();
+ camel_stream_write_to_stream (
+ filter_stream, constream, NULL, NULL);
+
+ g_object_unref (filter_stream);
+ g_object_unref (base_stream);
+
+ /* rewind to the beginning of a stream */
+ g_seekable_seek (G_SEEKABLE (constream), 0, G_SEEK_SET, NULL, NULL);
+ }
+
+ return constream;
+}
+
+/**
+ * camel_multipart_signed_set_content_stream:
+ * @mps: a #CamelMultipartSigned
+ * @content_stream: a #CamelStream
+ *
+ * Explicits sets the raw signed content stream of the multipart/signed
+ * MIME part.
+ *
+ * Since: 3.12
+ **/
+void
+camel_multipart_signed_set_content_stream (CamelMultipartSigned *mps,
+ CamelStream *content_stream)
+{
+ g_return_if_fail (CAMEL_IS_MULTIPART_SIGNED (mps));
+ g_return_if_fail (CAMEL_IS_STREAM (content_stream));
+
+ g_clear_object (&mps->priv->contentraw);
+ mps->priv->contentraw = g_object_ref (content_stream);
+}
+
+/**
+ * camel_multipart_signed_set_signature:
+ * @mps: a #CamelMultipartSigned
+ * @signature: a #CamelMimePart
+ *
+ * Explicitly sets the signature part of @mps.
+ *
+ * Since: 3.12
+ **/
+void
+camel_multipart_signed_set_signature (CamelMultipartSigned *mps,
+ CamelMimePart *signature)
+{
+ g_return_if_fail (CAMEL_IS_MULTIPART_SIGNED (mps));
+ g_return_if_fail (CAMEL_IS_MIME_PART (signature));
+
+ g_clear_object (&mps->priv->signature);
+ mps->priv->signature = g_object_ref (signature);
+}
diff --git a/src/camel/camel-multipart-signed.h b/src/camel/camel-multipart-signed.h
new file mode 100644
index 000000000..6d84d0f79
--- /dev/null
+++ b/src/camel/camel-multipart-signed.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-signed--multipart.h : class for a signed-multipart
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/* Should this be a subclass of multipart?
+ * No, because we dont have different parts?
+ * I'm not really sure yet ... ? */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MULTIPART_SIGNED_H
+#define CAMEL_MULTIPART_SIGNED_H
+
+#include <camel/camel-multipart.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MULTIPART_SIGNED \
+ (camel_multipart_signed_get_type ())
+#define CAMEL_MULTIPART_SIGNED(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MULTIPART_SIGNED, CamelMultipartSigned))
+#define CAMEL_MULTIPART_SIGNED_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MULTIPART_SIGNED, CamelMultipartSignedClass))
+#define CAMEL_IS_MULTIPART_SIGNED(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MULTIPART_SIGNED))
+#define CAMEL_IS_MULTIPART_SIGNED_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MULTIPART_SIGNED))
+#define CAMEL_MULTIPART_SIGNED_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MULTIPART_SIGNED, CamelMultipartSignedClass))
+
+G_BEGIN_DECLS
+
+/* 'handy' enums for getting the internal parts of the multipart */
+enum {
+ CAMEL_MULTIPART_SIGNED_CONTENT,
+ CAMEL_MULTIPART_SIGNED_SIGNATURE
+};
+
+typedef struct _CamelMultipartSigned CamelMultipartSigned;
+typedef struct _CamelMultipartSignedClass CamelMultipartSignedClass;
+typedef struct _CamelMultipartSignedPrivate CamelMultipartSignedPrivate;
+
+struct _CamelMultipartSigned {
+ CamelMultipart parent;
+ CamelMultipartSignedPrivate *priv;
+};
+
+struct _CamelMultipartSignedClass {
+ CamelMultipartClass parent_class;
+};
+
+GType camel_multipart_signed_get_type (void) G_GNUC_CONST;
+CamelMultipartSigned *
+ camel_multipart_signed_new (void);
+CamelStream * camel_multipart_signed_get_content_stream
+ (CamelMultipartSigned *mps,
+ GError **error);
+void camel_multipart_signed_set_content_stream
+ (CamelMultipartSigned *mps,
+ CamelStream *content_stream);
+void camel_multipart_signed_set_signature
+ (CamelMultipartSigned *mps,
+ CamelMimePart *signature);
+
+G_END_DECLS
+
+#endif /* CAMEL_MULTIPART_SIGNED_H */
diff --git a/src/camel/camel-multipart.c b/src/camel/camel-multipart.c
new file mode 100644
index 000000000..0836b5e70
--- /dev/null
+++ b/src/camel/camel-multipart.c
@@ -0,0 +1,642 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-multipart.c : Abstract class for a multipart
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <string.h> /* strlen() */
+#include <time.h> /* for time */
+#include <unistd.h> /* for getpid */
+
+#include "camel-mime-part.h"
+#include "camel-multipart.h"
+#include "camel-stream-mem.h"
+
+#define CAMEL_MULTIPART_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MULTIPART, CamelMultipartPrivate))
+
+struct _CamelMultipartPrivate {
+ GPtrArray *parts;
+ gchar *preface;
+ gchar *postface;
+};
+
+G_DEFINE_TYPE (CamelMultipart, camel_multipart, CAMEL_TYPE_DATA_WRAPPER)
+
+static void
+multipart_dispose (GObject *object)
+{
+ CamelMultipartPrivate *priv;
+
+ priv = CAMEL_MULTIPART_GET_PRIVATE (object);
+
+ g_ptr_array_set_size (priv->parts, 0);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_multipart_parent_class)->dispose (object);
+}
+
+static void
+multipart_finalize (GObject *object)
+{
+ CamelMultipartPrivate *priv;
+
+ priv = CAMEL_MULTIPART_GET_PRIVATE (object);
+
+ g_ptr_array_unref (priv->parts);
+
+ g_free (priv->preface);
+ g_free (priv->postface);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_multipart_parent_class)->finalize (object);
+}
+
+static gboolean
+multipart_is_offline (CamelDataWrapper *data_wrapper)
+{
+ CamelMultipartPrivate *priv;
+ CamelDataWrapper *part;
+ guint ii;
+
+ priv = CAMEL_MULTIPART_GET_PRIVATE (data_wrapper);
+
+ /* Chain up to parent's is_offline() method. */
+ if (CAMEL_DATA_WRAPPER_CLASS (camel_multipart_parent_class)->is_offline (data_wrapper))
+ return TRUE;
+
+ for (ii = 0; ii < priv->parts->len; ii++) {
+ part = g_ptr_array_index (priv->parts, ii);
+ if (camel_data_wrapper_is_offline (part))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* this is MIME specific, doesn't belong here really */
+static gssize
+multipart_write_to_stream_sync (CamelDataWrapper *data_wrapper,
+ CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMultipartPrivate *priv;
+ const gchar *boundary;
+ gchar *content;
+ gssize total = 0;
+ gssize count;
+ guint ii;
+
+ priv = CAMEL_MULTIPART_GET_PRIVATE (data_wrapper);
+
+ /* get the bundary text */
+ boundary = camel_multipart_get_boundary (
+ CAMEL_MULTIPART (data_wrapper));
+
+ /* we cannot write a multipart without a boundary string */
+ g_return_val_if_fail (boundary, -1);
+
+ /*
+ * write the preface text (usually something like
+ * "This is a mime message, if you see this, then
+ * your mail client probably doesn't support ...."
+ */
+ if (priv->preface != NULL) {
+ count = camel_stream_write_string (
+ stream, priv->preface, cancellable, error);
+ if (count == -1)
+ return -1;
+ total += count;
+ }
+
+ /*
+ * Now, write all the parts, separated by the boundary
+ * delimiter
+ */
+ for (ii = 0; ii < priv->parts->len; ii++) {
+ CamelDataWrapper *part;
+
+ part = g_ptr_array_index (priv->parts, ii);
+
+ content = g_strdup_printf ("\n--%s\n", boundary);
+ count = camel_stream_write_string (
+ stream, content, cancellable, error);
+ g_free (content);
+ if (count == -1)
+ return -1;
+ total += count;
+
+ count = camel_data_wrapper_write_to_stream_sync (
+ part, stream, cancellable, error);
+ if (count == -1)
+ return -1;
+ total += count;
+ }
+
+ /* write the terminating boudary delimiter */
+ content = g_strdup_printf ("\n--%s--\n", boundary);
+ count = camel_stream_write_string (
+ stream, content, cancellable, error);
+ g_free (content);
+ if (count == -1)
+ return -1;
+ total += count;
+
+ /* and finally the postface */
+ if (priv->postface != NULL) {
+ count = camel_stream_write_string (
+ stream, priv->postface, cancellable, error);
+ if (count == -1)
+ return -1;
+ total += count;
+ }
+
+ return total;
+}
+
+/* this is MIME specific, doesn't belong here really */
+static gssize
+multipart_write_to_output_stream_sync (CamelDataWrapper *data_wrapper,
+ GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMultipartPrivate *priv;
+ const gchar *boundary;
+ gchar *content;
+ gsize bytes_written;
+ gssize total = 0;
+ gboolean success;
+ guint ii;
+
+ priv = CAMEL_MULTIPART_GET_PRIVATE (data_wrapper);
+
+ /* get the bundary text */
+ boundary = camel_multipart_get_boundary (
+ CAMEL_MULTIPART (data_wrapper));
+
+ /* we cannot write a multipart without a boundary string */
+ g_return_val_if_fail (boundary, -1);
+
+ /*
+ * write the preface text (usually something like
+ * "This is a mime message, if you see this, then
+ * your mail client probably doesn't support ...."
+ */
+ if (priv->preface != NULL) {
+ success = g_output_stream_write_all (
+ output_stream,
+ priv->preface, strlen (priv->preface),
+ &bytes_written, cancellable, error);
+ if (!success)
+ return -1;
+ total += (gsize) bytes_written;
+ }
+
+ /*
+ * Now, write all the parts, separated by the boundary
+ * delimiter
+ */
+ for (ii = 0; ii < priv->parts->len; ii++) {
+ CamelDataWrapper *part;
+ gssize result;
+
+ part = g_ptr_array_index (priv->parts, ii);
+
+ content = g_strdup_printf ("\n--%s\n", boundary);
+ success = g_output_stream_write_all (
+ output_stream,
+ content, strlen (content),
+ &bytes_written, cancellable, error);
+ g_free (content);
+ if (!success)
+ return -1;
+ total += (gsize) bytes_written;
+
+ result = camel_data_wrapper_write_to_output_stream_sync (
+ part, output_stream, cancellable, error);
+ if (result == -1)
+ return -1;
+ total += result;
+ }
+
+ /* write the terminating boudary delimiter */
+ content = g_strdup_printf ("\n--%s--\n", boundary);
+ success = g_output_stream_write_all (
+ output_stream,
+ content, strlen (content),
+ &bytes_written, cancellable, error);
+ g_free (content);
+ if (!success)
+ return -1;
+ total += (gsize) bytes_written;
+
+ /* and finally the postface */
+ if (priv->postface != NULL) {
+ success = g_output_stream_write_all (
+ output_stream,
+ priv->postface, strlen (priv->postface),
+ &bytes_written, cancellable, error);
+ if (!success)
+ return -1;
+ total += (gsize) bytes_written;
+ }
+
+ return total;
+}
+
+static void
+multipart_add_part (CamelMultipart *multipart,
+ CamelMimePart *part)
+{
+ g_ptr_array_add (multipart->priv->parts, g_object_ref (part));
+}
+
+static CamelMimePart *
+multipart_get_part (CamelMultipart *multipart,
+ guint index)
+{
+ if (index >= multipart->priv->parts->len)
+ return NULL;
+
+ return g_ptr_array_index (multipart->priv->parts, index);
+}
+
+static guint
+multipart_get_number (CamelMultipart *multipart)
+{
+ return multipart->priv->parts->len;
+}
+
+static void
+multipart_set_boundary (CamelMultipart *multipart,
+ const gchar *boundary)
+{
+ CamelDataWrapper *cdw = CAMEL_DATA_WRAPPER (multipart);
+ gchar *bgen, bbuf[27], *p;
+ guint8 *digest;
+ gsize length;
+ gint state, save;
+
+ g_return_if_fail (cdw->mime_type != NULL);
+
+ length = g_checksum_type_get_length (G_CHECKSUM_MD5);
+ digest = g_alloca (length);
+
+ if (!boundary) {
+ GChecksum *checksum;
+
+ /* Generate a fairly random boundary string. */
+ bgen = g_strdup_printf (
+ "%p:%lu:%lu",
+ (gpointer) multipart,
+ (gulong) getpid (),
+ (gulong) time (NULL));
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (guchar *) bgen, -1);
+ g_checksum_get_digest (checksum, digest, &length);
+ g_checksum_free (checksum);
+
+ g_free (bgen);
+ g_strlcpy (bbuf, "=-", sizeof (bbuf));
+ p = bbuf + 2;
+ state = save = 0;
+ p += g_base64_encode_step (
+ (guchar *) digest, length, FALSE, p, &state, &save);
+ *p = '\0';
+
+ boundary = bbuf;
+ }
+
+ camel_content_type_set_param (cdw->mime_type, "boundary", boundary);
+}
+
+static const gchar *
+multipart_get_boundary (CamelMultipart *multipart)
+{
+ CamelDataWrapper *cdw = CAMEL_DATA_WRAPPER (multipart);
+
+ g_return_val_if_fail (cdw->mime_type != NULL, NULL);
+ return camel_content_type_param (cdw->mime_type, "boundary");
+}
+
+static gint
+multipart_construct_from_parser (CamelMultipart *multipart,
+ CamelMimeParser *mp)
+{
+ gint err;
+ CamelContentType *content_type;
+ CamelMimePart *bodypart;
+ gchar *buf;
+ gsize len;
+
+ g_return_val_if_fail (camel_mime_parser_state (mp) == CAMEL_MIME_PARSER_STATE_MULTIPART, -1);
+
+ content_type = camel_mime_parser_content_type (mp);
+ camel_multipart_set_boundary (
+ multipart,
+ camel_content_type_param (content_type, "boundary"));
+
+ while (camel_mime_parser_step (mp, &buf, &len) != CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
+ camel_mime_parser_unstep (mp);
+ bodypart = camel_mime_part_new ();
+ camel_mime_part_construct_from_parser_sync (
+ bodypart, mp, NULL, NULL);
+ camel_multipart_add_part (multipart, bodypart);
+ g_object_unref (bodypart);
+ }
+
+ /* these are only return valid data in the MULTIPART_END state */
+ camel_multipart_set_preface (multipart, camel_mime_parser_preface (mp));
+ camel_multipart_set_postface (multipart, camel_mime_parser_postface (mp));
+
+ err = camel_mime_parser_errno (mp);
+ if (err != 0) {
+ errno = err;
+ return -1;
+ } else
+ return 0;
+}
+
+static void
+camel_multipart_class_init (CamelMultipartClass *class)
+{
+ GObjectClass *object_class;
+ CamelDataWrapperClass *data_wrapper_class;
+
+ g_type_class_add_private (class, sizeof (CamelMultipartPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = multipart_dispose;
+ object_class->finalize = multipart_finalize;
+
+ data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (class);
+ data_wrapper_class->is_offline = multipart_is_offline;
+ data_wrapper_class->write_to_stream_sync = multipart_write_to_stream_sync;
+ data_wrapper_class->decode_to_stream_sync = multipart_write_to_stream_sync;
+ data_wrapper_class->write_to_output_stream_sync = multipart_write_to_output_stream_sync;
+ data_wrapper_class->decode_to_output_stream_sync = multipart_write_to_output_stream_sync;
+
+ class->add_part = multipart_add_part;
+ class->get_part = multipart_get_part;
+ class->get_number = multipart_get_number;
+ class->set_boundary = multipart_set_boundary;
+ class->get_boundary = multipart_get_boundary;
+ class->construct_from_parser = multipart_construct_from_parser;
+}
+
+static void
+camel_multipart_init (CamelMultipart *multipart)
+{
+ multipart->priv = CAMEL_MULTIPART_GET_PRIVATE (multipart);
+
+ multipart->priv->parts =
+ g_ptr_array_new_with_free_func (g_object_unref);
+
+ camel_data_wrapper_set_mime_type (
+ CAMEL_DATA_WRAPPER (multipart), "multipart/mixed");
+}
+
+/**
+ * camel_multipart_new:
+ *
+ * Create a new #CamelMultipart object.
+ *
+ * Returns: a new #CamelMultipart object
+ **/
+CamelMultipart *
+camel_multipart_new (void)
+{
+ return g_object_new (CAMEL_TYPE_MULTIPART, NULL);
+}
+
+/**
+ * camel_multipart_add_part:
+ * @multipart: a #CamelMultipart object
+ * @part: a #CamelMimePart to add
+ *
+ * Appends the part to the multipart object.
+ **/
+void
+camel_multipart_add_part (CamelMultipart *multipart,
+ CamelMimePart *part)
+{
+ CamelMultipartClass *class;
+
+ g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
+ g_return_if_fail (CAMEL_IS_MIME_PART (part));
+
+ class = CAMEL_MULTIPART_GET_CLASS (multipart);
+ g_return_if_fail (class->add_part != NULL);
+
+ class->add_part (multipart, part);
+}
+
+/**
+ * camel_multipart_get_part:
+ * @multipart: a #CamelMultipart object
+ * @index: a zero-based index indicating the part to get
+ *
+ * Returns: (transfer none): the indicated subpart, or %NULL
+ **/
+CamelMimePart *
+camel_multipart_get_part (CamelMultipart *multipart,
+ guint index)
+{
+ CamelMultipartClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), NULL);
+
+ class = CAMEL_MULTIPART_GET_CLASS (multipart);
+ g_return_val_if_fail (class->get_part != NULL, NULL);
+
+ return class->get_part (multipart, index);
+}
+
+/**
+ * camel_multipart_get_number:
+ * @multipart: a #CamelMultipart object
+ *
+ * Returns: the number of subparts in @multipart
+ **/
+guint
+camel_multipart_get_number (CamelMultipart *multipart)
+{
+ CamelMultipartClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), 0);
+
+ class = CAMEL_MULTIPART_GET_CLASS (multipart);
+ g_return_val_if_fail (class->get_number != NULL, 0);
+
+ return class->get_number (multipart);
+}
+
+/**
+ * camel_multipart_get_boundary:
+ * @multipart: a #CamelMultipart object
+ *
+ * Returns: the boundary
+ **/
+const gchar *
+camel_multipart_get_boundary (CamelMultipart *multipart)
+{
+ CamelMultipartClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), NULL);
+
+ class = CAMEL_MULTIPART_GET_CLASS (multipart);
+ g_return_val_if_fail (class->get_boundary != NULL, NULL);
+
+ return class->get_boundary (multipart);
+}
+
+/**
+ * camel_multipart_set_boundary:
+ * @multipart: a #CamelMultipart object
+ * @boundary: the message boundary, or %NULL
+ *
+ * Sets the message boundary for @multipart to @boundary. This should
+ * be a string which does not occur anywhere in any of @multipart's
+ * subparts. If @boundary is %NULL, a randomly-generated boundary will
+ * be used.
+ **/
+void
+camel_multipart_set_boundary (CamelMultipart *multipart,
+ const gchar *boundary)
+{
+ CamelMultipartClass *class;
+
+ g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
+
+ class = CAMEL_MULTIPART_GET_CLASS (multipart);
+ g_return_if_fail (class->set_boundary != NULL);
+
+ class->set_boundary (multipart, boundary);
+}
+
+/**
+ * camel_multipart_get_preface:
+ * @multipart: a #CamelMultipart
+ *
+ * Returns the preface text for @multipart.
+ *
+ * Returns: the preface text
+ *
+ * Since: 3.12
+ **/
+const gchar *
+camel_multipart_get_preface (CamelMultipart *multipart)
+{
+ g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), NULL);
+
+ return multipart->priv->preface;
+}
+
+/**
+ * camel_multipart_set_preface:
+ * @multipart: a #CamelMultipart object
+ * @preface: the multipart preface
+ *
+ * Set the preface text for this multipart. Will be written out infront
+ * of the multipart. This text should only include US-ASCII strings, and
+ * be relatively short, and will be ignored by any MIME mail client.
+ **/
+void
+camel_multipart_set_preface (CamelMultipart *multipart,
+ const gchar *preface)
+{
+ g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
+
+ if (multipart->priv->preface == preface)
+ return;
+
+ g_free (multipart->priv->preface);
+ multipart->priv->preface = g_strdup (preface);
+}
+
+/**
+ * camel_multipart_get_postface:
+ * @multipart: a #CamelMultipart
+ *
+ * Returns the postface text for @multipart.
+ *
+ * Returns: the postface text
+ *
+ * Since: 3.12
+ **/
+const gchar *
+camel_multipart_get_postface (CamelMultipart *multipart)
+{
+ g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), NULL);
+
+ return multipart->priv->postface;
+}
+
+/**
+ * camel_multipart_set_postface:
+ * @multipart: a #CamelMultipart object
+ * @postface: multipat postface
+ *
+ * Set the postface text for this multipart. Will be written out after
+ * the last boundary of the multipart, and ignored by any MIME mail
+ * client.
+ *
+ * Generally postface texts should not be sent with multipart messages.
+ **/
+void
+camel_multipart_set_postface (CamelMultipart *multipart,
+ const gchar *postface)
+{
+ g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
+
+ if (multipart->priv->postface == postface)
+ return;
+
+ g_free (multipart->priv->postface);
+ multipart->priv->postface = g_strdup (postface);
+}
+
+/**
+ * camel_multipart_construct_from_parser:
+ * @multipart: a #CamelMultipart object
+ * @parser: a #CamelMimeParser object
+ *
+ * Construct a multipart from a parser.
+ *
+ * Returns: %0 on success or %-1 on fail
+ **/
+gint
+camel_multipart_construct_from_parser (CamelMultipart *multipart,
+ CamelMimeParser *mp)
+{
+ CamelMultipartClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_MULTIPART (multipart), -1);
+ g_return_val_if_fail (CAMEL_IS_MIME_PARSER (mp), -1);
+
+ class = CAMEL_MULTIPART_GET_CLASS (multipart);
+ g_return_val_if_fail (class->construct_from_parser != NULL, -1);
+
+ return class->construct_from_parser (multipart, mp);
+}
diff --git a/src/camel/camel-multipart.h b/src/camel/camel-multipart.h
new file mode 100644
index 000000000..171b0ebb0
--- /dev/null
+++ b/src/camel/camel-multipart.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-multipart.h : class for a multipart
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_MULTIPART_H
+#define CAMEL_MULTIPART_H
+
+#include <camel/camel-data-wrapper.h>
+#include <camel/camel-mime-parser.h>
+#include <camel/camel-mime-part.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MULTIPART \
+ (camel_multipart_get_type ())
+#define CAMEL_MULTIPART(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MULTIPART, CamelMultipart))
+#define CAMEL_MULTIPART_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MULTIPART, CamelMultipartClass))
+#define CAMEL_IS_MULTIPART(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MULTIPART))
+#define CAMEL_IS_MULTIPART_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MULTIPART))
+#define CAMEL_MULTIPART_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MULTIPART, CamelMultipartClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMultipart CamelMultipart;
+typedef struct _CamelMultipartClass CamelMultipartClass;
+typedef struct _CamelMultipartPrivate CamelMultipartPrivate;
+
+struct _CamelMultipart {
+ CamelDataWrapper parent;
+ CamelMultipartPrivate *priv;
+};
+
+struct _CamelMultipartClass {
+ CamelDataWrapperClass parent_class;
+
+ void (*add_part) (CamelMultipart *multipart,
+ CamelMimePart *part);
+ CamelMimePart * (*get_part) (CamelMultipart *multipart,
+ guint index);
+ guint (*get_number) (CamelMultipart *multipart);
+ const gchar * (*get_boundary) (CamelMultipart *multipart);
+ void (*set_boundary) (CamelMultipart *multipart,
+ const gchar *boundary);
+ gint (*construct_from_parser)
+ (CamelMultipart *multipart,
+ CamelMimeParser *parser);
+};
+
+GType camel_multipart_get_type (void) G_GNUC_CONST;
+CamelMultipart *
+ camel_multipart_new (void);
+void camel_multipart_add_part (CamelMultipart *multipart,
+ CamelMimePart *part);
+CamelMimePart * camel_multipart_get_part (CamelMultipart *multipart,
+ guint index);
+guint camel_multipart_get_number (CamelMultipart *multipart);
+const gchar * camel_multipart_get_boundary (CamelMultipart *multipart);
+void camel_multipart_set_boundary (CamelMultipart *multipart,
+ const gchar *boundary);
+const gchar * camel_multipart_get_preface (CamelMultipart *multipart);
+void camel_multipart_set_preface (CamelMultipart *multipart,
+ const gchar *preface);
+const gchar * camel_multipart_get_postface (CamelMultipart *multipart);
+void camel_multipart_set_postface (CamelMultipart *multipart,
+ const gchar *postface);
+gint camel_multipart_construct_from_parser
+ (CamelMultipart *multipart,
+ CamelMimeParser *parser);
+
+G_END_DECLS
+
+#endif /* CAMEL_MULTIPART_H */
diff --git a/src/camel/camel-net-utils.c b/src/camel/camel-net-utils.c
new file mode 100644
index 000000000..cf56e821e
--- /dev/null
+++ b/src/camel/camel-net-utils.c
@@ -0,0 +1,848 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ * Chris Toshok <toshok@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <stdio.h>
+
+#include <glib/gi18n-lib.h>
+#include <unicode/uidna.h>
+#include <unicode/ustring.h>
+
+#include "camel-msgport.h"
+#include "camel-net-utils.h"
+#ifdef G_OS_WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#ifdef HAVE_WSPIAPI_H
+#include <wspiapi.h>
+#endif
+#endif
+#include "camel-object.h"
+#include "camel-operation.h"
+#include "camel-service.h"
+
+#define d(x)
+
+/* These are GNU extensions */
+#ifndef NI_MAXHOST
+#define NI_MAXHOST 1025
+#endif
+#ifndef NI_MAXSERV
+#define NI_MAXSERV 32
+#endif
+
+#ifdef G_OS_WIN32
+
+typedef gshort in_port_t;
+
+#undef gai_strerror
+#define gai_strerror my_gai_strerror
+
+/* gai_strerror() is implemented as an inline function in Microsoft's
+ * SDK, but mingw lacks that. So implement here. The EAI_* errors can
+ * be handled with the normal FormatMessage() API,
+ * i.e. g_win32_error_message().
+ */
+
+static const gchar *
+gai_strerror (gint error_code)
+{
+ gchar *msg = g_win32_error_message (error_code);
+ GQuark quark = g_quark_from_string (msg);
+ const gchar *retval = g_quark_to_string (quark);
+
+ g_free (msg);
+
+ return retval;
+}
+
+#endif
+
+/* gethostbyname emulation code for emulating getaddrinfo code ...
+ *
+ * This should probably go away */
+
+#ifdef NEED_ADDRINFO
+
+#if !defined (HAVE_GETHOSTBYNAME_R) || !defined (HAVE_GETHOSTBYADDR_R)
+G_LOCK_DEFINE_STATIC (gethost_mutex);
+#endif
+
+#define ALIGN(x) (((x) + (sizeof (gchar *) - 1)) & ~(sizeof (gchar *) - 1))
+
+#define GETHOST_PROCESS(h, host, buf, buflen, herr) G_STMT_START { \
+ gint num_aliases = 0, num_addrs = 0; \
+ gint req_length; \
+ gchar *p; \
+ gint i; \
+ \
+ /* check to make sure we have enough room in our buffer */ \
+ req_length = 0; \
+ if (h->h_aliases) { \
+ for (i = 0; h->h_aliases[i]; i++) \
+ req_length += strlen (h->h_aliases[i]) + 1; \
+ num_aliases = i; \
+ } \
+ \
+ if (h->h_addr_list) { \
+ for (i = 0; h->h_addr_list[i]; i++) \
+ req_length += h->h_length; \
+ num_addrs = i; \
+ } \
+ \
+ req_length += sizeof (gchar *) * (num_aliases + 1); \
+ req_length += sizeof (gchar *) * (num_addrs + 1); \
+ req_length += strlen (h->h_name) + 1; \
+ \
+ if (buflen < req_length) { \
+ *herr = ERANGE; \
+ G_UNLOCK (gethost_mutex); \
+ return ERANGE; \
+ } \
+ \
+ /* we store the alias/addr pointers in the buffer */ \
+ /* their addresses here. */ \
+ p = buf; \
+ if (num_aliases) { \
+ host->h_aliases = (gchar **) p; \
+ p += sizeof (gchar *) * (num_aliases + 1); \
+ } else \
+ host->h_aliases = NULL; \
+ \
+ if (num_addrs) { \
+ host->h_addr_list = (gchar **) p; \
+ p += sizeof (gchar *) * (num_addrs + 1); \
+ } else \
+ host->h_addr_list = NULL; \
+ \
+ /* copy the host name into the buffer */ \
+ host->h_name = p; \
+ strcpy (p, h->h_name); \
+ p += strlen (h->h_name) + 1; \
+ host->h_addrtype = h->h_addrtype; \
+ host->h_length = h->h_length; \
+ \
+ /* copy the aliases/addresses into the buffer */ \
+ /* and assign pointers into the hostent */ \
+ *p = 0; \
+ if (num_aliases) { \
+ for (i = 0; i < num_aliases; i++) { \
+ strcpy (p, h->h_aliases[i]); \
+ host->h_aliases[i] = p; \
+ p += strlen (h->h_aliases[i]); \
+ } \
+ host->h_aliases[num_aliases] = NULL; \
+ } \
+ \
+ if (num_addrs) { \
+ for (i = 0; i < num_addrs; i++) { \
+ memcpy (p, h->h_addr_list[i], h->h_length); \
+ host->h_addr_list[i] = p; \
+ p += h->h_length; \
+ } \
+ host->h_addr_list[num_addrs] = NULL; \
+ } \
+} G_STMT_END
+
+#ifdef ENABLE_IPv6
+/* some helpful utils for IPv6 lookups */
+#define IPv6_BUFLEN_MIN (sizeof (gchar *) * 3)
+
+static gint
+ai_to_herr (gint error)
+{
+ switch (error) {
+ case EAI_NONAME:
+ case EAI_FAIL:
+ return HOST_NOT_FOUND;
+ break;
+ case EAI_SERVICE:
+ return NO_DATA;
+ break;
+ case EAI_ADDRFAMILY:
+ return NO_ADDRESS;
+ break;
+ case EAI_NODATA:
+ return NO_DATA;
+ break;
+ case EAI_MEMORY:
+ return ENOMEM;
+ break;
+ case EAI_AGAIN:
+ return TRY_AGAIN;
+ break;
+ case EAI_SYSTEM:
+ return errno;
+ break;
+ default:
+ return NO_RECOVERY;
+ break;
+ }
+}
+
+#endif /* ENABLE_IPv6 */
+
+static gint
+camel_gethostbyname_r (const gchar *name,
+ struct hostent *host,
+ gchar *buf,
+ gsize buflen,
+ gint *herr)
+{
+#ifdef ENABLE_IPv6
+ struct addrinfo hints, *res;
+ gint retval, len;
+ gchar *addr;
+
+ memset (&hints, 0, sizeof (struct addrinfo));
+#ifdef HAVE_AI_ADDRCONFIG
+ hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
+#else
+ hints.ai_flags = AI_CANONNAME;
+#endif
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ if ((retval = getaddrinfo (name, NULL, &hints, &res)) != 0) {
+ *herr = ai_to_herr (retval);
+ return -1;
+ }
+
+ len = ALIGN (strlen (res->ai_canonname) + 1);
+ if (buflen < IPv6_BUFLEN_MIN + len + res->ai_addrlen + sizeof (gchar *))
+ return ERANGE;
+
+ /* h_name */
+ g_strlcpy (buf, res->ai_canonname, buflen);
+ host->h_name = buf;
+ buf += len;
+
+ /* h_aliases */
+ ((gchar **) buf)[0] = NULL;
+ host->h_aliases = (gchar **) buf;
+ buf += sizeof (gchar *);
+
+ /* h_addrtype and h_length */
+ host->h_length = res->ai_addrlen;
+ if (res->ai_family == PF_INET6) {
+ host->h_addrtype = AF_INET6;
+
+ addr = (gchar *) &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr;
+ } else {
+ host->h_addrtype = AF_INET;
+
+ addr = (gchar *) &((struct sockaddr_in *) res->ai_addr)->sin_addr;
+ }
+
+ memcpy (buf, addr, host->h_length);
+ addr = buf;
+ buf += ALIGN (host->h_length);
+
+ /* h_addr_list */
+ ((gchar **) buf)[0] = addr;
+ ((gchar **) buf)[1] = NULL;
+ host->h_addr_list = (gchar **) buf;
+
+ freeaddrinfo (res);
+
+ return 0;
+#else /* No support for IPv6 addresses */
+#ifdef HAVE_GETHOSTBYNAME_R
+#ifdef GETHOSTBYNAME_R_FIVE_ARGS
+ if (gethostbyname_r (name, host, buf, buflen, herr))
+ return 0;
+ else
+ return errno;
+#else
+ struct hostent *hp;
+ gint retval;
+
+ retval = gethostbyname_r (name, host, buf, buflen, &hp, herr);
+ if (hp != NULL) {
+ *herr = 0;
+ } else if (retval == 0) {
+ /* glibc 2.3.2 workaround - it seems that
+ * gethostbyname_r will sometimes return 0 on fail and
+ * not set the hostent values (hence the crash in bug
+ * #56337). Hopefully we can depend on @hp being NULL
+ * in this error case like we do with
+ * gethostbyaddr_r().
+ */
+ retval = -1;
+ }
+
+ return retval;
+#endif
+#else /* No support for gethostbyname_r */
+ struct hostent *h;
+
+ G_LOCK (gethost_mutex);
+
+ h = gethostbyname (name);
+
+ if (!h) {
+ *herr = h_errno;
+ G_UNLOCK (gethost_mutex);
+ return -1;
+ }
+
+ GETHOST_PROCESS (h, host, buf, buflen, herr);
+
+ G_UNLOCK (gethost_mutex);
+
+ return 0;
+#endif /* HAVE_GETHOSTBYNAME_R */
+#endif /* ENABLE_IPv6 */
+}
+
+static gint
+camel_gethostbyaddr_r (const gchar *addr,
+ gint addrlen,
+ gint type,
+ struct hostent *host,
+ gchar *buf,
+ gsize buflen,
+ gint *herr)
+{
+#ifdef ENABLE_IPv6
+ gint retval, len;
+
+ if ((retval = getnameinfo (addr, addrlen, buf, buflen, NULL, 0, NI_NAMEREQD)) != 0) {
+ *herr = ai_to_herr (retval);
+ return -1;
+ }
+
+ len = ALIGN (strlen (buf) + 1);
+ if (buflen < IPv6_BUFLEN_MIN + len + addrlen + sizeof (gchar *))
+ return ERANGE;
+
+ /* h_name */
+ host->h_name = buf;
+ buf += len;
+
+ /* h_aliases */
+ ((gchar **) buf)[0] = NULL;
+ host->h_aliases = (gchar **) buf;
+ buf += sizeof (gchar *);
+
+ /* h_addrtype and h_length */
+ host->h_length = addrlen;
+ host->h_addrtype = type;
+
+ memcpy (buf, addr, host->h_length);
+ addr = buf;
+ buf += ALIGN (host->h_length);
+
+ /* h_addr_list */
+ ((gchar **) buf)[0] = addr;
+ ((gchar **) buf)[1] = NULL;
+ host->h_addr_list = (gchar **) buf;
+
+ return 0;
+#else /* No support for IPv6 addresses */
+#ifdef HAVE_GETHOSTBYADDR_R
+#ifdef GETHOSTBYADDR_R_SEVEN_ARGS
+ if (gethostbyaddr_r (addr, addrlen, type, host, buf, buflen, herr))
+ return 0;
+ else
+ return errno;
+#else
+ struct hostent *hp;
+ gint retval;
+
+ retval = gethostbyaddr_r (addr, addrlen, type, host, buf, buflen, &hp, herr);
+ if (hp != NULL) {
+ *herr = 0;
+ retval = 0;
+ } else if (retval == 0) {
+ /* glibc 2.3.2 workaround - it seems that
+ * gethostbyaddr_r will sometimes return 0 on fail and
+ * fill @host with garbage strings from /etc/hosts
+ * (failure to parse the file? who knows). Luckily, it
+ * seems that we can rely on @hp being NULL on
+ * fail.
+ */
+ retval = -1;
+ }
+
+ return retval;
+#endif
+#else /* No support for gethostbyaddr_r */
+ struct hostent *h;
+
+ G_LOCK (gethost_mutex);
+
+ h = gethostbyaddr (addr, addrlen, type);
+
+ if (!h) {
+ *herr = h_errno;
+ G_UNLOCK (gethost_mutex);
+ return -1;
+ }
+
+ GETHOST_PROCESS (h, host, buf, buflen, herr);
+
+ G_UNLOCK (gethost_mutex);
+
+ return 0;
+#endif /* HAVE_GETHOSTBYADDR_R */
+#endif /* ENABLE_IPv6 */
+}
+#endif /* NEED_ADDRINFO */
+
+/* ********************************************************************** */
+struct _addrinfo_msg {
+ CamelMsg msg;
+ guint cancelled : 1;
+
+ /* for host lookup */
+ const gchar *name;
+ const gchar *service;
+ gint result;
+ const struct addrinfo *hints;
+ struct addrinfo **res;
+
+ /* for host lookup emulation */
+#ifdef NEED_ADDRINFO
+ struct hostent hostbuf;
+ gint hostbuflen;
+ gchar *hostbufmem;
+#endif
+
+ /* for name lookup */
+ const struct sockaddr *addr;
+ socklen_t addrlen;
+ gchar *host;
+ gint hostlen;
+ gchar *serv;
+ gint servlen;
+ gint 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 we didn't wait for reply from thread */
+static gint
+cs_waitinfo (gpointer (worker)(gpointer),
+ struct _addrinfo_msg *msg,
+ const gchar *errmsg,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMsgPort *reply_port;
+ GThread *thread;
+ gint cancel_fd, cancel = 0, fd;
+
+ cancel_fd = g_cancellable_get_fd (cancellable);
+ if (cancel_fd == -1) {
+ worker (msg);
+ return 0;
+ }
+
+ reply_port = msg->msg.reply_port = camel_msgport_new ();
+ fd = camel_msgport_fd (msg->msg.reply_port);
+ if ((thread = g_thread_new (NULL, worker, msg)) != NULL) {
+ gint status;
+#ifndef G_OS_WIN32
+ GPollFD polls[2];
+
+ polls[0].fd = fd;
+ polls[0].events = G_IO_IN;
+ polls[1].fd = cancel_fd;
+ polls[1].events = G_IO_IN;
+
+ d (printf ("waiting for name return/cancellation in main process\n"));
+ do {
+ polls[0].revents = 0;
+ polls[1].revents = 0;
+ status = g_poll (polls, 2, -1);
+ } while (status == -1 && errno == EINTR);
+#else
+ fd_set read_set;
+
+ FD_ZERO (&read_set);
+ FD_SET (fd, &read_set);
+ FD_SET (cancel_fd, &read_set);
+
+ status = select (MAX (fd, cancel_fd) + 1, &read_set, NULL, NULL, NULL);
+#endif
+
+ if (status == -1 ||
+#ifndef G_OS_WIN32
+ (polls[1].revents & G_IO_IN)
+#else
+ FD_ISSET (cancel_fd, &read_set)
+#endif
+ ) {
+ if (status == -1)
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s: %s", errmsg,
+#ifndef G_OS_WIN32
+ g_strerror (errno)
+#else
+ g_win32_error_message (WSAGetLastError ())
+#endif
+ );
+ else
+ g_set_error (
+ error, G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Cancelled"));
+
+ /* We cancel so if the thread impl is decent it causes immediate exit.
+ * We check the reply port incase we had a reply in the mean time, which we free later */
+ d (printf ("Canceling lookup thread and leaving it\n"));
+ msg->cancelled = 1;
+ g_thread_join (thread);
+ cancel = 1;
+ } else {
+ struct _addrinfo_msg *reply;
+
+ d (printf ("waiting for child to exit\n"));
+ g_thread_join (thread);
+ d (printf ("child done\n"));
+
+ reply = (struct _addrinfo_msg *) camel_msgport_try_pop (reply_port);
+ if (reply != msg)
+ g_warning ("%s: Received msg reply %p doesn't match msg %p", G_STRFUNC, reply, msg);
+ }
+ }
+ camel_msgport_destroy (reply_port);
+
+ g_cancellable_release_fd (cancellable);
+
+ return cancel;
+}
+
+#ifdef NEED_ADDRINFO
+static gpointer
+cs_getaddrinfo (gpointer data)
+{
+ struct _addrinfo_msg *msg = data;
+ gint herr;
+ struct hostent h;
+ struct addrinfo *res, *last = NULL;
+ struct sockaddr_in *sin;
+ in_port_t port = 0;
+ gint i;
+
+ /* This is a pretty simplistic emulation of getaddrinfo */
+
+ while ((msg->result = camel_gethostbyname_r (msg->name, &h, msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) {
+ if (msg->cancelled)
+ break;
+ 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 gchar *p = msg->service;
+
+ while (*p) {
+ if (*p < '0' || *p > '9')
+ break;
+ p++;
+ }
+
+ if (*p) {
+ const gchar *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] && !msg->cancelled; 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:
+ camel_msgport_reply ((CamelMsg *) msg);
+cancel:
+ return NULL;
+}
+#else
+static gpointer
+cs_getaddrinfo (gpointer data)
+{
+ struct _addrinfo_msg *info = data;
+
+ info->result = getaddrinfo (info->name, info->service, info->hints, info->res);
+
+ /* On Solaris, the service name 'http' or 'https' is not defined.
+ * Use the port as the service name directly. */
+ if (info->result && info->service) {
+ if (strcmp (info->service, "http") == 0)
+ info->result = getaddrinfo (info->name, "80", info->hints, info->res);
+ else if (strcmp (info->service, "https") == 0)
+ info->result = getaddrinfo (info->name, "443", info->hints, info->res);
+ }
+
+ if (!info->cancelled)
+ camel_msgport_reply ((CamelMsg *) info);
+
+ return NULL;
+}
+#endif /* NEED_ADDRINFO */
+
+/**
+ * camel_getaddrinfo:
+ *
+ * Returns: (transfer none):
+ *
+ * Since: 2.22
+ **/
+struct addrinfo *
+camel_getaddrinfo (const gchar *name,
+ const gchar *service,
+ const struct addrinfo *hints,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct _addrinfo_msg *msg;
+ struct addrinfo *res = NULL;
+#ifndef ENABLE_IPv6
+ struct addrinfo myhints;
+#endif
+ gchar *ascii_name;
+
+ g_return_val_if_fail (name != NULL, NULL);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return NULL;
+
+ camel_operation_push_message (
+ cancellable, _("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
+
+ ascii_name = camel_host_idna_to_ascii (name);
+
+ msg = g_malloc0 (sizeof (*msg));
+ msg->name = ascii_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"),
+ cancellable, error) == 0) {
+
+ if (msg->result == EAI_NONAME || msg->result == EAI_FAIL) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_URL_INVALID,
+ _("Host lookup '%s' failed. Check your host name for spelling errors."), name);
+ } else if (msg->result != 0) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_URL_INVALID,
+ _("Host lookup '%s' failed: %s"),
+ name, gai_strerror (msg->result));
+ }
+ } else
+ res = NULL;
+
+ cs_freeinfo (msg);
+ g_free (ascii_name);
+
+ camel_operation_pop_message (cancellable);
+
+ return res;
+}
+
+/**
+ * camel_freeaddrinfo:
+ *
+ * Since: 2.22
+ **/
+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
+}
+
+/**
+ * camel_host_idna_to_ascii:
+ * @host: Host name, with or without non-ascii letters in utf8
+ *
+ * Converts IDN (Internationalized Domain Name) into ASCII representation.
+ * If there's a failure or the @host has only ASCII letters, then a copy
+ * of @host is returned.
+ *
+ * Returns: Newly allocated string with only ASCII letters describing the @host.
+ * Free it with g_free() when done with it.
+ *
+ * Since: 3.16
+ **/
+gchar *
+camel_host_idna_to_ascii (const gchar *host)
+{
+ UErrorCode uerror = U_ZERO_ERROR;
+ int32_t uhost_len = 0;
+ const gchar *ptr;
+ gchar *ascii = NULL;
+
+ g_return_val_if_fail (host != NULL, NULL);
+
+ ptr = host;
+ while (*ptr > 0)
+ ptr++;
+
+ if (!*ptr) {
+ /* Did read whole buffer, it should be ASCII string already */
+ return g_strdup (host);
+ }
+
+ u_strFromUTF8 (NULL, 0, &uhost_len, host, -1, &uerror);
+ if (uhost_len > 0) {
+ UChar *uhost = g_new0 (UChar, uhost_len + 2);
+
+ uerror = U_ZERO_ERROR;
+ u_strFromUTF8 (uhost, uhost_len + 1, &uhost_len, host, -1, &uerror);
+ if (uerror == U_ZERO_ERROR && uhost_len > 0) {
+ int32_t buffer_len = uhost_len * 6 + 6, nconverted;
+ UChar *buffer = g_new0 (UChar, buffer_len);
+
+ nconverted = uidna_IDNToASCII (uhost, uhost_len, buffer, buffer_len, UIDNA_ALLOW_UNASSIGNED, 0, &uerror);
+ if (uerror == U_ZERO_ERROR && nconverted > 0) {
+ int32_t ascii_len = 0;
+
+ u_strToUTF8 (NULL, 0, &ascii_len, buffer, nconverted, &uerror);
+ if (ascii_len > 0) {
+ uerror = U_ZERO_ERROR;
+ ascii = g_new0 (gchar, ascii_len + 2);
+
+ u_strToUTF8 (ascii, ascii_len + 1, &ascii_len, buffer, nconverted, &uerror);
+ if (uerror == U_ZERO_ERROR && ascii_len > 0) {
+ ascii[ascii_len] = '\0';
+ } else {
+ g_free (ascii);
+ ascii = NULL;
+ }
+ }
+ }
+
+ g_free (buffer);
+ }
+
+ g_free (uhost);
+ }
+
+ if (!ascii)
+ ascii = g_strdup (host);
+
+ return ascii;
+}
diff --git a/src/camel/camel-net-utils.h b/src/camel/camel-net-utils.h
new file mode 100644
index 000000000..8090263f2
--- /dev/null
+++ b/src/camel/camel-net-utils.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_NET_UTILS_H
+#define CAMEL_NET_UTILS_H
+
+#include <gio/gio.h>
+#include <sys/types.h>
+
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netdb.h>
+#else
+#define socklen_t int
+struct sockaddr;
+struct addrinfo;
+#endif
+
+G_BEGIN_DECLS
+
+#ifndef _WIN32
+#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
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ */
+struct addrinfo {
+ gint ai_flags;
+ gint ai_family;
+ gint ai_socktype;
+ gint ai_protocol;
+ gsize ai_addrlen;
+ struct sockaddr *ai_addr;
+ gchar *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_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
+#endif
+
+struct addrinfo *
+ camel_getaddrinfo (const gchar *name,
+ const gchar *service,
+ const struct addrinfo *hints,
+ GCancellable *cancellable,
+ GError **error);
+void camel_freeaddrinfo (struct addrinfo *host);
+
+gchar * camel_host_idna_to_ascii (const gchar *host);
+
+G_END_DECLS
+
+#ifdef _WIN32
+#undef socklen_t
+#endif
+
+#endif /* CAMEL_NET_UTILS_H */
diff --git a/src/camel/camel-network-service.c b/src/camel/camel-network-service.c
new file mode 100644
index 000000000..5758c5b38
--- /dev/null
+++ b/src/camel/camel-network-service.c
@@ -0,0 +1,1150 @@
+/*
+ * camel-network-service.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include "evolution-data-server-config.h"
+
+#include "camel-network-service.h"
+
+#include <errno.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel.h"
+#include <camel/camel-enumtypes.h>
+#include <camel/camel-network-settings.h>
+#include <camel/camel-service.h>
+#include <camel/camel-session.h>
+
+#define PRIVATE_KEY "CamelNetworkService:private"
+
+#define CAMEL_NETWORK_SERVICE_GET_PRIVATE(obj) \
+ (g_object_get_data (G_OBJECT (obj), PRIVATE_KEY))
+
+#define G_IS_IO_ERROR(error, code) \
+ (g_error_matches ((error), G_IO_ERROR, (code)))
+
+#define G_IS_RESOLVER_ERROR(error, code) \
+ (g_error_matches ((error), G_RESOLVER_ERROR, (code)))
+
+typedef struct _CamelNetworkServicePrivate CamelNetworkServicePrivate;
+
+struct _CamelNetworkServicePrivate {
+ GMutex property_lock;
+ GSocketConnectable *connectable;
+ gboolean host_reachable;
+ gboolean host_reachable_set;
+
+ GWeakRef session_weakref;
+ gulong session_notify_network_monitor_handler_id;
+
+ GNetworkMonitor *network_monitor;
+ gulong network_changed_handler_id;
+
+ GCancellable *network_monitor_cancellable;
+ GMutex network_monitor_cancellable_lock;
+
+ GSource *update_host_reachable;
+ GMutex update_host_reachable_lock;
+};
+
+/* Forward Declarations */
+void camel_network_service_init (CamelNetworkService *service);
+
+G_DEFINE_INTERFACE (
+ CamelNetworkService,
+ camel_network_service,
+ CAMEL_TYPE_SERVICE)
+
+static gchar *
+network_service_generate_fingerprint (GTlsCertificate *certificate)
+{
+ GChecksum *checksum;
+ GString *fingerprint;
+ GByteArray *der;
+ guint8 *digest;
+ gsize length, ii;
+ const gchar tohex[16] = "0123456789abcdef";
+
+ /* XXX No accessor function for this property. */
+ g_object_get (certificate, "certificate", &der, NULL);
+ g_return_val_if_fail (der != NULL, NULL);
+
+ length = g_checksum_type_get_length (G_CHECKSUM_MD5);
+ digest = g_alloca (length);
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, der->data, der->len);
+ g_checksum_get_digest (checksum, digest, &length);
+ g_checksum_free (checksum);
+
+ g_byte_array_unref (der);
+
+ fingerprint = g_string_sized_new (50);
+
+ for (ii = 0; ii < length; ii++) {
+ guint8 byte = digest[ii];
+
+ g_string_append_c (fingerprint, tohex[(byte >> 4) & 0xf]);
+ g_string_append_c (fingerprint, tohex[byte & 0xf]);
+#ifndef G_OS_WIN32
+ g_string_append_c (fingerprint, ':');
+#else
+ /* The fingerprint is used as a filename, but can't have
+ * colons in filenames on Win32. Use underscore instead. */
+ g_string_append_c (fingerprint, '_');
+#endif
+ }
+
+ return g_string_free (fingerprint, FALSE);
+}
+
+static CamelCert *
+network_service_certdb_lookup (CamelCertDB *certdb,
+ GTlsCertificate *certificate,
+ const gchar *expected_host)
+{
+ CamelCert *cert = NULL;
+ GBytes *bytes;
+ GByteArray *der;
+ gchar *fingerprint;
+
+ fingerprint = network_service_generate_fingerprint (certificate);
+ g_return_val_if_fail (fingerprint != NULL, NULL);
+
+ cert = camel_certdb_get_host (certdb, expected_host, fingerprint);
+ if (cert == NULL)
+ goto exit;
+
+ if (cert->rawcert == NULL) {
+ GError *local_error = NULL;
+
+ camel_cert_load_cert_file (cert, &local_error);
+
+ /* Sanity check. */
+ g_warn_if_fail (
+ ((cert->rawcert != NULL) && (local_error == NULL)) ||
+ ((cert->rawcert == NULL) && (local_error != NULL)));
+
+ if (local_error != NULL) {
+ g_warning ("%s: %s", G_STRFUNC, local_error->message);
+ g_error_free (local_error);
+ }
+
+ if (cert->rawcert == NULL) {
+ camel_certdb_remove_host (
+ certdb, expected_host, fingerprint);
+ camel_certdb_touch (certdb);
+ goto exit;
+ }
+ }
+
+ /* XXX No accessor function for this property. */
+ g_object_get (certificate, "certificate", &der, NULL);
+ g_return_val_if_fail (der != NULL, cert);
+
+ bytes = g_bytes_new_static (der->data, der->len);
+
+ if (g_bytes_compare (bytes, cert->rawcert) != 0) {
+ cert->trust = CAMEL_CERT_TRUST_UNKNOWN;
+ camel_certdb_touch (certdb);
+ }
+
+ g_byte_array_unref (der);
+ g_bytes_unref (bytes);
+
+exit:
+ g_free (fingerprint);
+
+ return cert;
+}
+
+static void
+network_service_certdb_store (CamelCertDB *certdb,
+ CamelCert *cert,
+ GTlsCertificate *certificate)
+{
+ GByteArray *der = NULL;
+ GError *local_error = NULL;
+
+ g_object_get (certificate, "certificate", &der, NULL);
+ g_return_if_fail (der != NULL);
+
+ camel_cert_save_cert_file (cert, der, &local_error);
+
+ g_byte_array_unref (der);
+
+ /* Sanity check. */
+ g_warn_if_fail (
+ ((cert->rawcert != NULL) && (local_error == NULL)) ||
+ ((cert->rawcert == NULL) && (local_error != NULL)));
+
+ if (cert->rawcert != NULL)
+ camel_certdb_put (certdb, cert);
+
+ if (local_error != NULL) {
+ g_warning ("%s: %s", G_STRFUNC, local_error->message);
+ g_error_free (local_error);
+ }
+}
+
+static gboolean
+network_service_accept_certificate_cb (GTlsConnection *connection,
+ GTlsCertificate *peer_certificate,
+ GTlsCertificateFlags errors,
+ CamelNetworkService *service)
+{
+ CamelCert *cert;
+ CamelCertDB *certdb;
+ CamelSession *session;
+ CamelSettings *settings;
+ CamelNetworkSettings *network_settings;
+ gboolean new_cert = FALSE;
+ gboolean accept;
+ gchar *host;
+
+ session = camel_service_ref_session (CAMEL_SERVICE (service));
+ if (!session)
+ return FALSE;
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (service));
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+
+ certdb = camel_certdb_get_default ();
+ cert = network_service_certdb_lookup (certdb, peer_certificate, host);
+
+ if (cert == NULL) {
+ cert = camel_cert_new ();
+ cert->fingerprint =
+ network_service_generate_fingerprint (
+ peer_certificate);
+ cert->hostname = g_strdup (host);
+ cert->trust = CAMEL_CERT_TRUST_UNKNOWN;
+
+ /* Don't put() in the CamelCertDB yet. Since we can only
+ * store one entry per hostname, we'd rather not ruin any
+ * existing entry for this hostname if the user rejects
+ * the new certificate. */
+ new_cert = TRUE;
+ }
+
+ g_free (host);
+
+ if ((errors & G_TLS_CERTIFICATE_REVOKED) != 0) {
+ /* Always reject revoked certificates */
+ accept = FALSE;
+ } else {
+ if (cert->trust == CAMEL_CERT_TRUST_UNKNOWN) {
+ cert->trust = camel_session_trust_prompt (
+ session, CAMEL_SERVICE (service),
+ peer_certificate, errors);
+
+ if (new_cert)
+ network_service_certdb_store (
+ certdb, cert, peer_certificate);
+
+ camel_certdb_touch (certdb);
+ }
+
+ switch (cert->trust) {
+ case CAMEL_CERT_TRUST_MARGINAL:
+ case CAMEL_CERT_TRUST_FULLY:
+ case CAMEL_CERT_TRUST_ULTIMATE:
+ case CAMEL_CERT_TRUST_TEMPORARY:
+ accept = TRUE;
+ break;
+ default:
+ accept = FALSE;
+ break;
+ }
+ }
+
+ camel_cert_unref (cert);
+ camel_certdb_save (certdb);
+
+ g_clear_object (&certdb);
+ g_clear_object (&session);
+ g_clear_object (&settings);
+
+ return accept;
+}
+
+static void
+network_service_client_event_cb (GSocketClient *client,
+ GSocketClientEvent event,
+ GSocketConnectable *connectable,
+ GIOStream *connection,
+ CamelNetworkService *service)
+{
+ if (event == G_SOCKET_CLIENT_TLS_HANDSHAKING) {
+ g_signal_connect (
+ connection, "accept-certificate",
+ G_CALLBACK (network_service_accept_certificate_cb),
+ service);
+ }
+}
+
+static gboolean
+network_service_notify_host_reachable_cb (gpointer user_data)
+{
+ g_object_notify (G_OBJECT (user_data), "host-reachable");
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+network_service_notify_host_reachable (CamelNetworkService *service)
+{
+ CamelSession *session;
+
+ session = camel_service_ref_session (CAMEL_SERVICE (service));
+
+ if (session) {
+ camel_session_idle_add (
+ session, G_PRIORITY_DEFAULT_IDLE,
+ network_service_notify_host_reachable_cb,
+ g_object_ref (service),
+ (GDestroyNotify) g_object_unref);
+
+ g_object_unref (session);
+ }
+}
+
+static void
+network_service_set_host_reachable (CamelNetworkService *service,
+ gboolean host_reachable)
+{
+ CamelNetworkServicePrivate *priv;
+
+ priv = CAMEL_NETWORK_SERVICE_GET_PRIVATE (service);
+ g_return_if_fail (priv != NULL);
+
+ g_mutex_lock (&priv->property_lock);
+
+ /* Host reachability is in an indeterminate state until the first
+ * time this function is called. Don't let our arbitrary default
+ * value block the first notification signal. */
+ if (!priv->host_reachable_set) {
+ priv->host_reachable_set = TRUE;
+ } else if (host_reachable == priv->host_reachable) {
+ g_mutex_unlock (&priv->property_lock);
+ return;
+ }
+
+ priv->host_reachable = host_reachable;
+
+ g_mutex_unlock (&priv->property_lock);
+
+ network_service_notify_host_reachable (service);
+
+ /* Disconnect immediately if the host is not reachable.
+ * Then connect lazily when the host becomes reachable. */
+ if (!host_reachable) {
+ GError *local_error = NULL;
+
+ /* XXX Does this actually block in any providers?
+ * If so then we need to do it asynchronously. */
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (service), FALSE, NULL, &local_error);
+ if (local_error != NULL) {
+ if (!G_IS_IO_ERROR (local_error, G_IO_ERROR_CANCELLED))
+ g_warning ("%s: %s", G_STRFUNC, local_error->message);
+ g_error_free (local_error);
+ }
+ }
+}
+
+static gboolean
+network_service_update_host_reachable_timeout_cb (gpointer user_data)
+{
+ CamelNetworkService *service;
+ CamelNetworkServicePrivate *priv;
+ GCancellable *old_cancellable;
+ GCancellable *new_cancellable;
+ GSource *current_source;
+
+ current_source = g_main_current_source ();
+ if (current_source && g_source_is_destroyed (current_source))
+ return FALSE;
+
+ service = CAMEL_NETWORK_SERVICE (user_data);
+ priv = CAMEL_NETWORK_SERVICE_GET_PRIVATE (service);
+ g_return_val_if_fail (priv != NULL, FALSE);
+
+ g_mutex_lock (&priv->update_host_reachable_lock);
+ g_source_unref (priv->update_host_reachable);
+ priv->update_host_reachable = NULL;
+ g_mutex_unlock (&priv->update_host_reachable_lock);
+
+ new_cancellable = g_cancellable_new ();
+
+ g_mutex_lock (&priv->network_monitor_cancellable_lock);
+ old_cancellable = priv->network_monitor_cancellable;
+ priv->network_monitor_cancellable = g_object_ref (new_cancellable);
+ g_mutex_unlock (&priv->network_monitor_cancellable_lock);
+
+ g_cancellable_cancel (old_cancellable);
+
+ /* XXX This updates the "host-reachable" property on its own.
+ * There's nothing else to do with the result so omit the
+ * GAsyncReadyCallback; just needs to run asynchronously. */
+ camel_network_service_can_reach (service, new_cancellable, NULL, NULL);
+
+ g_clear_object (&old_cancellable);
+ g_clear_object (&new_cancellable);
+
+ return FALSE;
+}
+
+static void
+network_service_update_host_reachable (CamelNetworkService *service)
+{
+ CamelNetworkServicePrivate *priv;
+ CamelSession *session;
+ GMainContext *main_context;
+ GSource *timeout_source;
+
+ priv = CAMEL_NETWORK_SERVICE_GET_PRIVATE (service);
+
+ session = camel_service_ref_session (CAMEL_SERVICE (service));
+ if (!session)
+ return;
+
+ if (!camel_session_get_online (session)) {
+ g_object_unref (session);
+ return;
+ }
+
+ g_mutex_lock (&priv->update_host_reachable_lock);
+
+ /* Reference the service before destroying any already scheduled GSource,
+ in case the service's last reference is held by that GSource. */
+ g_object_ref (service);
+
+ if (priv->update_host_reachable) {
+ g_source_destroy (priv->update_host_reachable);
+ g_source_unref (priv->update_host_reachable);
+ priv->update_host_reachable = NULL;
+ }
+
+ main_context = camel_session_ref_main_context (session);
+
+ timeout_source = g_timeout_source_new_seconds (5);
+ g_source_set_priority (timeout_source, G_PRIORITY_LOW);
+ g_source_set_callback (
+ timeout_source,
+ network_service_update_host_reachable_timeout_cb,
+ service, (GDestroyNotify) g_object_unref);
+ g_source_attach (timeout_source, main_context);
+ priv->update_host_reachable = g_source_ref (timeout_source);
+ g_source_unref (timeout_source);
+
+ g_main_context_unref (main_context);
+
+ g_mutex_unlock (&priv->update_host_reachable_lock);
+
+ g_object_unref (session);
+}
+
+static void
+network_service_network_changed_cb (GNetworkMonitor *network_monitor,
+ gboolean network_available,
+ CamelNetworkService *service)
+{
+ network_service_update_host_reachable (service);
+}
+
+static void
+network_service_session_notify_network_monitor_cb (GObject *object,
+ GParamSpec *param,
+ gpointer user_data)
+{
+ CamelSession *session;
+ CamelNetworkService *service = user_data;
+ CamelNetworkServicePrivate *priv;
+ GNetworkMonitor *network_monitor;
+ gboolean update_host_reachable = FALSE;
+
+ g_return_if_fail (CAMEL_IS_SESSION (object));
+ g_return_if_fail (CAMEL_IS_NETWORK_SERVICE (service));
+
+ priv = CAMEL_NETWORK_SERVICE_GET_PRIVATE (service);
+ g_return_if_fail (priv != NULL);
+
+ g_object_ref (service);
+
+ session = CAMEL_SESSION (object);
+
+ network_monitor = camel_session_ref_network_monitor (session);
+
+ g_mutex_lock (&priv->property_lock);
+
+ if (network_monitor != priv->network_monitor) {
+ if (priv->network_monitor) {
+ g_signal_handler_disconnect (
+ priv->network_monitor,
+ priv->network_changed_handler_id);
+ g_object_unref (priv->network_monitor);
+ }
+
+ priv->network_monitor = g_object_ref (network_monitor);
+
+ priv->network_changed_handler_id = g_signal_connect (
+ priv->network_monitor, "network-changed",
+ G_CALLBACK (network_service_network_changed_cb), service);
+
+ update_host_reachable = TRUE;
+ }
+
+ g_mutex_unlock (&priv->property_lock);
+
+ g_clear_object (&network_monitor);
+
+ if (update_host_reachable)
+ network_service_update_host_reachable (service);
+
+ g_object_unref (service);
+}
+
+static CamelNetworkServicePrivate *
+network_service_private_new (CamelNetworkService *service)
+{
+ CamelNetworkServicePrivate *priv;
+ CamelSession *session;
+ gulong handler_id;
+
+ priv = g_slice_new0 (CamelNetworkServicePrivate);
+
+ g_mutex_init (&priv->property_lock);
+ g_mutex_init (&priv->network_monitor_cancellable_lock);
+ g_mutex_init (&priv->update_host_reachable_lock);
+
+ /* Configure network monitoring. */
+
+ session = camel_service_ref_session (CAMEL_SERVICE (service));
+ if (session) {
+ priv->network_monitor = camel_session_ref_network_monitor (session);
+
+ priv->session_notify_network_monitor_handler_id =
+ g_signal_connect (session, "notify::network-monitor",
+ G_CALLBACK (network_service_session_notify_network_monitor_cb), service);
+
+ g_weak_ref_init (&priv->session_weakref, session);
+
+ g_object_unref (session);
+ } else
+ g_weak_ref_init (&priv->session_weakref, NULL);
+
+ if (priv->network_monitor) {
+ handler_id = g_signal_connect (
+ priv->network_monitor, "network-changed",
+ G_CALLBACK (network_service_network_changed_cb), service);
+ priv->network_changed_handler_id = handler_id;
+ }
+
+ return priv;
+}
+
+static void
+network_service_private_free (CamelNetworkServicePrivate *priv)
+{
+ if (priv->network_changed_handler_id) {
+ g_signal_handler_disconnect (
+ priv->network_monitor,
+ priv->network_changed_handler_id);
+ }
+
+ if (priv->session_notify_network_monitor_handler_id) {
+ CamelSession *session;
+
+ session = g_weak_ref_get (&priv->session_weakref);
+ if (session) {
+ g_signal_handler_disconnect (
+ session,
+ priv->session_notify_network_monitor_handler_id);
+ g_object_unref (session);
+ }
+ }
+
+ g_clear_object (&priv->connectable);
+ g_clear_object (&priv->network_monitor);
+ g_clear_object (&priv->network_monitor_cancellable);
+ g_weak_ref_clear (&priv->session_weakref);
+
+ if (priv->update_host_reachable != NULL) {
+ g_source_destroy (priv->update_host_reachable);
+ g_source_unref (priv->update_host_reachable);
+ }
+
+ g_mutex_clear (&priv->property_lock);
+ g_mutex_clear (&priv->network_monitor_cancellable_lock);
+ g_mutex_clear (&priv->update_host_reachable_lock);
+
+ g_slice_free (CamelNetworkServicePrivate, priv);
+}
+
+static GIOStream *
+network_service_connect_sync (CamelNetworkService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GSocketClient *client;
+ GSocketConnection *connection;
+ GSocketConnectable *connectable;
+ CamelNetworkSecurityMethod method;
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (service));
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ method = camel_network_settings_get_security_method (network_settings);
+
+ connectable = camel_network_service_ref_connectable (service);
+ g_return_val_if_fail (connectable != NULL, NULL);
+
+ client = g_socket_client_new ();
+ g_socket_client_set_timeout (client, 90);
+
+ g_signal_connect (
+ client, "event",
+ G_CALLBACK (network_service_client_event_cb), service);
+
+ if (method == CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT)
+ g_socket_client_set_tls (client, TRUE);
+
+ camel_binding_bind_property (
+ service, "proxy-resolver",
+ client, "proxy-resolver",
+ G_BINDING_SYNC_CREATE);
+
+ connection = g_socket_client_connect (
+ client, connectable, cancellable, error);
+
+ g_object_unref (connectable);
+ g_object_unref (client);
+
+ g_object_unref (settings);
+
+ if (connection) {
+ GSocket *socket;
+
+ socket = g_socket_connection_get_socket (connection);
+ if (socket) {
+ g_socket_set_timeout (socket, 90);
+ g_socket_set_keepalive (socket, TRUE);
+ }
+ }
+
+ return (connection != NULL) ? G_IO_STREAM (connection) : NULL;
+}
+
+static GSocketConnectable *
+network_service_new_connectable (CamelNetworkService *service)
+{
+ GSocketConnectable *connectable = NULL;
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ guint16 port;
+ gchar *host;
+
+ /* Some services might want to override this method to
+ * create a GNetworkService instead of a GNetworkAddress. */
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (service));
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host_ensure_ascii (network_settings);
+ port = camel_network_settings_get_port (network_settings);
+
+ if (host && *host) {
+ CamelProvider *provider;
+
+ provider = camel_service_get_provider (CAMEL_SERVICE (service));
+
+ connectable = g_object_new (G_TYPE_NETWORK_ADDRESS,
+ "scheme", provider ? provider->protocol : "socks",
+ "hostname", host,
+ "port", port,
+ NULL);
+ }
+
+ g_free (host);
+
+ g_object_unref (settings);
+
+ return connectable;
+}
+
+static void
+camel_network_service_default_init (CamelNetworkServiceInterface *iface)
+{
+ iface->connect_sync = network_service_connect_sync;
+ iface->new_connectable = network_service_new_connectable;
+
+ g_object_interface_install_property (
+ iface,
+ g_param_spec_object (
+ "connectable",
+ "Connectable",
+ "Socket endpoint of a network service",
+ G_TYPE_SOCKET_CONNECTABLE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (
+ iface,
+ g_param_spec_boolean (
+ "host-reachable",
+ "Host Reachable",
+ "Whether the host is reachable",
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+void
+camel_network_service_init (CamelNetworkService *service)
+{
+ /* This is called from CamelService during instance
+ * construction. It is not part of the public API. */
+
+ g_return_if_fail (CAMEL_IS_NETWORK_SERVICE (service));
+
+ g_object_set_data_full (
+ G_OBJECT (service), PRIVATE_KEY,
+ network_service_private_new (service),
+ (GDestroyNotify) network_service_private_free);
+}
+
+/**
+ * camel_network_service_get_service_name:
+ * @service: a #CamelNetworkService
+ * @method: a #CamelNetworkSecurityMethod
+ *
+ * Returns the standard network service name for @service and the security
+ * method @method, as defined in /etc/services. For example, the service
+ * name for unencrypted IMAP or encrypted IMAP using STARTTLS is "imap",
+ * but the service name for IMAP over SSL is "imaps".
+ *
+ * Returns: the network service name for @service and @method, or %NULL
+ *
+ * Since: 3.2
+ **/
+const gchar *
+camel_network_service_get_service_name (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method)
+{
+ CamelNetworkServiceInterface *iface;
+ const gchar *service_name = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SERVICE (service), NULL);
+
+ iface = CAMEL_NETWORK_SERVICE_GET_INTERFACE (service);
+
+ if (iface->get_service_name != NULL)
+ service_name = iface->get_service_name (service, method);
+
+ return service_name;
+}
+
+/**
+ * camel_network_service_get_default_port:
+ * @service: a #CamelNetworkService
+ * @method: a #CamelNetworkSecurityMethod
+ *
+ * Returns the default network port number for @service and the security
+ * method @method, as defined in /etc/services. For example, the default
+ * port for unencrypted IMAP or encrypted IMAP using STARTTLS is 143, but
+ * the default port for IMAP over SSL is 993.
+ *
+ * Returns: the default port number for @service and @method
+ *
+ * Since: 3.2
+ **/
+guint16
+camel_network_service_get_default_port (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method)
+{
+ CamelNetworkServiceInterface *iface;
+ guint16 default_port = 0;
+
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SERVICE (service), 0);
+
+ iface = CAMEL_NETWORK_SERVICE_GET_INTERFACE (service);
+
+ if (iface->get_default_port != NULL)
+ default_port = iface->get_default_port (service, method);
+
+ return default_port;
+}
+
+/**
+ * camel_network_service_ref_connectable:
+ * @service: a #CamelNetworkService
+ *
+ * Returns the socket endpoint for the network service to which @service
+ * is a client.
+ *
+ * The returned #GSocketConnectable is referenced for thread-safety and
+ * must be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: (transfer full): a #GSocketConnectable
+ *
+ * Since: 3.8
+ **/
+GSocketConnectable *
+camel_network_service_ref_connectable (CamelNetworkService *service)
+{
+ CamelNetworkServicePrivate *priv;
+ GSocketConnectable *connectable = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SERVICE (service), NULL);
+
+ priv = CAMEL_NETWORK_SERVICE_GET_PRIVATE (service);
+ g_return_val_if_fail (priv != NULL, NULL);
+
+ g_mutex_lock (&priv->property_lock);
+
+ if (priv->connectable != NULL) {
+ connectable = g_object_ref (priv->connectable);
+ g_mutex_unlock (&priv->property_lock);
+ } else {
+ CamelNetworkServiceInterface *iface;
+
+ g_mutex_unlock (&priv->property_lock);
+
+ iface = CAMEL_NETWORK_SERVICE_GET_INTERFACE (service);
+ g_return_val_if_fail (iface->new_connectable != NULL, NULL);
+
+ /* This may return NULL if we don't have valid network
+ * settings from which to create a GSocketConnectable. */
+ connectable = iface->new_connectable (service);
+ }
+
+ return connectable;
+}
+
+/**
+ * camel_network_service_set_connectable:
+ * @service: a #CamelNetworkService
+ * @connectable: a #GSocketConnectable, or %NULL
+ *
+ * Sets the socket endpoint for the network service to which @service is
+ * a client. If @connectable is %NULL, a #GSocketConnectable is derived
+ * from the @service's #CamelNetworkSettings.
+ *
+ * Since: 3.8
+ **/
+void
+camel_network_service_set_connectable (CamelNetworkService *service,
+ GSocketConnectable *connectable)
+{
+ CamelNetworkServicePrivate *priv;
+
+ g_return_if_fail (CAMEL_IS_NETWORK_SERVICE (service));
+
+ priv = CAMEL_NETWORK_SERVICE_GET_PRIVATE (service);
+ g_return_if_fail (priv != NULL);
+
+ /* The GNetworkAddress is not thread safe, thus rather than precache it,
+ create a new instance whenever it's asked for it. Keep precached only
+ the connectable which had been explicitly set, because there cannot be
+ done exact copy of it.
+ */
+ if (connectable != NULL) {
+ g_return_if_fail (G_IS_SOCKET_CONNECTABLE (connectable));
+ g_object_ref (connectable);
+ }
+
+ g_mutex_lock (&priv->property_lock);
+
+ if (priv->connectable != NULL)
+ g_object_unref (priv->connectable);
+
+ priv->connectable = connectable;
+
+ g_mutex_unlock (&priv->property_lock);
+
+ network_service_update_host_reachable (service);
+
+ g_object_notify (G_OBJECT (service), "connectable");
+}
+
+/**
+ * camel_network_service_get_host_reachable:
+ * @service: a #CamelNetworkService
+ *
+ * Returns %TRUE if @service believes that the host pointed to by
+ * #CamelNetworkService:connectable can be reached. This property
+ * is updated automatically as network conditions change.
+ *
+ * Returns: whether the host is reachable
+ *
+ * Since: 3.8
+ **/
+gboolean
+camel_network_service_get_host_reachable (CamelNetworkService *service)
+{
+ CamelNetworkServicePrivate *priv;
+
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SERVICE (service), FALSE);
+
+ priv = CAMEL_NETWORK_SERVICE_GET_PRIVATE (service);
+ g_return_val_if_fail (priv != NULL, FALSE);
+
+ return priv->host_reachable;
+}
+
+/**
+ * camel_network_service_connect_sync:
+ * @service: a #CamelNetworkService
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Attempts to establish a network connection to the server described by
+ * @service, using the preferred #CamelNetworkSettings:security-method to
+ * secure the connection. If a connection cannot be established, or the
+ * connection attempt is cancelled, the function sets @error and returns
+ * %NULL.
+ *
+ * Returns: (transfer full): a #GIOStream, or %NULL
+ *
+ * Since: 3.2
+ **/
+GIOStream *
+camel_network_service_connect_sync (CamelNetworkService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNetworkServiceInterface *iface;
+
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SERVICE (service), NULL);
+
+ iface = CAMEL_NETWORK_SERVICE_GET_INTERFACE (service);
+ g_return_val_if_fail (iface->connect_sync != NULL, NULL);
+
+ return iface->connect_sync (service, cancellable, error);
+}
+
+/**
+ * camel_network_service_starttls:
+ * @service: a #CamelNetworkService
+ * @base_stream: a #GIOStream
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a #GTlsClientConnection wrapping @base_stream, which is
+ * assumed to communicate with the server identified by @service's
+ * #CamelNetworkService:connectable.
+ *
+ * This should typically be called after issuing a STARTTLS command
+ * to a server to initiate a Transport Layer Security handshake.
+ *
+ * Returns: (transfer full): the new #GTlsClientConnection, or %NULL on error
+ *
+ * Since: 3.12
+ **/
+GIOStream *
+camel_network_service_starttls (CamelNetworkService *service,
+ GIOStream *base_stream,
+ GError **error)
+{
+ GSocketConnectable *connectable;
+ GIOStream *tls_client_connection;
+
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SERVICE (service), NULL);
+ g_return_val_if_fail (G_IS_IO_STREAM (base_stream), NULL);
+
+ connectable = camel_network_service_ref_connectable (service);
+ g_return_val_if_fail (connectable != NULL, FALSE);
+
+ tls_client_connection = g_tls_client_connection_new (
+ base_stream, connectable, error);
+
+ if (tls_client_connection != NULL) {
+ g_signal_connect (
+ tls_client_connection, "accept-certificate",
+ G_CALLBACK (network_service_accept_certificate_cb),
+ service);
+ }
+
+ g_object_unref (connectable);
+
+ return tls_client_connection;
+}
+
+/**
+ * camel_network_service_can_reach_sync:
+ * @service: a #CamelNetworkService
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Attempts to determine whether or not the host described by @service's
+ * #CamelNetworkService:connectable property can be reached, without actually
+ * trying to connect to it.
+ *
+ * If @service believes an attempt to connect will succeed, the function
+ * returns %TRUE. Otherwise the function returns %FALSE and sets @error
+ * to an appropriate error (such as %G_IO_ERROR_HOST_UNREACHABLE).
+ *
+ * The function will also update the @service's
+ * #CamelNetworkService:host-reachable property based on the result.
+ *
+ * Returns: whether the host for @service can be reached
+ *
+ * Since: 3.12
+ **/
+gboolean
+camel_network_service_can_reach_sync (CamelNetworkService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNetworkServicePrivate *priv;
+ GSocketConnectable *connectable;
+ gboolean can_reach = FALSE;
+ gboolean update_property;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SERVICE (service), FALSE);
+
+ priv = CAMEL_NETWORK_SERVICE_GET_PRIVATE (service);
+ g_return_val_if_fail (priv != NULL, FALSE);
+
+ connectable = camel_network_service_ref_connectable (service);
+
+ if (connectable != NULL) {
+ can_reach = g_network_monitor_can_reach (
+ priv->network_monitor, connectable,
+ cancellable, &local_error);
+ } else {
+ /* No host information available, assume reachable */
+ can_reach = TRUE;
+ }
+
+ update_property =
+ can_reach ||
+ G_IS_IO_ERROR (local_error, G_IO_ERROR_HOST_UNREACHABLE) ||
+ G_IS_RESOLVER_ERROR (local_error, G_RESOLVER_ERROR_NOT_FOUND);
+
+ if (update_property) {
+ g_mutex_lock (&priv->update_host_reachable_lock);
+
+ if (priv->update_host_reachable) {
+ g_source_destroy (priv->update_host_reachable);
+ g_source_unref (priv->update_host_reachable);
+ priv->update_host_reachable = NULL;
+ }
+
+ g_mutex_unlock (&priv->update_host_reachable_lock);
+
+ network_service_set_host_reachable (service, can_reach);
+ }
+
+ g_clear_object (&connectable);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return can_reach;
+}
+
+/* Helper for camel_network_service_can_reach() */
+static void
+network_service_can_reach_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ GError *local_error = NULL;
+
+ success = camel_network_service_can_reach_sync (
+ CAMEL_NETWORK_SERVICE (source_object),
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_network_service_can_reach:
+ * @service: a #CamelNetworkService
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously attempts to determine whether or not the host described by
+ * @service's #CamelNetworkService:connectable property can be reached, without
+ * actually trying to connect to it.
+ *
+ * For more details, see camel_network_service_can_reach_sync().
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_network_service_can_reach_finish() to get the result of the
+ * operation.
+ *
+ * Since: 3.12
+ **/
+void
+camel_network_service_can_reach (CamelNetworkService *service,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_return_if_fail (CAMEL_IS_NETWORK_SERVICE (service));
+
+ task = g_task_new (service, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_network_service_can_reach);
+
+ g_task_run_in_thread (task, network_service_can_reach_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_network_service_can_reach_finish:
+ * @service: a #CamelNetworkService
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_network_service_can_reach().
+ *
+ * Returns: whether the host for @service can be reached
+ *
+ * Since: 3.12
+ **/
+gboolean
+camel_network_service_can_reach_finish (CamelNetworkService *service,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SERVICE (service), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, service), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_network_service_can_reach), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
diff --git a/src/camel/camel-network-service.h b/src/camel/camel-network-service.h
new file mode 100644
index 000000000..4795e9bf3
--- /dev/null
+++ b/src/camel/camel-network-service.h
@@ -0,0 +1,118 @@
+/*
+ * camel-network-service.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_NETWORK_SERVICE_H
+#define CAMEL_NETWORK_SERVICE_H
+
+#include <camel/camel-enums.h>
+#include <camel/camel-network-settings.h>
+#include <camel/camel-stream.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_NETWORK_SERVICE \
+ (camel_network_service_get_type ())
+#define CAMEL_NETWORK_SERVICE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_NETWORK_SERVICE, CamelNetworkService))
+#define CAMEL_NETWORK_SERVICE_INTERFACE(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_NETWORK_SERVICE, CamelNetworkServiceInterface))
+#define CAMEL_IS_NETWORK_SERVICE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_NETWORK_SERVICE))
+#define CAMEL_IS_NETWORK_SERVICE_INTERFACE(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_NETWORK_SERVICE))
+#define CAMEL_NETWORK_SERVICE_GET_INTERFACE(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE \
+ ((obj), CAMEL_TYPE_NETWORK_SERVICE, CamelNetworkServiceInterface))
+
+G_BEGIN_DECLS
+
+/**
+ * CamelNetworkService:
+ *
+ * Since: 3.2
+ **/
+typedef struct _CamelNetworkService CamelNetworkService;
+typedef struct _CamelNetworkServiceInterface CamelNetworkServiceInterface;
+
+struct _CamelNetworkServiceInterface {
+ GTypeInterface parent_interface;
+
+ const gchar * (*get_service_name)
+ (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method);
+ guint16 (*get_default_port)
+ (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method);
+
+ GIOStream * (*connect_sync) (CamelNetworkService *service,
+ GCancellable *cancellable,
+ GError **error);
+
+ GSocketConnectable *
+ (*new_connectable)
+ (CamelNetworkService *service);
+
+ gpointer reserved[15];
+};
+
+GType camel_network_service_get_type (void) G_GNUC_CONST;
+const gchar * camel_network_service_get_service_name
+ (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method);
+guint16 camel_network_service_get_default_port
+ (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method);
+GSocketConnectable *
+ camel_network_service_ref_connectable
+ (CamelNetworkService *service);
+void camel_network_service_set_connectable
+ (CamelNetworkService *service,
+ GSocketConnectable *connectable);
+gboolean camel_network_service_get_host_reachable
+ (CamelNetworkService *service);
+GIOStream * camel_network_service_connect_sync
+ (CamelNetworkService *service,
+ GCancellable *cancellable,
+ GError **error);
+GIOStream * camel_network_service_starttls
+ (CamelNetworkService *service,
+ GIOStream *base_stream,
+ GError **error);
+gboolean camel_network_service_can_reach_sync
+ (CamelNetworkService *service,
+ GCancellable *cancellable,
+ GError **error);
+void camel_network_service_can_reach
+ (CamelNetworkService *service,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_network_service_can_reach_finish
+ (CamelNetworkService *service,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_NETWORK_SERVICE_H */
diff --git a/src/camel/camel-network-settings.c b/src/camel/camel-network-settings.c
new file mode 100644
index 000000000..cfc514529
--- /dev/null
+++ b/src/camel/camel-network-settings.c
@@ -0,0 +1,489 @@
+/*
+ * camel-network-settings.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-network-settings.h"
+
+#include <camel/camel-enumtypes.h>
+#include <camel/camel-settings.h>
+#include <camel/camel-net-utils.h>
+
+#define AUTH_MECHANISM_KEY "CamelNetworkSettings:auth-mechanism"
+#define HOST_KEY "CamelNetworkSettings:host"
+#define PORT_KEY "CamelNetworkSettings:port"
+#define SECURITY_METHOD_KEY "CamelNetworkSettings:security-method"
+#define USER_KEY "CamelNetworkSettings:user"
+
+/* XXX Because interfaces have no initialization method, we can't
+ * allocate a per-instance mutex in a thread-safe manner. So
+ * we have to use a single static mutex for all instances. */
+G_LOCK_DEFINE_STATIC (property_lock);
+
+G_DEFINE_INTERFACE (
+ CamelNetworkSettings,
+ camel_network_settings,
+ CAMEL_TYPE_SETTINGS)
+
+static void
+camel_network_settings_default_init (CamelNetworkSettingsInterface *iface)
+{
+ g_object_interface_install_property (
+ iface,
+ g_param_spec_string (
+ "auth-mechanism",
+ "Auth Mechanism",
+ "Authentication mechanism name",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (
+ iface,
+ g_param_spec_string (
+ "host",
+ "Host",
+ "Host name for the network service",
+ "",
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (
+ iface,
+ g_param_spec_uint (
+ "port",
+ "Port",
+ "Port number for the network service",
+ 0, G_MAXUINT16, 0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (
+ iface,
+ g_param_spec_enum (
+ "security-method",
+ "Security Method",
+ "Method used to establish a network connection",
+ CAMEL_TYPE_NETWORK_SECURITY_METHOD,
+ CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (
+ iface,
+ g_param_spec_string (
+ "user",
+ "User",
+ "User name for the network account",
+ g_get_user_name (),
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+/**
+ * camel_network_settings_get_auth_mechanism:
+ * @settings: a #CamelNetworkSettings
+ *
+ * Returns the mechanism name used to authenticate to a network service.
+ * Often this refers to a SASL mechanism such as "LOGIN" or "GSSAPI".
+ *
+ * Returns: the authentication mechanism name
+ *
+ * Since: 3.4
+ **/
+const gchar *
+camel_network_settings_get_auth_mechanism (CamelNetworkSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ return g_object_get_data (G_OBJECT (settings), AUTH_MECHANISM_KEY);
+}
+
+/**
+ * camel_network_settings_dup_auth_mechanism:
+ * @settings: a #CamelNetworkSettings
+ *
+ * Thread-safe variation of camel_network_settings_get_auth_mechanism().
+ * Use this function when accessing @settings from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelNetworkSettings:auth-mechanism
+ *
+ * Since: 3.4
+ **/
+gchar *
+camel_network_settings_dup_auth_mechanism (CamelNetworkSettings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ G_LOCK (property_lock);
+
+ protected = camel_network_settings_get_auth_mechanism (settings);
+ duplicate = g_strdup (protected);
+
+ G_UNLOCK (property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_network_settings_set_auth_mechanism:
+ * @settings: a #CamelNetworkSettings
+ * @auth_mechanism: an authentication mechanism name, or %NULL
+ *
+ * Sets the mechanism name used to authenticate to a network service.
+ * Often this refers to a SASL mechanism such as "LOGIN" or "GSSAPI".
+ * The #CamelNetworkSettings:auth-mechanism property is automatically
+ * stripped of leading and trailing whitespace.
+ *
+ * Since: 3.4
+ **/
+void
+camel_network_settings_set_auth_mechanism (CamelNetworkSettings *settings,
+ const gchar *auth_mechanism)
+{
+ gchar *stripped_auth_mechanism = NULL;
+
+ g_return_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings));
+
+ /* Strip leading and trailing whitespace. */
+ if (auth_mechanism != NULL)
+ stripped_auth_mechanism =
+ g_strstrip (g_strdup (auth_mechanism));
+
+ G_LOCK (property_lock);
+
+ g_object_set_data_full (
+ G_OBJECT (settings),
+ AUTH_MECHANISM_KEY,
+ stripped_auth_mechanism,
+ (GDestroyNotify) g_free);
+
+ G_UNLOCK (property_lock);
+
+ g_object_notify (G_OBJECT (settings), "auth-mechanism");
+}
+
+/**
+ * camel_network_settings_get_host:
+ * @settings: a #CamelNetworkSettings
+ *
+ * Returns the host name used to authenticate to a network service.
+ *
+ * Returns: the host name of a network service
+ *
+ * Since: 3.4
+ **/
+const gchar *
+camel_network_settings_get_host (CamelNetworkSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ return g_object_get_data (G_OBJECT (settings), HOST_KEY);
+}
+
+/**
+ * camel_network_settings_dup_host:
+ * @settings: a #CamelNetworkSettings
+ *
+ * Thread-safe variation of camel_network_settings_get_host().
+ * Use this function when accessing @settings from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelNetworkSettings:host
+ *
+ * Since: 3.4
+ **/
+gchar *
+camel_network_settings_dup_host (CamelNetworkSettings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ G_LOCK (property_lock);
+
+ protected = camel_network_settings_get_host (settings);
+ duplicate = g_strdup (protected);
+
+ G_UNLOCK (property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_network_settings_dup_host_ensure_ascii:
+ * @settings: a #CamelNetworkSettings
+ *
+ * Just like camel_network_settings_dup_host(), only makes sure that
+ * the returned host name will be converted into its ASCII form in case
+ * of IDNA value.
+ *
+ * Returns: a newly-allocated copy of #CamelNetworkSettings:host with
+ * only ASCII letters.
+ *
+ * Since: 3.16
+ **/
+gchar *
+camel_network_settings_dup_host_ensure_ascii (CamelNetworkSettings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ G_LOCK (property_lock);
+
+ protected = camel_network_settings_get_host (settings);
+ if (protected && *protected)
+ duplicate = camel_host_idna_to_ascii (protected);
+ else
+ duplicate = g_strdup (protected);
+
+ G_UNLOCK (property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_network_settings_set_host:
+ * @settings: a #CamelNetworkSettings
+ * @host: a host name, or %NULL
+ *
+ * Sets the host name used to authenticate to a network service. The
+ * #CamelNetworkSettings:host property is automatically stripped of
+ * leading and trailing whitespace.
+ *
+ * Since: 3.4
+ **/
+void
+camel_network_settings_set_host (CamelNetworkSettings *settings,
+ const gchar *host)
+{
+ gchar *stripped_host = NULL;
+
+ g_return_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings));
+
+ /* Make sure this property is never NULL. */
+ if (host == NULL)
+ host = "";
+
+ /* Strip leading and trailing whitespace. */
+ stripped_host = g_strstrip (g_strdup (host));
+
+ G_LOCK (property_lock);
+
+ g_object_set_data_full (
+ G_OBJECT (settings),
+ HOST_KEY, stripped_host,
+ (GDestroyNotify) g_free);
+
+ G_UNLOCK (property_lock);
+
+ g_object_notify (G_OBJECT (settings), "host");
+}
+
+/**
+ * camel_network_settings_get_port:
+ * @settings: a #CamelNetworkSettings
+ *
+ * Returns the port number used to authenticate to a network service.
+ *
+ * Returns: the port number of a network service
+ *
+ * Since: 3.4
+ **/
+guint16
+camel_network_settings_get_port (CamelNetworkSettings *settings)
+{
+ gpointer data;
+
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), 0);
+
+ data = g_object_get_data (G_OBJECT (settings), PORT_KEY);
+
+ return (guint16) GPOINTER_TO_UINT (data);
+}
+
+/**
+ * camel_network_settings_set_port:
+ * @settings: a #CamelNetworkSettings
+ * @port: a port number
+ *
+ * Sets the port number used to authenticate to a network service.
+ *
+ * Since: 3.4
+ **/
+void
+camel_network_settings_set_port (CamelNetworkSettings *settings,
+ guint16 port)
+{
+ g_return_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings));
+
+ g_object_set_data (
+ G_OBJECT (settings), PORT_KEY,
+ GUINT_TO_POINTER ((guint) port));
+
+ g_object_notify (G_OBJECT (settings), "port");
+}
+
+/**
+ * camel_network_settings_get_security_method:
+ * @settings: a #CamelNetworkSettings
+ *
+ * Returns the method used to establish a secure (or unsecure) network
+ * connection.
+ *
+ * Returns: the security method
+ *
+ * Since: 3.2
+ **/
+CamelNetworkSecurityMethod
+camel_network_settings_get_security_method (CamelNetworkSettings *settings)
+{
+ gpointer data;
+
+ g_return_val_if_fail (
+ CAMEL_IS_NETWORK_SETTINGS (settings),
+ CAMEL_NETWORK_SECURITY_METHOD_NONE);
+
+ data = g_object_get_data (G_OBJECT (settings), SECURITY_METHOD_KEY);
+
+ return (CamelNetworkSecurityMethod) GPOINTER_TO_INT (data);
+}
+
+/**
+ * camel_network_settings_set_security_method:
+ * @settings: a #CamelNetworkSettings
+ * @method: the security method
+ *
+ * Sets the method used to establish a secure (or unsecure) network
+ * connection. Note that changing this setting has no effect on an
+ * already-established network connection.
+ *
+ * Since: 3.2
+ **/
+void
+camel_network_settings_set_security_method (CamelNetworkSettings *settings,
+ CamelNetworkSecurityMethod method)
+{
+ g_return_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings));
+
+ g_object_set_data (
+ G_OBJECT (settings),
+ SECURITY_METHOD_KEY,
+ GINT_TO_POINTER (method));
+
+ g_object_notify (G_OBJECT (settings), "security-method");
+}
+
+/**
+ * camel_network_settings_get_user:
+ * @settings: a #CamelNetworkSettings
+ *
+ * Returns the user name used to authenticate to a network service.
+ *
+ * Returns: the user name of a network service
+ *
+ * Since: 3.4
+ **/
+const gchar *
+camel_network_settings_get_user (CamelNetworkSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ return g_object_get_data (G_OBJECT (settings), USER_KEY);
+}
+
+/**
+ * camel_network_settings_dup_user:
+ * @settings: a #CamelNetworkSettings
+ *
+ * Thread-safe variation of camel_network_settings_get_user().
+ * Use this function when accessing @settings from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelNetworkSettings:user
+ *
+ * Since: 3.4
+ **/
+gchar *
+camel_network_settings_dup_user (CamelNetworkSettings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ G_LOCK (property_lock);
+
+ protected = camel_network_settings_get_user (settings);
+ duplicate = g_strdup (protected);
+
+ G_UNLOCK (property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_network_settings_set_user:
+ * @settings: a #CamelNetworkSettings
+ * @user: a user name, or %NULL
+ *
+ * Sets the user name used to authenticate to a network service. The
+ * #CamelNetworkSettings:user property is automatically stripped of
+ * leading and trailing whitespace.
+ *
+ * Since: 3.4
+ **/
+void
+camel_network_settings_set_user (CamelNetworkSettings *settings,
+ const gchar *user)
+{
+ gchar *stripped_user = NULL;
+
+ g_return_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings));
+
+ /* Make sure this property is never NULL. */
+ if (user == NULL)
+ user = "";
+
+ /* Strip leading and trailing whitespace. */
+ stripped_user = g_strstrip (g_strdup (user));
+
+ G_LOCK (property_lock);
+
+ g_object_set_data_full (
+ G_OBJECT (settings),
+ USER_KEY, stripped_user,
+ (GDestroyNotify) g_free);
+
+ G_UNLOCK (property_lock);
+
+ g_object_notify (G_OBJECT (settings), "user");
+}
+
diff --git a/src/camel/camel-network-settings.h b/src/camel/camel-network-settings.h
new file mode 100644
index 000000000..db794853c
--- /dev/null
+++ b/src/camel/camel-network-settings.h
@@ -0,0 +1,100 @@
+/*
+ * camel-network-settings.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_NETWORK_SETTINGS_H
+#define CAMEL_NETWORK_SETTINGS_H
+
+#include <glib-object.h>
+#include <camel/camel-enums.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_NETWORK_SETTINGS \
+ (camel_network_settings_get_type ())
+#define CAMEL_NETWORK_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_NETWORK_SETTINGS, CamelNetworkSettings))
+#define CAMEL_NETWORK_SETTINGS_INTERFACE(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_NETWORK_SETTINGS, CamelNetworkSettingsInterface))
+#define CAMEL_IS_NETWORK_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_NETWORK_SETTINGS))
+#define CAMEL_IS_NETWORK_SETTINGS_INTERFACE(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_NETWORK_SETTINGS))
+#define CAMEL_NETWORK_SETTINGS_GET_INTERFACE(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE \
+ ((obj), CAMEL_TYPE_NETWORK_SETTINGS, CamelNetworkSettingsInterface))
+
+G_BEGIN_DECLS
+
+/**
+ * CamelNetworkSettings:
+ *
+ * Since: 3.2
+ **/
+typedef struct _CamelNetworkSettings CamelNetworkSettings;
+typedef struct _CamelNetworkSettingsInterface CamelNetworkSettingsInterface;
+
+struct _CamelNetworkSettingsInterface {
+ GTypeInterface parent_interface;
+};
+
+GType camel_network_settings_get_type
+ (void) G_GNUC_CONST;
+const gchar * camel_network_settings_get_auth_mechanism
+ (CamelNetworkSettings *settings);
+gchar * camel_network_settings_dup_auth_mechanism
+ (CamelNetworkSettings *settings);
+void camel_network_settings_set_auth_mechanism
+ (CamelNetworkSettings *settings,
+ const gchar *auth_mechanism);
+const gchar * camel_network_settings_get_host
+ (CamelNetworkSettings *settings);
+gchar * camel_network_settings_dup_host
+ (CamelNetworkSettings *settings);
+gchar * camel_network_settings_dup_host_ensure_ascii
+ (CamelNetworkSettings *settings);
+void camel_network_settings_set_host
+ (CamelNetworkSettings *settings,
+ const gchar *host);
+guint16 camel_network_settings_get_port
+ (CamelNetworkSettings *settings);
+void camel_network_settings_set_port
+ (CamelNetworkSettings *settings,
+ guint16 port);
+CamelNetworkSecurityMethod
+ camel_network_settings_get_security_method
+ (CamelNetworkSettings *settings);
+void camel_network_settings_set_security_method
+ (CamelNetworkSettings *settings,
+ CamelNetworkSecurityMethod method);
+const gchar * camel_network_settings_get_user
+ (CamelNetworkSettings *settings);
+gchar * camel_network_settings_dup_user
+ (CamelNetworkSettings *settings);
+void camel_network_settings_set_user
+ (CamelNetworkSettings *settings,
+ const gchar *user);
+
+G_END_DECLS
+
+#endif /* CAMEL_NETWORK_SETTINGS_H */
diff --git a/src/camel/camel-nntp-address.c b/src/camel/camel-nntp-address.c
new file mode 100644
index 000000000..3e16a29cb
--- /dev/null
+++ b/src/camel/camel-nntp-address.c
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-mime-utils.h"
+#include "camel-nntp-address.h"
+
+#define d(x)
+
+struct _address {
+ gchar *name;
+ gchar *address;
+};
+
+G_DEFINE_TYPE (CamelNNTPAddress, camel_nntp_address, CAMEL_TYPE_ADDRESS)
+
+/* since newsgropus are 7bit ascii, decode/unformat are the same */
+static gint
+nntp_address_decode (CamelAddress *address,
+ const gchar *raw)
+{
+ GSList *ha, *n;
+ gint count = address->addresses->len;
+
+ ha = camel_header_newsgroups_decode (raw);
+ for (n = ha; n != NULL; n = n->next) {
+ camel_nntp_address_add (CAMEL_NNTP_ADDRESS (address), n->data);
+ }
+
+ g_slist_free_full (ha, g_free);
+ return address->addresses->len - count;
+}
+
+/* since newsgropus are 7bit ascii, encode/format are the same */
+static gchar *
+nntp_address_encode (CamelAddress *address)
+{
+ gint i;
+ GString *out;
+ gchar *ret;
+
+ if (address->addresses->len == 0)
+ return NULL;
+
+ out = g_string_new ("");
+
+ for (i = 0; i < address->addresses->len; i++) {
+ if (i != 0)
+ g_string_append (out, ", ");
+
+ g_string_append (out, g_ptr_array_index (address->addresses, i));
+ }
+
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ return ret;
+}
+
+static gint
+nntp_address_cat (CamelAddress *dest,
+ CamelAddress *source)
+{
+ gint ii;
+
+ g_return_val_if_fail (CAMEL_IS_NNTP_ADDRESS (source), -1);
+
+ for (ii = 0; ii < source->addresses->len; ii++)
+ camel_nntp_address_add (
+ CAMEL_NNTP_ADDRESS (dest),
+ g_ptr_array_index (source->addresses, ii));
+
+ return ii;
+}
+
+static void
+nntp_address_remove (CamelAddress *address,
+ gint index)
+{
+ if (index < 0 || index >= address->addresses->len)
+ return;
+
+ g_free (g_ptr_array_index (address->addresses, index));
+ g_ptr_array_remove_index (address->addresses, index);
+}
+
+static void
+camel_nntp_address_class_init (CamelNNTPAddressClass *class)
+{
+ CamelAddressClass *address_class;
+
+ address_class = CAMEL_ADDRESS_CLASS (class);
+ address_class->decode = nntp_address_decode;
+ address_class->encode = nntp_address_encode;
+ address_class->unformat = nntp_address_decode;
+ address_class->format = nntp_address_encode;
+ address_class->remove = nntp_address_remove;
+ address_class->cat = nntp_address_cat;
+}
+
+static void
+camel_nntp_address_init (CamelNNTPAddress *nntp_address)
+{
+}
+
+/**
+ * camel_nntp_address_new:
+ *
+ * Create a new CamelNNTPAddress object.
+ *
+ * Returns: A new CamelNNTPAddress object.
+ **/
+CamelNNTPAddress *
+camel_nntp_address_new (void)
+{
+ return g_object_new (CAMEL_TYPE_NNTP_ADDRESS, NULL);
+}
+
+/**
+ * camel_nntp_address_add:
+ * @a: nntp address object
+ * @name:
+ *
+ * Add a new nntp address to the address object. Duplicates are not added twice.
+ *
+ * Returns: Index of added entry, or existing matching entry.
+ **/
+gint
+camel_nntp_address_add (CamelNNTPAddress *a,
+ const gchar *name)
+{
+ gint index, i;
+
+ g_return_val_if_fail (CAMEL_IS_NNTP_ADDRESS (a), -1);
+
+ index = ((CamelAddress *) a)->addresses->len;
+ for (i = 0; i < index; i++)
+ if (!strcmp (g_ptr_array_index (((CamelAddress *) a)->addresses, i), name))
+ return i;
+
+ g_ptr_array_add (((CamelAddress *) a)->addresses, g_strdup (name));
+
+ return index;
+}
+
+/**
+ * camel_nntp_address_get:
+ * @a: nntp address object
+ * @index: address's array index
+ * @namep: Holder for the returned address, or NULL, if not required.
+ *
+ * Get the address at @index.
+ *
+ * Returns: TRUE if such an address exists, or FALSE otherwise.
+ **/
+gboolean
+camel_nntp_address_get (CamelNNTPAddress *a,
+ gint index,
+ const gchar **namep)
+{
+ g_return_val_if_fail (CAMEL_IS_NNTP_ADDRESS (a), FALSE);
+
+ if (index < 0 || index >= ((CamelAddress *) a)->addresses->len)
+ return FALSE;
+
+ if (namep)
+ *namep = g_ptr_array_index( ((CamelAddress *)a)->addresses, index);
+
+ return TRUE;
+}
diff --git a/src/camel/camel-nntp-address.h b/src/camel/camel-nntp-address.h
new file mode 100644
index 000000000..096fcf2f7
--- /dev/null
+++ b/src/camel/camel-nntp-address.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_NNTP_ADDRESS_H
+#define CAMEL_NNTP_ADDRESS_H
+
+#include <camel/camel-address.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_NNTP_ADDRESS \
+ (camel_nntp_address_get_type ())
+#define CAMEL_NNTP_ADDRESS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_NNTP_ADDRESS, CamelNNTPAddress))
+#define CAMEL_NNTP_ADDRESS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_NNTP_ADDRESS, CamelNNTPAddressClass))
+#define CAMEL_IS_NNTP_ADDRESS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_NNTP_ADDRESS))
+#define CAMEL_IS_NNTP_ADDRESS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_NNTP_ADDRESS))
+#define CAMEL_NNTP_ADDRESS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_NTTP_ADDRESS, CamelNNTPAddressClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelNNTPAddress CamelNNTPAddress;
+typedef struct _CamelNNTPAddressClass CamelNNTPAddressClass;
+typedef struct _CamelNNTPAddressPrivate CamelNNTPAddressPrivate;
+
+struct _CamelNNTPAddress {
+ CamelAddress parent;
+ CamelNNTPAddressPrivate *priv;
+};
+
+struct _CamelNNTPAddressClass {
+ CamelAddressClass parent_class;
+};
+
+GType camel_nntp_address_get_type (void);
+CamelNNTPAddress *
+ camel_nntp_address_new (void);
+gint camel_nntp_address_add (CamelNNTPAddress *a,
+ const gchar *name);
+gboolean camel_nntp_address_get (CamelNNTPAddress *a,
+ gint index,
+ const gchar **namep);
+
+G_END_DECLS
+
+#endif /* CAMEL_NNTP_ADDRESS_H */
diff --git a/src/camel/camel-null-output-stream.c b/src/camel/camel-null-output-stream.c
new file mode 100644
index 000000000..aff36a310
--- /dev/null
+++ b/src/camel/camel-null-output-stream.c
@@ -0,0 +1,111 @@
+/*
+ * camel-null-output-stream.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * SECTION: camel-null-output-stream
+ * @short_description: Null output stream
+ * @include: camel/camel.h
+ * @see_also: #GOutputStream
+ *
+ * #CamelNullOutputStream is analogous to writing to /dev/null, except it
+ * counts the total number of bytes written to it. This is primarily useful
+ * for determining the final size of some outgoing data, especially if using
+ * filters on the output stream.
+ **/
+
+#include "camel-null-output-stream.h"
+
+#define CAMEL_NULL_OUTPUT_STREAM_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_NULL_OUTPUT_STREAM, CamelNullOutputStreamPrivate))
+
+struct _CamelNullOutputStreamPrivate {
+ gsize bytes_written;
+};
+
+G_DEFINE_TYPE (
+ CamelNullOutputStream,
+ camel_null_output_stream,
+ G_TYPE_OUTPUT_STREAM)
+
+static gssize
+null_output_stream_write (GOutputStream *stream,
+ gconstpointer buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNullOutputStreamPrivate *priv;
+
+ priv = CAMEL_NULL_OUTPUT_STREAM_GET_PRIVATE (stream);
+
+ priv->bytes_written += count;
+
+ return count;
+}
+
+static void
+camel_null_output_stream_class_init (CamelNullOutputStreamClass *class)
+{
+ GOutputStreamClass *stream_class;
+
+ g_type_class_add_private (
+ class, sizeof (CamelNullOutputStreamPrivate));
+
+ stream_class = G_OUTPUT_STREAM_CLASS (class);
+ stream_class->write_fn = null_output_stream_write;
+}
+
+static void
+camel_null_output_stream_init (CamelNullOutputStream *null_stream)
+{
+ null_stream->priv = CAMEL_NULL_OUTPUT_STREAM_GET_PRIVATE (null_stream);
+}
+
+/**
+ * camel_null_output_stream_new:
+ *
+ * Creates a new "null" output stream.
+ *
+ * Returns: a new #GOutputStream
+ *
+ * Since: 3.12
+ **/
+GOutputStream *
+camel_null_output_stream_new (void)
+{
+ return g_object_new (CAMEL_TYPE_NULL_OUTPUT_STREAM, NULL);
+}
+
+/**
+ * camel_null_output_stream_get_bytes_written:
+ * @null_stream: a #CamelNullOutputStream
+ *
+ * Gets the total number of bytes written to @null_stream.
+ *
+ * Returns: total byte count
+ *
+ * Since: 3.12
+ **/
+gsize
+camel_null_output_stream_get_bytes_written (CamelNullOutputStream *null_stream)
+{
+ g_return_val_if_fail (CAMEL_IS_NULL_OUTPUT_STREAM (null_stream), 0);
+
+ return null_stream->priv->bytes_written;
+}
+
diff --git a/src/camel/camel-null-output-stream.h b/src/camel/camel-null-output-stream.h
new file mode 100644
index 000000000..f113ae4f7
--- /dev/null
+++ b/src/camel/camel-null-output-stream.h
@@ -0,0 +1,71 @@
+/*
+ * camel-null-output-stream.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_NULL_OUTPUT_STREAM_H
+#define CAMEL_NULL_OUTPUT_STREAM_H
+
+#include <gio/gio.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_NULL_OUTPUT_STREAM \
+ (camel_null_output_stream_get_type ())
+#define CAMEL_NULL_OUTPUT_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_NULL_OUTPUT_STREAM, CamelNullOutputStream))
+#define CAMEL_NULL_OUTPUT_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_NULL_OUTPUT_STREAM, CamelNullOutputStreamClass))
+#define CAMEL_IS_NULL_OUTPUT_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_NULL_OUTPUT_STREAM))
+#define CAMEL_IS_NULL_OUTPUT_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_NULL_OUTPUT_STREAM))
+#define CAMEL_NULL_OUTPUT_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_NULL_OUTPUT_STREAM, CamelNullOutputStreamClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelNullOutputStream CamelNullOutputStream;
+typedef struct _CamelNullOutputStreamClass CamelNullOutputStreamClass;
+typedef struct _CamelNullOutputStreamPrivate CamelNullOutputStreamPrivate;
+
+struct _CamelNullOutputStream {
+ GOutputStream parent;
+ CamelNullOutputStreamPrivate *priv;
+};
+
+struct _CamelNullOutputStreamClass {
+ GOutputStreamClass parent_class;
+};
+
+GType camel_null_output_stream_get_type
+ (void) G_GNUC_CONST;
+GOutputStream * camel_null_output_stream_new
+ (void);
+gsize camel_null_output_stream_get_bytes_written
+ (CamelNullOutputStream *null_stream);
+
+G_END_DECLS
+
+#endif /* CAMEL_NULL_OUTPUT_STREAM_H */
+
diff --git a/src/camel/camel-object-bag.c b/src/camel/camel-object-bag.c
new file mode 100644
index 000000000..79c3cecbd
--- /dev/null
+++ b/src/camel/camel-object-bag.c
@@ -0,0 +1,612 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-object-bag.h"
+
+#include <glib-object.h>
+
+typedef struct _KeyReservation KeyReservation;
+
+struct _KeyReservation {
+ gpointer key;
+ gint waiters;
+ GThread *owner;
+ GCond cond;
+};
+
+struct _CamelObjectBag {
+ GHashTable *key_table;
+ GHashTable *object_table;
+ GEqualFunc key_equal_func;
+ CamelCopyFunc key_copy_func;
+ GFreeFunc key_free_func;
+ GList *reserved; /* list of KeyReservations */
+ GMutex mutex;
+};
+
+static KeyReservation *
+key_reservation_new (CamelObjectBag *bag,
+ gconstpointer key)
+{
+ KeyReservation *reservation;
+
+ reservation = g_slice_new0 (KeyReservation);
+ reservation->key = bag->key_copy_func (key);
+ reservation->owner = g_thread_self ();
+ g_cond_init (&reservation->cond);
+
+ bag->reserved = g_list_prepend (bag->reserved, reservation);
+
+ return reservation;
+}
+
+static KeyReservation *
+key_reservation_lookup (CamelObjectBag *bag,
+ gconstpointer key)
+{
+ GList *iter;
+
+ /* XXX Might be easier to use a GHashTable for reservations. */
+ for (iter = bag->reserved; iter != NULL; iter = iter->next) {
+ KeyReservation *reservation = iter->data;
+ if (bag->key_equal_func (reservation->key, key))
+ return reservation;
+ }
+
+ return NULL;
+}
+
+static void
+key_reservation_free (CamelObjectBag *bag,
+ KeyReservation *reservation)
+{
+ /* Make sure the reservation is actually in the object bag. */
+ g_return_if_fail (key_reservation_lookup (bag, reservation->key) != NULL);
+
+ bag->reserved = g_list_remove (bag->reserved, reservation);
+
+ bag->key_free_func (reservation->key);
+ g_cond_clear (&reservation->cond);
+ g_slice_free (KeyReservation, reservation);
+}
+
+static void
+object_bag_unreserve (CamelObjectBag *bag,
+ gconstpointer key)
+{
+ KeyReservation *reservation;
+
+ reservation = key_reservation_lookup (bag, key);
+ g_return_if_fail (reservation != NULL);
+ g_return_if_fail (reservation->owner == g_thread_self ());
+
+ if (reservation->waiters > 0) {
+ reservation->owner = NULL;
+ g_cond_signal (&reservation->cond);
+ } else
+ key_reservation_free (bag, reservation);
+}
+
+/* Ick. We need to store the original gobject pointer too, since that's
+ * used as the key in one of the hash tables. So to clean up after the
+ * object dies and the GWeakRef starts returning NULL, we'll need to
+ * know where it *was*. This is safe because we'll always check and
+ * avoid adding a duplicate. But still, ick. */
+typedef struct {
+ GWeakRef ref;
+ gpointer obj;
+ CamelObjectBag *bag;
+} ObjRef;
+
+static void
+object_bag_notify (CamelObjectBag *bag,
+ GObject *where_the_object_was)
+{
+ gconstpointer key;
+
+ g_mutex_lock (&bag->mutex);
+
+ key = g_hash_table_lookup (bag->key_table, where_the_object_was);
+ if (key != NULL) {
+ g_hash_table_remove (bag->key_table, where_the_object_was);
+ g_hash_table_remove (bag->object_table, key);
+ }
+
+ g_mutex_unlock (&bag->mutex);
+}
+
+/* Properly destroy an ObjRef as it's freed from the hash table */
+static void
+wref_free_func (gpointer p)
+{
+ ObjRef *ref = p;
+ GObject *obj = g_weak_ref_get (&ref->ref);
+
+ if (obj) {
+ /* The object is being removed from the bag while it's
+ * still alive, e.g. by camel_object_bag_remove()
+ * or camel_object_bag_destroy(). Drop the weak_ref. */
+ g_object_weak_unref (
+ obj, (GWeakNotify) object_bag_notify,
+ ref->bag);
+ g_object_unref (obj);
+ }
+ g_weak_ref_clear (&ref->ref);
+ g_slice_free (ObjRef, ref);
+}
+
+/**
+ * camel_object_bag_new:
+ * @key_hash_func: (scope call): a hashing function for keys
+ * @key_equal_func: (scope call): a comparison function for keys
+ * @key_copy_func: (scope call): a function to copy keys
+ * @key_free_func: (scope call): a function to free keys
+ *
+ * Returns a new object bag. Object bags are keyed hash tables of objects
+ * that can be updated atomically using transaction semantics. Use
+ * camel_object_bag_destroy() to free the object bag.
+ *
+ * Returns: a newly-allocated #CamelObjectBag
+ **/
+CamelObjectBag *
+camel_object_bag_new (GHashFunc key_hash_func,
+ GEqualFunc key_equal_func,
+ CamelCopyFunc key_copy_func,
+ GFreeFunc key_free_func)
+{
+ CamelObjectBag *bag;
+ GHashTable *key_table;
+ GHashTable *object_table;
+
+ g_return_val_if_fail (key_hash_func != NULL, NULL);
+ g_return_val_if_fail (key_equal_func != NULL, NULL);
+ g_return_val_if_fail (key_copy_func != NULL, NULL);
+ g_return_val_if_fail (key_free_func != NULL, NULL);
+
+ /* Each key is shared between both hash tables, so only one
+ * table needs to be responsible for destroying keys. */
+
+ key_table = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ object_table = g_hash_table_new_full (
+ key_hash_func, key_equal_func,
+ (GDestroyNotify) key_free_func,
+ (GDestroyNotify) wref_free_func);
+
+ bag = g_slice_new0 (CamelObjectBag);
+ bag->key_table = key_table;
+ bag->object_table = object_table;
+ bag->key_equal_func = key_equal_func;
+ bag->key_copy_func = key_copy_func;
+ bag->key_free_func = key_free_func;
+ g_mutex_init (&bag->mutex);
+
+ return bag;
+}
+
+/**
+ * camel_object_bag_get:
+ * @bag: a #CamelObjectBag
+ * @key: a key
+ *
+ * Lookup an object by @key. If the key is currently reserved, the function
+ * will block until another thread commits or aborts the reservation. The
+ * caller owns the reference to the returned object. Use g_object_unref ()
+ * to unreference it.
+ *
+ * Returns: (transfer full) (nullable): the object corresponding to @key, or
+ * %NULL if not found
+ **/
+gpointer
+camel_object_bag_get (CamelObjectBag *bag,
+ gconstpointer key)
+{
+ KeyReservation *reservation;
+ ObjRef *ref;
+ gpointer object = NULL;
+
+ g_return_val_if_fail (bag != NULL, NULL);
+ g_return_val_if_fail (key != NULL, NULL);
+
+ g_mutex_lock (&bag->mutex);
+
+ /* Look for the key in the bag. */
+ ref = g_hash_table_lookup (bag->object_table, key);
+ if (ref != NULL) {
+ object = g_weak_ref_get (&ref->ref);
+ if (object != NULL) {
+ g_mutex_unlock (&bag->mutex);
+ return object;
+ }
+
+ /* Remove stale reference to dead object. */
+ g_hash_table_remove (bag->key_table, ref->obj);
+ g_hash_table_remove (bag->object_table, key);
+ }
+
+ /* Check if the key has been reserved. */
+ reservation = key_reservation_lookup (bag, key);
+ if (reservation == NULL) {
+ /* No such key, so return NULL. */
+ g_mutex_unlock (&bag->mutex);
+ return NULL;
+ }
+
+ /* Wait for the key to be unreserved. */
+ reservation->waiters++;
+ while (reservation->owner != NULL)
+ g_cond_wait (&reservation->cond, &bag->mutex);
+ reservation->waiters--;
+
+ /* Check if an object was added by another thread. */
+ ref = g_hash_table_lookup (bag->object_table, key);
+ if (ref != NULL) {
+ object = g_weak_ref_get (&ref->ref);
+ if (object == NULL) {
+ /* Remove stale reference to dead object. */
+ g_hash_table_remove (bag->key_table, ref->obj);
+ g_hash_table_remove (bag->object_table, key);
+ }
+ }
+
+ /* We're not reserving it. */
+ reservation->owner = g_thread_self ();
+ object_bag_unreserve (bag, key);
+
+ g_mutex_unlock (&bag->mutex);
+
+ return object;
+}
+
+/**
+ * camel_object_bag_peek:
+ * @bag: a #CamelObjectBag
+ * @key: an unreserved key
+ *
+ * Returns the object for @key in @bag, ignoring any reservations. If it
+ * isn't committed, then it isn't considered. This should only be used
+ * where reliable transactional-based state is not required.
+ *
+ * Unlink other "peek" operations, the caller owns the returned object
+ * reference. Use g_object_unref () to unreference it.
+ *
+ * Returns: (transfer full) (nullable): the object for @key, or %NULL if @key
+ * is reserved or not found
+ **/
+gpointer
+camel_object_bag_peek (CamelObjectBag *bag,
+ gconstpointer key)
+{
+ gpointer object = NULL;
+ ObjRef *ref;
+
+ g_return_val_if_fail (bag != NULL, NULL);
+ g_return_val_if_fail (key != NULL, NULL);
+
+ g_mutex_lock (&bag->mutex);
+
+ ref = g_hash_table_lookup (bag->object_table, key);
+ if (ref != NULL)
+ object = g_weak_ref_get (&ref->ref);
+
+ g_mutex_unlock (&bag->mutex);
+
+ return object;
+}
+
+/**
+ * camel_object_bag_reserve:
+ * @bag: a #CamelObjectBag
+ * @key: the key to reserve
+ *
+ * Reserves @key in @bag. If @key is already reserved in another thread,
+ * then wait until the reservation has been committed.
+ *
+ * After reserving @key, you either get a reference to the object
+ * corresponding to @key (similar to camel_object_bag_get()) or you get
+ * %NULL, signifying that you MUST call either camel_object_bag_add() or
+ * camel_object_bag_abort().
+ *
+ * Returns: (transfer full) (nullable): the object for @key, or %NULL if @key
+ * is not found
+ **/
+gpointer
+camel_object_bag_reserve (CamelObjectBag *bag,
+ gconstpointer key)
+{
+ KeyReservation *reservation;
+ ObjRef *ref;
+ gpointer object = NULL;
+
+ g_return_val_if_fail (bag != NULL, NULL);
+ g_return_val_if_fail (key != NULL, NULL);
+
+ g_mutex_lock (&bag->mutex);
+
+ /* If object for key already exists, return it immediately. */
+ ref = g_hash_table_lookup (bag->object_table, key);
+ if (ref != NULL) {
+ object = g_weak_ref_get (&ref->ref);
+ if (object != NULL) {
+ g_mutex_unlock (&bag->mutex);
+ return object;
+ }
+
+ /* Remove stale reference to dead object. */
+ g_hash_table_remove (bag->key_table, ref->obj);
+ g_hash_table_remove (bag->object_table, key);
+ }
+
+ /* If no such key exists in the bag, create a reservation. */
+ reservation = key_reservation_lookup (bag, key);
+ if (reservation == NULL) {
+ key_reservation_new (bag, key);
+ g_mutex_unlock (&bag->mutex);
+ return NULL;
+ }
+
+ /* Wait for the reservation to be committed or aborted. */
+ reservation->waiters++;
+ while (reservation->owner != NULL)
+ g_cond_wait (&reservation->cond, &bag->mutex);
+ reservation->owner = g_thread_self ();
+ reservation->waiters--;
+
+ /* Check if the object was added by another thread. */
+ ref = g_hash_table_lookup (bag->object_table, key);
+ if (ref != NULL) {
+ object = g_weak_ref_get (&ref->ref);
+ if (object != NULL) {
+ /* We have an object; no need to reserve the key. */
+ object_bag_unreserve (bag, key);
+ } else {
+ /* Remove stale reference to dead object. */
+ g_hash_table_remove (bag->key_table, ref->obj);
+ g_hash_table_remove (bag->object_table, key);
+ }
+ }
+
+ g_mutex_unlock (&bag->mutex);
+
+ return object;
+}
+
+static gboolean
+object_in_bag (CamelObjectBag *bag,
+ gpointer object)
+{
+ gconstpointer key;
+ ObjRef *ref;
+ GObject *obj2;
+
+ key = g_hash_table_lookup (bag->key_table, object);
+ if (key == NULL)
+ return FALSE;
+
+ ref = g_hash_table_lookup (bag->object_table, key);
+ if (ref == NULL)
+ return FALSE;
+
+ obj2 = g_weak_ref_get (&ref->ref);
+ if (obj2 == NULL) {
+ /* Remove stale reference to dead object. */
+ g_hash_table_remove (bag->key_table, object);
+ g_hash_table_remove (bag->object_table, key);
+ } else {
+ g_object_unref (obj2);
+ }
+
+ return obj2 == object;
+}
+
+/**
+ * camel_object_bag_add:
+ * @bag: a #CamelObjectBag
+ * @key: a reserved key
+ * @object: a #GObject
+ *
+ * Adds @object to @bag. The @key MUST have been previously reserved using
+ * camel_object_bag_reserve().
+ **/
+void
+camel_object_bag_add (CamelObjectBag *bag,
+ gconstpointer key,
+ gpointer object)
+{
+ g_return_if_fail (bag != NULL);
+ g_return_if_fail (key != NULL);
+ g_return_if_fail (G_IS_OBJECT (object));
+
+ g_mutex_lock (&bag->mutex);
+
+ /* Check it's the *same* object, not an old one at the same address */
+ if (!object_in_bag (bag, object)) {
+ ObjRef *ref;
+ gpointer copied_key;
+
+ ref = g_slice_new (ObjRef);
+ ref->bag = bag;
+ /* We need to stash a 'raw' pointer since that's the key we use
+ * in the key_table */
+ ref->obj = object;
+ g_weak_ref_init (&ref->ref, object);
+ copied_key = bag->key_copy_func (key);
+ g_hash_table_insert (bag->key_table, object, copied_key);
+ g_hash_table_insert (bag->object_table, copied_key, ref);
+ object_bag_unreserve (bag, key);
+
+ g_object_weak_ref (
+ G_OBJECT (object),
+ (GWeakNotify) object_bag_notify, bag);
+ }
+
+ g_mutex_unlock (&bag->mutex);
+}
+
+/**
+ * camel_object_bag_abort:
+ * @bag: a #CamelObjectBag
+ * @key: a reserved key
+ *
+ * Aborts a key reservation.
+ **/
+void
+camel_object_bag_abort (CamelObjectBag *bag,
+ gconstpointer key)
+{
+ g_return_if_fail (bag != NULL);
+ g_return_if_fail (key != NULL);
+
+ g_mutex_lock (&bag->mutex);
+
+ object_bag_unreserve (bag, key);
+
+ g_mutex_unlock (&bag->mutex);
+}
+
+/**
+ * camel_object_bag_rekey:
+ * @bag: a #CamelObjectBag
+ * @object: a #GObject
+ * @new_key: a new key for @object
+ *
+ * Changes the key for @object to @new_key, atomically.
+ *
+ * It is considered a programming error if @object is not found in @bag.
+ * In such case the function will emit a terminal warning and return.
+ **/
+void
+camel_object_bag_rekey (CamelObjectBag *bag,
+ gpointer object,
+ gconstpointer new_key)
+{
+ gpointer key;
+ ObjRef *ref;
+
+ g_return_if_fail (bag != NULL);
+ g_return_if_fail (G_IS_OBJECT (object));
+ g_return_if_fail (new_key != NULL);
+
+ g_mutex_lock (&bag->mutex);
+
+ key = g_hash_table_lookup (bag->key_table, object);
+ if (key != NULL) {
+ /* Remove the old key. */
+ ref = g_hash_table_lookup (bag->object_table, key);
+ g_hash_table_steal (bag->object_table, key);
+ g_hash_table_remove (bag->key_table, object);
+
+ /* Insert the new key. */
+ key = bag->key_copy_func (new_key);
+ g_hash_table_insert (bag->object_table, key, ref);
+ g_hash_table_insert (bag->key_table, object, key);
+ } else
+ g_warn_if_reached ();
+
+ g_mutex_unlock (&bag->mutex);
+}
+
+/**
+ * camel_object_bag_list:
+ * @bag: a #CamelObjectBag
+ *
+ * Returns a #GPtrArray of all the objects in the bag. The caller owns
+ * both the array and the object references, so to free the array use:
+ *
+ * <informalexample>
+ * <programlisting>
+ * g_ptr_array_foreach (array, g_object_unref, NULL);
+ * g_ptr_array_free (array, TRUE);
+ * </programlisting>
+ * </informalexample>
+ *
+ * Returns: (element-type GObject) (transfer full): an array of objects in @bag
+ **/
+GPtrArray *
+camel_object_bag_list (CamelObjectBag *bag)
+{
+ GPtrArray *array;
+ GList *values;
+
+ g_return_val_if_fail (bag != NULL, NULL);
+
+ /* XXX Too bad we're not returning a GList; this would be trivial. */
+
+ array = g_ptr_array_new ();
+
+ g_mutex_lock (&bag->mutex);
+
+ values = g_hash_table_get_values (bag->object_table);
+ while (values != NULL) {
+ ObjRef *ref = values->data;
+ GObject *obj = g_weak_ref_get (&ref->ref);
+ if (obj)
+ g_ptr_array_add (array, obj);
+ values = g_list_delete_link (values, values);
+ }
+
+ g_mutex_unlock (&bag->mutex);
+
+ return array;
+}
+
+/**
+ * camel_object_bag_remove:
+ * @bag: a #CamelObjectBag
+ * @object: a #GObject
+ *
+ * Removes @object from @bag.
+ **/
+void
+camel_object_bag_remove (CamelObjectBag *bag,
+ gpointer object)
+{
+ gpointer key;
+
+ g_return_if_fail (bag != NULL);
+ g_return_if_fail (G_IS_OBJECT (object));
+
+ g_mutex_lock (&bag->mutex);
+
+ key = g_hash_table_lookup (bag->key_table, object);
+ if (key != NULL) {
+ g_hash_table_remove (bag->key_table, object);
+ g_hash_table_remove (bag->object_table, key);
+ }
+
+ g_mutex_unlock (&bag->mutex);
+}
+
+/**
+ * camel_object_bag_destroy:
+ * @bag: a #CamelObjectBag
+ *
+ * Frees @bag. As a precaution, the function will emit a warning to standard
+ * error and return without freeing @bag if @bag still has reserved keys.
+ **/
+void
+camel_object_bag_destroy (CamelObjectBag *bag)
+{
+ g_return_if_fail (bag != NULL);
+ g_return_if_fail (bag->reserved == NULL);
+
+ g_hash_table_destroy (bag->key_table);
+ g_hash_table_destroy (bag->object_table);
+ g_mutex_clear (&bag->mutex);
+ g_slice_free (CamelObjectBag, bag);
+}
diff --git a/src/camel/camel-object-bag.h b/src/camel/camel-object-bag.h
new file mode 100644
index 000000000..8d73d1b61
--- /dev/null
+++ b/src/camel/camel-object-bag.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* Manages bags of weakly-referenced GObjects. */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_OBJECT_BAG_H
+#define CAMEL_OBJECT_BAG_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _CamelObjectBag CamelObjectBag;
+typedef gpointer (*CamelCopyFunc) (gconstpointer object);
+
+CamelObjectBag *camel_object_bag_new (GHashFunc key_hash_func,
+ GEqualFunc key_equal_func,
+ CamelCopyFunc key_copy_func,
+ GFreeFunc key_free_func);
+gpointer camel_object_bag_get (CamelObjectBag *bag,
+ gconstpointer key);
+gpointer camel_object_bag_peek (CamelObjectBag *bag,
+ gconstpointer key);
+gpointer camel_object_bag_reserve (CamelObjectBag *bag,
+ gconstpointer key);
+void camel_object_bag_add (CamelObjectBag *bag,
+ gconstpointer key,
+ gpointer object);
+void camel_object_bag_abort (CamelObjectBag *bag,
+ gconstpointer key);
+void camel_object_bag_rekey (CamelObjectBag *bag,
+ gpointer object,
+ gconstpointer new_key);
+GPtrArray * camel_object_bag_list (CamelObjectBag *bag);
+void camel_object_bag_remove (CamelObjectBag *bag,
+ gpointer object);
+void camel_object_bag_destroy (CamelObjectBag *bag);
+
+G_END_DECLS
+
+#endif /* CAMEL_OBJECT_BAG_H */
diff --git a/src/camel/camel-object.c b/src/camel/camel-object.c
new file mode 100644
index 000000000..ea7a0b88f
--- /dev/null
+++ b/src/camel/camel-object.c
@@ -0,0 +1,543 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include "camel-enums.h"
+#include "camel-enumtypes.h"
+#include "camel-file-utils.h"
+#include "camel-object.h"
+
+#define d(x)
+
+#define CAMEL_OBJECT_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_OBJECT, CamelObjectPrivate))
+
+struct _CamelObjectPrivate {
+ gchar *state_filename;
+};
+
+enum {
+ PROP_0,
+ PROP_STATE_FILENAME
+};
+
+G_DEFINE_ABSTRACT_TYPE (CamelObject, camel_object, G_TYPE_OBJECT)
+
+/* State file for CamelObject data.
+ * Any later versions should only append data.
+ *
+ * version:uint32
+ *
+ * Version 0 of the file:
+ *
+ * version:uint32 = 0
+ * count:uint32 -- count of meta-data items
+ * ( name:string value:string ) *count -- meta-data items
+ *
+ * Version 1 of the file adds:
+ * count:uint32 -- count of persistent properties
+ * ( tag:uing32 value:tagtype ) *count -- persistent properties
+ */
+
+#define CAMEL_OBJECT_STATE_FILE_MAGIC "CLMD"
+
+/* XXX This is a holdover from Camel's old homegrown type system.
+ * CamelArg was a kind of primitive version of GObject properties.
+ * The argument ID and data type were encoded into a 32-bit integer.
+ * Unfortunately the encoding was also used in the binary state file
+ * format, so we still need the secret decoder ring. */
+enum camel_arg_t {
+ CAMEL_ARG_END = 0,
+ CAMEL_ARG_IGNORE = 1, /* override/ignore an arg in-place */
+
+ CAMEL_ARG_FIRST = 1024, /* 1024 args reserved for arg system */
+
+ CAMEL_ARG_TYPE = 0xf0000000, /* type field for tags */
+ CAMEL_ARG_TAG = 0x0fffffff, /* tag field for args */
+
+ CAMEL_ARG_OBJ = 0x00000000, /* object */
+ CAMEL_ARG_INT = 0x10000000, /* gint */
+ CAMEL_ARG_DBL = 0x20000000, /* gdouble */
+ CAMEL_ARG_STR = 0x30000000, /* c string */
+ CAMEL_ARG_PTR = 0x40000000, /* ptr */
+ CAMEL_ARG_BOO = 0x50000000, /* bool */
+ CAMEL_ARG_3ST = 0x60000000 /* three-state */
+};
+
+#define CAMEL_ARGV_MAX (20)
+
+static void
+object_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_STATE_FILENAME:
+ camel_object_set_state_filename (
+ CAMEL_OBJECT (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+object_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_STATE_FILENAME:
+ g_value_set_string (
+ value, camel_object_get_state_filename (
+ CAMEL_OBJECT (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+object_finalize (GObject *object)
+{
+ CamelObjectPrivate *priv;
+
+ priv = CAMEL_OBJECT_GET_PRIVATE (object);
+
+ g_free (priv->state_filename);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_object_parent_class)->finalize (object);
+}
+
+static void
+object_notify (GObject *object,
+ GParamSpec *pspec)
+{
+ /* Placeholder so subclasses can safely chain up, since
+ * GObjectClass itself does not implement this method. */
+}
+
+static gint
+object_state_read (CamelObject *object,
+ FILE *fp)
+{
+ GValue value = G_VALUE_INIT;
+ GObjectClass *class;
+ GParamSpec **properties;
+ guint32 count, version;
+ guint ii, jj, n_properties;
+
+ if (camel_file_util_decode_uint32 (fp, &version) == -1)
+ return -1;
+
+ if (version > 2)
+ return -1;
+
+ if (camel_file_util_decode_uint32 (fp, &count) == -1)
+ return -1;
+
+ /* XXX Camel no longer supports meta-data in state
+ * files, so we're just eating dead data here. */
+ for (ii = 0; ii < count; ii++) {
+ gchar *name = NULL;
+ gchar *value = NULL;
+ gboolean success;
+
+ success =
+ camel_file_util_decode_string (fp, &name) == 0 &&
+ camel_file_util_decode_string (fp, &value) == 0;
+
+ g_free (name);
+ g_free (value);
+
+ if (!success)
+ return -1;
+ }
+
+ if (version == 0)
+ return 0;
+
+ if (camel_file_util_decode_uint32 (fp, &count) == -1)
+ return 0;
+
+ if (count == 0 || count > 1024)
+ /* Maybe it was just version 0 afterall. */
+ return 0;
+
+ count = MIN (count, CAMEL_ARGV_MAX);
+
+ class = G_OBJECT_GET_CLASS (object);
+ properties = g_object_class_list_properties (class, &n_properties);
+
+ for (ii = 0; ii < count; ii++) {
+ gboolean property_set = FALSE;
+ guint32 tag, v_uint32;
+
+ if (camel_file_util_decode_uint32 (fp, &tag) == -1)
+ goto exit;
+
+ /* Record state file values into GValues.
+ * XXX We currently only support booleans and three-state. */
+ switch (tag & CAMEL_ARG_TYPE) {
+ case CAMEL_ARG_BOO:
+ if (camel_file_util_decode_uint32 (fp, &v_uint32) == -1)
+ goto exit;
+ g_value_init (&value, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&value, (gboolean) v_uint32);
+ break;
+ case CAMEL_ARG_3ST:
+ if (camel_file_util_decode_uint32 (fp, &v_uint32) == -1)
+ goto exit;
+ g_value_init (&value, CAMEL_TYPE_THREE_STATE);
+ g_value_set_enum (&value, (CamelThreeState) v_uint32);
+ break;
+ default:
+ g_warn_if_reached ();
+ goto exit;
+ }
+
+ /* Now we have to match the legacy numeric CamelArg tag
+ * value with a GObject property. The GObject property
+ * IDs have been set to the same legacy tag values, but
+ * we have to access a private GParamSpec field to get
+ * to them (pspec->param_id). */
+
+ tag &= CAMEL_ARG_TAG; /* filter out the type code */
+
+ for (jj = 0; jj < n_properties; jj++) {
+ GParamSpec *pspec = properties[jj];
+
+ if (pspec->param_id != tag)
+ continue;
+
+ /* Sanity check. */
+ g_warn_if_fail (pspec->flags & CAMEL_PARAM_PERSISTENT);
+ if ((pspec->flags & CAMEL_PARAM_PERSISTENT) == 0)
+ continue;
+
+ if (version == 1 && pspec->value_type == CAMEL_TYPE_THREE_STATE &&
+ G_VALUE_HOLDS_BOOLEAN (&value)) {
+ /* Convert from boolean to three-state value. Assign the 'TRUE' to 'On'
+ and the rest keep as 'Inconsistent'. */
+ gboolean stored = g_value_get_boolean (&value);
+
+ g_value_unset (&value);
+ g_value_init (&value, CAMEL_TYPE_THREE_STATE);
+ g_value_set_enum (&value, stored ? CAMEL_THREE_STATE_ON : CAMEL_THREE_STATE_INCONSISTENT);
+ }
+
+ g_object_set_property (
+ G_OBJECT (object), pspec->name, &value);
+
+ property_set = TRUE;
+ break;
+ }
+
+ /* XXX This tag was used by the old IMAP backend.
+ * It may still show up in accounts that were
+ * migrated from IMAP to IMAPX. Silence the
+ * warning. */
+ if (tag == 0x2500)
+ property_set = TRUE;
+
+ if (!property_set)
+ g_warning (
+ "Could not find a corresponding %s "
+ "property for state file tag 0x%x",
+ G_OBJECT_TYPE_NAME (object), tag);
+
+ g_value_unset (&value);
+ }
+
+exit:
+ g_free (properties);
+
+ return 0;
+}
+
+static gint
+object_state_write (CamelObject *object,
+ FILE *fp)
+{
+ GValue value = G_VALUE_INIT;
+ GObjectClass *class;
+ GParamSpec **properties;
+ guint ii, n_properties;
+ guint32 n_persistent = 0;
+
+ class = G_OBJECT_GET_CLASS (object);
+ properties = g_object_class_list_properties (class, &n_properties);
+
+ /* Version = 2 */
+ if (camel_file_util_encode_uint32 (fp, 2) == -1)
+ goto exit;
+
+ /* No meta-data items. */
+ if (camel_file_util_encode_uint32 (fp, 0) == -1)
+ goto exit;
+
+ /* Count persistent properties. */
+ for (ii = 0; ii < n_properties; ii++)
+ if (properties[ii]->flags & CAMEL_PARAM_PERSISTENT)
+ n_persistent++;
+
+ if (camel_file_util_encode_uint32 (fp, n_persistent) == -1)
+ goto exit;
+
+ /* Write a tag + value pair for each persistent property.
+ * Tags identify the property ID and data type; they're an
+ * artifact of CamelArgs. The persistent GObject property
+ * IDs are set to match the legacy CamelArg tag values. */
+
+ for (ii = 0; ii < n_properties; ii++) {
+ GParamSpec *pspec = properties[ii];
+ guint32 tag, v_uint32;
+
+ if ((pspec->flags & CAMEL_PARAM_PERSISTENT) == 0)
+ continue;
+
+ g_value_init (&value, pspec->value_type);
+
+ g_object_get_property (
+ G_OBJECT (object), pspec->name, &value);
+
+ tag = pspec->param_id;
+
+ /* Record the GValue to the state file.
+ * XXX We currently only support booleans. */
+ switch (pspec->value_type) {
+ case G_TYPE_BOOLEAN:
+ tag |= CAMEL_ARG_BOO;
+ v_uint32 = g_value_get_boolean (&value);
+ if (camel_file_util_encode_uint32 (fp, tag) == -1)
+ goto exit;
+ if (camel_file_util_encode_uint32 (fp, v_uint32) == -1)
+ goto exit;
+ break;
+ default:
+ if (pspec->value_type == CAMEL_TYPE_THREE_STATE) {
+ tag |= CAMEL_ARG_3ST;
+ v_uint32 = g_value_get_enum (&value);
+ if (camel_file_util_encode_uint32 (fp, tag) == -1)
+ goto exit;
+ if (camel_file_util_encode_uint32 (fp, v_uint32) == -1)
+ goto exit;
+ } else {
+ g_warn_if_reached ();
+ goto exit;
+ }
+ break;
+ }
+
+ g_value_unset (&value);
+ }
+
+exit:
+ g_free (properties);
+
+ return 0;
+}
+
+static void
+camel_object_class_init (CamelObjectClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelObjectPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = object_set_property;
+ object_class->get_property = object_get_property;
+ object_class->finalize = object_finalize;
+ object_class->notify = object_notify;
+
+ class->state_read = object_state_read;
+ class->state_write = object_state_write;
+
+ /**
+ * CamelObject:state-filename
+ *
+ * The file in which to store persistent property values for this
+ * instance.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_STATE_FILENAME,
+ g_param_spec_string (
+ "state-filename",
+ "State Filename",
+ "File containing persistent property values",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+camel_object_init (CamelObject *object)
+{
+ object->priv = CAMEL_OBJECT_GET_PRIVATE (object);
+}
+
+G_DEFINE_QUARK (camel-error-quark, camel_error)
+
+/**
+ * camel_object_state_read:
+ * @object: a #CamelObject
+ *
+ * Read persistent object state from #CamelObject:state-filename.
+ *
+ * Returns: -1 on error.
+ **/
+gint
+camel_object_state_read (CamelObject *object)
+{
+ CamelObjectClass *class;
+ const gchar *state_filename;
+ gint res = -1;
+ FILE *fp;
+ gchar magic[4];
+
+ g_return_val_if_fail (CAMEL_IS_OBJECT (object), -1);
+
+ class = CAMEL_OBJECT_GET_CLASS (object);
+
+ state_filename = camel_object_get_state_filename (object);
+ if (state_filename == NULL)
+ return 0;
+
+ fp = g_fopen (state_filename, "rb");
+ if (fp != NULL) {
+ if (fread (magic, 4, 1, fp) == 1
+ && memcmp (magic, CAMEL_OBJECT_STATE_FILE_MAGIC, 4) == 0)
+ res = class->state_read (object, fp);
+ fclose (fp);
+ }
+
+ return res;
+}
+
+/**
+ * camel_object_state_write:
+ * @object: a #CamelObject
+ *
+ * Write persistent object state #CamelObject:state-filename.
+ *
+ * Returns: -1 on error.
+ **/
+gint
+camel_object_state_write (CamelObject *object)
+{
+ CamelObjectClass *class;
+ const gchar *state_filename;
+ gchar *savename, *dirname;
+ gint res = -1;
+ FILE *fp;
+
+ g_return_val_if_fail (CAMEL_IS_OBJECT (object), -1);
+
+ class = CAMEL_OBJECT_GET_CLASS (object);
+
+ state_filename = camel_object_get_state_filename (object);
+ if (state_filename == NULL)
+ return 0;
+
+ savename = camel_file_util_savename (state_filename);
+
+ dirname = g_path_get_dirname (savename);
+ g_mkdir_with_parents (dirname, 0700);
+ g_free (dirname);
+
+ fp = g_fopen (savename, "wb");
+ if (fp != NULL) {
+ if (fwrite (CAMEL_OBJECT_STATE_FILE_MAGIC, 4, 1, fp) == 1
+ && class->state_write (object, fp) == 0) {
+ if (fclose (fp) == 0) {
+ res = 0;
+ if (g_rename (savename, state_filename) == -1)
+ res = -1;
+ }
+ } else {
+ fclose (fp);
+ }
+ } else {
+ g_warning ("Could not save object state file to '%s': %s", savename, g_strerror (errno));
+ }
+
+ g_free (savename);
+
+ return res;
+}
+
+/**
+ * camel_object_get_state_filename:
+ * @object: a #CamelObject
+ *
+ * Returns the name of the file in which persistent property values for
+ * @object are stored. The file is used by camel_object_state_write()
+ * and camel_object_state_read() to save and restore object state.
+ *
+ * Returns: the name of the persistent property file
+ *
+ * Since: 2.32
+ **/
+const gchar *
+camel_object_get_state_filename (CamelObject *object)
+{
+ g_return_val_if_fail (CAMEL_IS_OBJECT (object), NULL);
+
+ return object->priv->state_filename;
+}
+
+/**
+ * camel_object_set_state_filename:
+ * @object: a #CamelObject
+ * @state_filename: path to a local file
+ *
+ * Sets the name of the file in which persistent property values for
+ * @object are stored. The file is used by camel_object_state_write()
+ * and camel_object_state_read() to save and restore object state.
+ *
+ * Since: 2.32
+ **/
+void
+camel_object_set_state_filename (CamelObject *object,
+ const gchar *state_filename)
+{
+ g_return_if_fail (CAMEL_IS_OBJECT (object));
+
+ if (g_strcmp0 (object->priv->state_filename, state_filename) == 0)
+ return;
+
+ g_free (object->priv->state_filename);
+ object->priv->state_filename = g_strdup (state_filename);
+
+ g_object_notify (G_OBJECT (object), "state-filename");
+}
diff --git a/src/camel/camel-object.h b/src/camel/camel-object.h
new file mode 100644
index 000000000..1d96f82a3
--- /dev/null
+++ b/src/camel/camel-object.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-object.h: Base class for Camel
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_OBJECT_H
+#define CAMEL_OBJECT_H
+
+#include <stdio.h> /* FILE */
+#include <stdlib.h> /* gsize */
+#include <stdarg.h>
+#include <gio/gio.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_OBJECT \
+ (camel_object_get_type ())
+#define CAMEL_OBJECT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_OBJECT, CamelObject))
+#define CAMEL_OBJECT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_OBJECT, CamelObjectClass))
+#define CAMEL_IS_OBJECT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_OBJECT))
+#define CAMEL_IS_OBJECT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_OBJECT))
+#define CAMEL_OBJECT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_OBJECT, CamelObjectClass))
+
+/**
+ * CAMEL_ERROR:
+ *
+ * Since: 2.32
+ **/
+#define CAMEL_ERROR \
+ (camel_error_quark ())
+
+G_BEGIN_DECLS
+
+typedef struct _CamelObject CamelObject;
+typedef struct _CamelObjectClass CamelObjectClass;
+typedef struct _CamelObjectPrivate CamelObjectPrivate;
+
+/**
+ * CamelParamFlags:
+ * @CAMEL_PARAM_PERSISTENT:
+ * The parameter is persistent, which means its value is saved to
+ * #CamelObject:state-filename during camel_object_state_write(),
+ * and restored during camel_object_state_read().
+ *
+ * These flags extend #GParamFlags. Most of the time you will use them
+ * in conjunction with g_object_class_install_property().
+ *
+ * Since: 2.32
+ **/
+typedef enum {
+ CAMEL_PARAM_PERSISTENT = 1 << (G_PARAM_USER_SHIFT + 0)
+} CamelParamFlags;
+
+/**
+ * CamelError:
+ *
+ * Since: 2.32
+ **/
+typedef enum {
+ CAMEL_ERROR_GENERIC /* lazy fallback error */
+} CamelError;
+
+struct _CamelObject {
+ GObject parent;
+ CamelObjectPrivate *priv;
+};
+
+struct _CamelObjectClass {
+ GObjectClass parent_class;
+
+ gint (*state_read) (CamelObject *object,
+ FILE *fp);
+ gint (*state_write) (CamelObject *object,
+ FILE *fp);
+};
+
+GType camel_object_get_type (void);
+GQuark camel_error_quark (void) G_GNUC_CONST;
+gint camel_object_state_read (CamelObject *object);
+gint camel_object_state_write (CamelObject *object);
+const gchar * camel_object_get_state_filename (CamelObject *object);
+void camel_object_set_state_filename (CamelObject *object,
+ const gchar *state_filename);
+
+G_END_DECLS
+
+#endif /* CAMEL_OBJECT_H */
diff --git a/src/camel/camel-offline-folder.c b/src/camel/camel-offline-folder.c
new file mode 100644
index 000000000..217515d19
--- /dev/null
+++ b/src/camel/camel-offline-folder.c
@@ -0,0 +1,660 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@novell.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-debug.h"
+#include "camel-enumtypes.h"
+#include "camel-offline-folder.h"
+#include "camel-offline-settings.h"
+#include "camel-offline-store.h"
+#include "camel-operation.h"
+#include "camel-session.h"
+
+#define CAMEL_OFFLINE_FOLDER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_OFFLINE_FOLDER, CamelOfflineFolderPrivate))
+
+typedef struct _AsyncContext AsyncContext;
+typedef struct _OfflineDownsyncData OfflineDownsyncData;
+
+struct _CamelOfflineFolderPrivate {
+ CamelThreeState offline_sync;
+
+ GMutex store_changes_lock;
+ guint store_changes_id;
+ gboolean store_changes_after_frozen;
+};
+
+struct _AsyncContext {
+ gchar *expression;
+};
+
+struct _OfflineDownsyncData {
+ CamelFolder *folder;
+ CamelFolderChangeInfo *changes;
+};
+
+/* The custom property ID is a CamelArg artifact.
+ * It still identifies the property in state files. */
+enum {
+ PROP_0,
+ PROP_OFFLINE_SYNC = 0x2400
+};
+
+G_DEFINE_TYPE (CamelOfflineFolder, camel_offline_folder, CAMEL_TYPE_FOLDER)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+ g_free (async_context->expression);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static void
+offline_downsync_data_free (OfflineDownsyncData *data)
+{
+ if (data->changes != NULL)
+ camel_folder_change_info_free (data->changes);
+
+ g_object_unref (data->folder);
+
+ g_slice_free (OfflineDownsyncData, data);
+}
+
+static void
+offline_folder_downsync_background (CamelSession *session,
+ GCancellable *cancellable,
+ OfflineDownsyncData *data,
+ GError **error)
+{
+ camel_operation_push_message (
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ cancellable, _("Downloading new messages for offline mode in '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (data->folder))),
+ camel_folder_get_full_name (data->folder));
+
+ if (data->changes) {
+ GPtrArray *uid_added;
+ gboolean success = TRUE;
+ gint ii;
+
+ uid_added = data->changes->uid_added;
+
+ for (ii = 0; success && ii < uid_added->len; ii++) {
+ const gchar *uid;
+ gint percent;
+
+ percent = ii * 100 / uid_added->len;
+ uid = g_ptr_array_index (uid_added, ii);
+
+ camel_operation_progress (cancellable, percent);
+
+ success = camel_folder_synchronize_message_sync (
+ data->folder, uid, cancellable, error);
+ }
+ } else {
+ camel_offline_folder_downsync_sync (
+ CAMEL_OFFLINE_FOLDER (data->folder),
+ "(match-all)", cancellable, error);
+ }
+
+ camel_operation_pop_message (cancellable);
+}
+
+static void
+offline_folder_store_changes_job_cb (CamelSession *session,
+ GCancellable *cancellable,
+ gpointer user_data,
+ GError **error)
+{
+ CamelFolder *folder = user_data;
+
+ g_return_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder));
+
+ camel_folder_synchronize_sync (folder, FALSE, cancellable, error);
+}
+
+static gboolean
+offline_folder_schedule_store_changes_job (gpointer user_data)
+{
+ CamelOfflineFolder *offline_folder = user_data;
+ GSource *source;
+
+ source = g_main_current_source ();
+
+ if (g_source_is_destroyed (source))
+ return FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_OFFLINE_FOLDER (offline_folder), FALSE);
+
+ g_mutex_lock (&offline_folder->priv->store_changes_lock);
+ if (offline_folder->priv->store_changes_id == g_source_get_id (source)) {
+ CamelSession *session;
+
+ offline_folder->priv->store_changes_id = 0;
+
+ session = camel_service_ref_session (CAMEL_SERVICE (camel_folder_get_parent_store (CAMEL_FOLDER (offline_folder))));
+ if (session) {
+ gchar *description;
+
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ description = g_strdup_printf (_("Storing changes in folder '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (CAMEL_FOLDER (offline_folder)))),
+ camel_folder_get_full_name (CAMEL_FOLDER (offline_folder)));
+
+ camel_session_submit_job (session, description,
+ offline_folder_store_changes_job_cb,
+ g_object_ref (offline_folder), g_object_unref);
+
+ g_free (description);
+ }
+
+ g_clear_object (&session);
+ }
+ g_mutex_unlock (&offline_folder->priv->store_changes_lock);
+
+ return FALSE;
+}
+
+static void
+offline_folder_maybe_schedule_folder_change_store (CamelOfflineFolder *offline_folder)
+{
+ CamelSession *session;
+ CamelStore *store;
+
+ g_return_if_fail (CAMEL_IS_OFFLINE_FOLDER (offline_folder));
+
+ g_mutex_lock (&offline_folder->priv->store_changes_lock);
+
+ if (offline_folder->priv->store_changes_id)
+ g_source_remove (offline_folder->priv->store_changes_id);
+ offline_folder->priv->store_changes_id = 0;
+ offline_folder->priv->store_changes_after_frozen = FALSE;
+
+ if (camel_folder_is_frozen (CAMEL_FOLDER (offline_folder))) {
+ offline_folder->priv->store_changes_after_frozen = TRUE;
+ g_mutex_unlock (&offline_folder->priv->store_changes_lock);
+
+ return;
+ }
+
+ store = camel_folder_get_parent_store (CAMEL_FOLDER (offline_folder));
+ session = camel_service_ref_session (CAMEL_SERVICE (store));
+
+ if (session && camel_session_get_online (session) && CAMEL_IS_OFFLINE_STORE (store) &&
+ camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store))) {
+ CamelSettings *settings;
+ gint interval = -1;
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (store));
+ if (settings && CAMEL_IS_OFFLINE_SETTINGS (settings))
+ interval = camel_offline_settings_get_store_changes_interval (CAMEL_OFFLINE_SETTINGS (settings));
+ g_clear_object (&settings);
+
+ if (interval == 0)
+ offline_folder_schedule_store_changes_job (offline_folder);
+ else if (interval > 0)
+ offline_folder->priv->store_changes_id = g_timeout_add_seconds (interval,
+ offline_folder_schedule_store_changes_job, offline_folder);
+ }
+
+ g_clear_object (&session);
+
+ g_mutex_unlock (&offline_folder->priv->store_changes_lock);
+}
+
+static void
+offline_folder_changed (CamelFolder *folder,
+ CamelFolderChangeInfo *changes)
+{
+ CamelStore *store;
+ CamelSession *session;
+
+ store = camel_folder_get_parent_store (folder);
+ session = camel_service_ref_session (CAMEL_SERVICE (store));
+
+ if (!session)
+ return;
+
+ if (changes && changes->uid_added->len > 0 && camel_offline_folder_can_downsync (CAMEL_OFFLINE_FOLDER (folder))) {
+ OfflineDownsyncData *data;
+ gchar *description;
+
+ data = g_slice_new0 (OfflineDownsyncData);
+ data->changes = camel_folder_change_info_new ();
+ camel_folder_change_info_cat (data->changes, changes);
+ data->folder = g_object_ref (folder);
+
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ description = g_strdup_printf (_("Checking download of new messages for offline in '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
+ camel_folder_get_full_name (folder));
+
+ camel_session_submit_job (
+ session, description, (CamelSessionCallback)
+ offline_folder_downsync_background, data,
+ (GDestroyNotify) offline_downsync_data_free);
+
+ g_free (description);
+ }
+
+ g_object_unref (session);
+
+ if (changes && changes->uid_changed && changes->uid_changed->len > 0)
+ offline_folder_maybe_schedule_folder_change_store (CAMEL_OFFLINE_FOLDER (folder));
+}
+
+static void
+offline_folder_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_OFFLINE_SYNC:
+ camel_offline_folder_set_offline_sync (
+ CAMEL_OFFLINE_FOLDER (object),
+ g_value_get_enum (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+offline_folder_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_OFFLINE_SYNC:
+ g_value_set_enum (
+ value, camel_offline_folder_get_offline_sync (
+ CAMEL_OFFLINE_FOLDER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+offline_folder_dispose (GObject *object)
+{
+ CamelOfflineFolder *offline_folder = CAMEL_OFFLINE_FOLDER (object);
+
+ g_mutex_lock (&offline_folder->priv->store_changes_lock);
+ if (offline_folder->priv->store_changes_id)
+ g_source_remove (offline_folder->priv->store_changes_id);
+ offline_folder->priv->store_changes_id = 0;
+ g_mutex_unlock (&offline_folder->priv->store_changes_lock);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_offline_folder_parent_class)->dispose (object);
+}
+
+static void
+offline_folder_finalize (GObject *object)
+{
+ CamelOfflineFolder *offline_folder = CAMEL_OFFLINE_FOLDER (object);
+
+ g_mutex_clear (&offline_folder->priv->store_changes_lock);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_offline_folder_parent_class)->finalize (object);
+}
+
+static void
+offline_folder_thaw (CamelFolder *folder)
+{
+ /* Chain up to parent's method. */
+ CAMEL_FOLDER_CLASS (camel_offline_folder_parent_class)->thaw (folder);
+
+ if (!camel_folder_is_frozen (folder)) {
+ CamelOfflineFolder *offline_folder;
+
+ g_return_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder));
+
+ offline_folder = CAMEL_OFFLINE_FOLDER (folder);
+
+ g_mutex_lock (&offline_folder->priv->store_changes_lock);
+ if (offline_folder->priv->store_changes_after_frozen) {
+ offline_folder->priv->store_changes_after_frozen = FALSE;
+ g_mutex_unlock (&offline_folder->priv->store_changes_lock);
+
+ offline_folder_maybe_schedule_folder_change_store (offline_folder);
+ } else {
+ g_mutex_unlock (&offline_folder->priv->store_changes_lock);
+ }
+ }
+}
+
+static gboolean
+offline_folder_downsync_sync (CamelOfflineFolder *offline,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder = (CamelFolder *) offline;
+ GPtrArray *uids, *uncached_uids = NULL;
+ gint i;
+
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ camel_operation_push_message (cancellable, _("Syncing messages in folder '%s : %s' to disk"),
+ camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
+ camel_folder_get_full_name (folder));
+
+ if (expression)
+ uids = camel_folder_search_by_expression (folder, expression, cancellable, NULL);
+ else
+ uids = camel_folder_get_uids (folder);
+
+ if (!uids)
+ goto done;
+ uncached_uids = camel_folder_get_uncached_uids (folder, uids, NULL);
+ if (uids) {
+ if (expression)
+ camel_folder_search_free (folder, uids);
+ else
+ camel_folder_free_uids (folder, uids);
+ }
+
+ if (!uncached_uids)
+ goto done;
+
+ for (i = 0; i < uncached_uids->len; i++) {
+ camel_folder_synchronize_message_sync (
+ folder, uncached_uids->pdata[i], cancellable, NULL);
+ camel_operation_progress (
+ cancellable, i * 100 / uncached_uids->len);
+ }
+
+done:
+ if (uncached_uids)
+ camel_folder_free_uids (folder, uncached_uids);
+
+ camel_operation_pop_message (cancellable);
+
+ return TRUE;
+}
+
+static void
+camel_offline_folder_class_init (CamelOfflineFolderClass *class)
+{
+ GObjectClass *object_class;
+ CamelFolderClass *folder_class;
+
+ g_type_class_add_private (class, sizeof (CamelOfflineFolderPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = offline_folder_set_property;
+ object_class->get_property = offline_folder_get_property;
+ object_class->dispose = offline_folder_dispose;
+ object_class->finalize = offline_folder_finalize;
+
+ folder_class = CAMEL_FOLDER_CLASS (class);
+ folder_class->thaw = offline_folder_thaw;
+
+ class->downsync_sync = offline_folder_downsync_sync;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_OFFLINE_SYNC,
+ g_param_spec_enum (
+ "offline-sync",
+ "Offline Sync",
+ _("Copy folder content locally for _offline operation"),
+ CAMEL_TYPE_THREE_STATE,
+ CAMEL_THREE_STATE_INCONSISTENT,
+ G_PARAM_READWRITE |
+ CAMEL_PARAM_PERSISTENT));
+}
+
+static void
+camel_offline_folder_init (CamelOfflineFolder *folder)
+{
+ folder->priv = CAMEL_OFFLINE_FOLDER_GET_PRIVATE (folder);
+
+ g_mutex_init (&folder->priv->store_changes_lock);
+ folder->priv->store_changes_after_frozen = FALSE;
+ folder->priv->offline_sync = CAMEL_THREE_STATE_INCONSISTENT;
+
+ g_signal_connect (
+ folder, "changed",
+ G_CALLBACK (offline_folder_changed), NULL);
+}
+
+/**
+ * camel_offline_folder_get_offline_sync:
+ * @folder: a #CamelOfflineFolder
+ *
+ * Since: 2.32
+ **/
+CamelThreeState
+camel_offline_folder_get_offline_sync (CamelOfflineFolder *folder)
+{
+ g_return_val_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder), CAMEL_THREE_STATE_INCONSISTENT);
+
+ return folder->priv->offline_sync;
+}
+
+/**
+ * camel_offline_folder_set_offline_sync:
+ * @folder: a #CamelOfflineFolder
+ * @offline_sync: whether to synchronize for offline use, as a #CamelThreeState enum
+ *
+ * The %CAMEL_THREE_STATE_INCONSISTENT means what the parent store has set.
+ *
+ * Since: 2.32
+ **/
+void
+camel_offline_folder_set_offline_sync (CamelOfflineFolder *folder,
+ CamelThreeState offline_sync)
+{
+ g_return_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder));
+
+ if (folder->priv->offline_sync == offline_sync)
+ return;
+
+ folder->priv->offline_sync = offline_sync;
+
+ g_object_notify (G_OBJECT (folder), "offline-sync");
+}
+
+/**
+ * camel_offline_folder_can_downsync:
+ * @folder: a #CamelOfflineFolder
+ *
+ * Checks whether the @folder can run downsync according to its
+ * settings (camel_offline_folder_get_offline_sync()) and to
+ * the parent's #CamelOfflineStore settings (camel_offline_settings_get_stay_synchronized()).
+ *
+ * Returns: %TRUE, when the @folder can be synchronized for offline; %FALSE otherwise.
+ *
+ * Since: 3.22
+ **/
+gboolean
+camel_offline_folder_can_downsync (CamelOfflineFolder *folder)
+{
+ CamelService *service;
+ CamelSettings *settings;
+ CamelThreeState sync_folder;
+ gboolean sync_store;
+
+ g_return_val_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder), FALSE);
+
+ service = CAMEL_SERVICE (camel_folder_get_parent_store (CAMEL_FOLDER (folder)));
+ settings = camel_service_ref_settings (service);
+
+ sync_store = camel_offline_settings_get_stay_synchronized (CAMEL_OFFLINE_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ sync_folder = camel_offline_folder_get_offline_sync (CAMEL_OFFLINE_FOLDER (folder));
+
+ return sync_folder == CAMEL_THREE_STATE_ON || (sync_store && sync_folder == CAMEL_THREE_STATE_INCONSISTENT);
+}
+
+/**
+ * camel_offline_folder_downsync_sync:
+ * @folder: a #CamelOfflineFolder
+ * @expression: search expression describing which set of messages
+ * to downsync (%NULL for all)
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronizes messages in @folder described by the search @expression to
+ * the local machine for offline availability.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_offline_folder_downsync_sync (CamelOfflineFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelOfflineFolderClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder), FALSE);
+
+ class = CAMEL_OFFLINE_FOLDER_GET_CLASS (folder);
+ g_return_val_if_fail (class->downsync_sync != NULL, FALSE);
+
+ success = class->downsync_sync (
+ folder, expression, cancellable, error);
+ CAMEL_CHECK_GERROR (folder, downsync_sync, success, error);
+
+ return success;
+}
+
+/* Helper for camel_offline_folder_downsync() */
+static void
+offline_folder_downsync_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_offline_folder_downsync_sync (
+ CAMEL_OFFLINE_FOLDER (source_object),
+ async_context->expression,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_offline_folder_downsync:
+ * @folder: a #CamelOfflineFolder
+ * @expression: search expression describing which set of messages
+ * to downsync (%NULL for all)
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULl
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Synchronizes messages in @folder described by the search @expression to
+ * the local machine asynchronously for offline availability.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_offline_folder_downsync_finish() to get the result of the
+ * operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_offline_folder_downsync (CamelOfflineFolder *folder,
+ const gchar *expression,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->expression = g_strdup (expression);
+
+ task = g_task_new (folder, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_offline_folder_downsync);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, offline_folder_downsync_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_offline_folder_downsync_finish:
+ * @folder: a #CamelOfflineFolder
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_offline_folder_downsync().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_offline_folder_downsync_finish (CamelOfflineFolder *folder,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, folder), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_offline_folder_downsync), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/src/camel/camel-offline-folder.h b/src/camel/camel-offline-folder.h
new file mode 100644
index 000000000..0182cb3cd
--- /dev/null
+++ b/src/camel/camel-offline-folder.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@novell.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_OFFLINE_FOLDER_H
+#define CAMEL_OFFLINE_FOLDER_H
+
+#include <camel/camel-enums.h>
+#include <camel/camel-folder.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_OFFLINE_FOLDER \
+ (camel_offline_folder_get_type ())
+#define CAMEL_OFFLINE_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_OFFLINE_FOLDER, CamelOfflineFolder))
+#define CAMEL_OFFLINE_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_OFFLINE_FOLDER, CamelOfflineFolderClass))
+#define CAMEL_IS_OFFLINE_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_OFFLINE_FOLDER))
+#define CAMEL_IS_OFFLINE_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_OFFLINE_FOLDER))
+#define CAMEL_OFFLINE_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_OFFLINE_FOLDER, CamelOfflineFolderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelOfflineFolder CamelOfflineFolder;
+typedef struct _CamelOfflineFolderClass CamelOfflineFolderClass;
+typedef struct _CamelOfflineFolderPrivate CamelOfflineFolderPrivate;
+
+struct _CamelOfflineFolder {
+ CamelFolder parent;
+ CamelOfflineFolderPrivate *priv;
+};
+
+struct _CamelOfflineFolderClass {
+ CamelFolderClass parent_class;
+
+ /* Synchronous I/O Methods */
+ gboolean (*downsync_sync) (CamelOfflineFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* Reserved slots. */
+ gpointer reserved[2];
+};
+
+GType camel_offline_folder_get_type (void);
+CamelThreeState camel_offline_folder_get_offline_sync
+ (CamelOfflineFolder *folder);
+void camel_offline_folder_set_offline_sync
+ (CamelOfflineFolder *folder,
+ CamelThreeState offline_sync);
+gboolean camel_offline_folder_can_downsync
+ (CamelOfflineFolder *folder);
+gboolean camel_offline_folder_downsync_sync
+ (CamelOfflineFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error);
+void camel_offline_folder_downsync (CamelOfflineFolder *folder,
+ const gchar *expression,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_offline_folder_downsync_finish
+ (CamelOfflineFolder *folder,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_OFFLINE_FOLDER_H */
diff --git a/src/camel/camel-offline-settings.c b/src/camel/camel-offline-settings.c
new file mode 100644
index 000000000..8b4aa6eae
--- /dev/null
+++ b/src/camel/camel-offline-settings.c
@@ -0,0 +1,222 @@
+/*
+ * camel-offline-settings.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-offline-settings.h"
+
+#include <camel/camel-store-settings.h>
+
+#define CAMEL_OFFLINE_SETTINGS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_OFFLINE_SETTINGS, CamelOfflineSettingsPrivate))
+
+struct _CamelOfflineSettingsPrivate {
+ gboolean stay_synchronized;
+ gint store_changes_interval;
+};
+
+enum {
+ PROP_0,
+ PROP_STAY_SYNCHRONIZED,
+ PROP_STORE_CHANGES_INTERVAL
+};
+
+G_DEFINE_TYPE (
+ CamelOfflineSettings,
+ camel_offline_settings,
+ CAMEL_TYPE_STORE_SETTINGS)
+
+static void
+offline_settings_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_STAY_SYNCHRONIZED:
+ camel_offline_settings_set_stay_synchronized (
+ CAMEL_OFFLINE_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_STORE_CHANGES_INTERVAL:
+ camel_offline_settings_set_store_changes_interval (
+ CAMEL_OFFLINE_SETTINGS (object),
+ g_value_get_int (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+offline_settings_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_STAY_SYNCHRONIZED:
+ g_value_set_boolean (
+ value,
+ camel_offline_settings_get_stay_synchronized (
+ CAMEL_OFFLINE_SETTINGS (object)));
+ return;
+
+ case PROP_STORE_CHANGES_INTERVAL:
+ g_value_set_int (
+ value,
+ camel_offline_settings_get_store_changes_interval (
+ CAMEL_OFFLINE_SETTINGS (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+camel_offline_settings_class_init (CamelOfflineSettingsClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelOfflineSettingsPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = offline_settings_set_property;
+ object_class->get_property = offline_settings_get_property;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_STAY_SYNCHRONIZED,
+ g_param_spec_boolean (
+ "stay-synchronized",
+ "Stay Synchronized",
+ "Stay synchronized with the remote server",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_STORE_CHANGES_INTERVAL,
+ g_param_spec_int (
+ "store-changes-interval",
+ "Store Changes Interval",
+ "Interval, in seconds, to store folder changes",
+ G_MININT,
+ G_MAXINT,
+ 3,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_offline_settings_init (CamelOfflineSettings *settings)
+{
+ settings->priv = CAMEL_OFFLINE_SETTINGS_GET_PRIVATE (settings);
+}
+
+/**
+ * camel_offline_settings_get_stay_synchronized:
+ * @settings: a #CamelOfflineSettings
+ *
+ * Returns whether to synchronize the local cache with the remote server
+ * before switching to offline mode, so the store's content can still be
+ * read while offline.
+ *
+ * Returns: whether to stay synchronized with the remote server
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_offline_settings_get_stay_synchronized (CamelOfflineSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_OFFLINE_SETTINGS (settings), FALSE);
+
+ return settings->priv->stay_synchronized;
+}
+
+/**
+ * camel_offline_settings_set_stay_synchronized:
+ * @settings: a #CamelOfflineSettings
+ * @stay_synchronized: whether to stay synchronized with the remote server
+ *
+ * Sets whether to synchronize the local cache with the remote server before
+ * switching to offline mode, so the store's content can still be read while
+ * offline.
+ *
+ * Since: 3.2
+ **/
+void
+camel_offline_settings_set_stay_synchronized (CamelOfflineSettings *settings,
+ gboolean stay_synchronized)
+{
+ g_return_if_fail (CAMEL_IS_OFFLINE_SETTINGS (settings));
+
+ if (settings->priv->stay_synchronized == stay_synchronized)
+ return;
+
+ settings->priv->stay_synchronized = stay_synchronized;
+
+ g_object_notify (G_OBJECT (settings), "stay-synchronized");
+}
+/**
+ * camel_offline_settings_get_store_changes_interval:
+ * @settings: a #CamelOfflineSettings
+ *
+ * Returns the interval, in seconds, for the changes in the folder being
+ * saved automatically. 0 means immediately, while -1 means turning off
+ * automatic folder change saving.
+ *
+ * Returns: the interval for automatic store of folder changes
+ *
+ * Since: 3.18
+ **/
+
+gint
+camel_offline_settings_get_store_changes_interval (CamelOfflineSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_OFFLINE_SETTINGS (settings), -1);
+
+ return settings->priv->store_changes_interval;
+}
+
+/**
+ * camel_offline_settings_set_store_changes_interval:
+ * @settings: a #CamelOfflineSettings
+ * @interval: the interval, in seconds
+ *
+ * Sets the interval, in seconds, for the changes in the folder being
+ * saved automatically. 0 means immediately, while -1 means turning off
+ * automatic folder change saving.
+ *
+ * Since: 3.18
+ **/
+void
+camel_offline_settings_set_store_changes_interval (CamelOfflineSettings *settings,
+ gint interval)
+{
+ g_return_if_fail (CAMEL_IS_OFFLINE_SETTINGS (settings));
+
+ if (settings->priv->store_changes_interval == interval)
+ return;
+
+ settings->priv->store_changes_interval = interval;
+
+ g_object_notify (G_OBJECT (settings), "store-changes-interval");
+}
diff --git a/src/camel/camel-offline-settings.h b/src/camel/camel-offline-settings.h
new file mode 100644
index 000000000..b336a62cc
--- /dev/null
+++ b/src/camel/camel-offline-settings.h
@@ -0,0 +1,84 @@
+/*
+ * camel-offline-settings.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_OFFLINE_SETTINGS_H
+#define CAMEL_OFFLINE_SETTINGS_H
+
+#include <camel/camel-store-settings.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_OFFLINE_SETTINGS \
+ (camel_offline_settings_get_type ())
+#define CAMEL_OFFLINE_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_OFFLINE_SETTINGS, CamelOfflineSettings))
+#define CAMEL_OFFLINE_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_OFFLINE_SETTINGS, CamelOfflineSettingsClass))
+#define CAMEL_IS_OFFLINE_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_OFFLINE_SETTINGS))
+#define CAMEL_IS_OFFLINE_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_OFFLINE_SETTINGS))
+#define CAMEL_OFFLINE_SETTINGS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_OFFLINE_SETTINGS, CamelOfflineSettingsClass))
+
+G_BEGIN_DECLS
+
+/**
+ * CamelOfflineSettings:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.2
+ **/
+typedef struct _CamelOfflineSettings CamelOfflineSettings;
+typedef struct _CamelOfflineSettingsClass CamelOfflineSettingsClass;
+typedef struct _CamelOfflineSettingsPrivate CamelOfflineSettingsPrivate;
+
+struct _CamelOfflineSettings {
+ CamelStoreSettings parent;
+ CamelOfflineSettingsPrivate *priv;
+};
+
+struct _CamelOfflineSettingsClass {
+ CamelStoreSettingsClass parent_class;
+};
+
+GType camel_offline_settings_get_type
+ (void) G_GNUC_CONST;
+gboolean camel_offline_settings_get_stay_synchronized
+ (CamelOfflineSettings *settings);
+void camel_offline_settings_set_stay_synchronized
+ (CamelOfflineSettings *settings,
+ gboolean stay_synchronized);
+gint camel_offline_settings_get_store_changes_interval
+ (CamelOfflineSettings *settings);
+void camel_offline_settings_set_store_changes_interval
+ (CamelOfflineSettings *settings,
+ gint interval);
+
+G_END_DECLS
+
+#endif /* CAMEL_OFFLINE_SETTINGS_H */
diff --git a/src/camel/camel-offline-store.c b/src/camel/camel-offline-store.c
new file mode 100644
index 000000000..b49b78640
--- /dev/null
+++ b/src/camel/camel-offline-store.c
@@ -0,0 +1,379 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@novell.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-folder.h"
+#include "camel-network-service.h"
+#include "camel-offline-folder.h"
+#include "camel-offline-settings.h"
+#include "camel-offline-store.h"
+#include "camel-session.h"
+
+#define CAMEL_OFFLINE_STORE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_OFFLINE_STORE, CamelOfflineStorePrivate))
+
+struct _CamelOfflineStorePrivate {
+ /* XXX The online flag stores whether the user has selected online or
+ * offline mode, but fetching the flag through the "get" function
+ * also takes into account CamelNetworkService's "host-reachable"
+ * property. So it's possible to set the "online" state to TRUE,
+ * but then immediately read back FALSE. Kinda weird, but mainly
+ * for temporary backward-compability. */
+ gboolean online;
+};
+
+enum {
+ PROP_0,
+ PROP_ONLINE
+};
+
+G_DEFINE_TYPE (CamelOfflineStore, camel_offline_store, CAMEL_TYPE_STORE)
+
+static void
+offline_store_constructed (GObject *object)
+{
+ CamelOfflineStorePrivate *priv;
+ CamelSession *session;
+
+ priv = CAMEL_OFFLINE_STORE_GET_PRIVATE (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (camel_offline_store_parent_class)->constructed (object);
+
+ session = camel_service_ref_session (CAMEL_SERVICE (object));
+ priv->online = session && camel_session_get_online (session);
+ g_clear_object (&session);
+}
+
+static void
+offline_store_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ONLINE:
+ g_value_set_boolean (
+ value, camel_offline_store_get_online (
+ CAMEL_OFFLINE_STORE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+offline_store_notify (GObject *object,
+ GParamSpec *pspec)
+{
+ if (g_strcmp0 (pspec->name, "host-reachable") == 0)
+ g_object_notify (object, "online");
+
+ /* Chain up to parent's notify() method. */
+ G_OBJECT_CLASS (camel_offline_store_parent_class)->
+ notify (object, pspec);
+}
+
+static void
+camel_offline_store_class_init (CamelOfflineStoreClass *class)
+{
+ GObjectClass *object_class;
+ CamelServiceClass *service_class;
+
+ g_type_class_add_private (class, sizeof (CamelOfflineStorePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->constructed = offline_store_constructed;
+ object_class->get_property = offline_store_get_property;
+ object_class->notify = offline_store_notify;
+
+ service_class = CAMEL_SERVICE_CLASS (class);
+ service_class->settings_type = CAMEL_TYPE_OFFLINE_SETTINGS;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ONLINE,
+ g_param_spec_boolean (
+ "online",
+ "Online",
+ "Whether the store is online",
+ FALSE,
+ G_PARAM_READABLE));
+}
+
+static void
+camel_offline_store_init (CamelOfflineStore *store)
+{
+ store->priv = CAMEL_OFFLINE_STORE_GET_PRIVATE (store);
+}
+
+/**
+ * camel_offline_store_get_online:
+ * @store: a #CamelOfflineStore
+ *
+ * Returns %TRUE if @store is online.
+ *
+ * Since: 2.24
+ **/
+gboolean
+camel_offline_store_get_online (CamelOfflineStore *store)
+{
+ g_return_val_if_fail (CAMEL_IS_OFFLINE_STORE (store), 0);
+
+ if (CAMEL_IS_NETWORK_SERVICE (store)) {
+ CamelNetworkService *service;
+
+ service = CAMEL_NETWORK_SERVICE (store);
+
+ /* Always return FALSE if the remote host is not reachable. */
+ if (!camel_network_service_get_host_reachable (service))
+ return FALSE;
+ }
+
+ return store->priv->online;
+}
+
+/**
+ * camel_offline_store_set_online_sync:
+ * @store: a #CamelOfflineStore
+ * @online: %TRUE for online, %FALSE for offline
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Sets the online/offline state of @store according to @online.
+ **/
+gboolean
+camel_offline_store_set_online_sync (CamelOfflineStore *store,
+ gboolean online,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelService *service;
+ CamelServiceConnectionStatus status;
+ gboolean host_reachable = TRUE;
+ gboolean store_is_online;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_OFFLINE_STORE (store), FALSE);
+
+ if (camel_offline_store_get_online (store) == online)
+ return TRUE;
+
+ service = CAMEL_SERVICE (store);
+ status = camel_service_get_connection_status (service);
+
+ if (CAMEL_IS_NETWORK_SERVICE (store)) {
+ /* When going to set the 'online' state, then check with up-to-date
+ value, otherwise use the cached value. The cached value is
+ updated with few seconds timeout, thus it can be stale here. */
+ if (online)
+ host_reachable =
+ camel_network_service_can_reach_sync (
+ CAMEL_NETWORK_SERVICE (store),
+ cancellable, NULL);
+ else
+ host_reachable =
+ camel_network_service_get_host_reachable (
+ CAMEL_NETWORK_SERVICE (store));
+ }
+
+ store_is_online = camel_offline_store_get_online (store);
+
+ /* Returning to online mode is the simpler case. */
+ if (!store_is_online) {
+ store->priv->online = online;
+
+ g_object_notify (G_OBJECT (store), "online");
+
+ if (status == CAMEL_SERVICE_CONNECTING)
+ return TRUE;
+
+ return camel_service_connect_sync (service, cancellable, error);
+ }
+
+ if (host_reachable) {
+ CamelSession *session;
+
+ session = camel_service_ref_session (service);
+ host_reachable = session && camel_session_get_online (session);
+ g_clear_object (&session);
+ }
+
+ if (host_reachable) {
+ GPtrArray *folders;
+ guint ii;
+
+ folders = camel_object_bag_list (
+ CAMEL_STORE (store)->folders);
+
+ for (ii = 0; ii < folders->len; ii++) {
+ CamelFolder *folder = folders->pdata[ii];
+ CamelOfflineFolder *offline_folder;
+
+ if (!CAMEL_IS_OFFLINE_FOLDER (folder))
+ continue;
+
+ offline_folder = CAMEL_OFFLINE_FOLDER (folder);
+
+ if (camel_offline_folder_can_downsync (offline_folder))
+ camel_offline_folder_downsync_sync (offline_folder, NULL, cancellable, NULL);
+ }
+
+ g_ptr_array_foreach (folders, (GFunc) g_object_unref, NULL);
+ g_ptr_array_free (folders, TRUE);
+
+ camel_store_synchronize_sync (
+ CAMEL_STORE (store), FALSE, cancellable, NULL);
+ }
+
+ if (status != CAMEL_SERVICE_DISCONNECTING) {
+ success = camel_service_disconnect_sync (
+ service, host_reachable, cancellable, error);
+ }
+
+ store->priv->online = online;
+
+ g_object_notify (G_OBJECT (store), "online");
+
+ return success;
+}
+
+/**
+ * camel_offline_store_prepare_for_offline_sync:
+ *
+ * Since: 2.22
+ **/
+gboolean
+camel_offline_store_prepare_for_offline_sync (CamelOfflineStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean host_reachable = TRUE;
+ gboolean store_is_online;
+
+ g_return_val_if_fail (CAMEL_IS_OFFLINE_STORE (store), FALSE);
+
+ store_is_online = camel_offline_store_get_online (store);
+
+ if (store_is_online && CAMEL_IS_NETWORK_SERVICE (store)) {
+ /* Check with up-to-date value. The cached value is updated with
+ few seconds timeout, thus it can be stale here. */
+ host_reachable =
+ camel_network_service_can_reach_sync (
+ CAMEL_NETWORK_SERVICE (store),
+ cancellable, NULL);
+ }
+
+ if (host_reachable && store_is_online) {
+ GPtrArray *folders;
+ guint ii;
+
+ folders = camel_object_bag_list (
+ CAMEL_STORE (store)->folders);
+
+ for (ii = 0; ii < folders->len; ii++) {
+ CamelFolder *folder = folders->pdata[ii];
+ CamelOfflineFolder *offline_folder;
+
+ if (!CAMEL_IS_OFFLINE_FOLDER (folder))
+ continue;
+
+ offline_folder = CAMEL_OFFLINE_FOLDER (folder);
+
+ if (camel_offline_folder_can_downsync (offline_folder))
+ camel_offline_folder_downsync_sync (offline_folder, NULL, cancellable, NULL);
+ }
+
+ g_ptr_array_foreach (folders, (GFunc) g_object_unref, NULL);
+ g_ptr_array_free (folders, TRUE);
+ }
+
+ if (host_reachable)
+ camel_store_synchronize_sync (
+ CAMEL_STORE (store), FALSE, cancellable, NULL);
+
+ return TRUE;
+}
+
+/**
+ * camel_offline_store_requires_downsync:
+ * @store: a #CamelOfflineStore
+ *
+ * Check whether the @store requires synchronization for offline usage.
+ * This is not blocking, it only checks settings on the store and its
+ * currently opened folders.
+ *
+ * Returns %TRUE if the @store requires synchronization for offline usage
+ *
+ * Since: 3.12
+ **/
+gboolean
+camel_offline_store_requires_downsync (CamelOfflineStore *store)
+{
+ gboolean host_reachable = TRUE;
+ gboolean store_is_online;
+ gboolean sync_any_folder = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_OFFLINE_STORE (store), FALSE);
+
+ if (CAMEL_IS_NETWORK_SERVICE (store)) {
+ host_reachable =
+ camel_network_service_get_host_reachable (
+ CAMEL_NETWORK_SERVICE (store));
+ }
+
+ store_is_online = camel_offline_store_get_online (store);
+
+ if (!store_is_online)
+ return FALSE;
+
+ if (host_reachable) {
+ CamelSession *session;
+
+ session = camel_service_ref_session (CAMEL_SERVICE (store));
+ host_reachable = session && camel_session_get_online (session);
+ g_clear_object (&session);
+ }
+
+ if (host_reachable) {
+ GPtrArray *folders;
+ guint ii;
+
+ folders = camel_object_bag_list (
+ CAMEL_STORE (store)->folders);
+
+ for (ii = 0; ii < folders->len && !sync_any_folder; ii++) {
+ CamelFolder *folder = folders->pdata[ii];
+
+ if (!CAMEL_IS_OFFLINE_FOLDER (folder))
+ continue;
+
+ sync_any_folder = camel_offline_folder_can_downsync (CAMEL_OFFLINE_FOLDER (folder));
+ }
+
+ g_ptr_array_foreach (folders, (GFunc) g_object_unref, NULL);
+ g_ptr_array_free (folders, TRUE);
+ }
+
+ return sync_any_folder && host_reachable;
+}
diff --git a/src/camel/camel-offline-store.h b/src/camel/camel-offline-store.h
new file mode 100644
index 000000000..35e5ddc43
--- /dev/null
+++ b/src/camel/camel-offline-store.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@novell.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_OFFLINE_STORE_H
+#define CAMEL_OFFLINE_STORE_H
+
+#include <camel/camel-store.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_OFFLINE_STORE \
+ (camel_offline_store_get_type ())
+#define CAMEL_OFFLINE_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_OFFLINE_STORE, CamelOfflineStore))
+#define CAMEL_OFFLINE_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_OFFLINE_STORE, CamelOfflineStoreClass))
+#define CAMEL_IS_OFFLINE_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_OFFLINE_STORE))
+#define CAMEL_IS_OFFLINE_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_OFFLINE_STORE))
+#define CAMEL_OFFLINE_STORE_GET_CLASS(obj) \
+ (CAMEL_CHECK_GET_CLASS \
+ ((obj), CAMEL_TYPE_OFFLINE_STORE, CamelOfflineStoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelOfflineStore CamelOfflineStore;
+typedef struct _CamelOfflineStoreClass CamelOfflineStoreClass;
+typedef struct _CamelOfflineStorePrivate CamelOfflineStorePrivate;
+
+struct _CamelOfflineStore {
+ CamelStore parent;
+ CamelOfflineStorePrivate *priv;
+};
+
+struct _CamelOfflineStoreClass {
+ CamelStoreClass parent_class;
+};
+
+GType camel_offline_store_get_type (void);
+gboolean camel_offline_store_get_online (CamelOfflineStore *store);
+gboolean camel_offline_store_set_online_sync
+ (CamelOfflineStore *store,
+ gboolean online,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_offline_store_prepare_for_offline_sync
+ (CamelOfflineStore *store,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_offline_store_requires_downsync
+ (CamelOfflineStore *store);
+
+G_END_DECLS
+
+#endif /* CAMEL_OFFLINE_STORE_H */
diff --git a/src/camel/camel-operation.c b/src/camel/camel-operation.c
new file mode 100644
index 000000000..b160c9076
--- /dev/null
+++ b/src/camel/camel-operation.c
@@ -0,0 +1,439 @@
+/*
+ * camel-operation.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#include <nspr.h>
+
+#include "camel-msgport.h"
+#include "camel-operation.h"
+
+#define CAMEL_OPERATION_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_OPERATION, CamelOperationPrivate))
+
+#define PROGRESS_DELAY 250 /* milliseconds */
+#define TRANSIENT_DELAY 250 /* milliseconds */
+#define POP_MESSAGE_DELAY 1 /* seconds */
+
+typedef struct _StatusNode StatusNode;
+
+struct _StatusNode {
+ volatile gint ref_count;
+ CamelOperation *operation;
+ guint source_id; /* for timeout or idle */
+ gchar *message;
+ gint percent;
+};
+
+struct _CamelOperationPrivate {
+ GQueue status_stack;
+};
+
+enum {
+ STATUS,
+ PUSH_MESSAGE,
+ POP_MESSAGE,
+ PROGRESS,
+ LAST_SIGNAL
+};
+
+static GRecMutex operation_lock;
+#define LOCK() g_rec_mutex_lock (&operation_lock)
+#define UNLOCK() g_rec_mutex_unlock (&operation_lock)
+
+static GQueue operation_list = G_QUEUE_INIT;
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (CamelOperation, camel_operation, G_TYPE_CANCELLABLE)
+
+static StatusNode *
+status_node_new (void)
+{
+ StatusNode *node;
+
+ node = g_slice_new0 (StatusNode);
+ node->ref_count = 1;
+
+ return node;
+}
+
+static StatusNode *
+status_node_ref (StatusNode *node)
+{
+ g_return_val_if_fail (node != NULL, NULL);
+ g_return_val_if_fail (node->ref_count > 0, node);
+
+ g_atomic_int_inc (&node->ref_count);
+
+ return node;
+}
+
+static void
+status_node_unref (StatusNode *node)
+{
+ g_return_if_fail (node != NULL);
+ g_return_if_fail (node->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test (&node->ref_count)) {
+
+ if (node->operation != NULL)
+ g_object_unref (node->operation);
+
+ if (node->source_id > 0)
+ g_source_remove (node->source_id);
+
+ g_free (node->message);
+
+ g_slice_free (StatusNode, node);
+ }
+}
+
+static gboolean
+operation_emit_status_cb (gpointer user_data)
+{
+ StatusNode *node = user_data;
+ StatusNode *head_node;
+ gboolean emit_status;
+
+ LOCK ();
+
+ node->source_id = 0;
+
+ /* Check if we've been preempted by another StatusNode,
+ * or if we've been cancelled and popped off the stack. */
+ head_node = g_queue_peek_head (&node->operation->priv->status_stack);
+ emit_status = (node == head_node);
+
+ UNLOCK ();
+
+ if (emit_status)
+ g_signal_emit (
+ node->operation,
+ signals[STATUS], 0,
+ node->message,
+ node->percent);
+
+ return FALSE;
+}
+
+static void
+operation_finalize (GObject *object)
+{
+ CamelOperationPrivate *priv;
+
+ priv = CAMEL_OPERATION_GET_PRIVATE (object);
+
+ LOCK ();
+
+ g_queue_remove (&operation_list, object);
+
+ /* Because each StatusNode holds a reference to its
+ * CamelOperation, the fact that we're being finalized
+ * implies the stack should be empty now. */
+ g_warn_if_fail (g_queue_is_empty (&priv->status_stack));
+
+ UNLOCK ();
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_operation_parent_class)->finalize (object);
+}
+
+static void
+camel_operation_class_init (CamelOperationClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelOperationPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = operation_finalize;
+
+ signals[STATUS] = g_signal_new (
+ "status",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CamelOperationClass, status),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_INT);
+
+ signals[PUSH_MESSAGE] = g_signal_new (
+ "push-message",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ signals[POP_MESSAGE] = g_signal_new (
+ "pop-message",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals[PROGRESS] = g_signal_new (
+ "progress",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+}
+
+static void
+camel_operation_init (CamelOperation *operation)
+{
+ operation->priv = CAMEL_OPERATION_GET_PRIVATE (operation);
+
+ g_queue_init (&operation->priv->status_stack);
+
+ LOCK ();
+ g_queue_push_tail (&operation_list, operation);
+ UNLOCK ();
+}
+
+/**
+ * camel_operation_new:
+ *
+ * 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.
+ *
+ * Returns: A new operation handle.
+ **/
+GCancellable *
+camel_operation_new (void)
+{
+ return g_object_new (CAMEL_TYPE_OPERATION, NULL);
+}
+
+/**
+ * camel_operation_cancel_all:
+ *
+ * Cancel all outstanding operations.
+ **/
+void
+camel_operation_cancel_all (void)
+{
+ GList *link;
+
+ LOCK ();
+
+ link = g_queue_peek_head_link (&operation_list);
+
+ while (link != NULL) {
+ GCancellable *cancellable = link->data;
+
+ g_cancellable_cancel (cancellable);
+
+ link = g_list_next (link);
+ }
+
+ UNLOCK ();
+}
+
+/**
+ * camel_operation_push_message:
+ * @cancellable: a #GCancellable or %NULL
+ * @format: a standard printf() format string
+ * @...: the parameters to insert into the format string
+ *
+ * Call this function to describe an operation being performed.
+ * Call camel_operation_progress() to report progress on the operation.
+ * Call camel_operation_pop_message() when the operation is complete.
+ *
+ * This function only works if @cancellable is a #CamelOperation cast as a
+ * #GCancellable. If @cancellable is a plain #GCancellable or %NULL, the
+ * function does nothing and returns silently.
+ **/
+void
+camel_operation_push_message (GCancellable *cancellable,
+ const gchar *format, ...)
+{
+ CamelOperation *operation;
+ StatusNode *node;
+ gchar *message;
+ va_list ap;
+
+ if (cancellable == NULL)
+ return;
+
+ if (G_OBJECT_TYPE (cancellable) == G_TYPE_CANCELLABLE)
+ return;
+
+ g_return_if_fail (CAMEL_IS_OPERATION (cancellable));
+
+ va_start (ap, format);
+ message = g_strdup_vprintf (format, ap);
+ va_end (ap);
+
+ g_signal_emit (cancellable, signals[PUSH_MESSAGE], 0, message);
+
+ LOCK ();
+
+ operation = CAMEL_OPERATION (cancellable);
+
+ node = status_node_new ();
+ node->message = message; /* takes ownership */
+ node->operation = g_object_ref (operation);
+
+ if (g_queue_is_empty (&operation->priv->status_stack)) {
+ node->source_id = g_idle_add_full (
+ G_PRIORITY_DEFAULT_IDLE,
+ operation_emit_status_cb,
+ status_node_ref (node),
+ (GDestroyNotify) status_node_unref);
+ } else {
+ node->source_id = g_timeout_add_full (
+ G_PRIORITY_DEFAULT, TRANSIENT_DELAY,
+ operation_emit_status_cb,
+ status_node_ref (node),
+ (GDestroyNotify) status_node_unref);
+ g_source_set_name_by_id (
+ node->source_id,
+ "[camel] operation_emit_status_cb");
+ }
+
+ g_queue_push_head (&operation->priv->status_stack, node);
+
+ UNLOCK ();
+}
+
+/**
+ * camel_operation_pop_message:
+ * @cancellable: a #GCancellable
+ *
+ * Pops the most recently pushed message.
+ *
+ * This function only works if @cancellable is a #CamelOperation cast as a
+ * #GCancellable. If @cancellable is a plain #GCancellable or %NULL, the
+ * function does nothing and returns silently.
+ **/
+void
+camel_operation_pop_message (GCancellable *cancellable)
+{
+ CamelOperation *operation;
+ StatusNode *node;
+
+ if (cancellable == NULL)
+ return;
+
+ if (G_OBJECT_TYPE (cancellable) == G_TYPE_CANCELLABLE)
+ return;
+
+ g_return_if_fail (CAMEL_IS_OPERATION (cancellable));
+
+ g_signal_emit (cancellable, signals[POP_MESSAGE], 0);
+
+ LOCK ();
+
+ operation = CAMEL_OPERATION (cancellable);
+ node = g_queue_pop_head (&operation->priv->status_stack);
+
+ if (node != NULL) {
+ if (node->source_id > 0) {
+ g_source_remove (node->source_id);
+ node->source_id = 0;
+ }
+ status_node_unref (node);
+ }
+
+ node = g_queue_peek_head (&operation->priv->status_stack);
+
+ if (node != NULL) {
+ if (node->source_id != 0)
+ g_source_remove (node->source_id);
+
+ node->source_id = g_timeout_add_seconds_full (
+ G_PRIORITY_DEFAULT, POP_MESSAGE_DELAY,
+ operation_emit_status_cb,
+ status_node_ref (node),
+ (GDestroyNotify) status_node_unref);
+ g_source_set_name_by_id (
+ node->source_id,
+ "[camel] operation_emit_status_cb");
+ }
+
+ UNLOCK ();
+}
+
+/**
+ * camel_operation_progress:
+ * @cancellable: a #GCancellable or %NULL
+ * @percent: percent complete, 0 to 100.
+ *
+ * Report progress on the current operation. @percent reports the current
+ * percentage of completion, which should be in the range of 0 to 100.
+ *
+ * This function only works if @cancellable is a #CamelOperation cast as a
+ * #GCancellable. If @cancellable is a plain #GCancellable or %NULL, the
+ * function does nothing and returns silently.
+ **/
+void
+camel_operation_progress (GCancellable *cancellable,
+ gint percent)
+{
+ CamelOperation *operation;
+ StatusNode *node;
+
+ if (cancellable == NULL)
+ return;
+
+ if (G_OBJECT_TYPE (cancellable) == G_TYPE_CANCELLABLE)
+ return;
+
+ g_return_if_fail (CAMEL_IS_OPERATION (cancellable));
+
+ g_signal_emit (cancellable, signals[PROGRESS], 0, percent);
+
+ LOCK ();
+
+ operation = CAMEL_OPERATION (cancellable);
+ node = g_queue_peek_head (&operation->priv->status_stack);
+
+ if (node != NULL) {
+ node->percent = percent;
+
+ /* Rate limit progress updates. */
+ if (node->source_id == 0) {
+ node->source_id = g_timeout_add_full (
+ G_PRIORITY_DEFAULT, PROGRESS_DELAY,
+ operation_emit_status_cb,
+ status_node_ref (node),
+ (GDestroyNotify) status_node_unref);
+ g_source_set_name_by_id (
+ node->source_id,
+ "[camel] operation_emit_status_cb");
+ }
+ }
+
+ UNLOCK ();
+}
+
diff --git a/src/camel/camel-operation.h b/src/camel/camel-operation.h
new file mode 100644
index 000000000..d8cbd9f82
--- /dev/null
+++ b/src/camel/camel-operation.h
@@ -0,0 +1,84 @@
+/*
+ * camel-operation.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_OPERATION_H
+#define CAMEL_OPERATION_H
+
+#include <gio/gio.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_OPERATION \
+ (camel_operation_get_type ())
+#define CAMEL_OPERATION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_OPERATION, CamelOperation))
+#define CAMEL_OPERATION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_OPERATION, CamelOperationClass))
+#define CAMEL_IS_OPERATION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_OPERATION))
+#define CAMEL_IS_OPERATION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_OPERATION))
+#define CAMEL_OPERATION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_OPERATION, CamelOperationClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelOperation CamelOperation;
+typedef struct _CamelOperationClass CamelOperationClass;
+typedef struct _CamelOperationPrivate CamelOperationPrivate;
+
+struct _CamelOperation {
+ GCancellable parent;
+ CamelOperationPrivate *priv;
+};
+
+struct _CamelOperationClass {
+ GCancellableClass parent_class;
+
+ /* Signals */
+ void (*status) (CamelOperation *operation,
+ const gchar *what,
+ gint pc);
+};
+
+GType camel_operation_get_type (void);
+GCancellable * camel_operation_new (void);
+void camel_operation_cancel_all (void);
+
+/* Since Camel methods pass around GCancellable pointers instead of
+ * CamelOperation pointers, it's more convenient to callers to take
+ * a GCancellable pointer and just return silently if the pointer is
+ * NULL or the pointed to object actually is a plain GCancellable. */
+
+void camel_operation_push_message (GCancellable *cancellable,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
+void camel_operation_pop_message (GCancellable *cancellable);
+void camel_operation_progress (GCancellable *cancellable,
+ gint percent);
+
+G_END_DECLS
+
+#endif /* CAMEL_OPERATION_H */
diff --git a/src/camel/camel-partition-table.c b/src/camel/camel-partition-table.c
new file mode 100644
index 000000000..e51c16bda
--- /dev/null
+++ b/src/camel/camel-partition-table.c
@@ -0,0 +1,1038 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "camel-block-file.h"
+#include "camel-partition-table.h"
+
+/* Do we synchronously write table updates - makes the
+ * tables consistent after program crash without sync */
+/*#define SYNC_UPDATES*/
+
+#define d(x) /*(printf ("%s (%d):%s: ", __FILE__, __LINE__, __PRETTY_FUNCTION__),(x))*/
+/* key index debug */
+#define k(x) /*(printf ("%s (%d):%s: ", __FILE__, __LINE__, __PRETTY_FUNCTION__),(x))*/
+
+#define CAMEL_PARTITION_TABLE_LOCK(kf, lock) \
+ (g_mutex_lock (&(kf)->priv->lock))
+#define CAMEL_PARTITION_TABLE_UNLOCK(kf, lock) \
+ (g_mutex_unlock (&(kf)->priv->lock))
+
+#define CAMEL_PARTITION_TABLE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_PARTITION_TABLE, CamelPartitionTablePrivate))
+
+struct _CamelPartitionTablePrivate {
+ GMutex lock; /* for locking partition */
+};
+
+G_DEFINE_TYPE (CamelPartitionTable, camel_partition_table, G_TYPE_OBJECT)
+
+static void
+partition_table_finalize (GObject *object)
+{
+ CamelPartitionTable *table = CAMEL_PARTITION_TABLE (object);
+ CamelBlock *bl;
+
+ if (table->blocks != NULL) {
+ while ((bl = g_queue_pop_head (&table->partition)) != NULL) {
+ camel_block_file_sync_block (table->blocks, bl);
+ camel_block_file_unref_block (table->blocks, bl);
+ }
+ camel_block_file_sync (table->blocks);
+
+ g_object_unref (table->blocks);
+ }
+
+ g_mutex_clear (&table->priv->lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_partition_table_parent_class)->finalize (object);
+}
+
+static void
+camel_partition_table_class_init (CamelPartitionTableClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelPartitionTablePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = partition_table_finalize;
+}
+
+static void
+camel_partition_table_init (CamelPartitionTable *cpi)
+{
+ cpi->priv = CAMEL_PARTITION_TABLE_GET_PRIVATE (cpi);
+
+ g_queue_init (&cpi->partition);
+ g_mutex_init (&cpi->priv->lock);
+}
+
+/* ********************************************************************** */
+
+/*
+ * Have 2 hashes:
+ * Name -> nameid
+ * Word -> wordid
+ *
+ * nameid is pointer to name file, includes a bit to say if name is deleted
+ * wordid is a pointer to word file, includes pointer to start of word entries
+ *
+ * delete a name -> set it as deleted, do nothing else though
+ *
+ * lookup word, if nameid is deleted, mark it in wordlist as unused and mark
+ * for write (?)
+ */
+
+/* ********************************************************************** */
+
+/* This simple hash seems to work quite well */
+static camel_hash_t hash_key (const gchar *key)
+{
+ camel_hash_t hash = 0xABADF00D;
+
+ while (*key) {
+ hash = hash * (*key) ^ (*key);
+ key++;
+ }
+
+ return hash;
+}
+
+/* Call with lock held */
+static GList *
+find_partition (CamelPartitionTable *cpi,
+ camel_hash_t id,
+ gint *indexp)
+{
+ gint index, jump;
+ CamelPartitionMapBlock *ptb;
+ CamelPartitionMap *part;
+ GList *head, *link;
+
+ /* first, find the block this key might be in, then binary search the block */
+ head = g_queue_peek_head_link (&cpi->partition);
+
+ for (link = head; link != NULL; link = g_list_next (link)) {
+ CamelBlock *bl = link->data;
+
+ ptb = (CamelPartitionMapBlock *) &bl->data;
+ part = ptb->partition;
+ if (ptb->used > 0 && id <= part[ptb->used - 1].hashid) {
+ index = ptb->used / 2;
+ jump = ptb->used / 4;
+
+ if (jump == 0)
+ jump = 1;
+
+ while (1) {
+ if (id <= part[index].hashid) {
+ if (index == 0 || id > part[index - 1].hashid)
+ break;
+ index -= jump;
+ } else {
+ if (index >= ptb->used - 1)
+ break;
+ index += jump;
+ }
+ jump = jump / 2;
+ if (jump == 0)
+ jump = 1;
+ }
+ *indexp = index;
+
+ return link;
+ }
+ }
+
+ g_warning ("could not find a partition that could fit! partition table corrupt!");
+
+ /* This should never be reached */
+
+ return NULL;
+}
+
+CamelPartitionTable *
+camel_partition_table_new (CamelBlockFile *bs,
+ camel_block_t root)
+{
+ CamelPartitionTable *cpi;
+ CamelPartitionMapBlock *ptb;
+ CamelPartitionKeyBlock *kb;
+ CamelBlock *block, *pblock;
+
+ g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), NULL);
+
+ cpi = g_object_new (CAMEL_TYPE_PARTITION_TABLE, NULL);
+ cpi->rootid = root;
+ cpi->blocks = g_object_ref (bs);
+
+ /* read the partition table into memory */
+ do {
+ block = camel_block_file_get_block (bs, root);
+ if (block == NULL)
+ goto fail;
+
+ ptb = (CamelPartitionMapBlock *) &block->data;
+
+ d (printf ("Adding partition block, used = %d, hashid = %08x\n", ptb->used, ptb->partition[0].hashid));
+
+ /* if we have no data, prime initial block */
+ if (ptb->used == 0 && g_queue_is_empty (&cpi->partition) && ptb->next == 0) {
+ pblock = camel_block_file_new_block (bs);
+ if (pblock == NULL) {
+ camel_block_file_unref_block (bs, block);
+ goto fail;
+ }
+ kb = (CamelPartitionKeyBlock *) &pblock->data;
+ kb->used = 0;
+ ptb->used = 1;
+ ptb->partition[0].hashid = 0xffffffff;
+ ptb->partition[0].blockid = pblock->id;
+ camel_block_file_touch_block (bs, pblock);
+ camel_block_file_unref_block (bs, pblock);
+ camel_block_file_touch_block (bs, block);
+#ifdef SYNC_UPDATES
+ camel_block_file_sync_block (bs, block);
+#endif
+ }
+
+ root = ptb->next;
+ camel_block_file_detach_block (bs, block);
+ g_queue_push_tail (&cpi->partition, block);
+ } while (root);
+
+ return cpi;
+
+fail:
+ g_object_unref (cpi);
+ return NULL;
+}
+
+/* sync our blocks, the caller must still sync the blockfile itself */
+gint
+camel_partition_table_sync (CamelPartitionTable *cpi)
+{
+ gint ret = 0;
+
+ g_return_val_if_fail (CAMEL_IS_PARTITION_TABLE (cpi), -1);
+
+ CAMEL_PARTITION_TABLE_LOCK (cpi, lock);
+
+ if (cpi->blocks) {
+ GList *head, *link;
+
+ head = g_queue_peek_head_link (&cpi->partition);
+
+ for (link = head; link != NULL; link = g_list_next (link)) {
+ CamelBlock *bl = link->data;
+
+ ret = camel_block_file_sync_block (cpi->blocks, bl);
+ if (ret == -1)
+ goto fail;
+ }
+ }
+
+fail:
+ CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
+
+ return ret;
+}
+
+camel_key_t
+camel_partition_table_lookup (CamelPartitionTable *cpi,
+ const gchar *key)
+{
+ CamelPartitionKeyBlock *pkb;
+ CamelPartitionMapBlock *ptb;
+ CamelBlock *block, *ptblock;
+ camel_hash_t hashid;
+ camel_key_t keyid = 0;
+ GList *ptblock_link;
+ gint index, i;
+
+ g_return_val_if_fail (CAMEL_IS_PARTITION_TABLE (cpi), 0);
+ g_return_val_if_fail (key != NULL, 0);
+
+ hashid = hash_key (key);
+
+ CAMEL_PARTITION_TABLE_LOCK (cpi, lock);
+
+ ptblock_link = find_partition (cpi, hashid, &index);
+ if (ptblock_link == NULL) {
+ CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
+ return 0;
+ }
+
+ ptblock = (CamelBlock *) ptblock_link->data;
+ ptb = (CamelPartitionMapBlock *) &ptblock->data;
+ block = camel_block_file_get_block (
+ cpi->blocks, ptb->partition[index].blockid);
+ if (block == NULL) {
+ CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
+ return 0;
+ }
+
+ pkb = (CamelPartitionKeyBlock *) &block->data;
+
+ /* What to do about duplicate hash's? */
+ for (i = 0; i < pkb->used; i++) {
+ if (pkb->keys[i].hashid == hashid) {
+ /* !! need to: lookup and compare string value */
+ /* get_key() if key == key ... */
+ keyid = pkb->keys[i].keyid;
+ break;
+ }
+ }
+
+ CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
+
+ camel_block_file_unref_block (cpi->blocks, block);
+
+ return keyid;
+}
+
+gboolean
+camel_partition_table_remove (CamelPartitionTable *cpi,
+ const gchar *key)
+{
+ CamelPartitionKeyBlock *pkb;
+ CamelPartitionMapBlock *ptb;
+ CamelBlock *block, *ptblock;
+ camel_hash_t hashid;
+ GList *ptblock_link;
+ gint index, i;
+
+ g_return_val_if_fail (CAMEL_IS_PARTITION_TABLE (cpi), FALSE);
+ g_return_val_if_fail (key != NULL, FALSE);
+
+ hashid = hash_key (key);
+
+ CAMEL_PARTITION_TABLE_LOCK (cpi, lock);
+
+ ptblock_link = find_partition (cpi, hashid, &index);
+ if (ptblock_link == NULL) {
+ CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
+ return TRUE;
+ }
+
+ ptblock = (CamelBlock *) ptblock_link->data;
+ ptb = (CamelPartitionMapBlock *) &ptblock->data;
+ block = camel_block_file_get_block (
+ cpi->blocks, ptb->partition[index].blockid);
+ if (block == NULL) {
+ CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
+ return FALSE;
+ }
+ pkb = (CamelPartitionKeyBlock *) &block->data;
+
+ /* What to do about duplicate hash's? */
+ for (i = 0; i < pkb->used; i++) {
+ if (pkb->keys[i].hashid == hashid) {
+ /* !! need to: lookup and compare string value */
+ /* get_key() if key == key ... */
+
+ /* remove this key */
+ pkb->used--;
+ for (; i < pkb->used; i++) {
+ pkb->keys[i].keyid = pkb->keys[i + 1].keyid;
+ pkb->keys[i].hashid = pkb->keys[i + 1].hashid;
+ }
+ camel_block_file_touch_block (cpi->blocks, block);
+ break;
+ }
+ }
+
+ CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
+
+ camel_block_file_unref_block (cpi->blocks, block);
+
+ return TRUE;
+}
+
+static gint
+keys_cmp (gconstpointer ap,
+ gconstpointer bp)
+{
+ const CamelPartitionKey *a = ap;
+ const CamelPartitionKey *b = bp;
+
+ if (a->hashid < b->hashid)
+ return -1;
+ else if (a->hashid > b->hashid)
+ return 1;
+
+ return 0;
+}
+
+gint
+camel_partition_table_add (CamelPartitionTable *cpi,
+ const gchar *key,
+ camel_key_t keyid)
+{
+ camel_hash_t hashid, partid;
+ gint index, newindex = 0; /* initialisation of this and pkb/nkb is just to silence compiler */
+ CamelPartitionMapBlock *ptb, *ptn;
+ CamelPartitionKeyBlock *kb, *newkb, *nkb = NULL, *pkb = NULL;
+ CamelBlock *block, *ptblock, *ptnblock;
+ gint i, half, len;
+ CamelPartitionKey keys[CAMEL_BLOCK_SIZE / 4];
+ GList *ptblock_link;
+ gint ret = -1;
+
+ g_return_val_if_fail (CAMEL_IS_PARTITION_TABLE (cpi), -1);
+ g_return_val_if_fail (key != NULL, -1);
+
+ hashid = hash_key (key);
+
+ CAMEL_PARTITION_TABLE_LOCK (cpi, lock);
+ ptblock_link = find_partition (cpi, hashid, &index);
+ if (ptblock_link == NULL) {
+ CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
+ return -1;
+ }
+
+ ptblock = (CamelBlock *) ptblock_link->data;
+ ptb = (CamelPartitionMapBlock *) &ptblock->data;
+ block = camel_block_file_get_block (
+ cpi->blocks, ptb->partition[index].blockid);
+ if (block == NULL) {
+ CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
+ return -1;
+ }
+ kb = (CamelPartitionKeyBlock *) &block->data;
+
+ /* TODO: Keep the key array in sorted order, cheaper lookups and split operation */
+
+ if (kb->used < G_N_ELEMENTS (kb->keys)) {
+ /* Have room, just put it in */
+ kb->keys[kb->used].hashid = hashid;
+ kb->keys[kb->used].keyid = keyid;
+ kb->used++;
+ } else {
+ CamelBlock *newblock = NULL, *nblock = NULL, *pblock = NULL;
+
+ /* Need to split? See if previous or next has room, then split across that instead */
+
+ /* TODO: Should look at next/previous partition table block as well ... */
+
+ if (index > 0) {
+ pblock = camel_block_file_get_block (
+ cpi->blocks, ptb->partition[index - 1].blockid);
+ if (pblock == NULL)
+ goto fail;
+ pkb = (CamelPartitionKeyBlock *) &pblock->data;
+ }
+ if (index < (ptb->used - 1)) {
+ nblock = camel_block_file_get_block (
+ cpi->blocks, ptb->partition[index + 1].blockid);
+ if (nblock == NULL) {
+ if (pblock)
+ camel_block_file_unref_block (cpi->blocks, pblock);
+ goto fail;
+ }
+ nkb = (CamelPartitionKeyBlock *) &nblock->data;
+ }
+
+ if (pblock && pkb->used < G_N_ELEMENTS (kb->keys)) {
+ if (nblock && nkb->used < G_N_ELEMENTS (kb->keys)) {
+ if (pkb->used < nkb->used) {
+ newindex = index + 1;
+ newblock = nblock;
+ } else {
+ newindex = index - 1;
+ newblock = pblock;
+ }
+ } else {
+ newindex = index - 1;
+ newblock = pblock;
+ }
+ } else {
+ if (nblock && nkb->used < G_N_ELEMENTS (kb->keys)) {
+ newindex = index + 1;
+ newblock = nblock;
+ }
+ }
+
+ /* We had no room, need to split across another block */
+ if (newblock == NULL) {
+ /* See if we have room in the partition table for this block or need to split that too */
+ if (ptb->used >= G_N_ELEMENTS (ptb->partition)) {
+ /* TODO: Could check next block to see if it'll fit there first */
+ ptnblock = camel_block_file_new_block (cpi->blocks);
+ if (ptnblock == NULL) {
+ if (nblock)
+ camel_block_file_unref_block (cpi->blocks, nblock);
+ if (pblock)
+ camel_block_file_unref_block (cpi->blocks, pblock);
+ goto fail;
+ }
+ camel_block_file_detach_block (cpi->blocks, ptnblock);
+
+ /* split block and link on-disk, always sorted */
+ ptn = (CamelPartitionMapBlock *) &ptnblock->data;
+ ptn->next = ptb->next;
+ ptb->next = ptnblock->id;
+ len = ptb->used / 2;
+ ptn->used = ptb->used - len;
+ ptb->used = len;
+ memcpy (ptn->partition, &ptb->partition[len], ptn->used * sizeof (ptb->partition[0]));
+
+ /* link in-memory */
+ g_queue_insert_after (
+ &cpi->partition,
+ ptblock_link, ptnblock);
+
+ /* write in right order to ensure structure */
+ camel_block_file_touch_block (cpi->blocks, ptnblock);
+#ifdef SYNC_UPDATES
+ camel_block_file_sync_block (cpi->blocks, ptnblock);
+#endif
+ if (index > len) {
+ camel_block_file_touch_block (cpi->blocks, ptblock);
+#ifdef SYNC_UPDATES
+ camel_block_file_sync_block (cpi->blocks, ptblock);
+#endif
+ index -= len;
+ ptb = ptn;
+ ptblock = ptnblock;
+ }
+ }
+
+ /* try get newblock before modifying existing */
+ newblock = camel_block_file_new_block (cpi->blocks);
+ if (newblock == NULL) {
+ if (nblock)
+ camel_block_file_unref_block (cpi->blocks, nblock);
+ if (pblock)
+ camel_block_file_unref_block (cpi->blocks, pblock);
+ goto fail;
+ }
+
+ for (i = ptb->used - 1; i > index; i--) {
+ ptb->partition[i + 1].hashid = ptb->partition[i].hashid;
+ ptb->partition[i + 1].blockid = ptb->partition[i].blockid;
+ }
+ ptb->used++;
+
+ newkb = (CamelPartitionKeyBlock *) &newblock->data;
+ newkb->used = 0;
+ newindex = index + 1;
+
+ ptb->partition[newindex].hashid = ptb->partition[index].hashid;
+ ptb->partition[newindex].blockid = newblock->id;
+
+ if (nblock)
+ camel_block_file_unref_block (cpi->blocks, nblock);
+ if (pblock)
+ camel_block_file_unref_block (cpi->blocks, pblock);
+ } else {
+ newkb = (CamelPartitionKeyBlock *) &newblock->data;
+
+ if (newblock == pblock) {
+ if (nblock)
+ camel_block_file_unref_block (cpi->blocks, nblock);
+ } else {
+ if (pblock)
+ camel_block_file_unref_block (cpi->blocks, pblock);
+ }
+ }
+
+ /* sort keys to find midpoint */
+ len = kb->used;
+ memcpy (keys, kb->keys, sizeof (kb->keys[0]) * len);
+ memcpy (keys + len, newkb->keys, sizeof (newkb->keys[0]) * newkb->used);
+ len += newkb->used;
+ keys[len].hashid = hashid;
+ keys[len].keyid = keyid;
+ len++;
+ qsort (keys, len, sizeof (keys[0]), keys_cmp);
+
+ /* Split keys, fix partition table */
+ half = len / 2;
+ partid = keys[half - 1].hashid;
+
+ if (index < newindex) {
+ memcpy (kb->keys, keys, sizeof (keys[0]) * half);
+ kb->used = half;
+ memcpy (newkb->keys, keys + half, sizeof (keys[0]) * (len - half));
+ newkb->used = len - half;
+ ptb->partition[index].hashid = partid;
+ } else {
+ memcpy (newkb->keys, keys, sizeof (keys[0]) * half);
+ newkb->used = half;
+ memcpy (kb->keys, keys + half, sizeof (keys[0]) * (len - half));
+ kb->used = len - half;
+ ptb->partition[newindex].hashid = partid;
+ }
+
+ camel_block_file_touch_block (cpi->blocks, ptblock);
+#ifdef SYNC_UPDATES
+ camel_block_file_sync_block (cpi->blocks, ptblock);
+#endif
+ camel_block_file_touch_block (cpi->blocks, newblock);
+ camel_block_file_unref_block (cpi->blocks, newblock);
+ }
+
+ camel_block_file_touch_block (cpi->blocks, block);
+ camel_block_file_unref_block (cpi->blocks, block);
+
+ ret = 0;
+fail:
+ CAMEL_PARTITION_TABLE_UNLOCK (cpi, lock);
+
+ return ret;
+}
+
+/* ********************************************************************** */
+
+#define CAMEL_KEY_TABLE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_KEY_TABLE, CamelKeyTablePrivate))
+
+#define CAMEL_KEY_TABLE_LOCK(kf, lock) \
+ (g_mutex_lock (&(kf)->priv->lock))
+#define CAMEL_KEY_TABLE_UNLOCK(kf, lock) \
+ (g_mutex_unlock (&(kf)->priv->lock))
+
+struct _CamelKeyTablePrivate {
+ GMutex lock; /* for locking key */
+};
+
+G_DEFINE_TYPE (CamelKeyTable, camel_key_table, G_TYPE_OBJECT)
+
+static void
+key_table_finalize (GObject *object)
+{
+ CamelKeyTable *table = CAMEL_KEY_TABLE (object);
+
+ if (table->blocks) {
+ if (table->root_block) {
+ camel_block_file_sync_block (table->blocks, table->root_block);
+ camel_block_file_unref_block (table->blocks, table->root_block);
+ }
+ camel_block_file_sync (table->blocks);
+ g_object_unref (table->blocks);
+ }
+
+ g_mutex_clear (&table->priv->lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_key_table_parent_class)->finalize (object);
+}
+
+static void
+camel_key_table_class_init (CamelKeyTableClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelKeyTablePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = key_table_finalize;
+}
+
+static void
+camel_key_table_init (CamelKeyTable *table)
+{
+ table->priv = CAMEL_KEY_TABLE_GET_PRIVATE (table);
+ g_mutex_init (&table->priv->lock);
+}
+
+CamelKeyTable *
+camel_key_table_new (CamelBlockFile *bs,
+ camel_block_t root)
+{
+ CamelKeyTable *ki;
+
+ g_return_val_if_fail (CAMEL_IS_BLOCK_FILE (bs), NULL);
+
+ ki = g_object_new (CAMEL_TYPE_KEY_TABLE, NULL);
+
+ ki->blocks = g_object_ref (bs);
+ ki->rootid = root;
+
+ ki->root_block = camel_block_file_get_block (bs, ki->rootid);
+ if (ki->root_block == NULL) {
+ g_object_unref (ki);
+ ki = NULL;
+ } else {
+ camel_block_file_detach_block (bs, ki->root_block);
+ ki->root = (CamelKeyRootBlock *) &ki->root_block->data;
+
+ k (printf ("Opening key index\n"));
+ k (printf (" first %u\n last %u\n free %u\n", ki->root->first, ki->root->last, ki->root->free));
+ }
+
+ return ki;
+}
+
+gint
+camel_key_table_sync (CamelKeyTable *ki)
+{
+ g_return_val_if_fail (CAMEL_IS_KEY_TABLE (ki), -1);
+
+#ifdef SYNC_UPDATES
+ return 0;
+#else
+ return camel_block_file_sync_block (ki->blocks, ki->root_block);
+#endif
+}
+
+camel_key_t
+camel_key_table_add (CamelKeyTable *ki,
+ const gchar *key,
+ camel_block_t data,
+ guint flags)
+{
+ CamelBlock *last, *next;
+ CamelKeyBlock *kblast, *kbnext;
+ gint len, left;
+ guint offset;
+ camel_key_t keyid = 0;
+
+ g_return_val_if_fail (CAMEL_IS_KEY_TABLE (ki), 0);
+ g_return_val_if_fail (key != NULL, 0);
+
+ /* Maximum key size = 128 chars */
+ len = strlen (key);
+ if (len > CAMEL_KEY_TABLE_MAX_KEY)
+ len = 128;
+
+ CAMEL_KEY_TABLE_LOCK (ki, lock);
+
+ if (ki->root->last == 0) {
+ last = camel_block_file_new_block (ki->blocks);
+ if (last == NULL)
+ goto fail;
+ ki->root->last = ki->root->first = last->id;
+ camel_block_file_touch_block (ki->blocks, ki->root_block);
+ k (printf ("adding first block, first = %u\n", ki->root->first));
+ } else {
+ last = camel_block_file_get_block (
+ ki->blocks, ki->root->last);
+ if (last == NULL)
+ goto fail;
+ }
+
+ kblast = (CamelKeyBlock *) &last->data;
+
+ if (kblast->used >= 127)
+ goto fail;
+
+ if (kblast->used > 0) {
+ /*left = &kblast->u.keydata[kblast->u.keys[kblast->used-1].offset] - (gchar *)(&kblast->u.keys[kblast->used+1]);*/
+ left = kblast->u.keys[kblast->used - 1].offset - sizeof (kblast->u.keys[0]) * (kblast->used + 1);
+ d (printf (
+ "key '%s' used = %d (%d), filled = %d, left = %d len = %d?\n",
+ key, kblast->used, kblast->used * sizeof (kblast->u.keys[0]),
+ sizeof (kblast->u.keydata) - kblast->u.keys[kblast->used - 1].offset,
+ left, len));
+ if (left < len) {
+ next = camel_block_file_new_block (ki->blocks);
+ if (next == NULL) {
+ camel_block_file_unref_block (ki->blocks, last);
+ goto fail;
+ }
+ kbnext = (CamelKeyBlock *) &next->data;
+ kblast->next = next->id;
+ ki->root->last = next->id;
+ d (printf ("adding new block, first = %u, last = %u\n", ki->root->first, ki->root->last));
+ camel_block_file_touch_block (ki->blocks, ki->root_block);
+ camel_block_file_touch_block (ki->blocks, last);
+ camel_block_file_unref_block (ki->blocks, last);
+ kblast = kbnext;
+ last = next;
+ }
+ }
+
+ if (kblast->used > 0)
+ offset = kblast->u.keys[kblast->used - 1].offset - len;
+ else
+ offset = sizeof (kblast->u.keydata) - len;
+
+ kblast->u.keys[kblast->used].flags = flags;
+ kblast->u.keys[kblast->used].data = data;
+ kblast->u.keys[kblast->used].offset = offset;
+ memcpy (kblast->u.keydata + offset, key, len);
+
+ keyid = (last->id & (~(CAMEL_BLOCK_SIZE - 1))) | kblast->used;
+
+ kblast->used++;
+
+ if (kblast->used >=127) {
+ g_warning ("Invalid value for used %d\n", kblast->used);
+ keyid = 0;
+ goto fail;
+ }
+
+ camel_block_file_touch_block (ki->blocks, last);
+ camel_block_file_unref_block (ki->blocks, last);
+
+#ifdef SYNC_UPDATES
+ camel_block_file_sync_block (ki->blocks, ki->root_block);
+#endif
+fail:
+ CAMEL_KEY_TABLE_UNLOCK (ki, lock);
+
+ return keyid;
+}
+
+gboolean
+camel_key_table_set_data (CamelKeyTable *ki,
+ camel_key_t keyid,
+ camel_block_t data)
+{
+ CamelBlock *bl;
+ camel_block_t blockid;
+ gint index;
+ CamelKeyBlock *kb;
+
+ g_return_val_if_fail (CAMEL_IS_KEY_TABLE (ki), FALSE);
+ g_return_val_if_fail (keyid != 0, FALSE);
+
+ blockid = keyid & (~(CAMEL_BLOCK_SIZE - 1));
+ index = keyid & (CAMEL_BLOCK_SIZE - 1);
+
+ bl = camel_block_file_get_block (ki->blocks, blockid);
+ if (bl == NULL)
+ return FALSE;
+ kb = (CamelKeyBlock *) &bl->data;
+
+ CAMEL_KEY_TABLE_LOCK (ki, lock);
+
+ if (kb->u.keys[index].data != data) {
+ kb->u.keys[index].data = data;
+ camel_block_file_touch_block (ki->blocks, bl);
+ }
+
+ CAMEL_KEY_TABLE_UNLOCK (ki, lock);
+
+ camel_block_file_unref_block (ki->blocks, bl);
+
+ return TRUE;
+}
+
+gboolean
+camel_key_table_set_flags (CamelKeyTable *ki,
+ camel_key_t keyid,
+ guint flags,
+ guint set)
+{
+ CamelBlock *bl;
+ camel_block_t blockid;
+ gint index;
+ CamelKeyBlock *kb;
+ guint old;
+
+ g_return_val_if_fail (CAMEL_IS_KEY_TABLE (ki), FALSE);
+ g_return_val_if_fail (keyid != 0, FALSE);
+
+ blockid = keyid & (~(CAMEL_BLOCK_SIZE - 1));
+ index = keyid & (CAMEL_BLOCK_SIZE - 1);
+
+ bl = camel_block_file_get_block (ki->blocks, blockid);
+ if (bl == NULL)
+ return FALSE;
+ kb = (CamelKeyBlock *) &bl->data;
+
+ if (kb->used >=127 || index >= kb->used) {
+ g_warning ("Block %x: Invalid index or content: index %d used %d\n", blockid, index, kb->used);
+ return FALSE;
+ }
+
+ CAMEL_KEY_TABLE_LOCK (ki, lock);
+
+ old = kb->u.keys[index].flags;
+ if ((old & set) != (flags & set)) {
+ kb->u.keys[index].flags = (old & (~set)) | (flags & set);
+ camel_block_file_touch_block (ki->blocks, bl);
+ }
+
+ CAMEL_KEY_TABLE_UNLOCK (ki, lock);
+
+ camel_block_file_unref_block (ki->blocks, bl);
+
+ return TRUE;
+}
+
+camel_block_t
+camel_key_table_lookup (CamelKeyTable *ki,
+ camel_key_t keyid,
+ gchar **keyp,
+ guint *flags)
+{
+ CamelBlock *bl;
+ camel_block_t blockid;
+ gint index, len, off;
+ gchar *key;
+ CamelKeyBlock *kb;
+
+ g_return_val_if_fail (CAMEL_IS_KEY_TABLE (ki), 0);
+ g_return_val_if_fail (keyid != 0, 0);
+
+ if (keyp)
+ *keyp = NULL;
+ if (flags)
+ *flags = 0;
+
+ blockid = keyid & (~(CAMEL_BLOCK_SIZE - 1));
+ index = keyid & (CAMEL_BLOCK_SIZE - 1);
+
+ bl = camel_block_file_get_block (ki->blocks, blockid);
+ if (bl == NULL)
+ return 0;
+
+ kb = (CamelKeyBlock *) &bl->data;
+
+ if (kb->used >=127 || index >= kb->used) {
+ g_warning ("Block %x: Invalid index or content: index %d used %d\n", blockid, index, kb->used);
+ return 0;
+ }
+
+ CAMEL_KEY_TABLE_LOCK (ki, lock);
+
+ blockid = kb->u.keys[index].data;
+ if (flags)
+ *flags = kb->u.keys[index].flags;
+
+ if (keyp) {
+ off = kb->u.keys[index].offset;
+ if (index == 0)
+ len = sizeof (kb->u.keydata) - off;
+ else
+ len = kb->u.keys[index - 1].offset - off;
+ *keyp = key = g_malloc (len+1);
+ memcpy (key, kb->u.keydata + off, len);
+ key[len] = 0;
+ }
+
+ CAMEL_KEY_TABLE_UNLOCK (ki, lock);
+
+ camel_block_file_unref_block (ki->blocks, bl);
+
+ return blockid;
+}
+
+/* iterate through all keys */
+camel_key_t
+camel_key_table_next (CamelKeyTable *ki,
+ camel_key_t next,
+ gchar **keyp,
+ guint *flagsp,
+ camel_block_t *datap)
+{
+ CamelBlock *bl;
+ CamelKeyBlock *kb;
+ camel_block_t blockid;
+ gint index;
+
+ g_return_val_if_fail (CAMEL_IS_KEY_TABLE (ki), 0);
+
+ if (keyp)
+ *keyp = NULL;
+ if (flagsp)
+ *flagsp = 0;
+ if (datap)
+ *datap = 0;
+
+ CAMEL_KEY_TABLE_LOCK (ki, lock);
+
+ if (next == 0) {
+ next = ki->root->first;
+ if (next == 0) {
+ CAMEL_KEY_TABLE_UNLOCK (ki, lock);
+ return 0;
+ }
+ } else
+ next++;
+
+ do {
+ blockid = next & (~(CAMEL_BLOCK_SIZE - 1));
+ index = next & (CAMEL_BLOCK_SIZE - 1);
+
+ bl = camel_block_file_get_block (ki->blocks, blockid);
+ if (bl == NULL) {
+ CAMEL_KEY_TABLE_UNLOCK (ki, lock);
+ return 0;
+ }
+
+ kb = (CamelKeyBlock *) &bl->data;
+
+ /* see if we need to goto the next block */
+ if (index >= kb->used) {
+ /* FIXME: check for loops */
+ next = kb->next;
+ camel_block_file_unref_block (ki->blocks, bl);
+ bl = NULL;
+ }
+ } while (bl == NULL);
+
+ /* invalid block data */
+ if ((kb->u.keys[index].offset >= sizeof (kb->u.keydata)
+ /*|| kb->u.keys[index].offset < kb->u.keydata - (gchar *)&kb->u.keys[kb->used])*/
+ || kb->u.keys[index].offset < sizeof (kb->u.keys[0]) * kb->used
+ || (index > 0 &&
+ (kb->u.keys[index - 1].offset >= sizeof (kb->u.keydata)
+ /*|| kb->u.keys[index-1].offset < kb->u.keydata - (gchar *)&kb->u.keys[kb->used]))) {*/
+ || kb->u.keys[index - 1].offset < sizeof (kb->u.keys[0]) * kb->used)))) {
+ g_warning ("Block %u invalid scanning keys", bl->id);
+ camel_block_file_unref_block (ki->blocks, bl);
+ CAMEL_KEY_TABLE_UNLOCK (ki, lock);
+ return 0;
+ }
+
+ if (datap)
+ *datap = kb->u.keys[index].data;
+
+ if (flagsp)
+ *flagsp = kb->u.keys[index].flags;
+
+ if (keyp) {
+ gint len, off = kb->u.keys[index].offset;
+ gchar *key;
+
+ if (index == 0)
+ len = sizeof (kb->u.keydata) - off;
+ else
+ len = kb->u.keys[index - 1].offset - off;
+ *keyp = key = g_malloc (len+1);
+ memcpy (key, kb->u.keydata + off, len);
+ key[len] = 0;
+ }
+
+ CAMEL_KEY_TABLE_UNLOCK (ki, lock);
+
+ camel_block_file_unref_block (ki->blocks, bl);
+
+ return next;
+}
+
+/* ********************************************************************** */
diff --git a/src/camel/camel-partition-table.h b/src/camel/camel-partition-table.h
new file mode 100644
index 000000000..8689d4b32
--- /dev/null
+++ b/src/camel/camel-partition-table.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_PARTITION_TABLE_H
+#define CAMEL_PARTITION_TABLE_H
+
+#include <camel/camel-block-file.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_PARTITION_TABLE \
+ (camel_partition_table_get_type ())
+#define CAMEL_PARTITION_TABLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_PARTITION_TABLE, CamelPartitionTable))
+#define CAMEL_PARTITION_TABLE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_PARTITION_TABLE, CamelPartitionTableClass))
+#define CAMEL_IS_PARTITION_TABLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_PARTITION_TABLE))
+#define CAMEL_IS_PARTITION_TABLE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_PARTITION_TABLE))
+#define CAMEL_PARTITION_TABLE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_PARTITION_TABLE, CamelPartitionTableClass))
+
+#define CAMEL_TYPE_KEY_TABLE \
+ (camel_key_table_get_type ())
+#define CAMEL_KEY_TABLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_KEY_TABLE, CamelKeyTable))
+#define CAMEL_KEY_TABLE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_KEY_TABLE, CamelKeyTableClass))
+#define CAMEL_IS_KEY_TABLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_KEY_TABLE))
+#define CAMEL_IS_KEY_TABLE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_KEY_TABLE))
+#define CAMEL_KEY_TABLE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_KEY_TABLE, CamelKeyTableClass))
+
+G_BEGIN_DECLS
+
+/* ********************************************************************** */
+
+/* CamelPartitionTable - index of key to keyid */
+
+typedef guint32 camel_hash_t; /* a hashed key */
+
+typedef struct _CamelPartitionKey CamelPartitionKey;
+typedef struct _CamelPartitionKeyBlock CamelPartitionKeyBlock;
+typedef struct _CamelPartitionMap CamelPartitionMap;
+typedef struct _CamelPartitionMapBlock CamelPartitionMapBlock;
+
+typedef struct _CamelPartitionTable CamelPartitionTable;
+typedef struct _CamelPartitionTableClass CamelPartitionTableClass;
+typedef struct _CamelPartitionTablePrivate CamelPartitionTablePrivate;
+
+struct _CamelPartitionKey {
+ camel_hash_t hashid;
+ camel_key_t keyid;
+};
+
+struct _CamelPartitionKeyBlock {
+ guint32 used;
+ struct _CamelPartitionKey keys[(CAMEL_BLOCK_SIZE - 4) / sizeof (struct _CamelPartitionKey)];
+};
+
+struct _CamelPartitionMap {
+ camel_hash_t hashid;
+ camel_block_t blockid;
+};
+
+struct _CamelPartitionMapBlock {
+ camel_block_t next;
+ guint32 used;
+ struct _CamelPartitionMap partition[(CAMEL_BLOCK_SIZE - 8) / sizeof (struct _CamelPartitionMap)];
+};
+
+struct _CamelPartitionTable {
+ GObject parent;
+ CamelPartitionTablePrivate *priv;
+
+ CamelBlockFile *blocks;
+ camel_block_t rootid;
+
+ gint (*is_key)(CamelPartitionTable *cpi, const gchar *key, camel_key_t keyid, gpointer data);
+ gpointer is_key_data;
+
+ /* we keep a list of partition blocks active at all times */
+ GQueue partition;
+};
+
+struct _CamelPartitionTableClass {
+ GObjectClass parent;
+};
+
+GType camel_partition_table_get_type (void);
+CamelPartitionTable *
+ camel_partition_table_new (CamelBlockFile *bs,
+ camel_block_t root);
+gint camel_partition_table_sync (CamelPartitionTable *cpi);
+gint camel_partition_table_add (CamelPartitionTable *cpi,
+ const gchar *key,
+ camel_key_t keyid);
+camel_key_t camel_partition_table_lookup (CamelPartitionTable *cpi,
+ const gchar *key);
+gboolean camel_partition_table_remove (CamelPartitionTable *cpi,
+ const gchar *key);
+
+/* ********************************************************************** */
+
+/* CamelKeyTable - index of keyid to key and flag and data mapping */
+
+typedef struct _CamelKeyBlock CamelKeyBlock;
+typedef struct _CamelKeyRootBlock CamelKeyRootBlock;
+
+typedef struct _CamelKeyTable CamelKeyTable;
+typedef struct _CamelKeyTableClass CamelKeyTableClass;
+typedef struct _CamelKeyTablePrivate CamelKeyTablePrivate;
+
+struct _CamelKeyRootBlock {
+ camel_block_t first;
+ camel_block_t last;
+ camel_key_t free; /* free list */
+};
+
+struct _CamelKeyKey {
+ camel_block_t data;
+ guint offset : 10;
+ guint flags : 22;
+};
+
+struct _CamelKeyBlock {
+ camel_block_t next;
+ guint32 used;
+ union {
+ struct _CamelKeyKey keys[(CAMEL_BLOCK_SIZE - 8) / sizeof (struct _CamelKeyKey)];
+ gchar keydata[CAMEL_BLOCK_SIZE - 8];
+ } u;
+};
+
+#define CAMEL_KEY_TABLE_MAX_KEY (128) /* max size of any key */
+
+struct _CamelKeyTable {
+ GObject parent;
+ CamelKeyTablePrivate *priv;
+
+ CamelBlockFile *blocks;
+
+ camel_block_t rootid;
+
+ CamelKeyRootBlock *root;
+ CamelBlock *root_block;
+};
+
+struct _CamelKeyTableClass {
+ GObjectClass parent;
+};
+
+GType camel_key_table_get_type (void);
+CamelKeyTable * camel_key_table_new (CamelBlockFile *bs,
+ camel_block_t root);
+gint camel_key_table_sync (CamelKeyTable *ki);
+camel_key_t camel_key_table_add (CamelKeyTable *ki,
+ const gchar *key,
+ camel_block_t data,
+ guint flags);
+gboolean camel_key_table_set_data (CamelKeyTable *ki,
+ camel_key_t keyid,
+ camel_block_t data);
+gboolean camel_key_table_set_flags (CamelKeyTable *ki,
+ camel_key_t keyid,
+ guint flags,
+ guint set);
+camel_block_t camel_key_table_lookup (CamelKeyTable *ki,
+ camel_key_t keyid,
+ gchar **key,
+ guint *flags);
+camel_key_t camel_key_table_next (CamelKeyTable *ki,
+ camel_key_t next,
+ gchar **keyp,
+ guint *flagsp,
+ camel_block_t *datap);
+
+G_END_DECLS
+
+#endif /* CAMEL_PARTITION_TABLE_H */
diff --git a/src/camel/camel-provider.c b/src/camel/camel-provider.c
new file mode 100644
index 000000000..45d2611e0
--- /dev/null
+++ b/src/camel/camel-provider.c
@@ -0,0 +1,465 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-provider.c: provider framework
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Dan Winship <danw@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+/* FIXME: Shouldn't we add a version number to providers ? */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+#include <gmodule.h>
+
+#include "camel-provider.h"
+#include "camel-string-utils.h"
+#include "camel-vee-store.h"
+#include "camel-win32.h"
+
+/* table of CamelProviderModule's */
+static GHashTable *module_table;
+/* table of CamelProvider's */
+static GHashTable *provider_table;
+static GRecMutex provider_lock;
+
+#define LOCK() (g_rec_mutex_lock(&provider_lock))
+#define UNLOCK() (g_rec_mutex_unlock(&provider_lock))
+
+/* The vfolder provider is always available */
+static CamelProvider vee_provider = {
+ "vfolder",
+ N_("Virtual folder email provider"),
+
+ N_("For reading mail as a query of another set of folders"),
+
+ "vfolder",
+
+ CAMEL_PROVIDER_IS_STORAGE | CAMEL_PROVIDER_IS_LOCAL,
+ CAMEL_URL_NEED_PATH | CAMEL_URL_PATH_IS_ABSOLUTE | CAMEL_URL_FRAGMENT_IS_PATH,
+
+ NULL, /* extra conf */
+
+ NULL, /* port providers */
+
+ /* ... */
+};
+
+static GOnce setup_once = G_ONCE_INIT;
+
+static void
+provider_register_internal (CamelProvider *provider)
+{
+ CamelProviderConfEntry *conf;
+ CamelProviderPortEntry *port;
+ GList *link;
+ gint ii;
+
+ g_return_if_fail (provider != NULL);
+ g_return_if_fail (provider->protocol != NULL);
+
+ LOCK ();
+
+ if (g_hash_table_lookup (provider_table, provider->protocol) != NULL) {
+ g_warning (
+ "Trying to re-register CamelProvider for protocol '%s'",
+ provider->protocol);
+ UNLOCK ();
+ return;
+ }
+
+ /* Translate all strings here */
+#define P_(string) dgettext (provider->translation_domain, string)
+
+ provider->name = P_(provider->name);
+ provider->description = P_(provider->description);
+
+ conf = provider->extra_conf;
+ if (conf != NULL) {
+ for (ii = 0; conf[ii].type != CAMEL_PROVIDER_CONF_END; ii++) {
+ if (conf[ii].text != NULL)
+ conf[ii].text = P_(conf[ii].text);
+ }
+ }
+
+ for (link = provider->authtypes; link != NULL; link = link->next) {
+ CamelServiceAuthType *auth = link->data;
+
+ auth->name = P_(auth->name);
+ auth->description = P_(auth->description);
+ }
+
+ if (provider->port_entries != NULL) {
+ provider->url_flags |= CAMEL_URL_NEED_PORT;
+ port = provider->port_entries;
+ for (ii = 0; port[ii].port != 0; ii++)
+ if (port[ii].desc != NULL)
+ port[ii].desc = P_(port[ii].desc);
+ } else {
+ provider->url_flags &= ~CAMEL_URL_NEED_PORT;
+ }
+
+ g_hash_table_insert (
+ provider_table,
+ (gpointer) provider->protocol, provider);
+
+ UNLOCK ();
+}
+
+static gpointer
+provider_setup (gpointer param)
+{
+ module_table = g_hash_table_new (
+ (GHashFunc) camel_strcase_hash,
+ (GEqualFunc) camel_strcase_equal);
+ provider_table = g_hash_table_new (
+ (GHashFunc) camel_strcase_hash,
+ (GEqualFunc) camel_strcase_equal);
+
+ vee_provider.object_types[CAMEL_PROVIDER_STORE] = CAMEL_TYPE_VEE_STORE;
+ vee_provider.url_hash = camel_url_hash;
+ vee_provider.url_equal = camel_url_equal;
+ provider_register_internal (&vee_provider);
+
+ return NULL;
+}
+
+/**
+ * camel_provider_init:
+ *
+ * Initialize the Camel provider system by reading in the .urls
+ * files in the provider directory and creating a hash table mapping
+ * URLs to module names.
+ *
+ * A .urls file has the same initial prefix as the shared library it
+ * correspond to, and consists of a series of lines containing the URL
+ * protocols that that library handles.
+ *
+ * TODO: This should be pathed?
+ * TODO: This should be plugin-d?
+ **/
+void
+camel_provider_init (void)
+{
+ GDir *dir;
+ const gchar *entry;
+ gchar *p, *name, buf[80];
+ CamelProviderModule *m;
+ static gint loaded = 0;
+ const gchar *provider_dir;
+
+ provider_dir = g_getenv (EDS_CAMEL_PROVIDER_DIR);
+ if (!provider_dir)
+ provider_dir = CAMEL_PROVIDERDIR;
+
+ g_once (&setup_once, provider_setup, NULL);
+
+ if (loaded)
+ return;
+
+ loaded = 1;
+
+ dir = g_dir_open (provider_dir, 0, NULL);
+ if (!dir) {
+ g_warning (
+ "Could not open camel provider directory (%s): %s",
+ provider_dir, g_strerror (errno));
+ return;
+ }
+
+ while ((entry = g_dir_read_name (dir))) {
+ FILE *fp;
+
+ p = strrchr (entry, '.');
+ if (!p || strcmp (p, ".urls") != 0)
+ continue;
+
+ name = g_strdup_printf ("%s/%s", provider_dir, entry);
+ fp = g_fopen (name, "r");
+ if (!fp) {
+ g_warning (
+ "Could not read provider info file %s: %s",
+ name, g_strerror (errno));
+ g_free (name);
+ continue;
+ }
+
+ p = strrchr (name, '.');
+ if (p)
+ strcpy (p, "." G_MODULE_SUFFIX);
+
+ m = g_malloc0 (sizeof (*m));
+ m->path = name;
+
+ while ((fgets (buf, sizeof (buf), fp))) {
+ buf[sizeof (buf) - 1] = '\0';
+ p = strchr (buf, '\n');
+ if (p)
+ *p = '\0';
+
+ if (*buf) {
+ gchar *protocol = g_strdup (buf);
+
+ m->types = g_slist_prepend (m->types, protocol);
+ g_hash_table_insert (module_table, protocol, m);
+ }
+ }
+
+ fclose (fp);
+ }
+
+ g_dir_close (dir);
+}
+
+/**
+ * camel_provider_load:
+ * @path: the path to a shared library
+ * @error: return location for a #GError, or %NULL
+ *
+ * Loads the provider at @path, and calls its initialization function,
+ * passing @session as an argument. The provider should then register
+ * itself with @session.
+ *
+ * Returns: %TRUE on success, %FALSE on failure
+ **/
+gboolean
+camel_provider_load (const gchar *path,
+ GError **error)
+{
+ GModule *module;
+ CamelProvider *(*provider_module_init) (void);
+
+ g_once (&setup_once, provider_setup, NULL);
+
+ if (!g_module_supported ()) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Could not load %s: Module loading "
+ "not supported on this system."), path);
+ return FALSE;
+ }
+
+ module = g_module_open (path, G_MODULE_BIND_LAZY);
+ if (module == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Could not load %s: %s"),
+ path, g_module_error ());
+ return FALSE;
+ }
+
+ if (!g_module_symbol (module, "camel_provider_module_init",
+ (gpointer *) &provider_module_init)) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Could not load %s: No initialization "
+ "code in module."), path);
+ g_module_close (module);
+ return FALSE;
+ }
+
+ provider_module_init ();
+
+ return TRUE;
+}
+
+/**
+ * camel_provider_register:
+ * @provider: provider object
+ *
+ * Registers a provider.
+ **/
+void
+camel_provider_register (CamelProvider *provider)
+{
+ g_once (&setup_once, provider_setup, NULL);
+
+ provider_register_internal (provider);
+}
+
+static gint
+provider_compare (gconstpointer a,
+ gconstpointer b)
+{
+ const CamelProvider *cpa = (const CamelProvider *) a;
+ const CamelProvider *cpb = (const CamelProvider *) b;
+
+ return strcmp (cpa->name, cpb->name);
+}
+
+static void
+add_to_list (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GList **list = user_data;
+
+ *list = g_list_prepend(*list, value);
+}
+
+/**
+ * camel_provider_list:
+ * @load: whether or not to load in providers that are not already loaded
+ *
+ * This returns a list of available providers. If @load is %TRUE, it will
+ * first load in all available providers that haven't yet been loaded.
+ *
+ * Free the returned list with g_list_free(). The #CamelProvider structs
+ * in the list are owned by Camel and should not be modified or freed.
+ *
+ * Returns: (element-type CamelProvider) (transfer container): a #GList of #CamelProvider structs
+ **/
+GList *
+camel_provider_list (gboolean load)
+{
+ GList *list = NULL;
+
+ /* provider_table can be NULL, so initialize it */
+ if (G_UNLIKELY (provider_table == NULL))
+ camel_provider_init ();
+
+ g_return_val_if_fail (provider_table != NULL, NULL);
+
+ LOCK ();
+
+ if (load) {
+ GList *w;
+
+ g_hash_table_foreach (module_table, add_to_list, &list);
+ for (w = list; w; w = w->next) {
+ CamelProviderModule *m = w->data;
+ GError *error = NULL;
+
+ if (!m->loaded) {
+ camel_provider_load (m->path, &error);
+ m->loaded = 1;
+ }
+
+ if (error != NULL) {
+ g_critical (
+ "%s: %s", G_STRFUNC,
+ error->message);
+ g_error_free (error);
+ }
+ }
+ g_list_free (list);
+ list = NULL;
+ }
+
+ g_hash_table_foreach (provider_table, add_to_list, &list);
+
+ UNLOCK ();
+
+ list = g_list_sort (list, provider_compare);
+
+ return list;
+}
+
+/**
+ * camel_provider_get:
+ * @protocol: a #CamelProvider protocol name
+ * @error: return location for a #GError, or %NULL
+ *
+ * Returns the registered #CamelProvider for @protocol, loading it
+ * from disk if necessary. If no #CamelProvider can be found for
+ * @protocol, or the provider module fails to load, the function
+ * sets @error and returns %NULL.
+ *
+ * The returned #CamelProvider is owned by Camel and should not be
+ * modified or freed.
+ *
+ * Returns: a #CamelProvider for %protocol, or %NULL
+ **/
+CamelProvider *
+camel_provider_get (const gchar *protocol,
+ GError **error)
+{
+ CamelProvider *provider = NULL;
+
+ g_return_val_if_fail (protocol != NULL, NULL);
+ g_return_val_if_fail (provider_table != NULL, NULL);
+
+ LOCK ();
+
+ provider = g_hash_table_lookup (provider_table, protocol);
+ if (provider == NULL) {
+ CamelProviderModule *module;
+
+ module = g_hash_table_lookup (module_table, protocol);
+ if (module != NULL && !module->loaded) {
+ module->loaded = 1;
+ if (!camel_provider_load (module->path, error))
+ goto fail;
+ }
+ provider = g_hash_table_lookup (provider_table, protocol);
+ }
+
+ if (provider == NULL)
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_URL_INVALID,
+ _("No provider available for protocol '%s'"),
+ protocol);
+fail:
+ UNLOCK ();
+
+ return provider;
+}
+
+/**
+ * camel_provider_auto_detect:
+ * @provider: camel provider
+ * @url: a #CamelURL
+ * @auto_detected: (inout): output hash table of auto-detected values
+ * @error: return location for a #GError, or %NULL
+ *
+ * After filling in the standard Username/Hostname/Port/Path settings
+ * (which must be set in @url), if the provider supports it, you
+ * may wish to have the provider auto-detect further settings based on
+ * the aformentioned settings.
+ *
+ * If the provider does not support auto-detection, @auto_detected
+ * will be set to %NULL. Otherwise the provider will attempt to
+ * auto-detect whatever it can and file them into @auto_detected. If
+ * for some reason it cannot auto-detect anything (not enough
+ * information provided in @url?) then @auto_detected will be
+ * set to %NULL and an exception may be set to explain why it failed.
+ *
+ * Returns: 0 on success or -1 on fail.
+ **/
+gint
+camel_provider_auto_detect (CamelProvider *provider,
+ CamelURL *url,
+ GHashTable **auto_detected,
+ GError **error)
+{
+ g_return_val_if_fail (provider != NULL, -1);
+
+ if (provider->auto_detect) {
+ return provider->auto_detect (url, auto_detected, error);
+ } else {
+ *auto_detected = NULL;
+ return 0;
+ }
+}
diff --git a/src/camel/camel-provider.h b/src/camel/camel-provider.h
new file mode 100644
index 000000000..0f09117f1
--- /dev/null
+++ b/src/camel/camel-provider.h
@@ -0,0 +1,217 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-provider.h : provider definition
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_PROVIDER_H
+#define CAMEL_PROVIDER_H
+
+#include <glib-object.h>
+
+#include <camel/camel-enums.h>
+#include <camel/camel-object-bag.h>
+#include <camel/camel-url.h>
+
+#define CAMEL_PROVIDER(obj) ((CamelProvider *)(obj))
+
+/**
+ * EDS_CAMEL_PROVIDER_DIR:
+ *
+ * This environment variable configures where the camel
+ * provider modules are loaded from.
+ */
+#define EDS_CAMEL_PROVIDER_DIR "EDS_CAMEL_PROVIDER_DIR"
+
+G_BEGIN_DECLS
+
+extern gchar *camel_provider_type_name[CAMEL_NUM_PROVIDER_TYPES];
+
+/* Flags for url_flags. "ALLOW" means the config dialog will let the
+ * user configure it. "NEED" implies "ALLOW" but means the user must
+ * configure it. Service code can assume that any url part for which
+ * it has set the NEED flag will be set when the service is
+ * created. "HIDE" also implies "ALLOW", but the setting will be
+ * hidden/no widgets created for it.
+ */
+#define CAMEL_URL_PART_USER (1 << 0)
+#define CAMEL_URL_PART_AUTH (1 << 1)
+#define CAMEL_URL_PART_PASSWORD (1 << 2)
+#define CAMEL_URL_PART_HOST (1 << 3)
+#define CAMEL_URL_PART_PORT (1 << 4)
+#define CAMEL_URL_PART_PATH (1 << 5)
+#define CAMEL_URL_PART_PATH_DIR (1 << 6)
+
+#define CAMEL_URL_PART_NEED 8
+#define CAMEL_URL_PART_HIDDEN (CAMEL_URL_PART_NEED + 8)
+
+/* Use these macros to test a provider's url_flags */
+#define CAMEL_PROVIDER_ALLOWS(prov, flags) \
+ (prov->url_flags & (flags | (flags << CAMEL_URL_PART_NEED) | (flags << CAMEL_URL_PART_HIDDEN)))
+#define CAMEL_PROVIDER_NEEDS(prov, flags) \
+ (prov->url_flags & (flags << CAMEL_URL_PART_NEED))
+#define CAMEL_PROVIDER_HIDDEN(prov, flags) \
+ (prov->url_flags & (flags << CAMEL_URL_PART_HIDDEN))
+
+/* Providers use these macros to actually define their url_flags */
+typedef enum {
+ CAMEL_URL_ALLOW_USER = CAMEL_URL_PART_USER,
+ CAMEL_URL_ALLOW_AUTH = CAMEL_URL_PART_AUTH,
+ CAMEL_URL_ALLOW_PASSWORD = CAMEL_URL_PART_PASSWORD,
+ CAMEL_URL_ALLOW_HOST = CAMEL_URL_PART_HOST,
+ CAMEL_URL_ALLOW_PORT = CAMEL_URL_PART_PORT,
+ CAMEL_URL_ALLOW_PATH = CAMEL_URL_PART_PATH,
+
+ CAMEL_URL_NEED_USER = CAMEL_URL_PART_USER << CAMEL_URL_PART_NEED,
+ CAMEL_URL_NEED_AUTH = CAMEL_URL_PART_AUTH << CAMEL_URL_PART_NEED,
+ CAMEL_URL_NEED_PASSWORD = CAMEL_URL_PART_PASSWORD << CAMEL_URL_PART_NEED,
+ CAMEL_URL_NEED_HOST = CAMEL_URL_PART_HOST << CAMEL_URL_PART_NEED,
+ CAMEL_URL_NEED_PORT = CAMEL_URL_PART_PORT << CAMEL_URL_PART_NEED,
+ CAMEL_URL_NEED_PATH = CAMEL_URL_PART_PATH << CAMEL_URL_PART_NEED,
+ CAMEL_URL_NEED_PATH_DIR = CAMEL_URL_PART_PATH_DIR << CAMEL_URL_PART_NEED,
+
+ CAMEL_URL_HIDDEN_USER = CAMEL_URL_PART_USER << CAMEL_URL_PART_HIDDEN,
+ CAMEL_URL_HIDDEN_AUTH = CAMEL_URL_PART_AUTH << CAMEL_URL_PART_HIDDEN,
+ CAMEL_URL_HIDDEN_PASSWORD = CAMEL_URL_PART_PASSWORD << CAMEL_URL_PART_HIDDEN,
+ CAMEL_URL_HIDDEN_HOST = CAMEL_URL_PART_HOST << CAMEL_URL_PART_HIDDEN,
+ CAMEL_URL_HIDDEN_PORT = CAMEL_URL_PART_PORT << CAMEL_URL_PART_HIDDEN,
+ CAMEL_URL_HIDDEN_PATH = CAMEL_URL_PART_PATH << CAMEL_URL_PART_HIDDEN,
+
+ CAMEL_URL_FRAGMENT_IS_PATH = 1 << 30, /* url uses fragment for folder name path, not path */
+ CAMEL_URL_PATH_IS_ABSOLUTE = 1 << 31,
+} CamelProviderURLFlags;
+
+#define CAMEL_PROVIDER_IS_STORE_AND_TRANSPORT(provider) \
+ ((provider != NULL) && \
+ (provider->object_types[CAMEL_PROVIDER_STORE] != G_TYPE_INVALID) && \
+ (provider->object_types[CAMEL_PROVIDER_TRANSPORT] != G_TYPE_INVALID))
+
+/* Generic extra config stuff */
+
+typedef struct {
+ CamelProviderConfType type;
+ const gchar *name, *depname;
+ const gchar *text, *value;
+} CamelProviderConfEntry;
+
+/**
+ * CamelProviderPortEntry:
+ *
+ * Since: 3.2
+ **/
+typedef struct {
+ gint port;
+ const gchar *desc;
+ gboolean is_ssl;
+} CamelProviderPortEntry;
+
+typedef gint (*CamelProviderAutoDetectFunc) (CamelURL *url, GHashTable **auto_detected, GError **error);
+
+typedef struct {
+ /* Provider protocol name (e.g. "imap", "smtp"). */
+ const gchar *protocol;
+
+ /* Provider name as used by people. (May be the same as protocol) */
+ const gchar *name;
+
+ /* Description of the provider. A novice user should be able
+ * to read this description, and the information provided by
+ * an ISP, IS department, etc, and determine whether or not
+ * this provider is relevant to him, and if so, which
+ * information goes with it.
+ */
+ const gchar *description;
+
+ /* The category of message that this provider works with.
+ * (evolution-mail will only list a provider in the store/transport
+ * config dialogs if its domain is "mail".)
+ */
+ const gchar *domain;
+
+ /* Flags describing the provider, flags describing its URLs */
+ CamelProviderFlags flags;
+ CamelProviderURLFlags url_flags;
+
+ /* The ConfEntry and AutoDetect functions will probably be
+ * DEPRECATED in a future release */
+
+ /* Extra configuration information */
+ CamelProviderConfEntry *extra_conf;
+
+ /* The list of CamelProviderPortEntry structs. Each struct contains
+ * port number and a short string description ("Default IMAP port"
+ * or "POP3 over SSL" etc.
+ */
+ CamelProviderPortEntry *port_entries;
+
+ /* auto-detection function */
+ CamelProviderAutoDetectFunc auto_detect;
+
+ /* GType(s) of its store and/or transport. If both are
+ * set, then they are assumed to be linked together and the
+ * transport type can only be used in an account that also
+ * uses the store type (eg, Exchange or NNTP).
+ */
+ GType object_types[CAMEL_NUM_PROVIDER_TYPES];
+
+ /* GList of CamelServiceAuthTypes the provider supports */
+ GList *authtypes;
+
+ GHashFunc url_hash;
+ GEqualFunc url_equal;
+
+ /* gettext translation domain (NULL for providers in the
+ * evolution source tree).
+ */
+ const gchar *translation_domain;
+
+ /* Private to the provider */
+ gpointer priv;
+} CamelProvider;
+
+typedef struct _CamelProviderModule CamelProviderModule;
+
+struct _CamelProviderModule {
+ gchar *path;
+ GSList *types;
+ guint loaded : 1;
+};
+
+void camel_provider_init (void);
+gboolean camel_provider_load (const gchar *path,
+ GError **error);
+void camel_provider_register (CamelProvider *provider);
+GList * camel_provider_list (gboolean load);
+CamelProvider * camel_provider_get (const gchar *protocol,
+ GError **error);
+
+/* This is defined by each module, not by camel-provider.c. */
+void camel_provider_module_init (void);
+
+gint camel_provider_auto_detect (CamelProvider *provider,
+ CamelURL *url,
+ GHashTable **auto_detected,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_PROVIDER_H */
diff --git a/src/camel/camel-sasl-anonymous.c b/src/camel/camel-sasl-anonymous.c
new file mode 100644
index 000000000..62c1fe0a3
--- /dev/null
+++ b/src/camel/camel-sasl-anonymous.c
@@ -0,0 +1,155 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-internet-address.h"
+#include "camel-sasl-anonymous.h"
+
+static CamelServiceAuthType sasl_anonymous_auth_type = {
+ N_("Anonymous"),
+
+ N_("This option will connect to the server using an anonymous login."),
+
+ "ANONYMOUS",
+ FALSE
+};
+
+G_DEFINE_TYPE (CamelSaslAnonymous, camel_sasl_anonymous, CAMEL_TYPE_SASL)
+
+static void
+sasl_anonymous_finalize (GObject *object)
+{
+ CamelSaslAnonymous *sasl = CAMEL_SASL_ANONYMOUS (object);
+
+ g_free (sasl->trace_info);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_sasl_anonymous_parent_class)->finalize (object);
+}
+
+static GByteArray *
+sasl_anonymous_challenge_sync (CamelSasl *sasl,
+ GByteArray *token,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSaslAnonymous *sasl_anon = CAMEL_SASL_ANONYMOUS (sasl);
+ CamelInternetAddress *cia;
+ GByteArray *ret = NULL;
+
+ if (token) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Authentication failed."));
+ return NULL;
+ }
+
+ switch (sasl_anon->type) {
+ case CAMEL_SASL_ANON_TRACE_EMAIL:
+ cia = camel_internet_address_new ();
+ if (camel_internet_address_add (cia, NULL, sasl_anon->trace_info) != 1) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Invalid email address trace information:\n%s"),
+ sasl_anon->trace_info);
+ g_object_unref (cia);
+ return NULL;
+ }
+ g_object_unref (cia);
+ ret = g_byte_array_new ();
+ g_byte_array_append (ret, (guint8 *) sasl_anon->trace_info, strlen (sasl_anon->trace_info));
+ break;
+ case CAMEL_SASL_ANON_TRACE_OPAQUE:
+ if (strchr (sasl_anon->trace_info, '@')) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Invalid opaque trace information:\n%s"),
+ sasl_anon->trace_info);
+ return NULL;
+ }
+ ret = g_byte_array_new ();
+ g_byte_array_append (ret, (guint8 *) sasl_anon->trace_info, strlen (sasl_anon->trace_info));
+ break;
+ case CAMEL_SASL_ANON_TRACE_EMPTY:
+ ret = g_byte_array_new ();
+ break;
+ default:
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Invalid trace information:\n%s"),
+ sasl_anon->trace_info);
+ return NULL;
+ }
+
+ camel_sasl_set_authenticated (sasl, TRUE);
+ return ret;
+}
+
+static void
+camel_sasl_anonymous_class_init (CamelSaslAnonymousClass *class)
+{
+ GObjectClass *object_class;
+ CamelSaslClass *sasl_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = sasl_anonymous_finalize;
+
+ sasl_class = CAMEL_SASL_CLASS (class);
+ sasl_class->auth_type = &sasl_anonymous_auth_type;
+ sasl_class->challenge_sync = sasl_anonymous_challenge_sync;
+}
+
+static void
+camel_sasl_anonymous_init (CamelSaslAnonymous *sasl_anonymous)
+{
+}
+
+/**
+ * camel_sasl_anonymous_new:
+ * @type: trace type
+ * @trace_info: trace info
+ *
+ * Create a new #CamelSaslAnonymous object.
+ *
+ * Returns: a new #CamelSasl object
+ **/
+CamelSasl *
+camel_sasl_anonymous_new (CamelSaslAnonTraceType type,
+ const gchar *trace_info)
+{
+ CamelSaslAnonymous *sasl_anon;
+
+ if (!trace_info && type != CAMEL_SASL_ANON_TRACE_EMPTY)
+ return NULL;
+
+ sasl_anon = g_object_new (CAMEL_TYPE_SASL_ANONYMOUS, NULL);
+ sasl_anon->trace_info = g_strdup (trace_info);
+ sasl_anon->type = type;
+
+ return CAMEL_SASL (sasl_anon);
+}
diff --git a/src/camel/camel-sasl-anonymous.h b/src/camel/camel-sasl-anonymous.h
new file mode 100644
index 000000000..56c570ef6
--- /dev/null
+++ b/src/camel/camel-sasl-anonymous.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SASL_ANONYMOUS_H
+#define CAMEL_SASL_ANONYMOUS_H
+
+#include <camel/camel-enums.h>
+#include <camel/camel-sasl.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SASL_ANONYMOUS \
+ (camel_sasl_anonymous_get_type ())
+#define CAMEL_SASL_ANONYMOUS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SASL_ANONYMOUS, CamelSaslAnonymous))
+#define CAMEL_SASL_ANONYMOUS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SASL_ANONYMOUS, CamelSaslAnonymousClass))
+#define CAMEL_IS_SASL_ANONYMOUS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SASL_ANONYMOUS))
+#define CAMEL_IS_SASL_ANONYMOUS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SASL_ANONYMOUS))
+#define CAMEL_SASL_ANONYMOUS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SASL_ANONYMOUS, CamelSaslAnonymousClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSaslAnonymous CamelSaslAnonymous;
+typedef struct _CamelSaslAnonymousClass CamelSaslAnonymousClass;
+
+struct _CamelSaslAnonymous {
+ CamelSasl parent;
+
+ gchar *trace_info;
+ CamelSaslAnonTraceType type;
+};
+
+struct _CamelSaslAnonymousClass {
+ CamelSaslClass parent_class;
+};
+
+GType camel_sasl_anonymous_get_type (void);
+
+/* public methods */
+CamelSasl *camel_sasl_anonymous_new (CamelSaslAnonTraceType type, const gchar *trace_info);
+
+G_END_DECLS
+
+#endif /* CAMEL_SASL_ANONYMOUS_H */
diff --git a/src/camel/camel-sasl-cram-md5.c b/src/camel/camel-sasl-cram-md5.c
new file mode 100644
index 000000000..3ca12b675
--- /dev/null
+++ b/src/camel/camel-sasl-cram-md5.c
@@ -0,0 +1,163 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-mime-utils.h"
+#include "camel-network-settings.h"
+#include "camel-sasl-cram-md5.h"
+#include "camel-service.h"
+
+#define CAMEL_SASL_CRAM_MD5_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SASL_CRAM_MD5, CamelSaslCramMd5Private))
+
+struct _CamelSaslCramMd5Private {
+ gint placeholder; /* allow for future expansion */
+};
+
+static CamelServiceAuthType sasl_cram_md5_auth_type = {
+ N_("CRAM-MD5"),
+
+ N_("This option will connect to the server using a "
+ "secure CRAM-MD5 password, if the server supports it."),
+
+ "CRAM-MD5",
+ TRUE
+};
+
+G_DEFINE_TYPE (CamelSaslCramMd5, camel_sasl_cram_md5, CAMEL_TYPE_SASL)
+
+/* CRAM-MD5 algorithm:
+ * MD5 ((passwd XOR opad), MD5 ((passwd XOR ipad), timestamp))
+ */
+
+static GByteArray *
+sasl_cram_md5_challenge_sync (CamelSasl *sasl,
+ GByteArray *token,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ GChecksum *checksum;
+ guint8 *digest;
+ gsize length;
+ const gchar *hex;
+ const gchar *password;
+ GByteArray *ret = NULL;
+ guchar ipad[64];
+ guchar opad[64];
+ gchar *user;
+ gint i, pw_len;
+
+ /* Need to wait for the server */
+ if (!token)
+ return NULL;
+
+ service = camel_sasl_get_service (sasl);
+
+ settings = camel_service_ref_settings (service);
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ user = camel_network_settings_dup_user (network_settings);
+
+ g_object_unref (settings);
+
+ g_return_val_if_fail (user != NULL, NULL);
+
+ password = camel_service_get_password (service);
+ g_return_val_if_fail (password != NULL, NULL);
+
+ length = g_checksum_type_get_length (G_CHECKSUM_MD5);
+ digest = g_alloca (length);
+
+ memset (ipad, 0, sizeof (ipad));
+ memset (opad, 0, sizeof (opad));
+
+ pw_len = strlen (password);
+ if (pw_len <= 64) {
+ memcpy (ipad, password, pw_len);
+ memcpy (opad, password, pw_len);
+ } else {
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (guchar *) password, pw_len);
+ g_checksum_get_digest (checksum, digest, &length);
+ g_checksum_free (checksum);
+
+ memcpy (ipad, digest, length);
+ memcpy (opad, digest, length);
+ }
+
+ for (i = 0; i < 64; i++) {
+ ipad[i] ^= 0x36;
+ opad[i] ^= 0x5c;
+ }
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (guchar *) ipad, sizeof (ipad));
+ g_checksum_update (checksum, (guchar *) token->data, token->len);
+ g_checksum_get_digest (checksum, digest, &length);
+ g_checksum_free (checksum);
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (guchar *) opad, sizeof (opad));
+ g_checksum_update (checksum, (guchar *) digest, length);
+
+ /* String is owned by the checksum. */
+ hex = g_checksum_get_string (checksum);
+
+ ret = g_byte_array_new ();
+ g_byte_array_append (ret, (guint8 *) user, strlen (user));
+ g_byte_array_append (ret, (guint8 *) " ", 1);
+ g_byte_array_append (ret, (guint8 *) hex, strlen (hex));
+
+ g_checksum_free (checksum);
+
+ camel_sasl_set_authenticated (sasl, TRUE);
+
+ g_free (user);
+
+ return ret;
+}
+
+static void
+camel_sasl_cram_md5_class_init (CamelSaslCramMd5Class *class)
+{
+ CamelSaslClass *sasl_class;
+
+ g_type_class_add_private (class, sizeof (CamelSaslCramMd5Private));
+
+ sasl_class = CAMEL_SASL_CLASS (class);
+ sasl_class->auth_type = &sasl_cram_md5_auth_type;
+ sasl_class->challenge_sync = sasl_cram_md5_challenge_sync;
+}
+
+static void
+camel_sasl_cram_md5_init (CamelSaslCramMd5 *sasl)
+{
+ sasl->priv = CAMEL_SASL_CRAM_MD5_GET_PRIVATE (sasl);
+}
diff --git a/src/camel/camel-sasl-cram-md5.h b/src/camel/camel-sasl-cram-md5.h
new file mode 100644
index 000000000..352c6f3af
--- /dev/null
+++ b/src/camel/camel-sasl-cram-md5.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SASL_CRAM_MD5_H
+#define CAMEL_SASL_CRAM_MD5_H
+
+#include <camel/camel-sasl.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SASL_CRAM_MD5 \
+ (camel_sasl_cram_md5_get_type ())
+#define CAMEL_SASL_CRAM_MD5(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SASL_CRAM_MD5, CamelSaslCramMd5))
+#define CAMEL_SASL_CRAM_MD5_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SASL_CRAM_MD5, CamelSaslCramMd5Class))
+#define CAMEL_IS_SASL_CRAM_MD5(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SASL_CRAM_MD5))
+#define CAMEL_IS_SASL_CRAM_MD5_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SASL_CRAM_MD5))
+#define CAMEL_SASL_CRAM_MD5_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SASL_CRAM_MD5, CamelSaslCramMd5Class))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSaslCramMd5 CamelSaslCramMd5;
+typedef struct _CamelSaslCramMd5Class CamelSaslCramMd5Class;
+typedef struct _CamelSaslCramMd5Private CamelSaslCramMd5Private;
+
+struct _CamelSaslCramMd5 {
+ CamelSasl parent;
+ CamelSaslCramMd5Private *priv;
+};
+
+struct _CamelSaslCramMd5Class {
+ CamelSaslClass parent_class;
+};
+
+GType camel_sasl_cram_md5_get_type (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_SASL_CRAM_MD5_H */
diff --git a/src/camel/camel-sasl-digest-md5.c b/src/camel/camel-sasl-digest-md5.c
new file mode 100644
index 000000000..0312ba0cd
--- /dev/null
+++ b/src/camel/camel-sasl-digest-md5.c
@@ -0,0 +1,983 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-charset-map.h"
+#include "camel-iconv.h"
+#include "camel-mime-utils.h"
+#include "camel-net-utils.h"
+#include "camel-network-settings.h"
+#ifdef G_OS_WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#ifdef HAVE_WSPIAPI_H
+#include <wspiapi.h>
+#endif
+#endif
+#include "camel-sasl-digest-md5.h"
+
+#define d(x)
+
+#define PARANOID(x) x
+
+#define CAMEL_SASL_DIGEST_MD5_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SASL_DIGEST_MD5, CamelSaslDigestMd5Private))
+
+/* Implements rfc2831 */
+
+static CamelServiceAuthType sasl_digest_md5_auth_type = {
+ 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
+};
+
+enum {
+ STATE_AUTH,
+ STATE_FINAL
+};
+
+typedef struct {
+ const gchar *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 {
+ gchar *name;
+ gchar *value;
+};
+
+struct _DigestChallenge {
+ GPtrArray *realms;
+ gchar *nonce;
+ guint qop;
+ gboolean stale;
+ gint32 maxbuf;
+ gchar *charset;
+ gchar *algorithm;
+ guint cipher;
+ GList *params;
+};
+
+struct _DigestURI {
+ gchar *type;
+ gchar *host;
+ gchar *name;
+};
+
+struct _DigestResponse {
+ gchar *username;
+ gchar *realm;
+ gchar *nonce;
+ gchar *cnonce;
+ gchar nc[9];
+ guint qop;
+ struct _DigestURI *uri;
+ gchar resp[33];
+ guint32 maxbuf;
+ gchar *charset;
+ guint cipher;
+ gchar *authzid;
+ gchar *param;
+};
+
+struct _CamelSaslDigestMd5Private {
+ struct _DigestChallenge *challenge;
+ struct _DigestResponse *response;
+ gint state;
+};
+
+G_DEFINE_TYPE (CamelSaslDigestMd5, camel_sasl_digest_md5, CAMEL_TYPE_SASL)
+
+static void
+decode_lwsp (const gchar **in)
+{
+ const gchar *inptr = *in;
+
+ while (isspace (*inptr))
+ inptr++;
+
+ *in = inptr;
+}
+
+static gchar *
+decode_quoted_string (const gchar **in)
+{
+ const gchar *inptr = *in;
+ gchar *out = NULL, *outptr;
+ gint outlen;
+ gint c;
+
+ decode_lwsp (&inptr);
+ if (*inptr == '"') {
+ const gchar *intmp;
+ gint 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 gchar *
+decode_token (const gchar **in)
+{
+ const gchar *inptr = *in;
+ const gchar *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 gchar *
+decode_value (const gchar **in)
+{
+ const gchar *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 gchar *tokens)
+{
+ GList *params = NULL;
+ struct _param *param;
+ const gchar *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 gchar *name)
+{
+ gint 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 gchar *
+type_to_string (DataType *dtype,
+ guint type)
+{
+ gint 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 gchar *tokens,
+ gboolean *abort)
+{
+ struct _DigestChallenge *challenge = NULL;
+ GList *params, *p;
+ const gchar *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;
+ gint type;
+
+ type = get_digest_arg (param->name);
+ switch (type) {
+ case DIGEST_REALM:
+ for (ptr = param->value; ptr && *ptr; ) {
+ gchar *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; ) {
+ gchar *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; ) {
+ gchar *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 gchar *
+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 gchar *passwd,
+ gboolean client,
+ guchar out[33])
+{
+ GString *buffer;
+ GChecksum *checksum;
+ guint8 *digest;
+ gsize length;
+ gchar *hex_a1;
+ gchar *hex_a2;
+ gchar *hex_kd;
+ gchar *uri;
+
+ buffer = g_string_sized_new (256);
+ length = g_checksum_type_get_length (G_CHECKSUM_MD5);
+ digest = g_alloca (length);
+
+ /* Compute A1. */
+
+ g_string_append (buffer, resp->username);
+ g_string_append_c (buffer, ':');
+ g_string_append (buffer, resp->realm);
+ g_string_append_c (buffer, ':');
+ g_string_append (buffer, passwd);
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (
+ checksum, (const guchar *) buffer->str, buffer->len);
+ g_checksum_get_digest (checksum, digest, &length);
+ g_checksum_free (checksum);
+
+ /* Clear the buffer. */
+ g_string_truncate (buffer, 0);
+
+ g_string_append_len (buffer, (gchar *) digest, length);
+ g_string_append_c (buffer, ':');
+ g_string_append (buffer, resp->nonce);
+ g_string_append_c (buffer, ':');
+ g_string_append (buffer, resp->cnonce);
+ if (resp->authzid != NULL) {
+ g_string_append_c (buffer, ':');
+ g_string_append (buffer, resp->authzid);
+ }
+
+ hex_a1 = g_compute_checksum_for_string (
+ G_CHECKSUM_MD5, buffer->str, buffer->len);
+
+ /* Clear the buffer. */
+ g_string_truncate (buffer, 0);
+
+ /* Compute A2. */
+
+ if (client) {
+ /* We are calculating the client response. */
+ g_string_append (buffer, "AUTHENTICATE:");
+ } else {
+ /* We are calculating the server rspauth. */
+ g_string_append_c (buffer, ':');
+ }
+
+ uri = digest_uri_to_string (resp->uri);
+ g_string_append (buffer, uri);
+ g_free (uri);
+
+ if (resp->qop == QOP_AUTH_INT || resp->qop == QOP_AUTH_CONF)
+ g_string_append (buffer, ":00000000000000000000000000000000");
+
+ hex_a2 = g_compute_checksum_for_string (
+ G_CHECKSUM_MD5, buffer->str, buffer->len);
+
+ /* Clear the buffer. */
+ g_string_truncate (buffer, 0);
+
+ /* Compute KD. */
+
+ g_string_append (buffer, hex_a1);
+ g_string_append_c (buffer, ':');
+ g_string_append (buffer, resp->nonce);
+ g_string_append_c (buffer, ':');
+ g_string_append_len (buffer, resp->nc, 8);
+ g_string_append_c (buffer, ':');
+ g_string_append (buffer, resp->cnonce);
+ g_string_append_c (buffer, ':');
+ g_string_append (buffer, qop_to_string (resp->qop));
+ g_string_append_c (buffer, ':');
+ g_string_append (buffer, hex_a2);
+
+ hex_kd = g_compute_checksum_for_string (
+ G_CHECKSUM_MD5, buffer->str, buffer->len);
+
+ g_strlcpy ((gchar *) out, hex_kd, 33);
+
+ g_free (hex_a1);
+ g_free (hex_a2);
+ g_free (hex_kd);
+
+ g_string_free (buffer, TRUE);
+}
+
+static struct _DigestResponse *
+generate_response (struct _DigestChallenge *challenge,
+ const gchar *host,
+ const gchar *protocol,
+ const gchar *user,
+ const gchar *passwd)
+{
+ struct _DigestResponse *resp;
+ struct _DigestURI *uri;
+ GChecksum *checksum;
+ guint8 *digest;
+ gsize length;
+ gchar *bgen;
+
+ length = g_checksum_type_get_length (G_CHECKSUM_MD5);
+ digest = g_alloca (length);
+
+ 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",
+ (gpointer) resp,
+ (gulong) getpid (),
+ (gulong) time (NULL));
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (guchar *) bgen, -1);
+ g_checksum_get_digest (checksum, digest, &length);
+ g_checksum_free (checksum);
+ g_free (bgen);
+
+ /* take our recommended 64 bits of entropy */
+ resp->cnonce = g_base64_encode ((guchar *) digest, 8);
+
+ /* we don't support re-auth so the nonce count is always 1 */
+ g_strlcpy (resp->nc, "00000001", sizeof (resp->nc));
+
+ /* 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, (guchar *) resp->resp);
+
+ return resp;
+}
+
+static GByteArray *
+digest_response (struct _DigestResponse *resp)
+{
+ GByteArray *buffer;
+ const gchar *str;
+ gchar *buf;
+
+ buffer = g_byte_array_new ();
+ g_byte_array_append (buffer, (guint8 *) "username=\"", 10);
+ if (resp->charset) {
+ /* Encode the username using the requested charset */
+ gchar *username, *outbuf;
+ const gchar *charset;
+ gsize len, outlen;
+ const gchar *inbuf;
+ GIConv cd;
+
+ charset = camel_iconv_locale_charset ();
+ if (!charset)
+ charset = "iso-8859-1";
+
+ cd = camel_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 == (GIConv) -1 || camel_iconv (cd, &inbuf, &len, &outbuf, &outlen) == (gsize) -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 != (GIConv) -1)
+ camel_iconv_close (cd);
+
+ g_byte_array_append (buffer, (guint8 *) username, strlen (username));
+ g_free (username);
+ } else {
+ g_byte_array_append (buffer, (guint8 *) resp->username, strlen (resp->username));
+ }
+
+ g_byte_array_append (buffer, (guint8 *) "\",realm=\"", 9);
+ g_byte_array_append (buffer, (guint8 *) resp->realm, strlen (resp->realm));
+
+ g_byte_array_append (buffer, (guint8 *) "\",nonce=\"", 9);
+ g_byte_array_append (buffer, (guint8 *) resp->nonce, strlen (resp->nonce));
+
+ g_byte_array_append (buffer, (guint8 *) "\",cnonce=\"", 10);
+ g_byte_array_append (buffer, (guint8 *) resp->cnonce, strlen (resp->cnonce));
+
+ g_byte_array_append (buffer, (guint8 *) "\",nc=", 5);
+ g_byte_array_append (buffer, (guint8 *) resp->nc, 8);
+
+ g_byte_array_append (buffer, (guint8 *) ",qop=", 5);
+ str = qop_to_string (resp->qop);
+ g_byte_array_append (buffer, (guint8 *) str, strlen (str));
+
+ g_byte_array_append (buffer, (guint8 *) ",digest-uri=\"", 13);
+ buf = digest_uri_to_string (resp->uri);
+ g_byte_array_append (buffer, (guint8 *) buf, strlen (buf));
+ g_free (buf);
+
+ g_byte_array_append (buffer, (guint8 *) "\",response=", 11);
+ g_byte_array_append (buffer, (guint8 *) resp->resp, 32);
+
+ if (resp->maxbuf > 0) {
+ g_byte_array_append (buffer, (guint8 *) ",maxbuf=", 8);
+ buf = g_strdup_printf ("%u", resp->maxbuf);
+ g_byte_array_append (buffer, (guint8 *) buf, strlen (buf));
+ g_free (buf);
+ }
+
+ if (resp->charset) {
+ g_byte_array_append (buffer, (guint8 *) ",charset=", 9);
+ g_byte_array_append (buffer, (guint8 *) resp->charset, strlen ((gchar *) resp->charset));
+ }
+
+ if (resp->cipher != CIPHER_INVALID) {
+ str = cipher_to_string (resp->cipher);
+ if (str) {
+ g_byte_array_append (buffer, (guint8 *) ",cipher=\"", 9);
+ g_byte_array_append (buffer, (guint8 *) str, strlen (str));
+ g_byte_array_append (buffer, (guint8 *) "\"", 1);
+ }
+ }
+
+ if (resp->authzid) {
+ g_byte_array_append (buffer, (guint8 *) ",authzid=\"", 10);
+ g_byte_array_append (buffer, (guint8 *) resp->authzid, strlen (resp->authzid));
+ g_byte_array_append (buffer, (guint8 *) "\"", 1);
+ }
+
+ return buffer;
+}
+
+static void
+sasl_digest_md5_finalize (GObject *object)
+{
+ CamelSaslDigestMd5 *sasl = CAMEL_SASL_DIGEST_MD5 (object);
+ struct _DigestChallenge *c = sasl->priv->challenge;
+ struct _DigestResponse *r = sasl->priv->response;
+ GList *p;
+ gint i;
+
+ if (c != NULL) {
+ 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);
+ }
+
+ if (r != NULL) {
+ 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);
+ }
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_sasl_digest_md5_parent_class)->finalize (object);
+}
+
+static GByteArray *
+sasl_digest_md5_challenge_sync (CamelSasl *sasl,
+ GByteArray *token,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (sasl);
+ struct _CamelSaslDigestMd5Private *priv = sasl_digest->priv;
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ struct _param *rspauth;
+ GByteArray *ret = NULL;
+ gboolean abort = FALSE;
+ const gchar *ptr;
+ guchar out[33];
+ gchar *tokens;
+ struct addrinfo *ai, hints;
+ const gchar *service_name;
+ const gchar *password;
+ gchar *host;
+ gchar *user;
+
+ /* Need to wait for the server */
+ if (!token)
+ return NULL;
+
+ service = camel_sasl_get_service (sasl);
+ service_name = camel_sasl_get_service_name (sasl);
+
+ settings = camel_service_ref_settings (service);
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ user = camel_network_settings_dup_user (network_settings);
+
+ g_object_unref (settings);
+
+ g_return_val_if_fail (user != NULL, NULL);
+
+ if (!host || !*host) {
+ g_free (host);
+ host = g_strdup ("localhost");
+ }
+
+ password = camel_service_get_password (service);
+ g_return_val_if_fail (password != NULL, NULL);
+
+ switch (priv->state) {
+ case STATE_AUTH:
+ if (token->len > 2048) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Server challenge too long (>2048 octets)"));
+ goto exit;
+ }
+
+ tokens = g_strndup ((gchar *) token->data, token->len);
+ priv->challenge = parse_server_challenge (tokens, &abort);
+ g_free (tokens);
+ if (!priv->challenge || abort) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Server challenge invalid\n"));
+ goto exit;
+ }
+
+ if (priv->challenge->qop == QOP_INVALID) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Server challenge contained invalid "
+ "\"Quality of Protection\" token"));
+ goto exit;
+ }
+
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_flags = AI_CANONNAME;
+ ai = camel_getaddrinfo (
+ host, NULL, &hints, cancellable, NULL);
+ if (ai && ai->ai_canonname)
+ ptr = ai->ai_canonname;
+ else
+ ptr = "localhost.localdomain";
+
+ priv->response = generate_response (
+ priv->challenge, ptr, service_name,
+ user, password);
+ if (ai)
+ camel_freeaddrinfo (ai);
+ ret = digest_response (priv->response);
+
+ break;
+ case STATE_FINAL:
+ if (token->len)
+ tokens = g_strndup ((gchar *) token->data, token->len);
+ else
+ tokens = NULL;
+
+ if (!tokens || !*tokens) {
+ g_free (tokens);
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Server response did not contain "
+ "authorization data"));
+ goto exit;
+ }
+
+ 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);
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Server response contained incomplete "
+ "authorization data"));
+ goto exit;
+ }
+
+ compute_response (priv->response, password, FALSE, out);
+ if (memcmp (out, rspauth->value, 32) != 0) {
+ g_free (rspauth->name);
+ g_free (rspauth->value);
+ g_free (rspauth);
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Server response does not match"));
+ camel_sasl_set_authenticated (sasl, TRUE);
+ goto exit;
+ }
+
+ g_free (rspauth->name);
+ g_free (rspauth->value);
+ g_free (rspauth);
+
+ ret = g_byte_array_new ();
+
+ camel_sasl_set_authenticated (sasl, TRUE);
+ default:
+ break;
+ }
+
+ priv->state++;
+
+exit:
+ g_free (host);
+ g_free (user);
+
+ return ret;
+}
+
+static void
+camel_sasl_digest_md5_class_init (CamelSaslDigestMd5Class *class)
+{
+ GObjectClass *object_class;
+ CamelSaslClass *sasl_class;
+
+ g_type_class_add_private (class, sizeof (CamelSaslDigestMd5Private));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = sasl_digest_md5_finalize;
+
+ sasl_class = CAMEL_SASL_CLASS (class);
+ sasl_class->auth_type = &sasl_digest_md5_auth_type;
+ sasl_class->challenge_sync = sasl_digest_md5_challenge_sync;
+}
+
+static void
+camel_sasl_digest_md5_init (CamelSaslDigestMd5 *sasl)
+{
+ sasl->priv = CAMEL_SASL_DIGEST_MD5_GET_PRIVATE (sasl);
+}
diff --git a/src/camel/camel-sasl-digest-md5.h b/src/camel/camel-sasl-digest-md5.h
new file mode 100644
index 000000000..9e30fa4b3
--- /dev/null
+++ b/src/camel/camel-sasl-digest-md5.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SASL_DIGEST_MD5_H
+#define CAMEL_SASL_DIGEST_MD5_H
+
+#include <sys/types.h>
+#include <camel/camel-sasl.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SASL_DIGEST_MD5 \
+ (camel_sasl_digest_md5_get_type ())
+#define CAMEL_SASL_DIGEST_MD5(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SASL_DIGEST_MD5, CamelSaslDigestMd5))
+#define CAMEL_SASL_DIGEST_MD5_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SASL_DIGEST_MD5, CamelSaslDigestMd5Class))
+#define CAMEL_IS_SASL_DIGEST_MD5(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SASL_DIGEST_MD5))
+#define CAMEL_IS_SASL_DIGEST_MD5_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SASL_DIGEST_MD5))
+#define CAMEL_SASL_DIGEST_MD5_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SASL_DIGEST_MD5, CamelSaslDigestMd5Class))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSaslDigestMd5 CamelSaslDigestMd5;
+typedef struct _CamelSaslDigestMd5Class CamelSaslDigestMd5Class;
+typedef struct _CamelSaslDigestMd5Private CamelSaslDigestMd5Private;
+
+struct _CamelSaslDigestMd5 {
+ CamelSasl parent;
+ CamelSaslDigestMd5Private *priv;
+};
+
+struct _CamelSaslDigestMd5Class {
+ CamelSaslClass parent_class;
+};
+
+GType camel_sasl_digest_md5_get_type (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_SASL_DIGEST_MD5_H */
diff --git a/src/camel/camel-sasl-gssapi.c b/src/camel/camel-sasl-gssapi.c
new file mode 100644
index 000000000..08d429dd6
--- /dev/null
+++ b/src/camel/camel-sasl-gssapi.c
@@ -0,0 +1,610 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+/* If building without Kerberos support, this class is an empty shell. */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+
+#include <string.h>
+#include <sys/types.h>
+
+#ifndef _WIN32
+#include <netdb.h>
+#include <sys/socket.h>
+#endif
+
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-net-utils.h"
+#include "camel-network-settings.h"
+#include "camel-sasl-gssapi.h"
+#include "camel-session.h"
+
+#ifdef HAVE_KRB5
+
+#ifdef HAVE_HEIMDAL_KRB5
+#include <krb5.h>
+#else
+#include <krb5/krb5.h>
+#endif /* HAVE_HEIMDAL_KRB5 */
+
+#ifdef HAVE_ET_COM_ERR_H
+#include <et/com_err.h>
+#else
+#ifdef HAVE_COM_ERR_H
+#include <com_err.h>
+#endif /* HAVE_COM_ERR_H */
+#endif /* HAVE_ET_COM_ERR_H */
+
+#ifdef HAVE_MIT_KRB5
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_generic.h>
+#endif /* HAVE_MIT_KRB5 */
+
+#ifdef HAVE_HEIMDAL_KRB5
+#include <gssapi.h>
+#else
+#ifdef HAVE_SUN_KRB5
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_ext.h>
+extern gss_OID gss_nt_service_name;
+#endif /* HAVE_SUN_KRB5 */
+#endif /* HAVE_HEIMDAL_KRB5 */
+
+#define CAMEL_SASL_GSSAPI_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SASL_GSSAPI, CamelSaslGssapiPrivate))
+
+static const char spnego_OID[] = "\x2b\x06\x01\x05\x05\x02";
+static const gss_OID_desc gss_mech_spnego = {
+ 6,
+ (gpointer) &spnego_OID
+};
+
+#ifndef GSS_C_OID_KRBV5_DES
+#define GSS_C_OID_KRBV5_DES GSS_C_NO_OID
+#endif
+
+#define DBUS_PATH "/org/gnome/KrbAuthDialog"
+#define DBUS_INTERFACE "org.gnome.KrbAuthDialog"
+#define DBUS_METHOD "org.gnome.KrbAuthDialog.acquireTgt"
+
+static CamelServiceAuthType sasl_gssapi_auth_type = {
+ 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 {
+ gint state;
+ gss_ctx_id_t ctx;
+ gss_name_t target;
+ gchar *override_host;
+ gchar *override_user;
+ gss_OID mech, used_mech;
+};
+
+#endif /* HAVE_KRB5 */
+
+G_DEFINE_TYPE (CamelSaslGssapi, camel_sasl_gssapi, CAMEL_TYPE_SASL)
+
+#ifdef HAVE_KRB5
+
+static void
+gssapi_set_mechanism_exception (gss_OID mech, OM_uint32 minor, GError **error)
+{
+ OM_uint32 tmajor, tminor, message_status = 0;
+ char *message = NULL;
+
+ do {
+ char *message_part;
+ char *new_message;
+ gss_buffer_desc status_string;
+
+ tmajor = gss_display_status (&tminor, minor, GSS_C_MECH_CODE,
+ mech, &message_status,
+ &status_string);
+
+ if (tmajor != GSS_S_COMPLETE) {
+ message_part = g_strdup_printf (
+ _("(Unknown GSSAPI mechanism code: %x)"),
+ minor);
+ message_status = 0;
+ } else {
+ message_part = g_strdup (status_string.value);
+ gss_release_buffer (&tminor, &status_string);
+ }
+ if (message) {
+ new_message = g_strconcat (message, message_part, NULL);
+ free (message_part);
+ } else {
+ new_message = message_part;
+ }
+ g_free (message);
+ message = new_message;
+ } while (message_status != 0);
+
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ "%s", message);
+
+ g_free (message);
+}
+
+static void
+gssapi_set_exception (gss_OID mech, OM_uint32 major, OM_uint32 minor,
+ GError **error)
+{
+ const gchar *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:
+ return gssapi_set_mechanism_exception (mech, minor, error);
+ break;
+ default:
+ str = _("Bad authentication response from server.");
+ }
+
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ "%s", str);
+}
+
+static void
+sasl_gssapi_finalize (GObject *object)
+{
+ CamelSaslGssapi *sasl = CAMEL_SASL_GSSAPI (object);
+ guint32 status;
+
+ if (sasl->priv->ctx != GSS_C_NO_CONTEXT)
+ gss_delete_sec_context (
+ &status, &sasl->priv->ctx, GSS_C_NO_BUFFER);
+
+ if (sasl->priv->target != GSS_C_NO_NAME)
+ gss_release_name (&status, &sasl->priv->target);
+
+ g_free (sasl->priv->override_host);
+ g_free (sasl->priv->override_user);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_sasl_gssapi_parent_class)->finalize (object);
+}
+
+/* DBUS Specific code */
+
+static gboolean
+send_dbus_message (const gchar *name)
+{
+ gint success = FALSE;
+ GError *error = NULL;
+ GDBusConnection *connection;
+ GDBusMessage *message, *reply;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (error) {
+ g_warning ("could not get system bus: %s\n", error->message);
+ g_error_free (error);
+
+ return FALSE;
+ }
+
+ g_dbus_connection_set_exit_on_close (connection, FALSE);
+
+ /* Create a new message on the DBUS_INTERFACE */
+ message = g_dbus_message_new_method_call (DBUS_INTERFACE, DBUS_PATH, DBUS_INTERFACE, "acquireTgt");
+ if (!message) {
+ g_object_unref (connection);
+ return FALSE;
+ }
+
+ /* Appends the data as an argument to the message */
+ if (strchr (name, '\\'))
+ name = strchr (name, '\\');
+ g_dbus_message_set_body (message, g_variant_new ("(s)", name));
+
+ /* Sends the message: Have a 300 sec wait timeout */
+ reply = g_dbus_connection_send_message_with_reply_sync (connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, 300 * 1000, NULL, NULL, &error);
+
+ if (!error && reply) {
+ if (g_dbus_message_to_gerror (reply, &error)) {
+ g_object_unref (reply);
+ reply = NULL;
+ }
+ }
+
+ if (error) {
+ g_dbus_error_strip_remote_error (error);
+ g_warning ("%s: %s\n", G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+
+ if (reply) {
+ GVariant *body = g_dbus_message_get_body (reply);
+
+ if (body)
+ g_variant_get (body, "(b)", &success);
+
+ g_object_unref (reply);
+ }
+
+ /* Free the message */
+ g_object_unref (message);
+ g_object_unref (connection);
+
+ return success;
+}
+
+/* END DBus stuff */
+
+static GByteArray *
+sasl_gssapi_challenge_sync (CamelSasl *sasl,
+ GByteArray *token,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSaslGssapiPrivate *priv;
+ OM_uint32 major, minor, flags, time;
+ gss_buffer_desc inbuf, outbuf;
+ GByteArray *challenge = NULL;
+ gss_buffer_t input_token;
+ gint conf_state;
+ gss_qop_t qop;
+ gchar *str;
+ struct addrinfo *ai, hints;
+ const gchar *service_name;
+ gchar *host = NULL;
+ gchar *user = NULL;
+
+ priv = CAMEL_SASL_GSSAPI_GET_PRIVATE (sasl);
+
+ service_name = camel_sasl_get_service_name (sasl);
+
+ if (priv->override_host && priv->override_user) {
+ host = g_strdup (priv->override_host);
+ user = g_strdup (priv->override_user);
+ }
+
+ if (!host || !user) {
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ CamelService *service;
+
+ service = camel_sasl_get_service (sasl);
+
+ settings = camel_service_ref_settings (service);
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ user = camel_network_settings_dup_user (network_settings);
+
+ g_object_unref (settings);
+ }
+
+ g_return_val_if_fail (user != NULL, NULL);
+
+ if (!host || !*host) {
+ g_free (host);
+ host = g_strdup ("localhost");
+ }
+
+ switch (priv->state) {
+ case GSSAPI_STATE_INIT:
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_flags = AI_CANONNAME;
+ ai = camel_getaddrinfo (
+ host, NULL, &hints, cancellable, error);
+ if (ai == NULL)
+ goto exit;
+
+ /* HTTP authentication should be SPNEGO not just KRB5 */
+ if (!strcmp (service_name, "HTTP"))
+ priv->mech = (gss_OID)&gss_mech_spnego;
+
+ str = g_strdup_printf ("%s@%s", service_name, ai->ai_canonname);
+ camel_freeaddrinfo (ai);
+
+ inbuf.value = str;
+ inbuf.length = strlen (str);
+ major = gss_import_name (&minor, &inbuf, GSS_C_NT_HOSTBASED_SERVICE, &priv->target);
+ g_free (str);
+
+ if (major != GSS_S_COMPLETE) {
+ gssapi_set_exception (priv->mech, major, minor, error);
+ goto exit;
+ }
+
+ input_token = GSS_C_NO_BUFFER;
+
+ goto challenge;
+ break;
+ case GSSAPI_STATE_CONTINUE_NEEDED:
+ if (token == NULL) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Bad authentication response from server."));
+ goto exit;
+ }
+
+ 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,
+ priv->mech,
+ GSS_C_MUTUAL_FLAG |
+ GSS_C_REPLAY_FLAG |
+ GSS_C_SEQUENCE_FLAG,
+ 0, GSS_C_NO_CHANNEL_BINDINGS,
+ input_token, &priv->used_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:
+ if (priv->used_mech == GSS_C_OID_KRBV5_DES &&
+ major == (OM_uint32) GSS_S_FAILURE &&
+ (minor == (OM_uint32) KRB5KRB_AP_ERR_TKT_EXPIRED ||
+ minor == (OM_uint32) KRB5KDC_ERR_NEVER_VALID) &&
+ send_dbus_message (user))
+ goto challenge;
+
+ gssapi_set_exception (priv->used_mech, major, minor, error);
+ goto exit;
+ }
+
+ 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) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Bad authentication response from server."));
+ goto exit;
+ }
+
+ 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 (priv->used_mech, major, minor, error);
+ goto exit;
+ }
+
+ if (outbuf.length < 4) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Bad authentication response from server."));
+#ifndef HAVE_HEIMDAL_KRB5
+ gss_release_buffer (&minor, &outbuf);
+#endif
+ goto exit;
+ }
+
+ /* check that our desired security layer is supported */
+ if ((((guchar *) outbuf.value)[0] & DESIRED_SECURITY_LAYER) != DESIRED_SECURITY_LAYER) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Unsupported security layer."));
+#ifndef HAVE_HEIMDAL_KRB5
+ gss_release_buffer (&minor, &outbuf);
+#endif
+ goto exit;
+ }
+
+ inbuf.length = 4 + strlen (user);
+ inbuf.value = str = g_malloc (inbuf.length);
+ memcpy (inbuf.value, outbuf.value, 4);
+ str[0] = DESIRED_SECURITY_LAYER;
+ memcpy (str + 4, 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 (priv->used_mech, major, minor, error);
+ g_free (str);
+ goto exit;
+ }
+
+ 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;
+
+ camel_sasl_set_authenticated (sasl, TRUE);
+ break;
+ default:
+ break;
+ }
+
+exit:
+ g_free (host);
+ g_free (user);
+
+ return challenge;
+}
+
+#endif /* HAVE_KRB5 */
+
+static void
+camel_sasl_gssapi_class_init (CamelSaslGssapiClass *class)
+{
+#ifdef HAVE_KRB5
+ GObjectClass *object_class;
+ CamelSaslClass *sasl_class;
+
+ g_type_class_add_private (class, sizeof (CamelSaslGssapiPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = sasl_gssapi_finalize;
+
+ sasl_class = CAMEL_SASL_CLASS (class);
+ sasl_class->auth_type = &sasl_gssapi_auth_type;
+ sasl_class->challenge_sync = sasl_gssapi_challenge_sync;
+#endif /* HAVE_KRB5 */
+}
+
+static void
+camel_sasl_gssapi_init (CamelSaslGssapi *sasl)
+{
+#ifdef HAVE_KRB5
+ sasl->priv = CAMEL_SASL_GSSAPI_GET_PRIVATE (sasl);
+ sasl->priv->state = GSSAPI_STATE_INIT;
+ sasl->priv->ctx = GSS_C_NO_CONTEXT;
+ sasl->priv->target = GSS_C_NO_NAME;
+ sasl->priv->override_host = NULL;
+ sasl->priv->override_user = NULL;
+ sasl->priv->mech = GSS_C_OID_KRBV5_DES;
+#endif /* HAVE_KRB5 */
+}
+
+/**
+ * camel_sasl_gssapi_is_available:
+ *
+ * Returns: Whether the GSSAPI/KRB5 sasl authentication mechanism is available,
+ * which means whether Camel was built with KRB5 enabled.
+ *
+ * Since: 3.12
+ **/
+gboolean
+camel_sasl_gssapi_is_available (void)
+{
+#ifdef HAVE_KRB5
+ return TRUE;
+#else /* HAVE_KRB5 */
+ return FALSE;
+#endif /* HAVE_KRB5 */
+}
+
+/**
+ * camel_sasl_gssapi_override_host_and_user:
+ * @override_host: Host name to use during challenge processing; can be %NULL
+ * @override_user: User name to use during challenge processing; can be %NULL
+ *
+ * Set host and user to use, instead of those in CamelService's settings.
+ * It's both or none, aka either set both, or the settings values are used.
+ * This is used to not require CamelService instance at all.
+ *
+ * Since: 3.12
+ **/
+void
+camel_sasl_gssapi_override_host_and_user (CamelSaslGssapi *sasl,
+ const gchar *override_host,
+ const gchar *override_user)
+{
+ g_return_if_fail (CAMEL_IS_SASL_GSSAPI (sasl));
+
+#ifdef HAVE_KRB5
+ if (sasl->priv->override_host != override_host) {
+ g_free (sasl->priv->override_host);
+ sasl->priv->override_host = g_strdup (override_host);
+ }
+
+ if (sasl->priv->override_user != override_user) {
+ g_free (sasl->priv->override_user);
+ sasl->priv->override_user = g_strdup (override_user);
+ }
+#else /* HAVE_KRB5 */
+ g_warning ("%s: KRB5 not available", G_STRFUNC);
+#endif /* HAVE_KRB5 */
+}
diff --git a/src/camel/camel-sasl-gssapi.h b/src/camel/camel-sasl-gssapi.h
new file mode 100644
index 000000000..7629fb3d8
--- /dev/null
+++ b/src/camel/camel-sasl-gssapi.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SASL_GSSAPI_H
+#define CAMEL_SASL_GSSAPI_H
+
+#include <sys/types.h>
+#include <camel/camel-sasl.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SASL_GSSAPI \
+ (camel_sasl_gssapi_get_type ())
+#define CAMEL_SASL_GSSAPI(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SASL_GSSAPI, CamelSaslGssapi))
+#define CAMEL_SASL_GSSAPI_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SASL_GSSAPI, CamelSaslGssapiClass))
+#define CAMEL_IS_SASL_GSSAPI(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SASL_GSSAPI))
+#define CAMEL_IS_SASL_GSSAPI_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SASL_GSSAPI))
+#define CAMEL_SASL_GSSAPI_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SASL_GSSAPI, CamelSaslGssapiClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSaslGssapi CamelSaslGssapi;
+typedef struct _CamelSaslGssapiClass CamelSaslGssapiClass;
+typedef struct _CamelSaslGssapiPrivate CamelSaslGssapiPrivate;
+
+struct _CamelSaslGssapi {
+ CamelSasl parent;
+ CamelSaslGssapiPrivate *priv;
+};
+
+struct _CamelSaslGssapiClass {
+ CamelSaslClass parent_class;
+};
+
+GType camel_sasl_gssapi_get_type (void);
+gboolean camel_sasl_gssapi_is_available (void);
+void camel_sasl_gssapi_override_host_and_user
+ (CamelSaslGssapi *sasl,
+ const gchar *override_host,
+ const gchar *override_user);
+
+G_END_DECLS
+
+#endif /* CAMEL_SASL_GSSAPI_H */
diff --git a/src/camel/camel-sasl-login.c b/src/camel/camel-sasl-login.c
new file mode 100644
index 000000000..703805efb
--- /dev/null
+++ b/src/camel/camel-sasl-login.c
@@ -0,0 +1,131 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-network-settings.h"
+#include "camel-sasl-login.h"
+#include "camel-service.h"
+
+#define CAMEL_SASL_LOGIN_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SASL_LOGIN, CamelSaslLoginPrivate))
+
+static CamelServiceAuthType sasl_login_auth_type = {
+ N_("Login"),
+
+ N_("This option will connect to the server using a "
+ "simple password."),
+
+ "LOGIN",
+ TRUE
+};
+
+enum {
+ LOGIN_USER,
+ LOGIN_PASSWD
+};
+
+struct _CamelSaslLoginPrivate {
+ gint state;
+};
+
+G_DEFINE_TYPE (CamelSaslLogin, camel_sasl_login, CAMEL_TYPE_SASL)
+
+static GByteArray *
+sasl_login_challenge_sync (CamelSasl *sasl,
+ GByteArray *token,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSaslLoginPrivate *priv;
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ GByteArray *buf = NULL;
+ const gchar *password;
+ gchar *user;
+
+ /* Need to wait for the server */
+ if (token == NULL)
+ return NULL;
+
+ priv = CAMEL_SASL_LOGIN_GET_PRIVATE (sasl);
+
+ service = camel_sasl_get_service (sasl);
+
+ settings = camel_service_ref_settings (service);
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ user = camel_network_settings_dup_user (network_settings);
+
+ g_object_unref (settings);
+
+ g_return_val_if_fail (user != NULL, NULL);
+
+ password = camel_service_get_password (service);
+ g_return_val_if_fail (password != NULL, NULL);
+
+ switch (priv->state) {
+ case LOGIN_USER:
+ buf = g_byte_array_new ();
+ g_byte_array_append (buf, (guint8 *) user, strlen (user));
+ break;
+ case LOGIN_PASSWD:
+ buf = g_byte_array_new ();
+ g_byte_array_append (buf, (guint8 *) password, strlen (password));
+
+ camel_sasl_set_authenticated (sasl, TRUE);
+ break;
+ default:
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Unknown authentication state."));
+ }
+
+ priv->state++;
+
+ g_free (user);
+
+ return buf;
+}
+
+static void
+camel_sasl_login_class_init (CamelSaslLoginClass *class)
+{
+ CamelSaslClass *sasl_class;
+
+ g_type_class_add_private (class, sizeof (CamelSaslLoginPrivate));
+
+ sasl_class = CAMEL_SASL_CLASS (class);
+ sasl_class->auth_type = &sasl_login_auth_type;
+ sasl_class->challenge_sync = sasl_login_challenge_sync;
+}
+
+static void
+camel_sasl_login_init (CamelSaslLogin *sasl)
+{
+ sasl->priv = CAMEL_SASL_LOGIN_GET_PRIVATE (sasl);
+}
diff --git a/src/camel/camel-sasl-login.h b/src/camel/camel-sasl-login.h
new file mode 100644
index 000000000..41a53cf35
--- /dev/null
+++ b/src/camel/camel-sasl-login.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SASL_LOGIN_H
+#define CAMEL_SASL_LOGIN_H
+
+#include <camel/camel-sasl.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SASL_LOGIN \
+ (camel_sasl_login_get_type ())
+#define CAMEL_SASL_LOGIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SASL_LOGIN, CamelSaslLogin))
+#define CAMEL_SASL_LOGIN_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SASL_LOGIN, CamelSaslLoginClass))
+#define CAMEL_IS_SASL_LOGIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SASL_LOGIN))
+#define CAMEL_IS_SASL_LOGIN_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SASL_LOGIN))
+#define CAMEL_SASL_LOGIN_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SASL_LOGIN, CamelSaslLoginClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSaslLogin CamelSaslLogin;
+typedef struct _CamelSaslLoginClass CamelSaslLoginClass;
+typedef struct _CamelSaslLoginPrivate CamelSaslLoginPrivate;
+
+struct _CamelSaslLogin {
+ CamelSasl parent;
+ CamelSaslLoginPrivate *priv;
+};
+
+struct _CamelSaslLoginClass {
+ CamelSaslClass parent_class;
+};
+
+GType camel_sasl_login_get_type (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_SASL_LOGIN_H */
diff --git a/src/camel/camel-sasl-ntlm.c b/src/camel/camel-sasl-ntlm.c
new file mode 100644
index 000000000..4ee5a7dc4
--- /dev/null
+++ b/src/camel/camel-sasl-ntlm.c
@@ -0,0 +1,1012 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-network-settings.h"
+#include "camel-sasl-ntlm.h"
+#include "camel-stream-process.h"
+
+#define CAMEL_SASL_NTLM_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SASL_NTLM, CamelSaslNTLMPrivate))
+
+struct _CamelSaslNTLMPrivate {
+ gint placeholder; /* allow for future expansion */
+#ifndef G_OS_WIN32
+ gboolean tried_helper;
+ CamelStream *helper_stream;
+ gchar *type1_msg;
+#endif
+};
+
+static CamelServiceAuthType sasl_ntlm_auth_type = {
+ N_("NTLM / SPA"),
+
+ N_("This option will connect to a Windows-based server using "
+ "NTLM / Secure Password Authentication."),
+
+ "NTLM",
+ TRUE
+};
+
+G_DEFINE_TYPE (CamelSaslNTLM, camel_sasl_ntlm, CAMEL_TYPE_SASL)
+
+#define NTLM_REQUEST "NTLMSSP\x00\x01\x00\x00\x00\x06\x82\x08\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_DOMAIN_OFFSET 12
+#define NTLM_CHALLENGE_FLAGS_OFFSET 20
+#define NTLM_CHALLENGE_NONCE_OFFSET 24
+
+#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
+
+#define NTLM_AUTH_HELPER "/usr/bin/ntlm_auth"
+
+typedef struct {
+ guint16 length;
+ guint16 allocated;
+ guint32 offset;
+} SecurityBuffer;
+
+static GString *
+ntlm_get_string (GByteArray *ba,
+ gint offset)
+{
+ SecurityBuffer *secbuf;
+ GString *string;
+ gchar *buf_string;
+ guint16 buf_length;
+ guint32 buf_offset;
+
+ secbuf = (SecurityBuffer *) &ba->data[offset];
+ buf_length = GUINT16_FROM_LE (secbuf->length);
+ buf_offset = GUINT32_FROM_LE (secbuf->offset);
+
+ if (ba->len < buf_offset + buf_length)
+ return NULL;
+
+ string = g_string_sized_new (buf_length);
+ buf_string = (gchar *) &ba->data[buf_offset];
+ g_string_append_len (string, buf_string, buf_length);
+
+ return string;
+}
+
+static void
+ntlm_set_string (GByteArray *ba,
+ gint offset,
+ const gchar *data,
+ gint len)
+{
+ SecurityBuffer *secbuf;
+
+ secbuf = (SecurityBuffer *) &ba->data[offset];
+ secbuf->length = GUINT16_TO_LE (len);
+ secbuf->offset = GUINT32_TO_LE (ba->len);
+ secbuf->allocated = secbuf->length;
+
+ g_byte_array_append (ba, (guint8 *) data, len);
+}
+
+/* MD4 */
+static void md4sum (const guchar *in,
+ gint nbytes,
+ guchar digest[16]);
+
+/* DES */
+typedef guint32 DES_KS[16][2]; /* Single-key DES key schedule */
+
+static void deskey (DES_KS, guchar *, gint);
+
+static void des (DES_KS, guchar *);
+
+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 gchar *password,
+ gchar hash[21])
+{
+ guchar lm_password[15];
+ DES_KS ks;
+ gint i;
+
+ for (i = 0; i < 14 && password && password[i]; i++)
+ lm_password[i] = toupper ((guchar) password[i]);
+
+ for (; i < 15; i++)
+ lm_password[i] = '\0';
+
+ memcpy (hash, LM_PASSWORD_MAGIC, 21);
+
+ setup_schedule (lm_password, ks);
+ des (ks, (guchar *) hash);
+
+ setup_schedule (lm_password + 7, ks);
+ des (ks, (guchar *) hash + 8);
+}
+
+static void
+ntlm_nt_hash (const gchar *password,
+ gchar hash[21])
+{
+ guchar *buf, *p;
+
+ if (!password)
+ password = "";
+
+ p = buf = g_malloc (strlen (password) * 2);
+
+ while (*password) {
+ *p++ = *password++;
+ *p++ = '\0';
+ }
+
+ md4sum (buf, p - buf, (guchar *) hash);
+ memset (hash + 16, 0, 5);
+
+ g_free (buf);
+}
+
+#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];
+ gint 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 guchar *in,
+ gint nbytes,
+ guchar digest[16])
+{
+ guchar *M;
+ guint32 A, B, C, D, AA, BB, CC, DD, X[16];
+ gint pbytes, nbits = nbytes * 8, i, j;
+
+ /* There is *always* padding of at least one bit. */
+ pbytes = ((119 - (nbytes % 64)) % 64) + 1;
+ 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 (guint32 ks[16][2],
+ guchar block[8])
+{
+ 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 guchar 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 guchar totrot[] = {
+ 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28
+};
+
+/* permuted choice key (table) */
+static guchar 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 gint 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 (DES_KS k,
+ guchar *key,
+ gint decrypt)
+{
+ guchar pc1m[56]; /* place to modify pc1 into */
+ guchar pcr[56]; /* place to rotate pc1 into */
+ register gint i,j,l;
+ gint m;
+ guchar 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]);
+ }
+}
+
+static gboolean
+sasl_ntlm_try_empty_password_sync (CamelSasl *sasl,
+ GCancellable *cancellable,
+ GError **error)
+{
+#ifndef G_OS_WIN32
+ CamelStream *stream;
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelSaslNTLM *ntlm = CAMEL_SASL_NTLM (sasl);
+ CamelSaslNTLMPrivate *priv = ntlm->priv;
+ const gchar *cp;
+ gchar *user;
+ gchar buf[1024];
+ gsize s;
+ gchar *command;
+ gint ret;
+
+ if (priv->tried_helper)
+ return !!priv->helper_stream;
+
+ priv->tried_helper = TRUE;
+
+ if (access (NTLM_AUTH_HELPER, X_OK))
+ return FALSE;
+
+ service = camel_sasl_get_service (sasl);
+
+ settings = camel_service_ref_settings (service);
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), FALSE);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ user = camel_network_settings_dup_user (network_settings);
+
+ g_object_unref (settings);
+
+ g_return_val_if_fail (user != NULL, FALSE);
+
+ cp = strchr (user, '\\');
+ if (cp != NULL) {
+ command = g_strdup_printf (
+ "%s --helper-protocol ntlmssp-client-1 "
+ "--use-cached-creds --username '%s' "
+ "--domain '%.*s'", NTLM_AUTH_HELPER,
+ cp + 1, (gint)(cp - user), user);
+ } else {
+ command = g_strdup_printf (
+ "%s --helper-protocol ntlmssp-client-1 "
+ "--use-cached-creds --username '%s'",
+ NTLM_AUTH_HELPER, user);
+ }
+
+ stream = camel_stream_process_new ();
+
+ ret = camel_stream_process_connect (
+ CAMEL_STREAM_PROCESS (stream), command, NULL, error);
+
+ g_free (command);
+ g_free (user);
+
+ if (ret) {
+ g_object_unref (stream);
+ return FALSE;
+ }
+
+ if (camel_stream_write_string (stream, "YR\n", cancellable, error) < 0) {
+ g_object_unref (stream);
+ return FALSE;
+ }
+
+ s = camel_stream_read (stream, buf, sizeof (buf), cancellable, NULL);
+ if (s < 4) {
+ g_object_unref (stream);
+ return FALSE;
+ }
+
+ if (buf[0] != 'Y' || buf[1] != 'R' || buf[2] != ' ' || buf[s - 1] != '\n') {
+ g_object_unref (stream);
+ return FALSE;
+ }
+
+ buf[s - 1] = 0;
+
+ priv->helper_stream = stream;
+ priv->type1_msg = g_strdup (buf + 3);
+ return TRUE;
+#else
+ /* Win32 should be able to use SSPI here. */
+ return FALSE;
+#endif
+}
+
+static GByteArray *
+sasl_ntlm_challenge_sync (CamelSasl *sasl,
+ GByteArray *token,
+ GCancellable *cancellable,
+ GError **error)
+{
+#ifndef G_OS_WIN32
+ CamelSaslNTLM *ntlm = CAMEL_SASL_NTLM (sasl);
+ CamelSaslNTLMPrivate *priv = ntlm->priv;
+#endif
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ GByteArray *ret;
+ guchar nonce[8], hash[21], lm_resp[24], nt_resp[24];
+ GString *domain = NULL;
+ const gchar *password;
+ const gchar *real_user;
+ const gchar *cp;
+ gchar *user = NULL;
+
+ service = camel_sasl_get_service (sasl);
+
+ settings = camel_service_ref_settings (service);
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ user = camel_network_settings_dup_user (network_settings);
+
+ g_object_unref (settings);
+
+ g_return_val_if_fail (user != NULL, NULL);
+
+ password = camel_service_get_password (service);
+ /* Assert a non-NULL password below, not here. */
+
+ ret = g_byte_array_new ();
+
+#ifndef G_OS_WIN32
+ if (!priv->tried_helper && password == NULL)
+ sasl_ntlm_try_empty_password_sync (sasl, cancellable, NULL);
+
+ if (priv->helper_stream && password == NULL) {
+ guchar *data;
+ gsize length = 0;
+ gchar buf[1024];
+ gsize s = 0;
+ buf[0] = 0;
+
+ if (!token || !token->len) {
+ if (priv->type1_msg) {
+ data = g_base64_decode (priv->type1_msg, &length);
+ g_byte_array_append (ret, data, length);
+ g_free (data);
+ g_free (priv->type1_msg);
+ priv->type1_msg = NULL;
+ }
+ goto exit;
+ } else {
+ gchar *type2;
+ gchar *string;
+
+ type2 = g_base64_encode (token->data, token->len);
+ string = g_strdup_printf ("TT %s\n", type2);
+ if (camel_stream_write_string (
+ priv->helper_stream, string, NULL, NULL) >= 0 &&
+ (s = camel_stream_read (
+ priv->helper_stream, buf,
+ sizeof (buf), cancellable, NULL)) > 4 &&
+ buf[0] == 'K' &&
+ buf[1] == 'K' &&
+ buf[2] == ' ' &&
+ buf[s - 1] == '\n') {
+ buf[s - 1] = 0;
+ data = g_base64_decode (buf + 3, &length);
+ g_byte_array_append (ret, data, length);
+ g_free (data);
+ } else
+ g_warning ("Didn't get valid response from ntlm_auth helper");
+
+ g_free (string);
+ g_free (type2);
+ }
+
+ /* On failure, we just return an empty string. Setting the
+ * GError would cause the providers to abort the whole
+ * connection, and we want them to ask the user for a password
+ * and continue. */
+ g_object_unref (priv->helper_stream);
+ priv->helper_stream = NULL;
+
+ goto exit;
+ }
+#endif
+
+ g_return_val_if_fail (password != NULL, NULL);
+
+ if (!token || token->len < NTLM_CHALLENGE_NONCE_OFFSET + 8)
+ goto fail;
+
+ /* 0x00080000: Negotiate NTLM2 Key */
+ if (token->data[NTLM_CHALLENGE_FLAGS_OFFSET + 2] & 8) {
+ /* NTLM2 session response */
+ struct {
+ guint32 srv[2];
+ guint32 clnt[2];
+ } sess_nonce;
+ GChecksum *md5;
+ guint8 digest[16];
+ gsize digest_len = sizeof (digest);
+
+ sess_nonce.clnt[0] = g_random_int ();
+ sess_nonce.clnt[1] = g_random_int ();
+
+ /* LM response is 8-byte client nonce, NUL-padded to 24 */
+ memcpy (lm_resp, sess_nonce.clnt, 8);
+ memset (lm_resp + 8, 0, 16);
+
+ /* Session nonce is client nonce + server nonce */
+ memcpy (
+ sess_nonce.srv,
+ token->data + NTLM_CHALLENGE_NONCE_OFFSET, 8);
+
+ /* Take MD5 of session nonce */
+ md5 = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (md5, (gpointer) &sess_nonce, 16);
+ g_checksum_get_digest (md5, (gpointer) &digest, &digest_len);
+ g_checksum_get_digest (md5, digest, &digest_len);
+
+ g_checksum_free (md5);
+ ntlm_nt_hash (password, (gchar *) hash);
+
+ ntlm_calc_response (hash, digest, nt_resp);
+ } else {
+ /* NTLM1 */
+ memcpy (nonce, token->data + NTLM_CHALLENGE_NONCE_OFFSET, 8);
+ ntlm_lanmanager_hash (password, (gchar *) hash);
+ ntlm_calc_response (hash, nonce, lm_resp);
+ ntlm_nt_hash (password, (gchar *) hash);
+ ntlm_calc_response (hash, nonce, nt_resp);
+ }
+
+ /* If a domain is supplied as part of the username, use it */
+ cp = strchr (user, '\\');
+ if (cp != NULL) {
+ domain = g_string_new_len (user, cp - user);
+ real_user = cp + 1;
+ } else
+ real_user = user;
+
+ /* Otherwise, fall back to the domain of the server, if possible */
+ if (domain == NULL)
+ domain = ntlm_get_string (token, NTLM_CHALLENGE_DOMAIN_OFFSET);
+ if (domain == NULL)
+ goto fail;
+
+ /* Don't jump to 'fail' label after this point. */
+ 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);
+ /* Mask in the NTLM2SESSION flag */
+ ret->data[NTLM_RESPONSE_FLAGS_OFFSET + 2] |=
+ token->data[NTLM_CHALLENGE_FLAGS_OFFSET + 2] & 8;
+
+ ntlm_set_string (
+ ret, NTLM_RESPONSE_DOMAIN_OFFSET,
+ domain->str, domain->len);
+ ntlm_set_string (
+ ret, NTLM_RESPONSE_USER_OFFSET,
+ real_user, strlen (real_user));
+ ntlm_set_string (
+ ret, NTLM_RESPONSE_HOST_OFFSET,
+ "UNKNOWN", sizeof ("UNKNOWN") - 1);
+ ntlm_set_string (
+ ret, NTLM_RESPONSE_LM_RESP_OFFSET,
+ (const gchar *) lm_resp, sizeof (lm_resp));
+ ntlm_set_string (
+ ret, NTLM_RESPONSE_NT_RESP_OFFSET,
+ (const gchar *) nt_resp, sizeof (nt_resp));
+
+ camel_sasl_set_authenticated (sasl, TRUE);
+
+ g_string_free (domain, TRUE);
+
+ goto exit;
+
+fail:
+ /* If the challenge is malformed, restart authentication.
+ * XXX A malicious server could make this loop indefinitely. */
+ g_byte_array_append (
+ ret, (guint8 *) NTLM_REQUEST,
+ sizeof (NTLM_REQUEST) - 1);
+
+exit:
+ g_free (user);
+
+ return ret;
+}
+
+static void
+sasl_ntlm_finalize (GObject *object)
+{
+#ifndef G_OS_WIN32
+ CamelSaslNTLM *ntlm = CAMEL_SASL_NTLM (object);
+ CamelSaslNTLMPrivate *priv = ntlm->priv;
+
+ if (priv->type1_msg)
+ g_free (priv->type1_msg);
+ if (priv->helper_stream)
+ g_object_unref (priv->helper_stream);
+#endif
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_sasl_ntlm_parent_class)->finalize (object);
+}
+
+static void
+camel_sasl_ntlm_class_init (CamelSaslNTLMClass *class)
+{
+ GObjectClass *object_class;
+ CamelSaslClass *sasl_class;
+
+ g_type_class_add_private (class, sizeof (CamelSaslNTLMPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = sasl_ntlm_finalize;
+
+ sasl_class = CAMEL_SASL_CLASS (class);
+ sasl_class->auth_type = &sasl_ntlm_auth_type;
+ sasl_class->challenge_sync = sasl_ntlm_challenge_sync;
+ sasl_class->try_empty_password_sync = sasl_ntlm_try_empty_password_sync;
+}
+
+static void
+camel_sasl_ntlm_init (CamelSaslNTLM *sasl)
+{
+ sasl->priv = CAMEL_SASL_NTLM_GET_PRIVATE (sasl);
+}
diff --git a/src/camel/camel-sasl-ntlm.h b/src/camel/camel-sasl-ntlm.h
new file mode 100644
index 000000000..31d063093
--- /dev/null
+++ b/src/camel/camel-sasl-ntlm.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SASL_NTLM_H
+#define CAMEL_SASL_NTLM_H
+
+#include <camel/camel-sasl.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SASL_NTLM \
+ (camel_sasl_ntlm_get_type ())
+#define CAMEL_SASL_NTLM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SASL_NTLM, CamelSaslNTLM))
+#define CAMEL_SASL_NTLM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SASL_NTLM, CamelSaslNTLMClass))
+#define CAMEL_IS_SASL_NTLM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SASL_NTLM))
+#define CAMEL_IS_SASL_NTLM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SASL_NTLM))
+#define CAMEL_SASL_NTLM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SASL_NTLM, CamelSaslNTLMClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSaslNTLM CamelSaslNTLM;
+typedef struct _CamelSaslNTLMClass CamelSaslNTLMClass;
+typedef struct _CamelSaslNTLMPrivate CamelSaslNTLMPrivate;
+
+struct _CamelSaslNTLM {
+ CamelSasl parent;
+ CamelSaslNTLMPrivate *priv;
+};
+
+struct _CamelSaslNTLMClass {
+ CamelSaslClass parent_class;
+};
+
+GType camel_sasl_ntlm_get_type (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_SASL_NTLM_H */
diff --git a/src/camel/camel-sasl-plain.c b/src/camel/camel-sasl-plain.c
new file mode 100644
index 000000000..09b4dd7c1
--- /dev/null
+++ b/src/camel/camel-sasl-plain.c
@@ -0,0 +1,108 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-network-settings.h"
+#include "camel-sasl-plain.h"
+#include "camel-service.h"
+
+#define CAMEL_SASL_PLAIN_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SASL_PLAIN, CamelSaslPlainPrivate))
+
+struct _CamelSaslPlainPrivate {
+ gint placeholder; /* allow for future expansion */
+};
+
+static CamelServiceAuthType sasl_plain_auth_type = {
+ N_("PLAIN"),
+
+ N_("This option will connect to the server using a "
+ "simple password."),
+
+ "PLAIN",
+ TRUE
+};
+
+G_DEFINE_TYPE (CamelSaslPlain, camel_sasl_plain, CAMEL_TYPE_SASL)
+
+static GByteArray *
+sasl_plain_challenge_sync (CamelSasl *sasl,
+ GByteArray *token,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ GByteArray *buf = NULL;
+ const gchar *password;
+ gchar *user;
+
+ service = camel_sasl_get_service (sasl);
+
+ settings = camel_service_ref_settings (service);
+ g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ user = camel_network_settings_dup_user (network_settings);
+
+ g_object_unref (settings);
+
+ g_return_val_if_fail (user != NULL, NULL);
+
+ password = camel_service_get_password (service);
+ g_return_val_if_fail (password != NULL, NULL);
+
+ /* FIXME: make sure these are "UTF8-SAFE" */
+ buf = g_byte_array_new ();
+ g_byte_array_append (buf, (guint8 *) "", 1);
+ g_byte_array_append (buf, (guint8 *) user, strlen (user));
+ g_byte_array_append (buf, (guint8 *) "", 1);
+ g_byte_array_append (buf, (guint8 *) password, strlen (password));
+
+ camel_sasl_set_authenticated (sasl, TRUE);
+
+ g_free (user);
+
+ return buf;
+}
+
+static void
+camel_sasl_plain_class_init (CamelSaslPlainClass *class)
+{
+ CamelSaslClass *sasl_class;
+
+ g_type_class_add_private (class, sizeof (CamelSaslPlainPrivate));
+
+ sasl_class = CAMEL_SASL_CLASS (class);
+ sasl_class->auth_type = &sasl_plain_auth_type;
+ sasl_class->challenge_sync = sasl_plain_challenge_sync;
+}
+
+static void
+camel_sasl_plain_init (CamelSaslPlain *sasl)
+{
+ sasl->priv = CAMEL_SASL_PLAIN_GET_PRIVATE (sasl);
+}
diff --git a/src/camel/camel-sasl-plain.h b/src/camel/camel-sasl-plain.h
new file mode 100644
index 000000000..8bdf7f18a
--- /dev/null
+++ b/src/camel/camel-sasl-plain.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SASL_PLAIN_H
+#define CAMEL_SASL_PLAIN_H
+
+#include <camel/camel-sasl.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SASL_PLAIN \
+ (camel_sasl_plain_get_type ())
+#define CAMEL_SASL_PLAIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SASL_PLAIN, CamelSaslPlain))
+#define CAMEL_SASL_PLAIN_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SASL_PLAIN, CamelSaslPlainClass))
+#define CAMEL_IS_SASL_PLAIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SASL_PLAIN))
+#define CAMEL_IS_SASL_PLAIN_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SASL_PLAIN))
+#define CAMEL_SASL_PLAIN_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SASL_PLAIN, CamelSaslPlainClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSaslPlain CamelSaslPlain;
+typedef struct _CamelSaslPlainClass CamelSaslPlainClass;
+typedef struct _CamelSaslPlainPrivate CamelSaslPlainPrivate;
+
+struct _CamelSaslPlain {
+ CamelSasl parent;
+ CamelSaslPlainPrivate *priv;
+};
+
+struct _CamelSaslPlainClass {
+ CamelSaslClass parent_class;
+};
+
+GType camel_sasl_plain_get_type (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_SASL_PLAIN_H */
diff --git a/src/camel/camel-sasl-popb4smtp.c b/src/camel/camel-sasl-popb4smtp.c
new file mode 100644
index 000000000..26ef51244
--- /dev/null
+++ b/src/camel/camel-sasl-popb4smtp.c
@@ -0,0 +1,176 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+#include <time.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-sasl-popb4smtp.h"
+#include "camel-service.h"
+#include "camel-session.h"
+#include "camel-store.h"
+
+#define CAMEL_SASL_POPB4SMTP_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SASL_POPB4SMTP, CamelSaslPOPB4SMTPPrivate))
+
+struct _CamelSaslPOPB4SMTPPrivate {
+ gint placeholder; /* allow for future expansion */
+};
+
+static CamelServiceAuthType sasl_popb4smtp_auth_type = {
+ N_("POP before SMTP"),
+
+ N_("This option will authorise a POP connection before attempting SMTP"),
+
+ "POPB4SMTP",
+ FALSE,
+};
+
+/* last time the pop was accessed (through the auth method anyway), *time_t */
+static GHashTable *poplast;
+
+/* use 1 hour as our pop timeout */
+#define POPB4SMTP_TIMEOUT (60*60)
+
+static GMutex lock;
+#define POPB4SMTP_LOCK(l) g_mutex_lock(&l)
+#define POPB4SMTP_UNLOCK(l) g_mutex_unlock(&l)
+
+G_DEFINE_TYPE (CamelSaslPOPB4SMTP, camel_sasl_popb4smtp, CAMEL_TYPE_SASL)
+
+static GByteArray *
+sasl_popb4smtp_challenge_sync (CamelSasl *sasl,
+ GByteArray *token,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelService *service;
+ CamelSession *session;
+ time_t now, *timep;
+ const gchar *type_name;
+ gchar *pop_uid;
+
+ service = camel_sasl_get_service (sasl);
+ session = camel_service_ref_session (service);
+ if (!session)
+ return NULL;
+
+ camel_sasl_set_authenticated (sasl, FALSE);
+
+ pop_uid = camel_session_get_password (
+ session, service, _("POP Source UID"),
+ "popb4smtp_uid", 0, error);
+
+ if (pop_uid != NULL)
+ service = camel_session_ref_service (session, pop_uid);
+ else
+ service = NULL;
+
+ g_object_unref (session);
+
+ if (service == NULL) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("POP Before SMTP authentication "
+ "using an unknown transport"));
+ g_free (pop_uid);
+ return NULL;
+ }
+
+ type_name = G_OBJECT_TYPE_NAME (service);
+
+ if (!CAMEL_IS_STORE (service)) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("POP Before SMTP authentication attempted "
+ "with a %s service"), type_name);
+ goto exit;
+ }
+
+ if (strstr (type_name, "POP") == NULL) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("POP Before SMTP authentication attempted "
+ "with a %s service"), type_name);
+ goto exit;
+ }
+
+ /* check if we've done it before recently in this session */
+ now = time (NULL);
+
+ /* need to lock around the whole thing until finished with timep */
+
+ POPB4SMTP_LOCK (lock);
+
+ timep = g_hash_table_lookup (poplast, pop_uid);
+ if (timep) {
+ if ((*timep + POPB4SMTP_TIMEOUT) > now) {
+ camel_sasl_set_authenticated (sasl, TRUE);
+ POPB4SMTP_UNLOCK (lock);
+ goto exit;
+ }
+ } else {
+ timep = g_malloc0 (sizeof (*timep));
+ g_hash_table_insert (poplast, g_strdup (pop_uid), timep);
+ }
+
+ /* connect to pop session */
+ if (camel_service_connect_sync (service, cancellable, error)) {
+ camel_sasl_set_authenticated (sasl, TRUE);
+ *timep = now;
+ } else {
+ camel_sasl_set_authenticated (sasl, FALSE);
+ *timep = 0;
+ }
+
+ POPB4SMTP_UNLOCK (lock);
+
+exit:
+ g_object_unref (service);
+ g_free (pop_uid);
+
+ return NULL;
+}
+
+static void
+camel_sasl_popb4smtp_class_init (CamelSaslPOPB4SMTPClass *class)
+{
+ CamelSaslClass *sasl_class;
+
+ g_type_class_add_private (class, sizeof (CamelSaslPOPB4SMTPPrivate));
+
+ sasl_class = CAMEL_SASL_CLASS (class);
+ sasl_class->auth_type = &sasl_popb4smtp_auth_type;
+ sasl_class->challenge_sync = sasl_popb4smtp_challenge_sync;
+
+ poplast = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+static void
+camel_sasl_popb4smtp_init (CamelSaslPOPB4SMTP *sasl)
+{
+ sasl->priv = CAMEL_SASL_POPB4SMTP_GET_PRIVATE (sasl);
+}
diff --git a/src/camel/camel-sasl-popb4smtp.h b/src/camel/camel-sasl-popb4smtp.h
new file mode 100644
index 000000000..a079d3988
--- /dev/null
+++ b/src/camel/camel-sasl-popb4smtp.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SASL_POPB4SMTP_H
+#define CAMEL_SASL_POPB4SMTP_H
+
+#include <camel/camel-sasl.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SASL_POPB4SMTP \
+ (camel_sasl_popb4smtp_get_type ())
+#define CAMEL_SASL_POPB4SMTP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SASL_POPB4SMTP, CamelSaslPOPB4SMTP))
+#define CAMEL_SASL_POPB4SMTP_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SASL_POPB4SMTP, CamelSaslPOPB4SMTPClass))
+#define CAMEL_IS_SASL_POPB4SMTP(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SASL_POPB4SMTP))
+#define CAMEL_IS_SASL_POPB4SMTP_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SASL_POPB4SMTP))
+#define CAMEL_SASL_POPB4SMTP_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SASL_POPB4SMTP, CamelSaslPOPB4SMTPClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSaslPOPB4SMTP CamelSaslPOPB4SMTP;
+typedef struct _CamelSaslPOPB4SMTPClass CamelSaslPOPB4SMTPClass;
+typedef struct _CamelSaslPOPB4SMTPPrivate CamelSaslPOPB4SMTPPrivate;
+
+struct _CamelSaslPOPB4SMTP {
+ CamelSasl parent;
+ CamelSaslPOPB4SMTPPrivate *priv;
+};
+
+struct _CamelSaslPOPB4SMTPClass {
+ CamelSaslClass parent_class;
+};
+
+GType camel_sasl_popb4smtp_get_type (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_SASL_POPB4SMTP_H */
diff --git a/src/camel/camel-sasl.c b/src/camel/camel-sasl.c
new file mode 100644
index 000000000..38a55ce78
--- /dev/null
+++ b/src/camel/camel-sasl.c
@@ -0,0 +1,951 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include "camel-debug.h"
+#include "camel-mime-utils.h"
+#include "camel-sasl-anonymous.h"
+#include "camel-sasl-cram-md5.h"
+#include "camel-sasl-digest-md5.h"
+#include "camel-sasl-gssapi.h"
+#include "camel-sasl-login.h"
+#include "camel-sasl-ntlm.h"
+#include "camel-sasl-plain.h"
+#include "camel-sasl-popb4smtp.h"
+#include "camel-sasl.h"
+#include "camel-service.h"
+
+#define CAMEL_SASL_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SASL, CamelSaslPrivate))
+
+#define w(x)
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _CamelSaslPrivate {
+ CamelService *service;
+ gboolean authenticated;
+ gchar *service_name;
+ gchar *mechanism;
+};
+
+struct _AsyncContext {
+ GByteArray *token;
+ gchar *base64_token;
+};
+
+enum {
+ PROP_0,
+ PROP_AUTHENTICATED,
+ PROP_MECHANISM,
+ PROP_SERVICE,
+ PROP_SERVICE_NAME
+};
+
+G_DEFINE_ABSTRACT_TYPE (CamelSasl, camel_sasl, G_TYPE_OBJECT)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+ if (async_context->token != NULL)
+ g_byte_array_free (async_context->token, TRUE);
+
+ g_free (async_context->base64_token);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static void
+sasl_build_class_table_rec (GType type,
+ GHashTable *class_table)
+{
+ GType *children;
+ guint n_children, ii;
+
+ children = g_type_children (type, &n_children);
+
+ for (ii = 0; ii < n_children; ii++) {
+ GType type = children[ii];
+ CamelSaslClass *sasl_class;
+ gpointer key;
+
+ /* Recurse over the child's children. */
+ sasl_build_class_table_rec (type, class_table);
+
+ /* Skip abstract types. */
+ if (G_TYPE_IS_ABSTRACT (type))
+ continue;
+
+ sasl_class = g_type_class_ref (type);
+
+ if (sasl_class->auth_type == NULL) {
+ g_critical (
+ "%s has an empty CamelServiceAuthType",
+ G_OBJECT_CLASS_NAME (sasl_class));
+ g_type_class_unref (sasl_class);
+ continue;
+ }
+
+ key = (gpointer) sasl_class->auth_type->authproto;
+ g_hash_table_insert (class_table, key, sasl_class);
+ }
+
+ g_free (children);
+}
+
+static GHashTable *
+sasl_build_class_table (void)
+{
+ GHashTable *class_table;
+
+ /* Register known types. */
+ CAMEL_TYPE_SASL_ANONYMOUS;
+ CAMEL_TYPE_SASL_CRAM_MD5;
+ CAMEL_TYPE_SASL_DIGEST_MD5;
+#ifdef HAVE_KRB5
+ CAMEL_TYPE_SASL_GSSAPI;
+#endif
+ CAMEL_TYPE_SASL_LOGIN;
+ CAMEL_TYPE_SASL_NTLM;
+ CAMEL_TYPE_SASL_PLAIN;
+ CAMEL_TYPE_SASL_POPB4SMTP;
+
+ class_table = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) NULL,
+ (GDestroyNotify) g_type_class_unref);
+
+ sasl_build_class_table_rec (CAMEL_TYPE_SASL, class_table);
+
+ return class_table;
+}
+
+static void
+sasl_set_mechanism (CamelSasl *sasl,
+ const gchar *mechanism)
+{
+ g_return_if_fail (mechanism != NULL);
+ g_return_if_fail (sasl->priv->mechanism == NULL);
+
+ sasl->priv->mechanism = g_strdup (mechanism);
+}
+
+static void
+sasl_set_service (CamelSasl *sasl,
+ CamelService *service)
+{
+ g_return_if_fail (!service || CAMEL_IS_SERVICE (service));
+ g_return_if_fail (sasl->priv->service == NULL);
+
+ if (service)
+ sasl->priv->service = g_object_ref (service);
+}
+
+static void
+sasl_set_service_name (CamelSasl *sasl,
+ const gchar *service_name)
+{
+ g_return_if_fail (service_name != NULL);
+ g_return_if_fail (sasl->priv->service_name == NULL);
+
+ sasl->priv->service_name = g_strdup (service_name);
+}
+
+static void
+sasl_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTHENTICATED:
+ camel_sasl_set_authenticated (
+ CAMEL_SASL (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_MECHANISM:
+ sasl_set_mechanism (
+ CAMEL_SASL (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_SERVICE:
+ sasl_set_service (
+ CAMEL_SASL (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_SERVICE_NAME:
+ sasl_set_service_name (
+ CAMEL_SASL (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+sasl_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTHENTICATED:
+ g_value_set_boolean (
+ value, camel_sasl_get_authenticated (
+ CAMEL_SASL (object)));
+ return;
+
+ case PROP_MECHANISM:
+ g_value_set_string (
+ value, camel_sasl_get_mechanism (
+ CAMEL_SASL (object)));
+ return;
+
+ case PROP_SERVICE:
+ g_value_set_object (
+ value, camel_sasl_get_service (
+ CAMEL_SASL (object)));
+ return;
+
+ case PROP_SERVICE_NAME:
+ g_value_set_string (
+ value, camel_sasl_get_service_name (
+ CAMEL_SASL (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+sasl_dispose (GObject *object)
+{
+ CamelSaslPrivate *priv;
+
+ priv = CAMEL_SASL_GET_PRIVATE (object);
+
+ if (priv->service != NULL) {
+ g_object_unref (priv->service);
+ priv->service = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_sasl_parent_class)->dispose (object);
+}
+
+static void
+sasl_finalize (GObject *object)
+{
+ CamelSaslPrivate *priv;
+
+ priv = CAMEL_SASL_GET_PRIVATE (object);
+
+ g_free (priv->mechanism);
+ g_free (priv->service_name);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_sasl_parent_class)->finalize (object);
+}
+
+static void
+camel_sasl_class_init (CamelSaslClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelSaslPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = sasl_set_property;
+ object_class->get_property = sasl_get_property;
+ object_class->dispose = sasl_dispose;
+ object_class->finalize = sasl_finalize;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_AUTHENTICATED,
+ g_param_spec_boolean (
+ "authenticated",
+ "Authenticated",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_MECHANISM,
+ g_param_spec_string (
+ "mechanism",
+ "Mechanism",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SERVICE,
+ g_param_spec_object (
+ "service",
+ "Service",
+ NULL,
+ CAMEL_TYPE_SERVICE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SERVICE_NAME,
+ g_param_spec_string (
+ "service-name",
+ "Service Name",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+camel_sasl_init (CamelSasl *sasl)
+{
+ sasl->priv = CAMEL_SASL_GET_PRIVATE (sasl);
+}
+
+/**
+ * camel_sasl_new:
+ * @service_name: the SASL service name
+ * @mechanism: the SASL mechanism
+ * @service: the CamelService that will be using this SASL
+ *
+ * Returns: a new #CamelSasl object for the given @service_name,
+ * @mechanism, and @service, or %NULL if the mechanism is not
+ * supported.
+ **/
+CamelSasl *
+camel_sasl_new (const gchar *service_name,
+ const gchar *mechanism,
+ CamelService *service)
+{
+ GHashTable *class_table;
+ CamelSaslClass *sasl_class;
+ CamelSasl *sasl = NULL;
+
+ g_return_val_if_fail (service_name != NULL, NULL);
+ g_return_val_if_fail (mechanism != NULL, NULL);
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ class_table = sasl_build_class_table ();
+ sasl_class = g_hash_table_lookup (class_table, mechanism);
+
+ if (sasl_class != NULL)
+ sasl = g_object_new (
+ G_OBJECT_CLASS_TYPE (sasl_class),
+ "mechanism", mechanism,
+ "service", service,
+ "service-name", service_name,
+ NULL);
+
+ g_hash_table_destroy (class_table);
+
+ return sasl;
+}
+
+/**
+ * camel_sasl_get_authenticated:
+ * @sasl: a #CamelSasl
+ *
+ * Returns: whether or not @sasl has successfully authenticated the
+ * user. This will be %TRUE after it returns the last needed response.
+ * The caller must still pass that information on to the server and
+ * verify that it has accepted it.
+ **/
+gboolean
+camel_sasl_get_authenticated (CamelSasl *sasl)
+{
+ g_return_val_if_fail (CAMEL_IS_SASL (sasl), FALSE);
+
+ return sasl->priv->authenticated;
+}
+
+/**
+ * camel_sasl_try_empty_password_sync:
+ * @sasl: a #CamelSasl object
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Returns: whether or not @sasl can attempt to authenticate without a
+ * password being provided by the caller. This will be %TRUE for an
+ * authentication method which can attempt to use single-sign-on
+ * credentials, but which can fall back to using a provided password
+ * so it still has the @need_password flag set in its description.
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_sasl_try_empty_password_sync (CamelSasl *sasl,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSaslClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_SASL (sasl), FALSE);
+
+ class = CAMEL_SASL_GET_CLASS (sasl);
+
+ if (class->try_empty_password_sync == NULL)
+ return FALSE;
+
+ return class->try_empty_password_sync (sasl, cancellable, error);
+}
+
+/* Helpder for camel_sasl_try_empty_password() */
+static void
+sasl_try_empty_password_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean result;
+ GError *local_error = NULL;
+
+ result = camel_sasl_try_empty_password_sync (
+ CAMEL_SASL (source_object),
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, result);
+ }
+}
+
+/**
+ * camel_sasl_try_empty_password:
+ * @sasl: a #CamelSasl
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously determine whether @sasl can be used for password-less
+ * authentication, for example single-sign-on using system credentials.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_sasl_try_empty_password_finish() to get the result of the
+ * operation.
+ *
+ * Since: 3.2
+ **/
+void
+camel_sasl_try_empty_password (CamelSasl *sasl,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_return_if_fail (CAMEL_IS_SASL (sasl));
+
+ task = g_task_new (sasl, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_sasl_try_empty_password);
+ g_task_set_priority (task, io_priority);
+
+ g_task_run_in_thread (task, sasl_try_empty_password_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_sasl_try_empty_password_finish:
+ * @sasl: a #CamelSasl
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_sasl_try_empty_password().
+ *
+ * Returns: the SASL response. If an error occurred, @error will also be set.
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_sasl_try_empty_password_finish (CamelSasl *sasl,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_SASL (sasl), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, sasl), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_sasl_try_empty_password), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_sasl_set_authenticated:
+ * @sasl: a #CamelSasl
+ * @authenticated: whether we have successfully authenticated
+ *
+ * Since: 2.32
+ **/
+void
+camel_sasl_set_authenticated (CamelSasl *sasl,
+ gboolean authenticated)
+{
+ g_return_if_fail (CAMEL_IS_SASL (sasl));
+
+ if (sasl->priv->authenticated == authenticated)
+ return;
+
+ sasl->priv->authenticated = authenticated;
+
+ g_object_notify (G_OBJECT (sasl), "authenticated");
+}
+
+/**
+ * camel_sasl_get_mechanism:
+ * @sasl: a #CamelSasl
+ *
+ * Since: 2.32
+ **/
+const gchar *
+camel_sasl_get_mechanism (CamelSasl *sasl)
+{
+ g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
+
+ return sasl->priv->mechanism;
+}
+
+/**
+ * camel_sasl_get_service:
+ * @sasl: a #CamelSasl
+ *
+ * Returns: (transfer none):
+ *
+ * Since: 2.32
+ **/
+CamelService *
+camel_sasl_get_service (CamelSasl *sasl)
+{
+ g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
+
+ return sasl->priv->service;
+}
+
+/**
+ * camel_sasl_get_service_name:
+ * @sasl: a #CamelSasl
+ *
+ * Since: 2.32
+ **/
+const gchar *
+camel_sasl_get_service_name (CamelSasl *sasl)
+{
+ g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
+
+ return sasl->priv->service_name;
+}
+
+/**
+ * camel_sasl_challenge_sync:
+ * @sasl: a #CamelSasl
+ * @token: a token, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * If @token is %NULL, generate the initial SASL message to send to
+ * the server. (This will be %NULL if the client doesn't initiate the
+ * exchange.) Otherwise, @token is a challenge from the server, and
+ * the return value is the response.
+ *
+ * Free the returned #GByteArray with g_byte_array_free().
+ *
+ * Returns: (transfer full): the SASL response or %NULL. If an error occurred, @error will
+ * also be set.
+ **/
+GByteArray *
+camel_sasl_challenge_sync (CamelSasl *sasl,
+ GByteArray *token,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSaslClass *class;
+ GByteArray *response;
+
+ g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
+
+ class = CAMEL_SASL_GET_CLASS (sasl);
+ g_return_val_if_fail (class->challenge_sync != NULL, NULL);
+
+ response = class->challenge_sync (sasl, token, cancellable, error);
+ if (token != NULL)
+ CAMEL_CHECK_GERROR (
+ sasl, challenge_sync, response != NULL, error);
+
+ return response;
+}
+
+/* Helper for camel_sasl_challenge() */
+static void
+sasl_challenge_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GByteArray *response;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ response = camel_sasl_challenge_sync (
+ CAMEL_SASL (source_object),
+ async_context->token,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_warn_if_fail (response == NULL);
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_pointer (
+ task, response,
+ (GDestroyNotify) g_byte_array_unref);
+ }
+}
+
+/**
+ * camel_sasl_challenge:
+ * @sasl: a #CamelSasl
+ * @token: a token, or %NULL
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * If @token is %NULL, asynchronously generate the initial SASL message
+ * to send to the server. (This will be %NULL if the client doesn't
+ * initiate the exchange.) Otherwise, @token is a challenge from the
+ * server, and the asynchronous result is the response.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_sasl_challenge_finish() to get the result of the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_sasl_challenge (CamelSasl *sasl,
+ GByteArray *token,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_SASL (sasl));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->token = g_byte_array_new ();
+
+ g_byte_array_append (async_context->token, token->data, token->len);
+
+ task = g_task_new (sasl, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_sasl_challenge);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, sasl_challenge_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_sasl_challenge_finish:
+ * @sasl: a #CamelSasl
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_sasl_challenge(). Free the
+ * returned #GByteArray with g_byte_array_free().
+ *
+ * Returns: (transfer full): the SASL response or %NULL. If an error occurred, @error will
+ * also be set.
+ *
+ * Since: 3.0
+ **/
+GByteArray *
+camel_sasl_challenge_finish (CamelSasl *sasl,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, sasl), NULL);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_sasl_challenge), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/**
+ * camel_sasl_challenge_base64_sync:
+ * @sasl: a #CamelSasl
+ * @token: a base64-encoded token
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * As with camel_sasl_challenge_sync(), but the challenge @token and the
+ * response are both base64-encoded.
+ *
+ * Returns: (transfer full): the base64-encoded response
+ *
+ * Since: 3.0
+ **/
+gchar *
+camel_sasl_challenge_base64_sync (CamelSasl *sasl,
+ const gchar *token,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GByteArray *token_binary;
+ GByteArray *response_binary;
+ gchar *response;
+
+ g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
+
+ if (token != NULL && *token != '\0') {
+ guchar *data;
+ gsize length = 0;
+
+ data = g_base64_decode (token, &length);
+ token_binary = g_byte_array_new ();
+ g_byte_array_append (token_binary, data, length);
+ g_free (data);
+ } else
+ token_binary = NULL;
+
+ response_binary = camel_sasl_challenge_sync (
+ sasl, token_binary, cancellable, error);
+ if (token_binary)
+ g_byte_array_free (token_binary, TRUE);
+ if (response_binary == NULL)
+ return NULL;
+
+ if (response_binary->len > 0)
+ response = g_base64_encode (
+ response_binary->data, response_binary->len);
+ else
+ response = g_strdup ("");
+
+ g_byte_array_free (response_binary, TRUE);
+
+ return response;
+}
+
+/* Helper for camel_sasl_challenge_base64() */
+static void
+sasl_challenge_base64_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gchar *base64_response;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ base64_response = camel_sasl_challenge_base64_sync (
+ CAMEL_SASL (source_object),
+ async_context->base64_token,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_warn_if_fail (base64_response == NULL);
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_pointer (
+ task, base64_response,
+ (GDestroyNotify) g_free);
+ }
+}
+
+/**
+ * camel_sasl_challenge_base64:
+ * @sasl: a #CamelSasl
+ * @token: a base64-encoded token
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * As with camel_sasl_challenge(), but the challenge @token and the
+ * response are both base64-encoded.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_store_challenge_base64_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_sasl_challenge_base64 (CamelSasl *sasl,
+ const gchar *token,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_SASL (sasl));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->base64_token = g_strdup (token);
+
+ task = g_task_new (sasl, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_sasl_challenge_base64);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, sasl_challenge_base64_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_sasl_challenge_base64_finish:
+ * @sasl: a #CamelSasl
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_sasl_challenge_base64().
+ *
+ * Returns: the base64-encoded response
+ *
+ * Since: 3.0
+ **/
+gchar *
+camel_sasl_challenge_base64_finish (CamelSasl *sasl,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, sasl), NULL);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_sasl_challenge_base64), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/**
+ * camel_sasl_authtype_list:
+ * @include_plain: whether or not to include the PLAIN mechanism
+ *
+ * Returns: (element-type CamelServiceAuthType) (transfer container): a #GList of SASL-supported authtypes. The caller must
+ * free the list, but not the contents.
+ **/
+GList *
+camel_sasl_authtype_list (gboolean include_plain)
+{
+ CamelSaslClass *sasl_class;
+ GHashTable *class_table;
+ GList *types = NULL;
+
+ /* XXX I guess these are supposed to be common SASL auth types,
+ * since this is called by the IMAP, POP and SMTP providers.
+ * The returned list can be extended with other auth types
+ * by way of camel_sasl_authtype(), so maybe we should just
+ * drop the ad-hoc "include_plain" parameter? */
+
+ class_table = sasl_build_class_table ();
+
+ sasl_class = g_hash_table_lookup (class_table, "CRAM-MD5");
+ g_return_val_if_fail (sasl_class != NULL, types);
+ types = g_list_prepend (types, sasl_class->auth_type);
+
+ sasl_class = g_hash_table_lookup (class_table, "DIGEST-MD5");
+ g_return_val_if_fail (sasl_class != NULL, types);
+ types = g_list_prepend (types, sasl_class->auth_type);
+
+#ifdef HAVE_KRB5
+ sasl_class = g_hash_table_lookup (class_table, "GSSAPI");
+ g_return_val_if_fail (sasl_class != NULL, types);
+ types = g_list_prepend (types, sasl_class->auth_type);
+#endif
+
+ sasl_class = g_hash_table_lookup (class_table, "NTLM");
+ g_return_val_if_fail (sasl_class != NULL, types);
+ types = g_list_prepend (types, sasl_class->auth_type);
+
+ if (include_plain) {
+ sasl_class = g_hash_table_lookup (class_table, "PLAIN");
+ g_return_val_if_fail (sasl_class != NULL, types);
+ types = g_list_prepend (types, sasl_class->auth_type);
+ }
+
+ g_hash_table_destroy (class_table);
+
+ return types;
+}
+
+/**
+ * camel_sasl_authtype:
+ * @mechanism: the SASL mechanism to get an authtype for
+ *
+ * Returns: a #CamelServiceAuthType for the given mechanism, if
+ * it is supported.
+ **/
+CamelServiceAuthType *
+camel_sasl_authtype (const gchar *mechanism)
+{
+ GHashTable *class_table;
+ CamelSaslClass *sasl_class;
+ CamelServiceAuthType *auth_type;
+
+ g_return_val_if_fail (mechanism != NULL, NULL);
+
+ class_table = sasl_build_class_table ();
+ sasl_class = g_hash_table_lookup (class_table, mechanism);
+ auth_type = (sasl_class != NULL) ? sasl_class->auth_type : NULL;
+ g_hash_table_destroy (class_table);
+
+ return auth_type;
+}
diff --git a/src/camel/camel-sasl.h b/src/camel/camel-sasl.h
new file mode 100644
index 000000000..e167199c0
--- /dev/null
+++ b/src/camel/camel-sasl.h
@@ -0,0 +1,138 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SASL_H
+#define CAMEL_SASL_H
+
+#include <camel/camel-service.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SASL \
+ (camel_sasl_get_type ())
+#define CAMEL_SASL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SASL, CamelSasl))
+#define CAMEL_SASL_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SASL, CamelSaslClass))
+#define CAMEL_IS_SASL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SASL))
+#define CAMEL_IS_SASL_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SASL))
+#define CAMEL_SASL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SASL, CamelSaslClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSasl CamelSasl;
+typedef struct _CamelSaslClass CamelSaslClass;
+typedef struct _CamelSaslPrivate CamelSaslPrivate;
+
+struct _CamelSasl {
+ GObject parent;
+ CamelSaslPrivate *priv;
+};
+
+struct _CamelSaslClass {
+ GObjectClass parent_class;
+
+ /* Auth Mechanism Details */
+ CamelServiceAuthType *auth_type;
+
+ /* Synchronous I/O Methods */
+ gboolean (*try_empty_password_sync)
+ (CamelSasl *sasl,
+ GCancellable *cancellable,
+ GError **error);
+ GByteArray * (*challenge_sync) (CamelSasl *sasl,
+ GByteArray *token,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* Reserved slots. */
+ gpointer reserved[4];
+};
+
+GType camel_sasl_get_type (void);
+CamelSasl * camel_sasl_new (const gchar *service_name,
+ const gchar *mechanism,
+ CamelService *service);
+gboolean camel_sasl_try_empty_password_sync
+ (CamelSasl *sasl,
+ GCancellable *cancellable,
+ GError **error);
+void camel_sasl_try_empty_password (CamelSasl *sasl,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_sasl_try_empty_password_finish
+ (CamelSasl *sasl,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_sasl_get_authenticated (CamelSasl *sasl);
+void camel_sasl_set_authenticated (CamelSasl *sasl,
+ gboolean authenticated);
+const gchar * camel_sasl_get_mechanism (CamelSasl *sasl);
+CamelService * camel_sasl_get_service (CamelSasl *sasl);
+const gchar * camel_sasl_get_service_name (CamelSasl *sasl);
+
+GByteArray * camel_sasl_challenge_sync (CamelSasl *sasl,
+ GByteArray *token,
+ GCancellable *cancellable,
+ GError **error);
+void camel_sasl_challenge (CamelSasl *sasl,
+ GByteArray *token,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GByteArray * camel_sasl_challenge_finish (CamelSasl *sasl,
+ GAsyncResult *result,
+ GError **error);
+gchar * camel_sasl_challenge_base64_sync
+ (CamelSasl *sasl,
+ const gchar *token,
+ GCancellable *cancellable,
+ GError **error);
+void camel_sasl_challenge_base64 (CamelSasl *sasl,
+ const gchar *token,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gchar * camel_sasl_challenge_base64_finish
+ (CamelSasl *sasl,
+ GAsyncResult *result,
+ GError **error);
+
+GList * camel_sasl_authtype_list (gboolean include_plain);
+CamelServiceAuthType *
+ camel_sasl_authtype (const gchar *mechanism);
+
+G_END_DECLS
+
+#endif /* CAMEL_SASL_H */
diff --git a/src/camel/camel-search-private.c b/src/camel/camel-search-private.c
new file mode 100644
index 000000000..ced34e989
--- /dev/null
+++ b/src/camel/camel-search-private.c
@@ -0,0 +1,874 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ * Michael Zucchi <NotZed@Ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+/* POSIX requires <sys/types.h> be included before <regex.h> */
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <regex.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-mime-message.h"
+#include "camel-multipart.h"
+#include "camel-search-private.h"
+#include "camel-stream-mem.h"
+
+#define d(x)
+
+/* builds the regex into pattern */
+/* taken from camel-folder-search, with added isregex & exception parameter */
+/* Basically, we build a new regex, either based on subset regex's, or
+ * substrings, that can be executed once over the whoel body, to match
+ * anything suitable. This is more efficient than multiple searches,
+ * and probably most (naive) strstr implementations, over long content.
+ *
+ * A small issue is that case-insenstivity won't work entirely correct
+ * for utf8 strings. */
+gint
+camel_search_build_match_regex (regex_t *pattern,
+ camel_search_flags_t type,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ GError **error)
+{
+ GString *match = g_string_new ("");
+ gint c, i, count = 0, err;
+ gchar *word;
+ gint flags;
+
+ /* Build a regex pattern we can use to match the words,
+ * we OR them together. */
+ if (argc > 1)
+ g_string_append_c (match, '(');
+ for (i = 0; i < argc; i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING) {
+ if (count > 0)
+ g_string_append_c (match, '|');
+
+ word = argv[i]->value.string;
+ if (type & CAMEL_SEARCH_MATCH_REGEX) {
+ /* No need to escape because this
+ * should already be a valid regex. */
+ g_string_append (match, word);
+ } else {
+ /* Escape any special chars (not
+ * sure if this list is complete). */
+ if (type & CAMEL_SEARCH_MATCH_START)
+ g_string_append_c (match, '^');
+ while ((c = *word++)) {
+ if (strchr ("*\\.()[]^$+", c) != NULL) {
+ g_string_append_c (match, '\\');
+ }
+ g_string_append_c (match, c);
+ }
+ if (type & CAMEL_SEARCH_MATCH_END)
+ g_string_append_c (match, '^');
+ }
+ count++;
+ } else {
+ g_warning ("Invalid type passed to body-contains match function");
+ }
+ }
+ if (argc > 1)
+ g_string_append_c (match, ')');
+ flags = REG_EXTENDED | REG_NOSUB;
+ if (type & CAMEL_SEARCH_MATCH_ICASE)
+ flags |= REG_ICASE;
+ if (type & CAMEL_SEARCH_MATCH_NEWLINE)
+ flags |= REG_NEWLINE;
+ err = regcomp (pattern, match->str, flags);
+ if (err != 0) {
+ /* regerror gets called twice to get the full error
+ * string length to do proper posix error reporting. */
+ gint len = regerror (err, pattern, NULL, 0);
+ gchar *buffer = g_malloc0 (len + 1);
+
+ regerror (err, pattern, buffer, len);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Regular expression compilation failed: %s: %s"),
+ match->str, buffer);
+
+ regfree (pattern);
+ }
+ d (printf ("Built regex: '%s'\n", match->str));
+ g_string_free (match, TRUE);
+
+ return err;
+}
+
+static guchar soundex_table[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 49, 50, 51, 0, 49, 50, 0, 0, 50, 50, 52, 53, 53, 0,
+ 49, 50, 54, 50, 51, 0, 49, 0, 50, 0, 50, 0, 0, 0, 0, 0,
+ 0, 0, 49, 50, 51, 0, 49, 50, 0, 0, 50, 50, 52, 53, 53, 0,
+ 49, 50, 54, 50, 51, 0, 49, 0, 50, 0, 50, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static void
+soundexify (const gchar *sound,
+ gchar code[5])
+{
+ guchar *c, last = '\0';
+ gint n;
+
+ for (c = (guchar *) sound; *c && !isalpha (*c); c++);
+ code[0] = toupper (*c);
+ memset (code + 1, 0, 3);
+ for (n = 1; *c && n < 5; c++) {
+ guchar ch = soundex_table[*c];
+
+ if (ch && ch != last) {
+ code[n++] = ch;
+ last = ch;
+ }
+ }
+ code[4] = '\0';
+}
+
+static gboolean
+header_soundex (const gchar *header,
+ const gchar *match)
+{
+ gchar mcode[5], hcode[5];
+ const gchar *p;
+ gchar c;
+ GString *word;
+ gint truth = FALSE;
+
+ soundexify (match, mcode);
+
+ /* Split the header into words and soundexify and compare each one. */
+ /* FIXME: Should this convert to utf8, and split based on that,
+ * and what not?
+ * soundex only makes sense for us-ascii though ... */
+
+ word = g_string_new ("");
+ p = header;
+ do {
+ c = *p++;
+ if (c == 0 || isspace (c)) {
+ if (word->len > 0) {
+ soundexify (word->str, hcode);
+ if (strcmp (hcode, mcode) == 0)
+ truth = TRUE;
+ }
+ g_string_truncate (word, 0);
+ } else if (isalpha (c))
+ g_string_append_c (word, c);
+ } while (c && !truth);
+ g_string_free (word, TRUE);
+
+ return truth;
+}
+
+const gchar *
+camel_ustrstrcase (const gchar *haystack,
+ const gchar *needle)
+{
+ gunichar *nuni, *puni;
+ gunichar u;
+ const guchar *p;
+
+ g_return_val_if_fail (haystack != NULL, NULL);
+ g_return_val_if_fail (needle != NULL, NULL);
+
+ if (strlen (needle) == 0)
+ return haystack;
+ if (strlen (haystack) == 0)
+ return NULL;
+
+ puni = nuni = g_alloca (sizeof (gunichar) * (strlen (needle) + 1));
+ nuni[0] = 0;
+
+ p = (const guchar *) needle;
+ while ((u = camel_utf8_getc (&p)))
+ *puni++ = g_unichar_tolower (u);
+
+ /* NULL means there was illegal utf-8 sequence. */
+ if (!p)
+ return NULL;
+
+ p = (const guchar *) haystack;
+ while ((u = camel_utf8_getc (&p))) {
+ gunichar c;
+
+ c = g_unichar_tolower (u);
+ /* We have valid stripped gchar. */
+ if (c == nuni[0]) {
+ const guchar *q = p;
+ gint npos = 1;
+
+ while (nuni + npos < puni) {
+ u = camel_utf8_getc (&q);
+ if (!q || !u)
+ return NULL;
+
+ c = g_unichar_tolower (u);
+ if (c != nuni[npos])
+ break;
+
+ npos++;
+ }
+
+ if (nuni + npos == puni)
+ return (const gchar *) p;
+ }
+ }
+
+ return NULL;
+}
+
+#define CAMEL_SEARCH_COMPARE(x, y, z) G_STMT_START { \
+ if ((x) == (z)) { \
+ if ((y) == (z)) \
+ return 0; \
+ else \
+ return -1; \
+ } else if ((y) == (z)) \
+ return 1; \
+} G_STMT_END
+
+static gint
+camel_ustrcasecmp (const gchar *ps1,
+ const gchar *ps2)
+{
+ gunichar u1, u2 = 0;
+ const guchar *s1 = (const guchar *) ps1;
+ const guchar *s2 = (const guchar *) ps2;
+
+ CAMEL_SEARCH_COMPARE (s1, s2, NULL);
+
+ u1 = camel_utf8_getc (&s1);
+ u2 = camel_utf8_getc (&s2);
+ while (u1 && u2) {
+ u1 = g_unichar_tolower (u1);
+ u2 = g_unichar_tolower (u2);
+ if (u1 < u2)
+ return -1;
+ else if (u1 > u2)
+ return 1;
+
+ u1 = camel_utf8_getc (&s1);
+ u2 = camel_utf8_getc (&s2);
+ }
+
+ /* end of one of the strings ? */
+ CAMEL_SEARCH_COMPARE (u1, u2, 0);
+
+ /* if we have invalid utf8 sequence ? */
+ /* coverity[dead_error_begin] */
+ CAMEL_SEARCH_COMPARE (s1, s2, NULL);
+
+ return 0;
+}
+
+static gchar *
+depunct_string (const gchar *str)
+{
+ gchar *res;
+ gint ii;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ res = g_strdup (str);
+ for (ii = 0; res[ii]; ii++) {
+ if (ispunct (res[ii]))
+ res[ii] = ' ';
+ }
+
+ return res;
+}
+
+static gboolean
+camel_uwordcase (const gchar *haystack,
+ const gchar *needle)
+{
+ struct _camel_search_words *hwords, *nwords;
+ gchar *copy_haystack, *copy_needle;
+ gboolean found_all;
+ gint ii, jj;
+
+ g_return_val_if_fail (haystack != NULL, FALSE);
+ g_return_val_if_fail (needle != NULL, FALSE);
+
+ if (!*needle)
+ return TRUE;
+ if (!*haystack)
+ return FALSE;
+
+ copy_haystack = depunct_string (haystack);
+ copy_needle = depunct_string (needle);
+ hwords = camel_search_words_split ((const guchar *) copy_haystack);
+ nwords = camel_search_words_split ((const guchar *) copy_needle);
+ g_free (copy_haystack);
+ g_free (copy_needle);
+
+ found_all = TRUE;
+ for (ii = 0; ii < nwords->len && found_all; ii++) {
+ found_all = FALSE;
+
+ for (jj = 0; jj < hwords->len; jj++) {
+ if (camel_ustrcasecmp (hwords->words[jj]->word, nwords->words[ii]->word) == 0) {
+ found_all = TRUE;
+ break;
+ }
+ }
+ }
+
+ camel_search_words_free (hwords);
+ camel_search_words_free (nwords);
+
+ return found_all;
+}
+
+static gint
+camel_ustrncasecmp (const gchar *ps1,
+ const gchar *ps2,
+ gsize len)
+{
+ gunichar u1, u2 = 0;
+ const guchar *s1 = (const guchar *) ps1;
+ const guchar *s2 = (const guchar *) ps2;
+
+ CAMEL_SEARCH_COMPARE (s1, s2, NULL);
+
+ u1 = camel_utf8_getc (&s1);
+ u2 = camel_utf8_getc (&s2);
+ while (len > 0 && u1 && u2) {
+ u1 = g_unichar_tolower (u1);
+ u2 = g_unichar_tolower (u2);
+ if (u1 < u2)
+ return -1;
+ else if (u1 > u2)
+ return 1;
+
+ len--;
+ u1 = camel_utf8_getc (&s1);
+ u2 = camel_utf8_getc (&s2);
+ }
+
+ if (len == 0)
+ return 0;
+
+ /* end of one of the strings ? */
+ CAMEL_SEARCH_COMPARE (u1, u2, 0);
+
+ /* if we have invalid utf8 sequence ? */
+ /* coverity[dead_error_begin] */
+ CAMEL_SEARCH_COMPARE (s1, s2, NULL);
+
+ return 0;
+}
+
+/* Value is the match value suitable for exact match if required. */
+static gint
+header_match (const gchar *value,
+ const gchar *match,
+ camel_search_match_t how)
+{
+ gint vlen, mlen;
+
+ if (how == CAMEL_SEARCH_MATCH_SOUNDEX)
+ return header_soundex (value, match);
+
+ vlen = strlen (value);
+ mlen = strlen (match);
+ if (vlen < mlen)
+ return FALSE;
+
+ switch (how) {
+ case CAMEL_SEARCH_MATCH_EXACT:
+ return camel_ustrcasecmp (value, match) == 0;
+ case CAMEL_SEARCH_MATCH_CONTAINS:
+ return camel_ustrstrcase (value, match) != NULL;
+ case CAMEL_SEARCH_MATCH_WORD:
+ return camel_uwordcase (value, match);
+ case CAMEL_SEARCH_MATCH_STARTS:
+ return camel_ustrncasecmp (value, match, mlen) == 0;
+ case CAMEL_SEARCH_MATCH_ENDS:
+ return camel_ustrcasecmp (value + vlen - mlen, match) == 0;
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+/* Searches for match inside value. If match is mixed
+ * case, then use case-sensitive, else insensitive. */
+gboolean
+camel_search_header_match (const gchar *value,
+ const gchar *match,
+ camel_search_match_t how,
+ camel_search_t type,
+ const gchar *default_charset)
+{
+ const gchar *name, *addr;
+ const guchar *ptr;
+ gint truth = FALSE, i;
+ CamelInternetAddress *cia;
+ gchar *v, *vdom, *mdom;
+ gunichar c;
+
+ ptr = (const guchar *) value;
+ while ((c = camel_utf8_getc (&ptr)) && g_unichar_isspace (c))
+ value = (const gchar *) ptr;
+
+ switch (type) {
+ case CAMEL_SEARCH_TYPE_ENCODED:
+ /* FIXME Find header charset. */
+ v = camel_header_decode_string (value, default_charset);
+ truth = header_match (v, match, how);
+ g_free (v);
+ break;
+ case CAMEL_SEARCH_TYPE_MLIST:
+ /* Special mailing list old-version domain hack.
+ * If one of the mailing list names doesn't have an @ in it,
+ * its old-style, so only match against the pre-domain part,
+ * which should be common. */
+ vdom = strchr (value, '@');
+ mdom = strchr (match, '@');
+ if (mdom != NULL && vdom == NULL) {
+ v = g_alloca (mdom - match + 1);
+ memcpy (v, match, mdom - match);
+ v[mdom - match] = 0;
+ match = (gchar *) v;
+ }
+ /* Falls through */
+ case CAMEL_SEARCH_TYPE_ASIS:
+ truth = header_match (value, match, how);
+ break;
+ case CAMEL_SEARCH_TYPE_ADDRESS_ENCODED:
+ case CAMEL_SEARCH_TYPE_ADDRESS:
+ /* Possible simple case to save some work if we can. */
+ if (header_match (value, match, how))
+ return TRUE;
+
+ /* Now we decode any addresses, and try
+ * as-is matches on name and address parts. */
+ cia = camel_internet_address_new ();
+ if (type == CAMEL_SEARCH_TYPE_ADDRESS_ENCODED)
+ camel_address_decode ((CamelAddress *) cia, value);
+ else
+ camel_address_unformat ((CamelAddress *) cia, value);
+
+ for (i = 0; !truth && camel_internet_address_get (cia, i, &name, &addr); i++)
+ truth =
+ (name && header_match (name, match, how)) ||
+ (addr && header_match (addr, match, how));
+
+ g_object_unref (cia);
+ break;
+ }
+
+ return truth;
+}
+
+/* Performs a 'slow' content-based match. */
+/* There is also an identical copy of this in camel-filter-search.c. */
+gboolean
+camel_search_message_body_contains (CamelDataWrapper *object,
+ regex_t *pattern)
+{
+ CamelDataWrapper *containee;
+ gint truth = FALSE;
+ gint parts, i;
+
+ containee = camel_medium_get_content (CAMEL_MEDIUM (object));
+
+ if (containee == NULL)
+ return FALSE;
+
+ /* Using the object types is more accurate than using 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 = camel_search_message_body_contains (part, pattern);
+ }
+ } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
+ /* For messages we only look at its contents. */
+ truth = camel_search_message_body_contains ((CamelDataWrapper *) containee, pattern);
+ } else if (camel_content_type_is (CAMEL_DATA_WRAPPER (containee)->mime_type, "text", "*")
+ || camel_content_type_is (CAMEL_DATA_WRAPPER (containee)->mime_type, "x-evolution", "evolution-rss-feed")) {
+ /* For all other text parts we look
+ * inside, otherwise we don't care. */
+ CamelStream *stream;
+ GByteArray *byte_array;
+ const gchar *charset;
+
+ byte_array = g_byte_array_new ();
+ stream = camel_stream_mem_new_with_byte_array (byte_array);
+
+ charset = camel_content_type_param (CAMEL_DATA_WRAPPER (containee)->mime_type, "charset");
+ if (charset && *charset) {
+ CamelMimeFilter *filter = camel_mime_filter_charset_new (charset, "UTF-8");
+ if (filter) {
+ CamelStream *filtered = camel_stream_filter_new (stream);
+
+ if (filtered) {
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (filtered), filter);
+ g_object_unref (stream);
+ stream = filtered;
+ }
+
+ g_object_unref (filter);
+ }
+ }
+
+ camel_data_wrapper_decode_to_stream_sync (
+ containee, stream, NULL, NULL);
+ camel_stream_write (stream, "", 1, NULL, NULL);
+ truth = regexec (pattern, (gchar *) byte_array->data, 0, NULL, 0) == 0;
+ g_object_unref (stream);
+ }
+
+ return truth;
+}
+
+static void
+output_c (GString *w,
+ guint32 c,
+ gint *type)
+{
+ gint utf8len;
+ gchar utf8[8];
+
+ if (!g_unichar_isalnum (c))
+ *type = CAMEL_SEARCH_WORD_COMPLEX | (*type & CAMEL_SEARCH_WORD_8BIT);
+ else
+ c = g_unichar_tolower (c);
+
+ if (c > 0x80)
+ *type |= CAMEL_SEARCH_WORD_8BIT;
+
+ /* FIXME: use camel_utf8_putc */
+ utf8len = g_unichar_to_utf8 (c, utf8);
+ utf8[utf8len] = 0;
+ g_string_append (w, utf8);
+}
+
+static void
+output_w (GString *w,
+ GPtrArray *list,
+ gint type)
+{
+ struct _camel_search_word *word;
+
+ if (w->len) {
+ word = g_malloc0 (sizeof (*word));
+ word->word = g_strdup (w->str);
+ word->type = type;
+ g_ptr_array_add (list, word);
+ g_string_truncate (w, 0);
+ }
+}
+
+struct _camel_search_words *
+camel_search_words_split (const guchar *in)
+{
+ gint type = CAMEL_SEARCH_WORD_SIMPLE, all = 0;
+ GString *w;
+ struct _camel_search_words *words;
+ GPtrArray *list = g_ptr_array_new ();
+ guint32 c;
+ gint inquote = 0;
+
+ words = g_malloc0 (sizeof (*words));
+ w = g_string_new ("");
+
+ do {
+ c = camel_utf8_getc (&in);
+
+ if (c == 0
+ || (inquote && c == '"')
+ || (!inquote && g_unichar_isspace (c))) {
+ output_w (w, list, type);
+ all |= type;
+ type = CAMEL_SEARCH_WORD_SIMPLE;
+ inquote = 0;
+ } else {
+ if (c == '\\') {
+ c = camel_utf8_getc (&in);
+ if (c)
+ output_c (w, c, &type);
+ else {
+ output_w (w, list, type);
+ all |= type;
+ }
+ } else if (c == '\"') {
+ inquote = 1;
+ } else {
+ output_c (w, c, &type);
+ }
+ }
+ } while (c);
+
+ g_string_free (w, TRUE);
+ words->len = list->len;
+ words->words = (struct _camel_search_word **) list->pdata;
+ words->type = all;
+ g_ptr_array_free (list, FALSE);
+
+ return words;
+}
+
+/* Takes an existing 'words' list, and converts it to another consisting
+ * of only simple words, with any punctuation, etc stripped. */
+struct _camel_search_words *
+camel_search_words_simple (struct _camel_search_words *wordin)
+{
+ gint i;
+ const guchar *ptr, *start, *last;
+ gint type = CAMEL_SEARCH_WORD_SIMPLE, all = 0;
+ GPtrArray *list = g_ptr_array_new ();
+ struct _camel_search_word *word;
+ struct _camel_search_words *words;
+ guint32 c;
+
+ words = g_malloc0 (sizeof (*words));
+
+ for (i = 0; i < wordin->len; i++) {
+ if ((wordin->words[i]->type & CAMEL_SEARCH_WORD_COMPLEX) == 0) {
+ word = g_malloc0 (sizeof (*word));
+ word->type = wordin->words[i]->type;
+ word->word = g_strdup (wordin->words[i]->word);
+ g_ptr_array_add (list, word);
+ } else {
+ ptr = (const guchar *) wordin->words[i]->word;
+ start = last = ptr;
+ do {
+ c = camel_utf8_getc (&ptr);
+ if (c == 0 || !g_unichar_isalnum (c)) {
+ if (last > start) {
+ word = g_malloc0 (sizeof (*word));
+ word->word = g_strndup ((gchar *) start, last - start);
+ word->type = type;
+ g_ptr_array_add (list, word);
+ all |= type;
+ type = CAMEL_SEARCH_WORD_SIMPLE;
+ }
+ start = ptr;
+ }
+ if (c > 0x80)
+ type = CAMEL_SEARCH_WORD_8BIT;
+ last = ptr;
+ } while (c);
+ }
+ }
+
+ words->len = list->len;
+ words->words = (struct _camel_search_word **) list->pdata;
+ words->type = all;
+ g_ptr_array_free (list, FALSE);
+
+ return words;
+}
+
+void
+camel_search_words_free (struct _camel_search_words *words)
+{
+ gint i;
+
+ for (i = 0; i < words->len; i++) {
+ struct _camel_search_word *word = words->words[i];
+
+ g_free (word->word);
+ g_free (word);
+ }
+ g_free (words->words);
+ g_free (words);
+}
+
+/**
+ * camel_search_header_is_address:
+ * @header_name: A header name, like "Subject"
+ *
+ * Returns: Whether the @header_name is a header with a mail address
+ *
+ * Since: 3.22
+ **/
+gboolean
+camel_search_header_is_address (const gchar *header_name)
+{
+ const gchar *headers[] = {
+ "Reply-To",
+ "From",
+ "To",
+ "Cc",
+ "Bcc",
+ "Resent-From",
+ "Resent-To",
+ "Resent-Cc",
+ "Resent-Bcc",
+ NULL };
+ gint ii;
+
+ if (!header_name || !*header_name)
+ return FALSE;
+
+ for (ii = 0; headers[ii]; ii++) {
+ if (g_ascii_strcasecmp (headers[ii], header_name) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * camel_search_get_default_charset_from_message:
+ * @message: a #CamelMimeMessage
+ *
+ * Returns: Default charset of the @message; if none cannot be determined,
+ * UTF-8 is returned.
+ *
+ * Since: 3.22
+ **/
+const gchar *
+camel_search_get_default_charset_from_message (CamelMimeMessage *message)
+{
+ CamelContentType *ct;
+ const gchar *charset;
+
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
+
+ ct = camel_mime_part_get_content_type (CAMEL_MIME_PART (message));
+ charset = camel_content_type_param (ct, "charset");
+ if (!charset)
+ charset = "utf-8";
+
+ charset = camel_iconv_charset_name (charset);
+
+ return charset;
+}
+
+/**
+ * camel_search_get_header_decoded:
+ * @header_name: the header name
+ * @header_value: the header value
+ * @default_charset: (nullable): the default charset to use for the decode, or %NULL
+ *
+ * Decodes @header_value, if needed, either from an address header
+ * or the Subject header. Other @header_name headers are returned
+ * as is.
+ *
+ * Returns: (transfer full): decoded header value, suitable for text comparison.
+ * Free the returned pointer with g_free() when done with it.
+ *
+ * Since: 3.22
+ **/
+gchar *
+camel_search_get_header_decoded (const gchar *header_name,
+ const gchar *header_value,
+ const gchar *default_charset)
+{
+ gchar *unfold, *decoded;
+
+ if (!header_value || !*header_value)
+ return NULL;
+
+ unfold = camel_header_unfold (header_value);
+
+ if (g_ascii_strcasecmp (header_name, "Subject") == 0 ||
+ camel_search_header_is_address (header_name)) {
+ decoded = camel_header_decode_string (unfold, default_charset);
+ } else {
+ decoded = unfold;
+ unfold = NULL;
+ }
+
+ g_free (unfold);
+
+ return decoded;
+}
+
+/**
+ * camel_search_get_all_headers_decoded:
+ * @message: a #CamelMessage
+ *
+ * Returns: (transfer full): All headers of the @message, decoded where needed.
+ * Free the returned pointer with g_free() when done with it.
+ *
+ * Since: 3.22
+ **/
+gchar *
+camel_search_get_all_headers_decoded (CamelMimeMessage *message)
+{
+ CamelMedium *medium;
+ GString *str;
+ GArray *headers;
+ const gchar *default_charset;
+ guint ii;
+
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
+
+ medium = CAMEL_MEDIUM (message);
+ headers = camel_medium_get_headers (medium);
+ if (!headers)
+ return NULL;
+
+ default_charset = camel_search_get_default_charset_from_message (message);
+ str = g_string_new ("");
+
+ for (ii = 0; ii < headers->len; ii++) {
+ CamelMediumHeader *header;
+ gchar *content;
+
+ header = &g_array_index (headers, CamelMediumHeader, ii);
+ if (!header->value)
+ continue;
+
+ content = camel_search_get_header_decoded (header->name, header->value, default_charset);
+ if (!content)
+ continue;
+
+ g_string_append (str, header->name);
+ if (isspace (content[0]))
+ g_string_append (str, ":");
+ else
+ g_string_append (str, ": ");
+ g_string_append (str, content);
+ g_string_append_c (str, '\n');
+
+ g_free (content);
+ }
+
+ camel_medium_free_headers (medium, headers);
+
+ return g_string_free (str, FALSE);
+}
diff --git a/src/camel/camel-search-private.h b/src/camel/camel-search-private.h
new file mode 100644
index 000000000..5fee6f7ce
--- /dev/null
+++ b/src/camel/camel-search-private.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_SEARCH_PRIVATE_H
+#define CAMEL_SEARCH_PRIVATE_H
+
+/* POSIX requires <sys/types.h> be included before <regex.h> */
+#include <sys/types.h>
+
+#include <regex.h>
+
+#include <camel/camel.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ CAMEL_SEARCH_MATCH_START = 1 << 0,
+ CAMEL_SEARCH_MATCH_END = 1 << 1,
+ CAMEL_SEARCH_MATCH_REGEX = 1 << 2, /* disables the first 2 */
+ CAMEL_SEARCH_MATCH_ICASE = 1 << 3,
+ CAMEL_SEARCH_MATCH_NEWLINE = 1 << 4
+} camel_search_flags_t;
+
+typedef enum {
+ CAMEL_SEARCH_MATCH_EXACT,
+ CAMEL_SEARCH_MATCH_CONTAINS,
+ CAMEL_SEARCH_MATCH_WORD,
+ CAMEL_SEARCH_MATCH_STARTS,
+ CAMEL_SEARCH_MATCH_ENDS,
+ CAMEL_SEARCH_MATCH_SOUNDEX
+} camel_search_match_t;
+
+typedef enum {
+ CAMEL_SEARCH_TYPE_ASIS,
+ CAMEL_SEARCH_TYPE_ENCODED,
+ CAMEL_SEARCH_TYPE_ADDRESS,
+ CAMEL_SEARCH_TYPE_ADDRESS_ENCODED,
+ CAMEL_SEARCH_TYPE_MLIST /* its a mailing list pseudo-header */
+} camel_search_t;
+
+/* builds a regex that represents a string search */
+gint camel_search_build_match_regex (regex_t *pattern,
+ camel_search_flags_t type,
+ gint argc,
+ CamelSExpResult **argv,
+ GError **error);
+gboolean camel_search_message_body_contains
+ (CamelDataWrapper *object,
+ regex_t *pattern);
+
+gboolean camel_search_header_match (const gchar *value,
+ const gchar *match,
+ camel_search_match_t how,
+ camel_search_t type,
+ const gchar *default_charset);
+gboolean camel_search_camel_header_soundex
+ (const gchar *header,
+ const gchar *match);
+
+/* TODO: replace with a real search function */
+const gchar * camel_ustrstrcase (const gchar *haystack,
+ const gchar *needle);
+
+/* Some crappy utility functions for handling multiple search words */
+typedef enum _camel_search_word_t {
+ CAMEL_SEARCH_WORD_SIMPLE = 1,
+ CAMEL_SEARCH_WORD_COMPLEX = 2,
+ CAMEL_SEARCH_WORD_8BIT = 4
+} camel_search_word_t;
+
+struct _camel_search_word {
+ camel_search_word_t type;
+ gchar *word;
+};
+
+struct _camel_search_words {
+ gint len;
+ camel_search_word_t type; /* OR of all word types in list */
+ struct _camel_search_word **words;
+};
+
+struct _camel_search_words *
+ camel_search_words_split (const guchar *in);
+struct _camel_search_words *
+ camel_search_words_simple (struct _camel_search_words *words);
+void camel_search_words_free (struct _camel_search_words *words);
+
+gboolean camel_search_header_is_address (const gchar *header_name);
+const gchar * camel_search_get_default_charset_from_message
+ (CamelMimeMessage *message);
+gchar * camel_search_get_header_decoded (const gchar *header_name,
+ const gchar *header_value,
+ const gchar *default_charset);
+gchar * camel_search_get_all_headers_decoded
+ (CamelMimeMessage *message);
+
+G_END_DECLS
+
+#endif /* CAMEL_SEARCH_PRIVATE_H */
diff --git a/src/camel/camel-search-sql-sexp.c b/src/camel/camel-search-sql-sexp.c
new file mode 100644
index 000000000..2ecbf21c1
--- /dev/null
+++ b/src/camel/camel-search-sql-sexp.c
@@ -0,0 +1,915 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Srinivasa Ragavan <sragavan@novell.com>
+ */
+
+/* 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. */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "camel-search-sql-sexp.h"
+#define d(x) /* x;printf("\n"); */
+
+#ifdef TEST_MAIN
+#include <sqlite3.h>
+typedef enum {
+ CAMEL_SEARCH_MATCH_EXACT,
+ CAMEL_SEARCH_MATCH_CONTAINS,
+ CAMEL_SEARCH_MATCH_STARTS,
+ CAMEL_SEARCH_MATCH_ENDS,
+ CAMEL_SEARCH_MATCH_SOUNDEX
+} camel_search_match_t;
+gchar * camel_db_get_column_name (const gchar *raw_name);
+
+gchar *
+camel_db_sqlize_string (const gchar *string)
+{
+ return sqlite3_mprintf ("%Q", string);
+}
+
+void
+camel_db_free_sqlized_string (gchar *string)
+{
+ sqlite3_free (string);
+ string = NULL;
+}
+
+#else
+#include "camel-db.h"
+#include "camel-folder-search.h"
+#include "camel-search-private.h"
+#endif
+
+static gchar *
+get_db_safe_string (const gchar *str)
+{
+ gchar *tmp = camel_db_sqlize_string (str);
+ gchar *ret;
+
+ ret = g_strdup (tmp);
+ camel_db_free_sqlized_string (tmp);
+
+ return ret;
+}
+
+/* Configuration of your sexp expression */
+
+static CamelSExpResult *
+func_and (CamelSExp *f,
+ gint argc,
+ struct _CamelSExpTerm **argv,
+ gpointer data)
+{
+ CamelSExpResult *r, *r1;
+ GString *string;
+ gint i;
+
+ d (printf ("executing and: %d", argc));
+
+ string = g_string_new ("( ");
+ for (i = 0; i < argc; i++) {
+ r1 = camel_sexp_term_eval (f, argv[i]);
+
+ if (r1->type != CAMEL_SEXP_RES_STRING) {
+ camel_sexp_result_free (f, r1);
+ continue;
+ }
+ if (r1->value.string && *r1->value.string)
+ g_string_append_printf (string, "%s%s", r1->value.string, ((argc > 1) && (i != argc - 1)) ? " AND ":"");
+ camel_sexp_result_free (f, r1);
+ }
+ g_string_append (string, " )");
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+
+ if (strlen (string->str) == 4)
+ r->value.string = g_strdup ("");
+ else
+ r->value.string = string->str;
+ g_string_free (string, FALSE);
+
+ return r;
+}
+
+static CamelSExpResult *
+func_or (CamelSExp *f,
+ gint argc,
+ struct _CamelSExpTerm **argv,
+ gpointer data)
+{
+ CamelSExpResult *r, *r1;
+ GString *string;
+ gint i;
+
+ d (printf ("executing or: %d", argc));
+
+ string = g_string_new ("( ");
+ for (i = 0; i < argc; i++) {
+ r1 = camel_sexp_term_eval (f, argv[i]);
+
+ if (r1->type != CAMEL_SEXP_RES_STRING) {
+ camel_sexp_result_free (f, r1);
+ continue;
+ }
+ g_string_append_printf (string, "%s%s", r1->value.string, ((argc > 1) && (i != argc - 1)) ? " OR ":"");
+ camel_sexp_result_free (f, r1);
+ }
+ g_string_append (string, " )");
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+ r->value.string = string->str;
+ g_string_free (string, FALSE);
+ return r;
+}
+
+static CamelSExpResult *
+func_not (CamelSExp *f,
+ gint argc,
+ struct _CamelSExpTerm **argv,
+ gpointer data)
+{
+ CamelSExpResult *r = NULL, *r1;
+
+ d (printf ("executing not: %d", argc));
+ r1 = camel_sexp_term_eval (f, argv[0]);
+
+ if (r1->type == CAMEL_SEXP_RES_STRING) {
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+ /* HACK: Fix and handle completed-on better. */
+ if (g_strcmp0 (r1->value.string, "( (usertags LIKE '%completed-on 0%' AND usertags LIKE '%completed-on%') )") == 0)
+ r->value.string = g_strdup ("( (not (usertags LIKE '%completed-on 0%')) AND usertags LIKE '%completed-on%' )");
+ else
+ r->value.string = g_strdup_printf (
+ "(NOT (%s))", r1->value.string);
+ }
+ camel_sexp_result_free (f, r1);
+
+ return r;
+}
+
+/* this should support all arguments ...? */
+static CamelSExpResult *
+eval_eq (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpTerm **argv,
+ gpointer data)
+{
+ struct _CamelSExpResult *r, *r1, *r2;
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+
+ if (argc == 2) {
+ GString *str = g_string_new ("( ");
+ r1 = camel_sexp_term_eval (f, argv[0]);
+ r2 = camel_sexp_term_eval (f, argv[1]);
+
+ if (r1->type == CAMEL_SEXP_RES_INT)
+ g_string_append_printf (str, "%d", r1->value.number);
+ else if (r1->type == CAMEL_SEXP_RES_TIME)
+ g_string_append_printf (str, "%" G_GINT64_FORMAT, (gint64) r1->value.time);
+ else if (r1->type == CAMEL_SEXP_RES_STRING)
+ g_string_append_printf (str, "%s", r1->value.string);
+
+ if (g_str_equal (str->str, "( msgid") || g_str_equal (str->str, "( references")) {
+ gboolean is_msgid = g_str_equal (str->str, "( msgid");
+
+ g_string_assign (str, "( part LIKE ");
+ if (r2->type == CAMEL_SEXP_RES_STRING) {
+ gchar *tmp, *safe;
+
+ /* Expects CamelSummaryMessageID encoded as "%lu %lu", id.part.hi, id.part.lo.
+ The 'msgid' is always the first, while 'references' is inside. */
+ /* Beware, the 'references' can return false positives, thus recheck returned UID-s. */
+ tmp = g_strdup_printf ("%s%s%%", is_msgid ? "" : "%", r2->value.string);
+ safe = get_db_safe_string (tmp);
+ g_string_append_printf (str, "%s", safe);
+ g_free (safe);
+ g_free (tmp);
+ } else {
+ g_warn_if_reached ();
+ }
+ } else if (!strstr (str->str, "completed-on") && !strstr (str->str, "follow-up")) {
+ gboolean ut = FALSE;
+
+ if (strstr (str->str, "usertags"))
+ ut = TRUE;
+ if (ut)
+ g_string_append_printf (str, " LIKE ");
+ else
+ g_string_append_printf (str, " = ");
+ if (r2->type == CAMEL_SEXP_RES_INT)
+ g_string_append_printf (str, "%d", r2->value.number);
+ if (r2->type == CAMEL_SEXP_RES_BOOL)
+ g_string_append_printf (str, "%d", r2->value.boolean);
+ else if (r2->type == CAMEL_SEXP_RES_TIME)
+ g_string_append_printf (str, "%" G_GINT64_FORMAT, (gint64) r2->value.time);
+ else if (r2->type == CAMEL_SEXP_RES_STRING) {
+ gchar *tmp = g_strdup_printf ("%c%s%c", ut ? '%':' ', r2->value.string, ut ? '%':' ');
+ gchar *safe = get_db_safe_string (tmp);
+ g_string_append_printf (str, "%s", safe);
+ g_free (safe);
+ g_free (tmp);
+ }
+ }
+ camel_sexp_result_free (f, r1);
+ camel_sexp_result_free (f, r2);
+ g_string_append (str, " )");
+ r->value.string = str->str;
+ g_string_free (str, FALSE);
+ } else {
+ r->value.string = g_strdup ("(0)");
+ }
+ return r;
+}
+
+static CamelSExpResult *
+eval_lt (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpTerm **argv,
+ gpointer data)
+{
+ struct _CamelSExpResult *r, *r1, *r2;
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+
+ if (argc == 2) {
+ GString *str = g_string_new ("( ");
+ r1 = camel_sexp_term_eval (f, argv[0]);
+ r2 = camel_sexp_term_eval (f, argv[1]);
+
+ if (r1->type == CAMEL_SEXP_RES_INT)
+ g_string_append_printf (str, "%d", r1->value.number);
+ else if (r1->type == CAMEL_SEXP_RES_TIME)
+ g_string_append_printf (str, "%" G_GINT64_FORMAT, (gint64) r1->value.time);
+ else if (r1->type == CAMEL_SEXP_RES_STRING)
+ g_string_append_printf (str, "%s", r1->value.string);
+
+ g_string_append_printf (str, " < ");
+ if (r2->type == CAMEL_SEXP_RES_INT)
+ g_string_append_printf (str, "%d", r2->value.number);
+ if (r2->type == CAMEL_SEXP_RES_BOOL)
+ g_string_append_printf (str, "%d", r2->value.boolean);
+ else if (r2->type == CAMEL_SEXP_RES_TIME)
+ g_string_append_printf (str, "%" G_GINT64_FORMAT, (gint64) r2->value.time);
+ else if (r2->type == CAMEL_SEXP_RES_STRING)
+ g_string_append_printf (str, "%s", r2->value.string);
+ camel_sexp_result_free (f, r1);
+ camel_sexp_result_free (f, r2);
+ g_string_append (str, " )");
+
+ r->value.string = str->str;
+ g_string_free (str, FALSE);
+ }
+ return r;
+}
+
+/* this should support all arguments ...? */
+static CamelSExpResult *
+eval_gt (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpTerm **argv,
+ gpointer data)
+{
+ struct _CamelSExpResult *r, *r1, *r2;
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+
+ if (argc == 2) {
+ GString *str = g_string_new ("( ");
+ r1 = camel_sexp_term_eval (f, argv[0]);
+ r2 = camel_sexp_term_eval (f, argv[1]);
+
+ if (r1->type == CAMEL_SEXP_RES_INT)
+ g_string_append_printf (str, "%d", r1->value.number);
+ else if (r1->type == CAMEL_SEXP_RES_TIME)
+ g_string_append_printf (str, "%" G_GINT64_FORMAT, (gint64) r1->value.time);
+ else if (r1->type == CAMEL_SEXP_RES_STRING)
+ g_string_append_printf (str, "%s", r1->value.string);
+
+ g_string_append_printf (str, " > ");
+ if (r2->type == CAMEL_SEXP_RES_INT)
+ g_string_append_printf (str, "%d", r2->value.number);
+ if (r2->type == CAMEL_SEXP_RES_BOOL)
+ g_string_append_printf (str, "%d", r2->value.boolean);
+ else if (r2->type == CAMEL_SEXP_RES_TIME)
+ g_string_append_printf (str, "%" G_GINT64_FORMAT, (gint64) r2->value.time);
+ else if (r2->type == CAMEL_SEXP_RES_STRING)
+ g_string_append_printf (str, "%s", r2->value.string);
+ camel_sexp_result_free (f, r1);
+ camel_sexp_result_free (f, r2);
+ g_string_append (str, " )");
+
+ r->value.string = str->str;
+ g_string_free (str, FALSE);
+ }
+ return r;
+}
+
+static CamelSExpResult *
+match_all (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpTerm **argv,
+ gpointer data)
+{
+ CamelSExpResult *r;
+
+ d (printf ("executing match-all: %d", argc));
+ if (argc == 0) {
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+ r->value.string = g_strdup ("1");
+ } else if (argv[0]->type != CAMEL_SEXP_TERM_BOOL)
+ r = camel_sexp_term_eval (f, argv[0]);
+ else {
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+ r->value.string = g_strdup (argv[0]->value.boolean ? "1" : "0");
+ }
+
+ return r;
+
+}
+
+static CamelSExpResult *
+match_threads (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpTerm **argv,
+ gpointer data)
+{
+ CamelSExpResult *r;
+ gint i;
+ GString *str = g_string_new ("( ");
+
+ d (printf ("executing match-threads: %d", argc));
+
+ for (i = 1; i < argc; i++) {
+ r = camel_sexp_term_eval (f, argv[i]);
+ g_string_append_printf (str, "%s%s", r->value.string, ((argc > 1) && (i != argc - 1)) ? " AND ":"");
+ camel_sexp_result_free (f, r);
+ }
+
+ g_string_append (str, " )");
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+ r->value.string = str->str;
+ g_string_free (str, FALSE);
+
+ return r;
+}
+
+static CamelSExpResult *
+check_header (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data,
+ camel_search_match_t how)
+{
+ CamelSExpResult *r;
+ gchar *str = NULL;
+
+ d (printf ("executing check-header %d\n", how));
+
+ /* are we inside a match-all? */
+ if (argc > 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
+ gchar *headername;
+ gint i;
+
+ /* only a subset of headers are supported .. */
+ headername = camel_db_get_column_name (argv[0]->value.string);
+ if (!headername) {
+ gboolean *pcontains_unknown_column = (gboolean *) data;
+ *pcontains_unknown_column = TRUE;
+
+ headername = g_strdup ("unknown");
+ }
+
+ /* performs an OR of all words */
+ for (i = 1; i < argc; i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING) {
+ gchar *value = NULL, *tstr = NULL;
+ if (argv[i]->value.string[0] == 0)
+ continue;
+ if (how == CAMEL_SEARCH_MATCH_CONTAINS || how == CAMEL_SEARCH_MATCH_WORD) {
+ tstr = g_strdup_printf ("%c%s%c", '%', argv[i]->value.string, '%');
+ value = get_db_safe_string (tstr);
+ g_free (tstr);
+ } else if (how == CAMEL_SEARCH_MATCH_ENDS) {
+ tstr = g_strdup_printf ("%c%s", '%', argv[i]->value.string);
+ value = get_db_safe_string (tstr);
+ g_free (tstr);
+ } else if (how == CAMEL_SEARCH_MATCH_STARTS) {
+ tstr = g_strdup_printf ("%s%c", argv[i]->value.string, '%');
+ value = get_db_safe_string (tstr);
+ g_free (tstr);
+ } else if (how == CAMEL_SEARCH_MATCH_EXACT) {
+ tstr = g_strdup_printf ("%c%s%c", '%', argv[i]->value.string, '%');
+ value = get_db_safe_string (tstr);
+ g_free (tstr);
+ }
+ str = g_strdup_printf ("(%s IS NOT NULL AND %s LIKE %s)", headername, headername, value);
+ g_free (value);
+ }
+ }
+ g_free (headername);
+ }
+ /* TODO: else, find all matches */
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+ r->value.string = str;
+
+ return r;
+}
+
+static CamelSExpResult *
+header_contains (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ d (printf ("executing header-contains: %d", argc));
+
+ return check_header (f, argc, argv, data, CAMEL_SEARCH_MATCH_CONTAINS);
+}
+
+static CamelSExpResult *
+header_has_words (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ d (printf ("executing header-has-word: %d", argc));
+
+ return check_header (f, argc, argv, data, CAMEL_SEARCH_MATCH_WORD);
+}
+
+static CamelSExpResult *
+header_matches (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ d (printf ("executing header-matches: %d", argc));
+
+ return check_header (f, argc, argv, data, CAMEL_SEARCH_MATCH_EXACT);
+}
+
+static CamelSExpResult *
+header_starts_with (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ d (printf ("executing header-starts-with: %d", argc));
+
+ return check_header (f, argc, argv, data, CAMEL_SEARCH_MATCH_STARTS);
+}
+
+static CamelSExpResult *
+header_ends_with (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ d (printf ("executing header-ends-with: %d", argc));
+
+ return check_header (f, argc, argv, data, CAMEL_SEARCH_MATCH_ENDS);
+}
+
+static CamelSExpResult *
+header_exists (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *r;
+ gchar *headername;
+
+ d (printf ("executing header-exists: %d", argc));
+
+ headername = camel_db_get_column_name (argv[0]->value.string);
+ if (!headername) {
+ gboolean *pcontains_unknown_column = (gboolean *) data;
+ *pcontains_unknown_column = TRUE;
+
+ headername = g_strdup ("unknown");
+ }
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+ r->value.string = g_strdup_printf ("(%s NOTNULL)", headername);
+ g_free (headername);
+
+ return r;
+}
+
+static CamelSExpResult *
+user_tag (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *r;
+
+ d (printf ("executing user-tag: %d", argc));
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+ /* Hacks no otherway to fix these really :( */
+ if (g_strcmp0 (argv[0]->value.string, "completed-on") == 0)
+ r->value.string = g_strdup_printf ("(usertags LIKE '%ccompleted-on 0%c' AND usertags LIKE '%ccompleted-on%c')", '%', '%', '%', '%');
+ else if (g_strcmp0 (argv[0]->value.string, "follow-up") == 0)
+ r->value.string = g_strdup_printf ("usertags NOT LIKE '%cfollow-up%c'", '%', '%');
+ else
+ r->value.string = g_strdup ("usertags");
+
+ return r;
+}
+
+static CamelSExpResult *
+user_flag (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *r;
+ gchar *tstr, *qstr;
+
+ d (printf ("executing user-flag: %d", argc));
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+
+ if (argc != 1) {
+ r->value.string = g_strdup ("(0)");
+ } else {
+ tstr = g_strdup_printf ("%s", argv[0]->value.string);
+ qstr = get_db_safe_string (tstr);
+ g_free (tstr);
+ r->value.string = g_strdup_printf ("(labels MATCH %s)", qstr);
+ g_free (qstr);
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+system_flag (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *r;
+ gchar *tstr;
+
+ d (printf ("executing system-flag: %d", argc));
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+
+ if (argc != 1) {
+ r->value.string = g_strdup ("(0)");
+ } else {
+ tstr = camel_db_get_column_name (argv[0]->value.string);
+ if (!tstr) {
+ gboolean *pcontains_unknown_column = (gboolean *) data;
+ *pcontains_unknown_column = TRUE;
+
+ tstr = g_strdup ("unknown");
+ }
+
+ r->value.string = g_strdup_printf ("(%s = 1)", tstr);
+ g_free (tstr);
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+get_sent_date (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *r;
+
+ d (printf ("executing get-sent-date\n"));
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+ r->value.string = g_strdup ("dsent");
+
+ return r;
+}
+
+static CamelSExpResult *
+get_received_date (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *r;
+
+ d (printf ("executing get-received-date\n"));
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+ r->value.string = g_strdup ("dreceived");
+
+ return r;
+}
+
+static CamelSExpResult *
+get_current_date (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *r;
+
+ d (printf ("executing get-current-date\n"));
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
+ r->value.number = time (NULL);
+ return r;
+}
+
+static CamelSExpResult *
+get_relative_months (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *r;
+
+ d (printf ("executing get-relative-months\n"));
+
+ if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_INT) {
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
+ r->value.boolean = FALSE;
+
+ g_debug ("%s: Expecting 1 argument, an integer, but got %d arguments", G_STRFUNC, argc);
+ } else {
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
+ r->value.number = camel_folder_search_util_add_months (time (NULL), argv[0]->value.number);
+ }
+
+ return r;
+}
+
+static CamelSExpResult *
+get_size (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *r;
+
+ d (printf ("executing get-size\n"));
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+ r->value.string = g_strdup ("size/1024");
+
+ return r;
+}
+
+static CamelSExpResult *
+sql_exp (struct _CamelSExp *f,
+ gint argc,
+ struct _CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *r;
+ gint i;
+ GString *str = g_string_new (NULL);
+
+ d (printf ("executing sql-exp\n"));
+
+ r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
+ for (i = 0; i < argc; i++) {
+ if (argv[i]->type == CAMEL_SEXP_RES_STRING && argv[i]->value.string)
+ g_string_append (str, argv[i]->value.string);
+ }
+ r->value.string = str->str;
+ g_string_free (str, FALSE);
+
+ return r;
+}
+
+/* 'builtin' functions */
+static struct {
+ const gchar *name;
+ CamelSExpFunc func;
+ guint immediate :1;
+} symbols[] = {
+ { "and", (CamelSExpFunc) func_and, 1 },
+ { "or", (CamelSExpFunc) func_or, 1},
+ { "not", (CamelSExpFunc) func_not, 1},
+ { "=", (CamelSExpFunc) eval_eq, 1},
+ { ">", (CamelSExpFunc) eval_gt, 1},
+ { "<", (CamelSExpFunc) eval_lt, 1},
+
+ { "match-all", (CamelSExpFunc) match_all, 1 },
+ { "match-threads", (CamelSExpFunc) match_threads, 1 },
+/* { "body-contains", body_contains}, */ /* We don't store body on the db. */
+ { "header-contains", header_contains, 0},
+ { "header-has-words", header_has_words, 0},
+ { "header-matches", header_matches, 0},
+ { "header-starts-with", header_starts_with, 0},
+ { "header-ends-with", header_ends_with, 0},
+ { "header-exists", header_exists, 0},
+ { "user-tag", user_tag, 0},
+ { "user-flag", user_flag, 0},
+ { "system-flag", system_flag, 0},
+ { "get-sent-date", get_sent_date, 0},
+ { "get-received-date", get_received_date, 0},
+ { "get-current-date", get_current_date, 0},
+ { "get-relative-months", get_relative_months, 0},
+ { "get-size", get_size, 0},
+ { "sql-exp", sql_exp, 0},
+
+/* { "uid", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, uid), 1 }, */
+};
+
+/**
+ * camel_sexp_to_sql_sexp:
+ *
+ * Since: 2.26
+ **/
+gchar *
+camel_sexp_to_sql_sexp (const gchar *sql)
+{
+ CamelSExp *sexp;
+ CamelSExpResult *r;
+ gint i;
+ gchar *res = NULL;
+ gboolean contains_unknown_column = FALSE;
+
+ sexp = camel_sexp_new ();
+
+ for (i = 0; i < G_N_ELEMENTS (symbols); i++) {
+ if (symbols[i].immediate)
+ camel_sexp_add_ifunction (sexp, 0, symbols[i].name,
+ (CamelSExpIFunc) symbols[i].func, &contains_unknown_column);
+ else
+ camel_sexp_add_function (
+ sexp, 0, symbols[i].name,
+ symbols[i].func, &contains_unknown_column);
+ }
+
+ camel_sexp_input_text (sexp, sql, strlen (sql));
+ if (camel_sexp_parse (sexp)) {
+ g_object_unref (sexp);
+ return NULL;
+ }
+
+ r = camel_sexp_eval (sexp);
+ if (!r) {
+ g_object_unref (sexp);
+ return NULL;
+ }
+
+ if (!contains_unknown_column && r->type == CAMEL_SEXP_RES_STRING) {
+ res = g_strdup (r->value.string);
+ }
+
+ camel_sexp_result_free (sexp, r);
+ g_object_unref (sexp);
+
+ return res;
+}
+
+#ifdef TEST_MAIN
+/*
+ *
+ * (and (match-all (and (not (system-flag "deleted")) (not (system-flag "junk"))))
+ * (and (or
+ *
+ * (match-all (not (system-flag "Attachments")))
+ *
+ * )
+ * ))
+ *
+ *"
+ * replied INTEGER , (match-all (system-flag "Answered"))
+ * size INTEGER , (match-all (< (get-size) 100))
+ * dsent NUMERIC , (match-all (< (get-sent-date) (- (get-current-date) 10)))
+ * dreceived NUMERIC , (match-all (< (get-received-date) (- (get-current-date) 10)))
+ * //mlist TEXT , x-camel-mlist (match-all (header-matches "x-camel-mlist" "gnome.org"))
+ * //attachment, system-flag "Attachments" (match-all (system-flag "Attachments"))
+ * //followup_flag TEXT , (match-all (not (= (user-tag "follow-up") "")))
+ * //followup_completed_on TEXT , (match-all (not (= (user-tag "completed-on") "")))
+ * //followup_due_by TEXT ," //NOTREQD
+ */
+
+gchar * camel_db_get_column_name (const gchar *raw_name)
+{
+ /* d(g_print ("\n\aRAW name is : [%s] \n\a", raw_name)); */
+ if (!g_ascii_strcasecmp (raw_name, "Subject"))
+ return g_strdup ("subject");
+ else if (!g_ascii_strcasecmp (raw_name, "from"))
+ return g_strdup ("mail_from");
+ else if (!g_ascii_strcasecmp (raw_name, "Cc"))
+ return g_strdup ("mail_cc");
+ else if (!g_ascii_strcasecmp (raw_name, "To"))
+ return g_strdup ("mail_to");
+ else if (!g_ascii_strcasecmp (raw_name, "Flagged"))
+ return g_strdup ("important");
+ else if (!g_ascii_strcasecmp (raw_name, "deleted"))
+ return g_strdup ("deleted");
+ else if (!g_ascii_strcasecmp (raw_name, "junk"))
+ return g_strdup ("junk");
+ else if (!g_ascii_strcasecmp (raw_name, "Answered"))
+ return g_strdup ("replied");
+ else if (!g_ascii_strcasecmp (raw_name, "Seen"))
+ return g_strdup ("read");
+ else if (!g_ascii_strcasecmp (raw_name, "user-tag"))
+ return g_strdup ("usertags");
+ else if (!g_ascii_strcasecmp (raw_name, "user-flag"))
+ return g_strdup ("labels");
+ else if (!g_ascii_strcasecmp (raw_name, "Attachments"))
+ return g_strdup ("attachment");
+ else if (!g_ascii_strcasecmp (raw_name, "x-camel-mlist"))
+ return g_strdup ("mlist");
+ else {
+ /* Let it crash for all unknown columns for now.
+ * We need to load the messages into memory and search etc.
+ * We should extend this for camel-folder-search system flags search as well
+ * otherwise, search-for-signed-messages will not work etc.*/
+
+ return g_strdup (raw_name);
+ }
+
+}
+
+gint main ()
+{
+
+ gint i = 0;
+ gchar *txt[] = {
+#if 0
+ "(match-all (header-contains \"From\" \"org\"))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (or (header-ends-with \"To\" \"novell.com\") (header-ends-with \"Cc\" \"novell.com\"))) (match-all (or (= (user-tag \"label\") \"work\") (user-flag \"work\"))) )))",
+
+ "(and (and (match-all (header-contains \"From\" \"org\")) ) (match-all (not (system-flag \"junk\"))))",
+
+ "(and (and (match-all (header-contains \"From\" \"org\"))) (and (match-all (not (system-flag \"junk\"))) (and (or (match-all (header-contains \"Subject\" \"test\")) (match-all (header-contains \"From\" \"test\"))))))",
+ "(and (and (match-all (header-exists \"From\")) ) (match-all (not (system-flag \"junk\"))))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (header-contains \"Subject\" \"org\")) (match-all (header-contains \"From\" \"org\")) (match-all (system-flag \"Flagged\")) (match-all (system-flag \"Seen\")) )))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (or (header-ends-with \"To\" \"novell.com\") (header-ends-with \"Cc\" \"novell.com\"))) (= (user-tag \"label\") \"work\") )))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (or (header-ends-with \"To\" \"novell.com\") (header-ends-with \"Cc\" \"novell.com\"))) (user-flag \"work\") )))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (or (header-ends-with \"To\" \"novell.com\") (header-ends-with \"Cc\" \"novell.com\"))) (user-flag (+ \"$Label\" \"work\")) )))"
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (not (= (user-tag \"follow-up\") \"\"))) )))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (= (user-tag \"follow-up\") \"\")) )))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (not (= (user-tag \"completed-on\") \"\"))) )))",
+ "(match-all (and (match-all #t) (system-flag \"deleted\")))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (or (= (user-tag \"label\") \"important\") (user-flag (+ \"$Label\" \"important\")) (user-flag \"important\"))) )))",
+ "(or (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")) (not (system-flag \"Attachments\")) (not (system-flag \"Answered\")))) (and (or (match-all (= (user-tag \"completed-on\") \"\")) )))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (= (user-tag \"completed-on\") \"\")) (match-all (= (user-tag \"follow-up\") \"\")) )))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (> (get-sent-date) (- (get-current-date) 100))) )))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (< (get-sent-date) (+ (get-current-date) 100))) )))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (not (= (get-sent-date) 1216146600))) )))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (= (get-sent-date) 1216146600)) )))" ,
+ "(match-threads \"all\" (or (match-all (header-contains \"From\" \"@edesvcs.com\")) (match-all (or (header-contains \"To\" \"@edesvcs.com\") (header-contains \"Cc\" \"@edesvcs.com\"))) ))",
+ "(match-all (not (system-flag \"deleted\")))",
+ "(match-all (system-flag \"seen\"))",
+ "(match-all (and (match-all #t) (system-flag \"deleted\")))",
+ "(match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\"))))",
+
+ "(and (or (match-all (header-contains \"Subject\" \"lin\")) ) (and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (header-contains \"Subject\" \"case\")) (match-all (header-contains \"From\" \"case\"))))))",
+ "(and ( match-all(or (match-all (header-contains \"Subject\" \"lin\")) (match-all (header-contains \"From\" \"in\")))) (and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (header-contains \"Subject\" \"proc\")) (match-all (header-contains \"From\" \"proc\"))))))",
+ "(and (or (match-all (header-contains \"Subject\" \"[LDTP-NOSIP]\")) ) (and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all (header-contains \"Subject\" \"vamsi\")) (match-all (header-contains \"From\" \"vamsi\"))))))",
+ /* Last one doesn't work so well and fails on one case. But I doubt, you can create a query like that in Evo. */
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (match-all (or (= (user-tag \"label\") \"_office\") (user-flag \"$Label_office\") (user-flag \"_office\"))))",
+ "(and (and (match-all #t))(and(match-all #t)))",
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (and (match-all (header-contains \"Subject\" \"mysubject\")) (match-all (not (header-matches \"From\" \"mysender\"))) (match-all (= (get-sent-date) (+ (get-current-date) 1))) (match-all (= (get-received-date) (- (get-current-date) 604800))) (match-all (or (= (user-tag \"label\") \"important\") (user-flag (+ \"$Label\" \"important\")) (match-all (< (get-size) 7000)) (match-all (not (= (get-sent-date) 1216146600))) (match-all (> (cast-int (user-tag \"score\")) 3)) (user-flag \"important\"))) (match-all (system-flag \"Deleted\")) (match-all (not (= (user-tag \"follow-up\") \"\"))) (match-all (= (user-tag \"completed-on\") \"\")) (match-all (system-flag \"Attachments\")) (match-all (header-contains \"x-camel-mlist\" \"evo-hackers\")) )))",
+ "(and (or (match-all (or (= (user-tag \"label\") \"important\") (user-flag (+ \"$Label\" \"important\")) (user-flag \"important\"))) (match-all (or (= (user-tag \"label\") \"work\") (user-flag (+ \"$Label\" \"work\")) (user-flag \"work\"))) (match-all (or (= (user-tag \"label\") \"personal\") (user-flag (+ \"$Label\" \"personal\")) (user-flag \"personal\"))) (match-all (or (= (user-tag \"label\") \"todo\") (user-flag (+ \"$Label\" \"todo\")) (user-flag \"todo\"))) (match-all (or (= (user-tag \"label\") \"later\") (user-flag (+ \"$Label\" \"later\")) (user-flag \"later\"))) ) (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))))",
+ "(or (header-matches \"to\" \"maw@ximian.com\") (header-matches \"to\" \"mw@ximian.com\") (header-matches \"to\" \"maw@novell.com\") (header-matches \"to\" \"maw.AMERICAS3.AMERICAS@novell.com\") (header-matches \"cc\" \"maw@ximian.com\") (header-matches \"cc\" \"mw@ximian.com\") (header-matches \"cc\" \"maw@novell.com\") (header-matches \"cc\" \"maw.AMERICAS3.AMERICAS@novell.com\"))",
+ "(not (or (header-matches \"from\" \"bugzilla-daemon@bugzilla.ximian.com\") (header-matches \"from\" \"bugzilla-daemon@bugzilla.gnome.org\") (header-matches \"from\" \"bugzilla_noreply@novell.com\") (header-matches \"from\" \"bugzilla-daemon@mozilla.org\") (header-matches \"from\" \"root@dist.suse.de\") (header-matches \"from\" \"root@hilbert3.suse.de\") (header-matches \"from\" \"root@hilbert4.suse.de\") (header-matches \"from\" \"root@hilbert5.suse.de\") (header-matches \"from\" \"root@hilbert6.suse.de\") (header-matches \"from\" \"root@suse.de\") (header-matches \"from\" \"swamp_noreply@suse.de\") (and (header-matches \"from\" \"hermes@opensuse.org\") (header-starts-with \"subject\" \"submit-Request\"))))",
+ "(and (match-threads \"replies_parents\" (and (match-all (or (header-matches \"to\" \"maw@ximian.com\") (header-matches \"to\" \"mw@ximian.com\") (header-matches \"to\" \"maw@novell.com\") (header-matches \"to\" \"maw.AMERICAS3.AMERICAS@novell.com\") (header-matches \"cc\" \"maw@ximian.com\") (header-matches \"cc\" \"mw@ximian.com\") (header-matches \"cc\" \"maw@novell.com\") (header-matches \"cc\" \"maw.AMERICAS3.AMERICAS@novell.com\"))) (match-all (not (or (header-matches \"from\" \"bugzilla-daemon@bugzilla.ximian.com\") (header-matches \"from\" \"bugzilla-daemon@bugzilla.gnome.org\") (header-matches \"from\" \"bugzilla_noreply@novell.com\") (header-matches \"from\" \"bugzilla-daemon@mozilla.org\") (header-matches \"from\" \"root@dist.suse.de\") (header-matches \"from\" \"root@hilbert3.suse.de\") (header-matches \"from\" \"root@hilbert4.suse.de\") (header-matches \"from\" \"root@hilbert5.suse.de\") (header-matches \"from\" \"root@hilbert6.suse.de\") (header-matches \"from\" \"root@suse.de\") (header-matches \"from\" \"swamp_noreply@suse.de\") (and (header-matches \"from\" \"hermes@opensuse.org\") (header-starts-with \"subject\" \"submit-Request\"))))) (match-all (> (get-sent-date) (- (get-current-date) 1209600))) )) (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))))",
+ "and ((match-all (system-flag \"Deleted\")) (match-all (system-flag \"junk\")))",
+ "(and (match-threads \"replies_parents\" (and (match-all (or (header-matches \"to\" \"maw@ximian.com\")))))))",
+ "(and (sql-exp \"folder_key = 'ASDGASd' AND folder_key = 'DSFWEA'\") (match-threads \"replies_parents\" (and (match-all (or (header-matches \"to\" \"maw@ximian.com\")))))))"
+#endif
+ "(and (match-all (and (not (system-flag \"deleted\")) (not (system-flag \"junk\")))) (and (or (match-all list-post.*zypp-devel) ) ))"
+ };
+
+ for (i = 0; i < G_N_ELEMENTS (txt); i++) {
+ gchar *sql = NULL;
+ printf ("Q: %s\n\"%c\"\n", txt[i], 40);
+ sql = camel_sexp_to_sql_sexp (txt[i]);
+ printf ("A: %s\n\n\n", sql);
+ g_free (sql);
+ }
+
+}
+#endif
+
diff --git a/src/camel/camel-search-sql-sexp.h b/src/camel/camel-search-sql-sexp.h
new file mode 100644
index 000000000..0432678c0
--- /dev/null
+++ b/src/camel/camel-search-sql-sexp.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Srinivsa Ragavan <sragavan@novell.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SEARCH_SQL_SEXP_H
+#define CAMEL_SEARCH_SQL_SEXP_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/* FIXME: Weird naming, since, I want both parsers to be there for some time.*/
+gchar * camel_sexp_to_sql_sexp (const gchar *sexp);
+
+G_END_DECLS
+
+#endif /* CAMEL_SEARCH_SQL_H */
diff --git a/src/camel/camel-service.c b/src/camel/camel-service.c
new file mode 100644
index 000000000..cc4cac933
--- /dev/null
+++ b/src/camel/camel-service.c
@@ -0,0 +1,2380 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-service.c : Abstract class for an email service
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-async-closure.h"
+#include "camel-debug.h"
+#include "camel-enumtypes.h"
+#include "camel-local-settings.h"
+#include "camel-network-service.h"
+#include "camel-network-settings.h"
+#include "camel-operation.h"
+#include "camel-service.h"
+#include "camel-session.h"
+
+#define d(x)
+#define w(x)
+
+#define CAMEL_SERVICE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SERVICE, CamelServicePrivate))
+
+#define DISPATCH_DATA_KEY "camel-service-dispatch-data"
+
+typedef struct _AsyncContext AsyncContext;
+typedef struct _ConnectionOp ConnectionOp;
+typedef struct _DispatchData DispatchData;
+
+struct _CamelServicePrivate {
+ GWeakRef session;
+
+ GMutex property_lock;
+ CamelSettings *settings;
+ GProxyResolver *proxy_resolver;
+
+ CamelProvider *provider;
+
+ gchar *display_name;
+ gchar *user_data_dir;
+ gchar *user_cache_dir;
+ gchar *uid;
+ gchar *password;
+
+ GMutex connection_lock;
+ ConnectionOp *connection_op;
+ CamelServiceConnectionStatus status;
+
+ /* Queues of GTasks, by source object. */
+ GHashTable *task_table;
+ GMutex task_table_lock;
+
+ gboolean network_service_inited;
+};
+
+struct _AsyncContext {
+ gchar *auth_mechanism;
+ gboolean clean;
+};
+
+/* The GQueue is only modified while CamelService's
+ * connection_lock is held, so it does not need its
+ * own mutex. */
+struct _ConnectionOp {
+ volatile gint ref_count;
+ GQueue pending;
+ GMutex task_lock;
+ GTask *task;
+ GCancellable *cancellable;
+ gulong cancel_id;
+};
+
+struct _DispatchData {
+ GWeakRef service;
+ gboolean return_on_cancel;
+ GTaskThreadFunc task_func;
+};
+
+enum {
+ PROP_0,
+ PROP_CONNECTION_STATUS,
+ PROP_DISPLAY_NAME,
+ PROP_PASSWORD,
+ PROP_PROVIDER,
+ PROP_PROXY_RESOLVER,
+ PROP_SESSION,
+ PROP_SETTINGS,
+ PROP_UID
+};
+
+/* Forward Declarations */
+void camel_network_service_init (CamelNetworkService *service);
+static void camel_service_initable_init (GInitableIface *iface);
+static void service_task_dispatch (CamelService *service,
+ GTask *task);
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (
+ CamelService, camel_service, CAMEL_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, camel_service_initable_init))
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+ g_free (async_context->auth_mechanism);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static ConnectionOp *
+connection_op_new (GTask *task,
+ GCancellable *cancellable)
+{
+ ConnectionOp *op;
+
+ op = g_slice_new0 (ConnectionOp);
+ op->ref_count = 1;
+ g_mutex_init (&op->task_lock);
+ op->task = g_object_ref (task);
+
+ if (G_IS_CANCELLABLE (cancellable))
+ op->cancellable = g_object_ref (cancellable);
+
+ return op;
+}
+
+static ConnectionOp *
+connection_op_ref (ConnectionOp *op)
+{
+ g_return_val_if_fail (op != NULL, NULL);
+ g_return_val_if_fail (op->ref_count > 0, NULL);
+
+ g_atomic_int_inc (&op->ref_count);
+
+ return op;
+}
+
+static void
+connection_op_unref (ConnectionOp *op)
+{
+ g_return_if_fail (op != NULL);
+ g_return_if_fail (op->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test (&op->ref_count)) {
+
+ /* The pending queue should be empty. */
+ g_warn_if_fail (g_queue_is_empty (&op->pending));
+
+ g_mutex_clear (&op->task_lock);
+
+ if (op->task != NULL)
+ g_object_unref (op->task);
+
+ if (op->cancel_id > 0)
+ g_cancellable_disconnect (
+ op->cancellable, op->cancel_id);
+
+ if (op->cancellable != NULL)
+ g_object_unref (op->cancellable);
+
+ g_slice_free (ConnectionOp, op);
+ }
+}
+
+static void
+connection_op_complete (ConnectionOp *op,
+ const GError *error)
+{
+ g_mutex_lock (&op->task_lock);
+
+ if (op->task != NULL) {
+ if (error != NULL) {
+ g_task_return_error (op->task, g_error_copy (error));
+ } else {
+ g_task_return_boolean (op->task, TRUE);
+ }
+
+ g_clear_object (&op->task);
+ }
+
+ g_mutex_unlock (&op->task_lock);
+}
+
+static void
+connection_op_cancelled (GCancellable *cancellable,
+ ConnectionOp *op)
+{
+ /* A cancelled GTask will always propagate an error, so we
+ * don't need to explicitly set a G_IO_ERROR_CANCELLED here. */
+
+ g_mutex_lock (&op->task_lock);
+
+ g_clear_object (&op->task);
+
+ g_mutex_unlock (&op->task_lock);
+}
+
+static void
+connection_op_add_pending (ConnectionOp *op,
+ GTask *task,
+ GCancellable *cancellable)
+{
+ ConnectionOp *pending_op;
+
+ g_return_if_fail (op != NULL);
+
+ pending_op = connection_op_new (task, cancellable);
+
+ if (pending_op->cancellable != NULL)
+ pending_op->cancel_id = g_cancellable_connect (
+ pending_op->cancellable,
+ G_CALLBACK (connection_op_cancelled),
+ pending_op, (GDestroyNotify) NULL);
+
+ g_queue_push_tail (&op->pending, pending_op);
+}
+
+static void
+connection_op_complete_pending (ConnectionOp *op,
+ const GError *error)
+{
+ ConnectionOp *pending_op;
+
+ g_return_if_fail (op != NULL);
+
+ while (!g_queue_is_empty (&op->pending)) {
+ pending_op = g_queue_pop_head (&op->pending);
+ connection_op_complete (pending_op, error);
+ connection_op_unref (pending_op);
+ }
+}
+
+static void
+dispatch_data_free (DispatchData *dispatch_data)
+{
+ g_weak_ref_clear (&dispatch_data->service);
+
+ g_slice_free (DispatchData, dispatch_data);
+}
+
+static void
+task_queue_free (GQueue *task_queue)
+{
+ g_queue_free_full (task_queue, g_object_unref);
+}
+
+static void
+service_task_table_push (CamelService *service,
+ GTask *task)
+{
+ GQueue *task_queue;
+ gpointer source_object;
+ gboolean queue_was_empty;
+
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+ g_return_if_fail (G_IS_TASK (task));
+
+ source_object = g_task_get_source_object (task);
+ if (source_object == NULL)
+ source_object = service;
+
+ g_mutex_lock (&service->priv->task_table_lock);
+
+ task_queue = g_hash_table_lookup (
+ service->priv->task_table, source_object);
+
+ /* Create on demand. */
+ if (task_queue == NULL) {
+ task_queue = g_queue_new ();
+ g_hash_table_insert (
+ service->priv->task_table,
+ source_object, task_queue);
+ }
+
+ queue_was_empty = g_queue_is_empty (task_queue);
+ g_queue_push_tail (task_queue, g_object_ref (task));
+
+ g_mutex_unlock (&service->priv->task_table_lock);
+
+ if (queue_was_empty)
+ service_task_dispatch (service, task);
+}
+
+static void
+service_task_table_done (CamelService *service,
+ GTask *task)
+{
+ GQueue *task_queue;
+ gpointer source_object;
+ GTask *next = NULL;
+
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+ g_return_if_fail (G_IS_TASK (task));
+
+ source_object = g_task_get_source_object (task);
+ if (source_object == NULL)
+ source_object = service;
+
+ g_mutex_lock (&service->priv->task_table_lock);
+
+ task_queue = g_hash_table_lookup (
+ service->priv->task_table, source_object);
+
+ if (task_queue != NULL) {
+ if (g_queue_remove (task_queue, task))
+ g_object_unref (task);
+
+ next = g_queue_peek_head (task_queue);
+ if (next != NULL)
+ g_object_ref (next);
+ }
+
+ g_mutex_unlock (&service->priv->task_table_lock);
+
+ if (next != NULL) {
+ service_task_dispatch (service, next);
+ g_object_unref (next);
+ }
+}
+
+static void
+service_task_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelService *service;
+ DispatchData *data;
+
+ data = g_object_get_data (G_OBJECT (task), DISPATCH_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ service = g_weak_ref_get (&data->service);
+ g_return_if_fail (service != NULL);
+
+ data->task_func (task, source_object, task_data, cancellable);
+
+ service_task_table_done (service, task);
+
+ g_object_unref (service);
+}
+
+static void
+service_task_dispatch (CamelService *service,
+ GTask *task)
+{
+ DispatchData *data;
+
+ data = g_object_get_data (G_OBJECT (task), DISPATCH_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ /* Restore the task's previous "return-on-cancel" flag.
+ * This returns FALSE if the task is already cancelled,
+ * in which case we skip calling g_task_run_in_thread()
+ * so the task doesn't complete twice. */
+ if (g_task_set_return_on_cancel (task, data->return_on_cancel))
+ g_task_run_in_thread (task, service_task_thread);
+ else
+ service_task_table_done (service, task);
+}
+
+static gchar *
+service_find_old_data_dir (CamelService *service)
+{
+ CamelProvider *provider;
+ CamelSession *session;
+ CamelURL *url;
+ GString *path;
+ gboolean allows_host;
+ gboolean allows_user;
+ gboolean needs_host;
+ gboolean needs_path;
+ gboolean needs_user;
+ const gchar *base_dir;
+ gchar *old_data_dir;
+
+ provider = camel_service_get_provider (service);
+ url = camel_service_new_camel_url (service);
+
+ allows_host = CAMEL_PROVIDER_ALLOWS (provider, CAMEL_URL_PART_HOST);
+ allows_user = CAMEL_PROVIDER_ALLOWS (provider, CAMEL_URL_PART_USER);
+
+ needs_host = CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_HOST);
+ needs_path = CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_PATH);
+ needs_user = CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_USER);
+
+ /* This function reproduces the way service data directories used
+ * to be determined before we moved to just using the UID. If the
+ * old data directory exists, try renaming it to the new form.
+ *
+ * A virtual class method was used to determine the directory path,
+ * but no known CamelProviders ever overrode the default algorithm
+ * below. So this should work for everyone. */
+
+ path = g_string_new (provider->protocol);
+
+ if (allows_user) {
+ g_string_append_c (path, '/');
+ if (url->user != NULL)
+ g_string_append (path, url->user);
+ if (allows_host) {
+ g_string_append_c (path, '@');
+ if (url->host != NULL)
+ g_string_append (path, url->host);
+ if (url->port) {
+ g_string_append_c (path, ':');
+ g_string_append_printf (path, "%d", url->port);
+ }
+ } else if (!needs_user) {
+ g_string_append_c (path, '@');
+ }
+
+ } else if (allows_host) {
+ g_string_append_c (path, '/');
+ if (!needs_host)
+ g_string_append_c (path, '@');
+ if (url->host != NULL)
+ g_string_append (path, url->host);
+ if (url->port) {
+ g_string_append_c (path, ':');
+ g_string_append_printf (path, "%d", url->port);
+ }
+ }
+
+ if (needs_path && url->path) {
+ if (*url->path != '/')
+ g_string_append_c (path, '/');
+ g_string_append (path, url->path);
+ }
+
+ session = camel_service_ref_session (service);
+ if (session) {
+ base_dir = camel_session_get_user_data_dir (session);
+ old_data_dir = g_build_filename (base_dir, path->str, NULL);
+
+ g_object_unref (session);
+ } else {
+ old_data_dir = NULL;
+ }
+
+ g_string_free (path, TRUE);
+
+ if (old_data_dir && !g_file_test (old_data_dir, G_FILE_TEST_IS_DIR)) {
+ g_free (old_data_dir);
+ old_data_dir = NULL;
+ }
+
+ camel_url_free (url);
+
+ return old_data_dir;
+}
+
+static gboolean
+service_notify_connection_status_cb (gpointer user_data)
+{
+ CamelService *service = CAMEL_SERVICE (user_data);
+
+ g_object_notify (G_OBJECT (service), "connection-status");
+
+ return FALSE;
+}
+
+static void
+service_queue_notify_connection_status (CamelService *service)
+{
+ CamelSession *session;
+
+ session = camel_service_ref_session (service);
+
+ /* most-likely exitting the application */
+ if (!session)
+ return;
+
+ /* Prioritize ahead of GTK+ redraws. */
+ camel_session_idle_add (
+ session, G_PRIORITY_HIGH_IDLE,
+ service_notify_connection_status_cb,
+ g_object_ref (service),
+ (GDestroyNotify) g_object_unref);
+
+ g_object_unref (session);
+}
+
+static void
+service_shared_connect_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CamelService *service;
+ ConnectionOp *op = user_data;
+ gboolean success;
+ GError *local_error = NULL;
+
+ service = CAMEL_SERVICE (source_object);
+ success = g_task_propagate_boolean (G_TASK (result), &local_error);
+
+ g_mutex_lock (&service->priv->connection_lock);
+
+ if (service->priv->connection_op == op) {
+ connection_op_unref (service->priv->connection_op);
+ service->priv->connection_op = NULL;
+ if (success)
+ service->priv->status = CAMEL_SERVICE_CONNECTED;
+ else
+ service->priv->status = CAMEL_SERVICE_DISCONNECTED;
+ service_queue_notify_connection_status (service);
+ }
+
+ connection_op_complete (op, local_error);
+ connection_op_complete_pending (op, local_error);
+
+ g_mutex_unlock (&service->priv->connection_lock);
+
+ connection_op_unref (op);
+ g_clear_error (&local_error);
+}
+
+static void
+service_shared_connect_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelServiceClass *class;
+ gboolean success;
+ GError *local_error = NULL;
+
+ /* Note we call the class method directly here. */
+
+ class = CAMEL_SERVICE_GET_CLASS (source_object);
+ g_return_if_fail (class->connect_sync != NULL);
+
+ success = class->connect_sync (
+ CAMEL_SERVICE (source_object),
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+static void
+service_shared_connect (CamelService *service,
+ gint io_priority,
+ ConnectionOp *op)
+{
+ GTask *task;
+
+ task = g_task_new (
+ service, op->cancellable,
+ service_shared_connect_cb,
+ connection_op_ref (op));
+
+ g_task_set_source_tag (task, service_shared_connect);
+ g_task_set_priority (task, io_priority);
+
+ g_task_run_in_thread (task, service_shared_connect_thread);
+
+ g_object_unref (task);
+}
+
+static void
+service_shared_disconnect_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CamelService *service;
+ ConnectionOp *op = user_data;
+ gboolean success;
+ GError *local_error = NULL;
+
+ service = CAMEL_SERVICE (source_object);
+ success = g_task_propagate_boolean (G_TASK (result), &local_error);
+
+ g_mutex_lock (&service->priv->connection_lock);
+
+ if (service->priv->connection_op == op) {
+ connection_op_unref (service->priv->connection_op);
+ service->priv->connection_op = NULL;
+ if (success || service->priv->status == CAMEL_SERVICE_CONNECTING)
+ service->priv->status = CAMEL_SERVICE_DISCONNECTED;
+ else
+ service->priv->status = CAMEL_SERVICE_CONNECTED;
+ service_queue_notify_connection_status (service);
+ }
+
+ connection_op_complete (op, local_error);
+ connection_op_complete_pending (op, local_error);
+
+ g_mutex_unlock (&service->priv->connection_lock);
+
+ connection_op_unref (op);
+ g_clear_error (&local_error);
+}
+
+static void
+service_shared_disconnect_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelServiceClass *class;
+ AsyncContext *async_context;
+ gboolean success;
+ GError *local_error = NULL;
+
+ /* Note we call the class method directly here. */
+
+ async_context = (AsyncContext *) task_data;
+
+ class = CAMEL_SERVICE_GET_CLASS (source_object);
+ g_return_if_fail (class->disconnect_sync != NULL);
+
+ success = class->disconnect_sync (
+ CAMEL_SERVICE (source_object),
+ async_context->clean,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+static void
+service_shared_disconnect (CamelService *service,
+ gboolean clean,
+ gint io_priority,
+ ConnectionOp *op)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->clean = clean;
+
+ task = g_task_new (
+ service, op->cancellable,
+ service_shared_disconnect_cb,
+ connection_op_ref (op));
+
+ g_task_set_source_tag (task, service_shared_disconnect);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, service_shared_disconnect_thread);
+
+ g_object_unref (task);
+}
+
+static void
+service_set_provider (CamelService *service,
+ CamelProvider *provider)
+{
+ g_return_if_fail (provider != NULL);
+ g_return_if_fail (service->priv->provider == NULL);
+
+ service->priv->provider = provider;
+}
+
+static void
+service_set_session (CamelService *service,
+ CamelSession *session)
+{
+ g_return_if_fail (CAMEL_IS_SESSION (session));
+
+ g_weak_ref_set (&service->priv->session, session);
+}
+
+static void
+service_set_uid (CamelService *service,
+ const gchar *uid)
+{
+ g_return_if_fail (uid != NULL);
+ g_return_if_fail (service->priv->uid == NULL);
+
+ service->priv->uid = g_strdup (uid);
+}
+
+static void
+service_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_DISPLAY_NAME:
+ camel_service_set_display_name (
+ CAMEL_SERVICE (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_PASSWORD:
+ camel_service_set_password (
+ CAMEL_SERVICE (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_PROVIDER:
+ service_set_provider (
+ CAMEL_SERVICE (object),
+ g_value_get_pointer (value));
+ return;
+
+ case PROP_PROXY_RESOLVER:
+ camel_service_set_proxy_resolver (
+ CAMEL_SERVICE (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_SESSION:
+ service_set_session (
+ CAMEL_SERVICE (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_SETTINGS:
+ camel_service_set_settings (
+ CAMEL_SERVICE (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_UID:
+ service_set_uid (
+ CAMEL_SERVICE (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+service_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONNECTION_STATUS:
+ g_value_set_enum (
+ value,
+ camel_service_get_connection_status (
+ CAMEL_SERVICE (object)));
+ return;
+
+ case PROP_DISPLAY_NAME:
+ g_value_take_string (
+ value,
+ camel_service_dup_display_name (
+ CAMEL_SERVICE (object)));
+ return;
+
+ case PROP_PASSWORD:
+ g_value_take_string (
+ value,
+ camel_service_dup_password (
+ CAMEL_SERVICE (object)));
+ return;
+
+ case PROP_PROVIDER:
+ g_value_set_pointer (
+ value,
+ camel_service_get_provider (
+ CAMEL_SERVICE (object)));
+ return;
+
+ case PROP_PROXY_RESOLVER:
+ g_value_take_object (
+ value,
+ camel_service_ref_proxy_resolver (
+ CAMEL_SERVICE (object)));
+ return;
+
+ case PROP_SESSION:
+ g_value_take_object (
+ value,
+ camel_service_ref_session (
+ CAMEL_SERVICE (object)));
+ return;
+
+ case PROP_SETTINGS:
+ g_value_take_object (
+ value,
+ camel_service_ref_settings (
+ CAMEL_SERVICE (object)));
+ return;
+
+ case PROP_UID:
+ g_value_set_string (
+ value,
+ camel_service_get_uid (
+ CAMEL_SERVICE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+service_dispose (GObject *object)
+{
+ CamelServicePrivate *priv;
+
+ priv = CAMEL_SERVICE_GET_PRIVATE (object);
+
+ if (priv->status == CAMEL_SERVICE_CONNECTED)
+ CAMEL_SERVICE_GET_CLASS (object)->disconnect_sync (
+ CAMEL_SERVICE (object), TRUE, NULL, NULL);
+
+ g_weak_ref_set (&priv->session, NULL);
+
+ g_clear_object (&priv->settings);
+ g_clear_object (&priv->proxy_resolver);
+
+ g_hash_table_remove_all (priv->task_table);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_service_parent_class)->dispose (object);
+}
+
+static void
+service_finalize (GObject *object)
+{
+ CamelServicePrivate *priv;
+
+ priv = CAMEL_SERVICE_GET_PRIVATE (object);
+
+ g_mutex_clear (&priv->property_lock);
+
+ g_free (priv->display_name);
+ g_free (priv->user_data_dir);
+ g_free (priv->user_cache_dir);
+ g_free (priv->uid);
+ g_free (priv->password);
+
+ /* There should be no outstanding connection operations. */
+ g_warn_if_fail (priv->connection_op == NULL);
+ g_mutex_clear (&priv->connection_lock);
+
+ g_hash_table_destroy (priv->task_table);
+ g_mutex_clear (&priv->task_table_lock);
+
+ g_weak_ref_clear (&priv->session);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_service_parent_class)->finalize (object);
+}
+
+static void
+service_constructed (GObject *object)
+{
+ CamelService *service;
+ CamelSession *session;
+ const gchar *base_dir;
+ const gchar *uid;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (camel_service_parent_class)->constructed (object);
+
+ service = CAMEL_SERVICE (object);
+ session = camel_service_ref_session (service);
+
+ uid = camel_service_get_uid (service);
+
+ base_dir = camel_session_get_user_data_dir (session);
+ service->priv->user_data_dir = g_build_filename (base_dir, uid, NULL);
+
+ base_dir = camel_session_get_user_cache_dir (session);
+ service->priv->user_cache_dir = g_build_filename (base_dir, uid, NULL);
+
+ g_object_unref (session);
+
+ /* The CamelNetworkService interface needs initialization. */
+ if (CAMEL_IS_NETWORK_SERVICE (service)) {
+ camel_network_service_init (CAMEL_NETWORK_SERVICE (service));
+ service->priv->network_service_inited = TRUE;
+ }
+}
+
+static gchar *
+service_get_name (CamelService *service,
+ gboolean brief)
+{
+ g_warning (
+ "%s does not implement CamelServiceClass::get_name()",
+ G_OBJECT_TYPE_NAME (service));
+
+ return g_strdup (G_OBJECT_TYPE_NAME (service));
+}
+
+static gboolean
+service_connect_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return TRUE;
+}
+
+static gboolean
+service_disconnect_sync (CamelService *service,
+ gboolean clean,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (CAMEL_IS_NETWORK_SERVICE (service))
+ camel_network_service_set_connectable (
+ CAMEL_NETWORK_SERVICE (service), NULL);
+
+ return TRUE;
+}
+
+static GList *
+service_query_auth_types_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return NULL;
+}
+
+static gboolean
+service_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* Nothing to do here, but we may need add something in the future.
+ * For now this is a placeholder so subclasses can safely chain up. */
+
+ return TRUE;
+}
+
+static void
+camel_service_class_init (CamelServiceClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelServicePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = service_set_property;
+ object_class->get_property = service_get_property;
+ object_class->dispose = service_dispose;
+ object_class->finalize = service_finalize;
+ object_class->constructed = service_constructed;
+
+ class->settings_type = CAMEL_TYPE_SETTINGS;
+ class->get_name = service_get_name;
+ class->connect_sync = service_connect_sync;
+ class->disconnect_sync = service_disconnect_sync;
+ class->query_auth_types_sync = service_query_auth_types_sync;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CONNECTION_STATUS,
+ g_param_spec_enum (
+ "connection-status",
+ "Connection Status",
+ "The connection status for the service",
+ CAMEL_TYPE_SERVICE_CONNECTION_STATUS,
+ CAMEL_SERVICE_DISCONNECTED,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_DISPLAY_NAME,
+ g_param_spec_string (
+ "display-name",
+ "Display Name",
+ "The display name for the service",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PASSWORD,
+ g_param_spec_string (
+ "password",
+ "Password",
+ "The password for the service",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PROVIDER,
+ g_param_spec_pointer (
+ "provider",
+ "Provider",
+ "The CamelProvider for the service",
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PROXY_RESOLVER,
+ g_param_spec_object (
+ "proxy-resolver",
+ "Proxy Resolver",
+ "The proxy resolver for the service",
+ G_TYPE_PROXY_RESOLVER,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SESSION,
+ g_param_spec_object (
+ "session",
+ "Session",
+ "A CamelSession instance",
+ CAMEL_TYPE_SESSION,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SETTINGS,
+ g_param_spec_object (
+ "settings",
+ "Settings",
+ "A CamelSettings instance",
+ CAMEL_TYPE_SETTINGS,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_UID,
+ g_param_spec_string (
+ "uid",
+ "UID",
+ "The unique identity of the service",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_service_initable_init (GInitableIface *iface)
+{
+ iface->init = service_initable_init;
+}
+
+static void
+camel_service_init (CamelService *service)
+{
+ GHashTable *task_table;
+
+ task_table = g_hash_table_new_full (
+ (GHashFunc) g_direct_hash,
+ (GEqualFunc) g_direct_equal,
+ (GDestroyNotify) NULL,
+ (GDestroyNotify) task_queue_free);
+
+ service->priv = CAMEL_SERVICE_GET_PRIVATE (service);
+
+ g_mutex_init (&service->priv->property_lock);
+ g_mutex_init (&service->priv->connection_lock);
+ g_weak_ref_init (&service->priv->session, NULL);
+ service->priv->status = CAMEL_SERVICE_DISCONNECTED;
+
+ service->priv->proxy_resolver = g_proxy_resolver_get_default ();
+ if (service->priv->proxy_resolver != NULL)
+ g_object_ref (service->priv->proxy_resolver);
+
+ service->priv->task_table = task_table;
+ g_mutex_init (&service->priv->task_table_lock);
+}
+
+G_DEFINE_QUARK (camel-service-error-quark, camel_service_error)
+
+/**
+ * camel_service_migrate_files:
+ * @service: a #CamelService
+ *
+ * Performs any necessary file migrations for @service. This should be
+ * called after installing or configuring the @service's #CamelSettings,
+ * since it requires building a URL string for @service.
+ *
+ * Since: 3.4
+ **/
+void
+camel_service_migrate_files (CamelService *service)
+{
+ const gchar *new_data_dir;
+ gchar *old_data_dir;
+
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ new_data_dir = camel_service_get_user_data_dir (service);
+ old_data_dir = service_find_old_data_dir (service);
+
+ /* If the old data directory name exists, try renaming
+ * it to the new data directory. Failure is non-fatal. */
+ if (old_data_dir != NULL) {
+ if (g_rename (old_data_dir, new_data_dir) == -1) {
+ g_warning (
+ "%s: Failed to rename '%s' to '%s': %s",
+ G_STRFUNC, old_data_dir, new_data_dir, g_strerror (errno));
+ }
+ g_free (old_data_dir);
+ }
+}
+
+/**
+ * camel_service_new_camel_url:
+ * @service: a #CamelService
+ *
+ * Returns a new #CamelURL representing @service.
+ * Free the returned #CamelURL with camel_url_free().
+ *
+ * Returns: a new #CamelURL
+ *
+ * Since: 3.2
+ **/
+CamelURL *
+camel_service_new_camel_url (CamelService *service)
+{
+ CamelURL *url;
+ CamelProvider *provider;
+ CamelSettings *settings;
+ gchar *host = NULL;
+ gchar *user = NULL;
+ gchar *path = NULL;
+ guint16 port = 0;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ provider = camel_service_get_provider (service);
+ g_return_val_if_fail (provider != NULL, NULL);
+
+ settings = camel_service_ref_settings (service);
+
+ /* Allocate as camel_url_new_with_base() does. */
+ url = g_new0 (CamelURL, 1);
+
+ if (CAMEL_IS_NETWORK_SETTINGS (settings)) {
+ CamelNetworkSettings *network_settings;
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ port = camel_network_settings_get_port (network_settings);
+ user = camel_network_settings_dup_user (network_settings);
+ }
+
+ if (CAMEL_IS_LOCAL_SETTINGS (settings)) {
+ CamelLocalSettings *local_settings;
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+ }
+
+ camel_url_set_protocol (url, provider->protocol);
+ camel_url_set_host (url, host);
+ camel_url_set_port (url, port);
+ camel_url_set_user (url, user);
+ camel_url_set_path (url, path);
+
+ g_free (host);
+ g_free (user);
+ g_free (path);
+
+ g_object_unref (settings);
+
+ return url;
+}
+
+/**
+ * camel_service_get_connection_status:
+ * @service: a #CamelService
+ *
+ * Returns the connection status for @service.
+ *
+ * Returns: the connection status
+ *
+ * Since: 3.2
+ **/
+CamelServiceConnectionStatus
+camel_service_get_connection_status (CamelService *service)
+{
+ g_return_val_if_fail (
+ CAMEL_IS_SERVICE (service),
+ CAMEL_SERVICE_DISCONNECTED);
+
+ return service->priv->status;
+}
+
+/**
+ * camel_service_get_display_name:
+ * @service: a #CamelService
+ *
+ * Returns the display name for @service, or %NULL if @service has not
+ * been given a display name. The display name is intended for use in
+ * a user interface and should generally be given a user-defined name.
+ *
+ * Compare this with camel_service_get_name(), which returns a built-in
+ * description of the type of service (IMAP, SMTP, etc.).
+ *
+ * Returns: the display name for @service, or %NULL
+ *
+ * Since: 3.2
+ **/
+const gchar *
+camel_service_get_display_name (CamelService *service)
+{
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ return service->priv->display_name;
+}
+
+/**
+ * camel_service_dup_display_name:
+ * @service: a #CamelService
+ *
+ * Thread-safe variation of camel_service_get_display_name().
+ * Use this function when accessing @service from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelService:display-name
+ *
+ * Since: 3.12
+ **/
+gchar *
+camel_service_dup_display_name (CamelService *service)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ g_mutex_lock (&service->priv->property_lock);
+
+ protected = camel_service_get_display_name (service);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&service->priv->property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_service_set_display_name:
+ * @service: a #CamelService
+ * @display_name: a valid UTF-8 string, or %NULL
+ *
+ * Assigns a UTF-8 display name to @service. The display name is intended
+ * for use in a user interface and should generally be given a user-defined
+ * name.
+ *
+ * Compare this with camel_service_get_name(), which returns a built-in
+ * description of the type of service (IMAP, SMTP, etc.).
+ *
+ * Since: 3.2
+ **/
+void
+camel_service_set_display_name (CamelService *service,
+ const gchar *display_name)
+{
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ if (display_name != NULL)
+ g_return_if_fail (g_utf8_validate (display_name, -1, NULL));
+
+ g_mutex_lock (&service->priv->property_lock);
+
+ if (g_strcmp0 (service->priv->display_name, display_name) == 0) {
+ g_mutex_unlock (&service->priv->property_lock);
+ return;
+ }
+
+ g_free (service->priv->display_name);
+ service->priv->display_name = g_strdup (display_name);
+
+ g_mutex_unlock (&service->priv->property_lock);
+
+ g_object_notify (G_OBJECT (service), "display-name");
+}
+
+/**
+ * camel_service_get_password:
+ * @service: a #CamelService
+ *
+ * Returns the password for @service. Some SASL mechanisms use this
+ * when attempting to authenticate.
+ *
+ * Returns: the password for @service
+ *
+ * Since: 3.4
+ **/
+const gchar *
+camel_service_get_password (CamelService *service)
+{
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ return service->priv->password;
+}
+
+/**
+ * camel_service_dup_password:
+ * @service: a #CamelService
+ *
+ * Thread-safe variation of camel_service_get_password().
+ * Use this function when accessing @service from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelService:password
+ *
+ * Since: 3.12
+ **/
+gchar *
+camel_service_dup_password (CamelService *service)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ g_mutex_lock (&service->priv->property_lock);
+
+ protected = camel_service_get_password (service);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&service->priv->property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_service_set_password:
+ * @service: a #CamelService
+ * @password: the password for @service
+ *
+ * Sets the password for @service. Use this function to cache the password
+ * in memory after obtaining it through camel_session_get_password(). Some
+ * SASL mechanisms use this when attempting to authenticate.
+ *
+ * Since: 3.4
+ **/
+void
+camel_service_set_password (CamelService *service,
+ const gchar *password)
+{
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ g_mutex_lock (&service->priv->property_lock);
+
+ if (g_strcmp0 (service->priv->password, password) == 0) {
+ g_mutex_unlock (&service->priv->property_lock);
+ return;
+ }
+
+ g_free (service->priv->password);
+ service->priv->password = g_strdup (password);
+
+ g_mutex_unlock (&service->priv->property_lock);
+
+ g_object_notify (G_OBJECT (service), "password");
+}
+
+/**
+ * camel_service_get_user_data_dir:
+ * @service: a #CamelService
+ *
+ * Returns the base directory under which to store user-specific data
+ * for @service. The directory is formed by appending the directory
+ * returned by camel_session_get_user_data_dir() with the service's
+ * #CamelService:uid value.
+ *
+ * Returns: the base directory for @service
+ *
+ * Since: 3.2
+ **/
+const gchar *
+camel_service_get_user_data_dir (CamelService *service)
+{
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ return service->priv->user_data_dir;
+}
+
+/**
+ * camel_service_get_user_cache_dir:
+ * @service: a #CamelService
+ *
+ * Returns the base directory under which to store cache data
+ * for @service. The directory is formed by appending the directory
+ * returned by camel_session_get_user_cache_dir() with the service's
+ * #CamelService:uid value.
+ *
+ * Returns: the base cache directory for @service
+ *
+ * Since: 3.4
+ **/
+const gchar *
+camel_service_get_user_cache_dir (CamelService *service)
+{
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ return service->priv->user_cache_dir;
+}
+
+/**
+ * camel_service_get_name:
+ * @service: a #CamelService
+ * @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.
+ *
+ * Returns: a description of the service which the caller must free
+ **/
+gchar *
+camel_service_get_name (CamelService *service,
+ gboolean brief)
+{
+ CamelServiceClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ class = CAMEL_SERVICE_GET_CLASS (service);
+ g_return_val_if_fail (class->get_name != NULL, NULL);
+
+ return class->get_name (service, brief);
+}
+
+/**
+ * camel_service_get_provider:
+ * @service: a #CamelService
+ *
+ * Gets the #CamelProvider associated with the service.
+ *
+ * Returns: the #CamelProvider
+ **/
+CamelProvider *
+camel_service_get_provider (CamelService *service)
+{
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ return service->priv->provider;
+}
+
+/**
+ * camel_service_ref_proxy_resolver:
+ * @service: a #CamelService
+ *
+ * Returns the #GProxyResolver for @service. If an application needs to
+ * override this, it should do so prior to calling functions on @service
+ * that may require a network connection.
+ *
+ * The returned #GProxyResolver is referenced for thread-safety and must
+ * be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: (transfer full): a #GProxyResolver, or %NULL
+ *
+ * Since: 3.12
+ **/
+GProxyResolver *
+camel_service_ref_proxy_resolver (CamelService *service)
+{
+ GProxyResolver *proxy_resolver = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ g_mutex_lock (&service->priv->property_lock);
+
+ if (service->priv->proxy_resolver != NULL)
+ proxy_resolver = g_object_ref (service->priv->proxy_resolver);
+
+ g_mutex_unlock (&service->priv->property_lock);
+
+ return proxy_resolver;
+}
+
+/**
+ * camel_service_set_proxy_resolver:
+ * @service: a #CamelService
+ * @proxy_resolver: a #GProxyResolver, or %NULL for the default
+ *
+ * Sets the #GProxyResolver for @service. If an application needs to
+ * override this, it should do so prior to calling functions on @service
+ * that may require a network connection.
+ *
+ * Since: 3.12
+ **/
+void
+camel_service_set_proxy_resolver (CamelService *service,
+ GProxyResolver *proxy_resolver)
+{
+ gboolean notify = FALSE;
+
+ if (proxy_resolver == NULL)
+ proxy_resolver = g_proxy_resolver_get_default ();
+
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+ g_return_if_fail (G_IS_PROXY_RESOLVER (proxy_resolver));
+
+ g_mutex_lock (&service->priv->property_lock);
+
+ /* Emitting a "notify" signal unnecessarily might have
+ * unwanted side effects like cancelling a SoupMessage.
+ * Only emit if we now have a different GProxyResolver. */
+
+ if (proxy_resolver != service->priv->proxy_resolver) {
+ g_clear_object (&service->priv->proxy_resolver);
+ service->priv->proxy_resolver = proxy_resolver;
+
+ if (proxy_resolver != NULL)
+ g_object_ref (proxy_resolver);
+
+ notify = TRUE;
+ }
+
+ g_mutex_unlock (&service->priv->property_lock);
+
+ if (notify)
+ g_object_notify (G_OBJECT (service), "proxy-resolver");
+}
+
+/**
+ * camel_service_ref_session:
+ * @service: (type CamelService): a #CamelService
+ *
+ * Returns the #CamelSession associated with the service.
+ *
+ * The returned #CamelSession is referenced for thread-safety. Unreference
+ * the #CamelSession with g_object_unref() when finished with it.
+ *
+ * Returns: (transfer full): the #CamelSession
+ *
+ * Since: 3.8
+ **/
+CamelSession *
+camel_service_ref_session (CamelService *service)
+{
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ return g_weak_ref_get (&service->priv->session);
+}
+
+/**
+ * camel_service_ref_settings:
+ * @service: a #CamelService
+ *
+ * Returns the #CamelSettings instance associated with the service.
+ *
+ * The returned #CamelSettings is referenced for thread-safety and must
+ * be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: (transfer full): the #CamelSettings
+ *
+ * Since: 3.6
+ **/
+CamelSettings *
+camel_service_ref_settings (CamelService *service)
+{
+ CamelSettings *settings;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ /* Every service should have a settings object. */
+ g_return_val_if_fail (service->priv->settings != NULL, NULL);
+
+ g_mutex_lock (&service->priv->property_lock);
+
+ settings = g_object_ref (service->priv->settings);
+
+ g_mutex_unlock (&service->priv->property_lock);
+
+ return settings;
+}
+
+/**
+ * camel_service_set_settings:
+ * @service: a #CamelService
+ * @settings: an instance derviced from #CamelSettings, or %NULL
+ *
+ * Associates a new #CamelSettings instance with the service.
+ * The @settings instance must match the settings type defined in
+ * #CamelServiceClass. If @settings is %NULL, a new #CamelSettings
+ * instance of the appropriate type is created with all properties
+ * set to defaults.
+ *
+ * Since: 3.2
+ **/
+void
+camel_service_set_settings (CamelService *service,
+ CamelSettings *settings)
+{
+ CamelServiceClass *class;
+
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ class = CAMEL_SERVICE_GET_CLASS (service);
+
+ if (settings != NULL) {
+ g_return_if_fail (
+ g_type_is_a (
+ G_OBJECT_TYPE (settings),
+ class->settings_type));
+ g_object_ref (settings);
+
+ } else {
+ g_return_if_fail (
+ g_type_is_a (
+ class->settings_type,
+ CAMEL_TYPE_SETTINGS));
+ settings = g_object_new (class->settings_type, NULL);
+ }
+
+ g_mutex_lock (&service->priv->property_lock);
+
+ if (service->priv->settings != NULL)
+ g_object_unref (service->priv->settings);
+
+ service->priv->settings = settings; /* takes ownership */
+
+ g_mutex_unlock (&service->priv->property_lock);
+
+ /* If the service is a CamelNetworkService, it needs to
+ * replace its GSocketConnectable for the new settings. */
+ if (service->priv->network_service_inited)
+ camel_network_service_set_connectable (
+ CAMEL_NETWORK_SERVICE (service), NULL);
+
+ g_object_notify (G_OBJECT (service), "settings");
+}
+
+/**
+ * camel_service_get_uid:
+ * @service: a #CamelService
+ *
+ * Gets the unique identifier string associated with the service.
+ *
+ * Returns: the UID string
+ *
+ * Since: 3.2
+ **/
+const gchar *
+camel_service_get_uid (CamelService *service)
+{
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ return service->priv->uid;
+}
+
+/**
+ * camel_service_queue_task:
+ * @service: a #CamelService
+ * @task: a #GTask
+ * @task_func: (scope async): function to call when @task is dispatched
+ *
+ * Adds @task to a queue of waiting tasks with the same source object.
+ * Queued tasks execute one at a time in the order they were added. When
+ * @task reaches the front of the queue, it will be dispatched by invoking
+ * @task_func in a separate thread. If @task is cancelled while queued,
+ * it will complete immediately with an appropriate error.
+ *
+ * This is primarily intended for use by #CamelStore, #CamelTransport and
+ * #CamelFolder to achieve ordered invocation of synchronous class methods.
+ *
+ * Since: 3.12
+ **/
+void
+camel_service_queue_task (CamelService *service,
+ GTask *task,
+ GTaskThreadFunc task_func)
+{
+ DispatchData *dispatch_data;
+ gboolean return_on_cancel;
+
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+ g_return_if_fail (G_IS_TASK (task));
+ g_return_if_fail (task_func != NULL);
+
+ return_on_cancel = g_task_get_return_on_cancel (task);
+
+ dispatch_data = g_slice_new0 (DispatchData);
+ g_weak_ref_init (&dispatch_data->service, service);
+ dispatch_data->return_on_cancel = return_on_cancel;
+ dispatch_data->task_func = task_func;
+
+ /* Complete immediately if cancelled while queued. */
+ g_task_set_return_on_cancel (task, TRUE);
+
+ /* Stash this until it's time to dispatch the GTask. */
+ g_object_set_data_full (
+ G_OBJECT (task), DISPATCH_DATA_KEY,
+ dispatch_data, (GDestroyNotify) dispatch_data_free);
+
+ service_task_table_push (service, task);
+}
+
+/**
+ * camel_service_connect_sync:
+ * @service: a #CamelService
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Connects @service to a remote server using the information in its
+ * #CamelService:settings instance.
+ *
+ * If a connect operation is already in progress when this function is
+ * called, its results will be reflected in this connect operation.
+ *
+ * Returns: %TRUE if the connection is made or %FALSE otherwise
+ *
+ * Since: 3.6
+ **/
+gboolean
+camel_service_connect_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelAsyncClosure *closure;
+ GAsyncResult *result;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
+
+ closure = camel_async_closure_new ();
+
+ camel_service_connect (
+ service, G_PRIORITY_DEFAULT, cancellable,
+ camel_async_closure_callback, closure);
+
+ result = camel_async_closure_wait (closure);
+
+ success = camel_service_connect_finish (service, result, error);
+
+ camel_async_closure_free (closure);
+
+ return success;
+}
+
+/**
+ * camel_service_connect:
+ * @service: a #CamelService
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously connects @service to a remote server using the information
+ * in its #CamelService:settings instance.
+ *
+ * If a connect operation is already in progress when this function is
+ * called, its results will be reflected in this connect operation.
+ *
+ * If any disconnect operations are in progress when this function is
+ * called, they will be cancelled.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_service_connect_finish() to get the result of the
+ * operation.
+ *
+ * Since: 3.6
+ **/
+void
+camel_service_connect (CamelService *service,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ ConnectionOp *op;
+
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ if (cancellable)
+ g_object_ref (cancellable);
+ else
+ cancellable = g_cancellable_new ();
+
+ task = g_task_new (service, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_service_connect);
+ g_task_set_priority (task, io_priority);
+
+ g_mutex_lock (&service->priv->connection_lock);
+
+ switch (service->priv->status) {
+
+ /* If a connect operation is already in progress,
+ * queue this operation so it completes at the same
+ * time the first connect operation completes. */
+ case CAMEL_SERVICE_CONNECTING:
+ connection_op_add_pending (
+ service->priv->connection_op,
+ task, cancellable);
+ break;
+
+ /* If we're already connected, just report success. */
+ case CAMEL_SERVICE_CONNECTED:
+ g_task_return_boolean (task, TRUE);
+ break;
+
+ /* If a disconnect operation is currently in progress,
+ * cancel it and make room for the connect operation. */
+ case CAMEL_SERVICE_DISCONNECTING:
+ g_return_if_fail (
+ service->priv->connection_op != NULL);
+ g_cancellable_cancel (
+ service->priv->connection_op->cancellable);
+ connection_op_unref (service->priv->connection_op);
+ service->priv->connection_op = NULL;
+ /* fall through */
+
+ /* Start a new connect operation. Subsequent connect
+ * operations are queued until this operation completes
+ * and will share this operation's result. */
+ case CAMEL_SERVICE_DISCONNECTED:
+ g_return_if_fail (
+ service->priv->connection_op == NULL);
+
+ op = connection_op_new (task, cancellable);
+ service->priv->connection_op = op;
+
+ service->priv->status = CAMEL_SERVICE_CONNECTING;
+ service_queue_notify_connection_status (service);
+
+ service_shared_connect (service, io_priority, op);
+ break;
+
+ default:
+ g_warn_if_reached ();
+ }
+
+ g_mutex_unlock (&service->priv->connection_lock);
+
+ g_object_unref (cancellable);
+ g_object_unref (task);
+}
+
+/**
+ * camel_service_connect_finish:
+ * @service: a #CamelService
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_service_connect().
+ *
+ * Returns: %TRUE if the connection was made or %FALSE otherwise
+ *
+ * Since: 3.6
+ **/
+gboolean
+camel_service_connect_finish (CamelService *service,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, service), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_service_connect), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_service_disconnect_sync:
+ * @service: a #CamelService
+ * @clean: whether or not to try to disconnect cleanly
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Disconnect from the service. If @clean is %FALSE, it should not
+ * try to do any synchronizing or other cleanup of the connection.
+ *
+ * If a disconnect operation is already in progress when this function is
+ * called, its results will be reflected in this disconnect operation.
+ *
+ * If any connect operations are in progress when this function is called,
+ * they will be cancelled.
+ *
+ * Returns: %TRUE if the connection was severed or %FALSE otherwise
+ *
+ * Since: 3.6
+ **/
+gboolean
+camel_service_disconnect_sync (CamelService *service,
+ gboolean clean,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelAsyncClosure *closure;
+ GAsyncResult *result;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
+
+ closure = camel_async_closure_new ();
+
+ camel_service_disconnect (
+ service, clean, G_PRIORITY_DEFAULT, cancellable,
+ camel_async_closure_callback, closure);
+
+ result = camel_async_closure_wait (closure);
+
+ success = camel_service_disconnect_finish (service, result, error);
+
+ camel_async_closure_free (closure);
+
+ return success;
+}
+
+/**
+ * camel_service_disconnect:
+ * @service: a #CamelService
+ * @clean: whether or not to try to disconnect cleanly
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * If a disconnect operation is already in progress when this function is
+ * called, its results will be reflected in this disconnect operation.
+ *
+ * If any connect operations are in progress when this function is called,
+ * they will be cancelled.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_service_disconnect_finish() to get the result of the
+ * operation.
+ *
+ * Since: 3.6
+ **/
+void
+camel_service_disconnect (CamelService *service,
+ gboolean clean,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ ConnectionOp *op;
+
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ if (cancellable)
+ g_object_ref (cancellable);
+ else
+ cancellable = g_cancellable_new ();
+
+ task = g_task_new (service, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_service_disconnect);
+ g_task_set_priority (task, io_priority);
+
+ g_mutex_lock (&service->priv->connection_lock);
+
+ switch (service->priv->status) {
+
+ /* If a connect operation is currently in progress,
+ * cancel it and make room for the disconnect operation. */
+ case CAMEL_SERVICE_CONNECTING:
+ g_return_if_fail (
+ service->priv->connection_op != NULL);
+ g_cancellable_cancel (
+ service->priv->connection_op->cancellable);
+ connection_op_unref (service->priv->connection_op);
+ service->priv->connection_op = NULL;
+ /* fall through */
+
+ /* Start a new disconnect operation. Subsequent disconnect
+ * operations are queued until this operation completes and
+ * will share this operation's result. */
+ case CAMEL_SERVICE_CONNECTED:
+ g_return_if_fail (
+ service->priv->connection_op == NULL);
+
+ op = connection_op_new (task, cancellable);
+ service->priv->connection_op = op;
+
+ /* Do not change the status if CONNECTING, in case a
+ * provider calls disconnect() during the connection
+ * phase, which confuses the other logic here and
+ * effectively makes the service's connection state
+ * CONNECTED instead of DISCONNECTED at the end. */
+ if (service->priv->status != CAMEL_SERVICE_CONNECTING) {
+ service->priv->status = CAMEL_SERVICE_DISCONNECTING;
+ service_queue_notify_connection_status (service);
+ }
+
+ service_shared_disconnect (
+ service, clean, io_priority, op);
+ break;
+
+ /* If a disconnect operation is already in progress,
+ * queue this operation so it completes at the same
+ * time the first disconnect operation completes. */
+ case CAMEL_SERVICE_DISCONNECTING:
+ connection_op_add_pending (
+ service->priv->connection_op,
+ task, cancellable);
+ break;
+
+ /* If we're already disconnected, just report success. */
+ case CAMEL_SERVICE_DISCONNECTED:
+ g_task_return_boolean (task, TRUE);
+ break;
+
+ default:
+ g_warn_if_reached ();
+ }
+
+ g_mutex_unlock (&service->priv->connection_lock);
+
+ g_object_unref (cancellable);
+ g_object_unref (task);
+}
+
+/**
+ * camel_service_disconnect_finish:
+ * @service: a #CamelService
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_service_disconnect().
+ *
+ * Returns: %TRUE if the connection was severed or %FALSE otherwise
+ *
+ * Since: 3.6
+ **/
+gboolean
+camel_service_disconnect_finish (CamelService *service,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, service), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_service_disconnect), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_service_authenticate_sync:
+ * @service: a #CamelService
+ * @mechanism: (nullable): a SASL mechanism name, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Attempts to authenticate @service using @mechanism and, if necessary,
+ * @service's #CamelService:password property. The function makes only
+ * ONE attempt at authentication and does not loop.
+ *
+ * If the authentication attempt completed and the server accepted the
+ * credentials, the function returns #CAMEL_AUTHENTICATION_ACCEPTED.
+ *
+ * If the authentication attempt completed but the server rejected the
+ * credentials, the function returns #CAMEL_AUTHENTICATION_REJECTED.
+ *
+ * If the authentication attempt failed to complete due to a network
+ * communication issue or some other mishap, the function sets @error
+ * and returns #CAMEL_AUTHENTICATION_ERROR.
+ *
+ * Generally this function should only be called from a #CamelSession
+ * subclass in order to implement its own authentication loop.
+ *
+ * Returns: the authentication result
+ *
+ * Since: 3.4
+ **/
+CamelAuthenticationResult
+camel_service_authenticate_sync (CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelServiceClass *class;
+ CamelAuthenticationResult result;
+
+ g_return_val_if_fail (
+ CAMEL_IS_SERVICE (service),
+ CAMEL_AUTHENTICATION_ERROR);
+
+ class = CAMEL_SERVICE_GET_CLASS (service);
+ g_return_val_if_fail (
+ class->authenticate_sync != NULL,
+ CAMEL_AUTHENTICATION_ERROR);
+
+ result = class->authenticate_sync (
+ service, mechanism, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ service, authenticate_sync,
+ result != CAMEL_AUTHENTICATION_ERROR, error);
+
+ return result;
+}
+
+/* Helper for camel_service_authenticate() */
+static void
+service_authenticate_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelAuthenticationResult auth_result;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ auth_result = camel_service_authenticate_sync (
+ CAMEL_SERVICE (source_object),
+ async_context->auth_mechanism,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_int (task, auth_result);
+ }
+}
+
+/**
+ * camel_service_authenticate:
+ * @service: a #CamelService
+ * @mechanism: (nullable): a SASL mechanism name, or %NULL
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously attempts to authenticate @service using @mechanism and,
+ * if necessary, @service's #CamelService:password property. The function
+ * makes only ONE attempt at authentication and does not loop.
+ *
+ * Generally this function should only be called from a #CamelSession
+ * subclass in order to implement its own authentication loop.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_service_authenticate_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.4
+ **/
+void
+camel_service_authenticate (CamelService *service,
+ const gchar *mechanism,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->auth_mechanism = g_strdup (mechanism);
+
+ task = g_task_new (service, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_service_authenticate);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, service_authenticate_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_service_authenticate_finish:
+ * @service: a #CamelService
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_service_authenticate().
+ *
+ * If the authentication attempt completed and the server accepted the
+ * credentials, the function returns #CAMEL_AUTHENTICATION_ACCEPTED.
+ *
+ * If the authentication attempt completed but the server rejected the
+ * credentials, the function returns #CAMEL_AUTHENTICATION_REJECTED.
+ *
+ * If the authentication attempt failed to complete due to a network
+ * communication issue or some other mishap, the function sets @error
+ * and returns #CAMEL_AUTHENTICATION_ERROR.
+ *
+ * Returns: the authentication result
+ *
+ * Since: 3.4
+ **/
+CamelAuthenticationResult
+camel_service_authenticate_finish (CamelService *service,
+ GAsyncResult *result,
+ GError **error)
+{
+ CamelAuthenticationResult auth_result;
+
+ g_return_val_if_fail (
+ CAMEL_IS_SERVICE (service),
+ CAMEL_AUTHENTICATION_ERROR);
+ g_return_val_if_fail (
+ g_task_is_valid (result, service),
+ CAMEL_AUTHENTICATION_ERROR);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_service_authenticate),
+ CAMEL_AUTHENTICATION_ERROR);
+
+ /* XXX A little hackish, but best way to return enum values
+ * from GTask in GLib 2.36. Recommended by Dan Winship. */
+
+ auth_result = g_task_propagate_int (G_TASK (result), error);
+
+ if (auth_result == (CamelAuthenticationResult) -1)
+ return CAMEL_AUTHENTICATION_ERROR;
+
+ return auth_result;
+}
+
+/**
+ * camel_service_query_auth_types_sync:
+ * @service: a #CamelService
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Obtains a list of authentication types supported by @service.
+ * Free the returned list with g_list_free().
+ *
+ * Returns: (element-type CamelServiceAuthType) (transfer container): a list of #CamelServiceAuthType structs
+ **/
+GList *
+camel_service_query_auth_types_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelServiceClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ class = CAMEL_SERVICE_GET_CLASS (service);
+ g_return_val_if_fail (class->query_auth_types_sync != NULL, NULL);
+
+ return class->query_auth_types_sync (service, cancellable, error);
+}
+
+/* Helper for camel_service_query_auth_types() */
+static void
+service_query_auth_types_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GList *auth_types;
+ GError *local_error = NULL;
+
+ auth_types = camel_service_query_auth_types_sync (
+ CAMEL_SERVICE (source_object),
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_warn_if_fail (auth_types == NULL);
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_pointer (
+ task, auth_types,
+ (GDestroyNotify) g_list_free);
+ }
+}
+
+/**
+ * camel_service_query_auth_types:
+ * @service: a #CamelService
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously obtains a list of authentication types supported by
+ * @service.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_service_query_auth_types_finish() to get the result
+ * of the operation.
+ *
+ * Since: 3.2
+ **/
+void
+camel_service_query_auth_types (CamelService *service,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ task = g_task_new (service, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_service_query_auth_types);
+ g_task_set_priority (task, io_priority);
+
+ g_task_run_in_thread (task, service_query_auth_types_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_service_query_auth_types_finish:
+ * @service: a #CamelService
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_service_query_auth_types().
+ * Free the returned list with g_list_free().
+ *
+ * Returns: (element-type CamelServiceAuthType) (transfer container): a list of #CamelServiceAuthType structs
+ *
+ * Since: 3.2
+ **/
+GList *
+camel_service_query_auth_types_finish (CamelService *service,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, service), NULL);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_service_query_auth_types), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
diff --git a/src/camel/camel-service.h b/src/camel/camel-service.h
new file mode 100644
index 000000000..5dd70f7b0
--- /dev/null
+++ b/src/camel/camel-service.h
@@ -0,0 +1,221 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-service.h : Abstract class for an email service
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SERVICE_H
+#define CAMEL_SERVICE_H
+
+#include <camel/camel-enums.h>
+#include <camel/camel-object.h>
+#include <camel/camel-url.h>
+#include <camel/camel-provider.h>
+#include <camel/camel-operation.h>
+#include <camel/camel-settings.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SERVICE \
+ (camel_service_get_type ())
+#define CAMEL_SERVICE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SERVICE, CamelService))
+#define CAMEL_SERVICE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SERVICE, CamelServiceClass))
+#define CAMEL_IS_SERVICE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SERVICE))
+#define CAMEL_IS_SERVICE_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SERVICE))
+#define CAMEL_SERVICE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SERVICE, CamelServiceClass))
+
+/**
+ * CAMEL_SERVICE_ERROR:
+ *
+ * Since: 2.32
+ **/
+#define CAMEL_SERVICE_ERROR \
+ (camel_service_error_quark ())
+
+G_BEGIN_DECLS
+
+struct _CamelSession;
+
+typedef struct _CamelService CamelService;
+typedef struct _CamelServiceClass CamelServiceClass;
+typedef struct _CamelServicePrivate CamelServicePrivate;
+
+/**
+ * CamelServiceError:
+ *
+ * Since: 2.32
+ **/
+typedef enum {
+ CAMEL_SERVICE_ERROR_INVALID,
+ CAMEL_SERVICE_ERROR_URL_INVALID,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ CAMEL_SERVICE_ERROR_NOT_CONNECTED
+} CamelServiceError;
+
+struct _CamelService {
+ CamelObject parent;
+ CamelServicePrivate *priv;
+};
+
+struct _CamelServiceClass {
+ CamelObjectClass parent_class;
+
+ GType settings_type;
+
+ /* Non-Blocking Methods */
+ gchar * (*get_name) (CamelService *service,
+ gboolean brief);
+
+ /* Synchronous I/O Methods */
+ gboolean (*connect_sync) (CamelService *service,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*disconnect_sync) (CamelService *service,
+ gboolean clean,
+ GCancellable *cancellable,
+ GError **error);
+ CamelAuthenticationResult
+ (*authenticate_sync) (CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error);
+ GList * (*query_auth_types_sync)
+ (CamelService *service,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* Reserved slots. */
+ gpointer reserved[8];
+};
+
+/* query_auth_types returns a GList of these */
+typedef struct {
+ const gchar *name; /* user-friendly name */
+ const gchar *description;
+ const gchar *authproto;
+
+ gboolean need_password; /* needs a password to authenticate */
+} CamelServiceAuthType;
+
+GType camel_service_get_type (void);
+GQuark camel_service_error_quark (void) G_GNUC_CONST;
+void camel_service_migrate_files (CamelService *service);
+CamelURL * camel_service_new_camel_url (CamelService *service);
+CamelServiceConnectionStatus
+ camel_service_get_connection_status
+ (CamelService *service);
+const gchar * camel_service_get_display_name (CamelService *service);
+gchar * camel_service_dup_display_name (CamelService *service);
+void camel_service_set_display_name (CamelService *service,
+ const gchar *display_name);
+const gchar * camel_service_get_password (CamelService *service);
+gchar * camel_service_dup_password (CamelService *service);
+void camel_service_set_password (CamelService *service,
+ const gchar *password);
+const gchar * camel_service_get_user_data_dir (CamelService *service);
+const gchar * camel_service_get_user_cache_dir
+ (CamelService *service);
+gchar * camel_service_get_name (CamelService *service,
+ gboolean brief);
+CamelProvider * camel_service_get_provider (CamelService *service);
+GProxyResolver *
+ camel_service_ref_proxy_resolver
+ (CamelService *service);
+void camel_service_set_proxy_resolver
+ (CamelService *service,
+ GProxyResolver *proxy_resolver);
+struct _CamelSession *
+ camel_service_ref_session (CamelService *service);
+CamelSettings * camel_service_ref_settings (CamelService *service);
+void camel_service_set_settings (CamelService *service,
+ CamelSettings *settings);
+const gchar * camel_service_get_uid (CamelService *service);
+void camel_service_queue_task (CamelService *service,
+ GTask *task,
+ GTaskThreadFunc task_func);
+gboolean camel_service_connect_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error);
+void camel_service_connect (CamelService *service,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_service_connect_finish (CamelService *service,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_service_disconnect_sync (CamelService *service,
+ gboolean clean,
+ GCancellable *cancellable,
+ GError **error);
+void camel_service_disconnect (CamelService *service,
+ gboolean clean,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_service_disconnect_finish (CamelService *service,
+ GAsyncResult *result,
+ GError **error);
+CamelAuthenticationResult
+ camel_service_authenticate_sync (CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error);
+void camel_service_authenticate (CamelService *service,
+ const gchar *mechanism,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelAuthenticationResult
+ camel_service_authenticate_finish
+ (CamelService *service,
+ GAsyncResult *result,
+ GError **error);
+GList * camel_service_query_auth_types_sync
+ (CamelService *service,
+ GCancellable *cancellable,
+ GError **error);
+void camel_service_query_auth_types (CamelService *service,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GList * camel_service_query_auth_types_finish
+ (CamelService *service,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_SERVICE_H */
diff --git a/src/camel/camel-session.c b/src/camel/camel-session.c
new file mode 100644
index 000000000..1c4fcc5e8
--- /dev/null
+++ b/src/camel/camel-session.c
@@ -0,0 +1,1910 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-session.c : Abstract class for an email session
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ * Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include "camel-debug.h"
+#include "camel-enumtypes.h"
+#include "camel-file-utils.h"
+#include "camel-folder.h"
+#include "camel-mime-message.h"
+#include "camel-sasl.h"
+#include "camel-session.h"
+#include "camel-store.h"
+#include "camel-string-utils.h"
+#include "camel-transport.h"
+#include "camel-url.h"
+
+#define CAMEL_SESSION_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SESSION, CamelSessionPrivate))
+
+/* Prioritize ahead of GTK+ redraws. */
+#define JOB_PRIORITY G_PRIORITY_HIGH_IDLE
+
+#define d(x)
+
+typedef struct _AsyncContext AsyncContext;
+typedef struct _SignalClosure SignalClosure;
+typedef struct _JobData JobData;
+
+struct _CamelSessionPrivate {
+ gchar *user_data_dir;
+ gchar *user_cache_dir;
+
+ GHashTable *services;
+ GMutex services_lock;
+
+ GHashTable *junk_headers;
+ CamelJunkFilter *junk_filter;
+
+ GMainContext *main_context;
+
+ GMutex property_lock;
+ GNetworkMonitor *network_monitor;
+
+ guint online : 1;
+};
+
+struct _AsyncContext {
+ CamelFolder *folder;
+ CamelMimeMessage *message;
+ CamelService *service;
+ gchar *address;
+ gchar *auth_mechanism;
+};
+
+struct _SignalClosure {
+ GWeakRef session;
+ CamelService *service;
+ CamelSessionAlertType alert_type;
+ gchar *alert_message;
+};
+
+struct _JobData {
+ CamelSession *session;
+ GCancellable *cancellable;
+ CamelSessionCallback callback;
+ gpointer user_data;
+ GDestroyNotify notify;
+ GMainContext *main_context;
+ GError *error;
+};
+
+enum {
+ PROP_0,
+ PROP_JUNK_FILTER,
+ PROP_MAIN_CONTEXT,
+ PROP_NETWORK_MONITOR,
+ PROP_ONLINE,
+ PROP_USER_DATA_DIR,
+ PROP_USER_CACHE_DIR
+};
+
+enum {
+ JOB_STARTED,
+ JOB_FINISHED,
+ USER_ALERT,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (CamelSession, camel_session, G_TYPE_OBJECT)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+ if (async_context->folder != NULL)
+ g_object_unref (async_context->folder);
+
+ if (async_context->message != NULL)
+ g_object_unref (async_context->message);
+
+ if (async_context->service != NULL)
+ g_object_unref (async_context->service);
+
+ g_free (async_context->address);
+ g_free (async_context->auth_mechanism);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static void
+signal_closure_free (SignalClosure *signal_closure)
+{
+ g_weak_ref_clear (&signal_closure->session);
+
+ if (signal_closure->service != NULL)
+ g_object_unref (signal_closure->service);
+
+ g_free (signal_closure->alert_message);
+
+ g_slice_free (SignalClosure, signal_closure);
+}
+
+static void
+job_data_free (JobData *job_data)
+{
+ camel_operation_pop_message (job_data->cancellable);
+
+ g_object_unref (job_data->session);
+ g_object_unref (job_data->cancellable);
+ g_clear_error (&job_data->error);
+
+ if (job_data->main_context)
+ g_main_context_unref (job_data->main_context);
+
+ if (job_data->notify != NULL)
+ job_data->notify (job_data->user_data);
+
+ g_slice_free (JobData, job_data);
+}
+
+static gboolean
+session_finish_job_cb (gpointer user_data)
+{
+ JobData *job_data = (JobData *) user_data;
+
+ g_return_val_if_fail (job_data != NULL, FALSE);
+
+ g_signal_emit (
+ job_data->session,
+ signals[JOB_FINISHED], 0,
+ job_data->cancellable, job_data->error);
+
+ return FALSE;
+}
+
+static void
+session_job_thread (gpointer data,
+ gpointer user_data)
+{
+ JobData *job_data = (JobData *) data;
+ GSource *source;
+
+ g_return_if_fail (job_data != NULL);
+
+ job_data->callback (
+ job_data->session,
+ job_data->cancellable,
+ job_data->user_data,
+ &job_data->error);
+
+ source = g_idle_source_new ();
+ g_source_set_priority (source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (source, session_finish_job_cb, job_data, (GDestroyNotify) job_data_free);
+ g_source_attach (source, job_data->main_context);
+ g_source_unref (source);
+}
+
+static gboolean
+session_start_job_cb (gpointer user_data)
+{
+ static GThreadPool *job_pool = NULL;
+ static GMutex job_pool_mutex;
+ JobData *job_data = user_data;
+
+ g_signal_emit (
+ job_data->session,
+ signals[JOB_STARTED], 0,
+ job_data->cancellable);
+
+ g_mutex_lock (&job_pool_mutex);
+
+ if (!job_pool)
+ job_pool = g_thread_pool_new (session_job_thread, NULL, 20, FALSE, NULL);
+
+ job_data->main_context = g_main_context_ref_thread_default ();
+
+ g_thread_pool_push (job_pool, job_data, NULL);
+
+ g_mutex_unlock (&job_pool_mutex);
+
+ return FALSE;
+}
+
+static gboolean
+session_emit_user_alert_cb (gpointer user_data)
+{
+ SignalClosure *signal_closure = user_data;
+ CamelSession *session;
+
+ session = g_weak_ref_get (&signal_closure->session);
+
+ if (session != NULL) {
+ g_signal_emit (
+ session,
+ signals[USER_ALERT], 0,
+ signal_closure->service,
+ signal_closure->alert_type,
+ signal_closure->alert_message);
+ g_object_unref (session);
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+session_set_user_data_dir (CamelSession *session,
+ const gchar *user_data_dir)
+{
+ g_return_if_fail (user_data_dir != NULL);
+ g_return_if_fail (session->priv->user_data_dir == NULL);
+
+ session->priv->user_data_dir = g_strdup (user_data_dir);
+}
+
+static void
+session_set_user_cache_dir (CamelSession *session,
+ const gchar *user_cache_dir)
+{
+ g_return_if_fail (user_cache_dir != NULL);
+ g_return_if_fail (session->priv->user_cache_dir == NULL);
+
+ session->priv->user_cache_dir = g_strdup (user_cache_dir);
+}
+
+static void
+session_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_JUNK_FILTER:
+ camel_session_set_junk_filter (
+ CAMEL_SESSION (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_NETWORK_MONITOR:
+ camel_session_set_network_monitor (
+ CAMEL_SESSION (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_ONLINE:
+ camel_session_set_online (
+ CAMEL_SESSION (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_USER_DATA_DIR:
+ session_set_user_data_dir (
+ CAMEL_SESSION (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_USER_CACHE_DIR:
+ session_set_user_cache_dir (
+ CAMEL_SESSION (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+session_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_JUNK_FILTER:
+ g_value_set_object (
+ value, camel_session_get_junk_filter (
+ CAMEL_SESSION (object)));
+ return;
+
+ case PROP_MAIN_CONTEXT:
+ g_value_take_boxed (
+ value, camel_session_ref_main_context (
+ CAMEL_SESSION (object)));
+ return;
+
+ case PROP_NETWORK_MONITOR:
+ g_value_take_object (
+ value, camel_session_ref_network_monitor (
+ CAMEL_SESSION (object)));
+ return;
+
+ case PROP_ONLINE:
+ g_value_set_boolean (
+ value, camel_session_get_online (
+ CAMEL_SESSION (object)));
+ return;
+
+ case PROP_USER_DATA_DIR:
+ g_value_set_string (
+ value, camel_session_get_user_data_dir (
+ CAMEL_SESSION (object)));
+ return;
+
+ case PROP_USER_CACHE_DIR:
+ g_value_set_string (
+ value, camel_session_get_user_cache_dir (
+ CAMEL_SESSION (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+session_dispose (GObject *object)
+{
+ CamelSessionPrivate *priv;
+
+ priv = CAMEL_SESSION_GET_PRIVATE (object);
+
+ g_hash_table_remove_all (priv->services);
+
+ g_clear_object (&priv->junk_filter);
+ g_clear_object (&priv->network_monitor);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_session_parent_class)->dispose (object);
+}
+
+static void
+session_finalize (GObject *object)
+{
+ CamelSessionPrivate *priv;
+
+ priv = CAMEL_SESSION_GET_PRIVATE (object);
+
+ g_free (priv->user_data_dir);
+ g_free (priv->user_cache_dir);
+
+ g_hash_table_destroy (priv->services);
+
+ if (priv->main_context != NULL)
+ g_main_context_unref (priv->main_context);
+
+ g_mutex_clear (&priv->services_lock);
+ g_mutex_clear (&priv->property_lock);
+
+ if (priv->junk_headers) {
+ g_hash_table_remove_all (priv->junk_headers);
+ g_hash_table_destroy (priv->junk_headers);
+ }
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_session_parent_class)->finalize (object);
+}
+
+static CamelService *
+session_add_service (CamelSession *session,
+ const gchar *uid,
+ const gchar *protocol,
+ CamelProviderType type,
+ GError **error)
+{
+ CamelService *service;
+ CamelProvider *provider;
+ GType service_type = G_TYPE_INVALID;
+
+ service = camel_session_ref_service (session, uid);
+ if (CAMEL_IS_SERVICE (service))
+ return service;
+
+ /* Try to find a suitable CamelService subclass. */
+ provider = camel_provider_get (protocol, error);
+ if (provider != NULL)
+ service_type = provider->object_types[type];
+
+ if (error && *error)
+ return NULL;
+
+ if (service_type == G_TYPE_INVALID) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_URL_INVALID,
+ _("No provider available for protocol '%s'"),
+ protocol);
+ return NULL;
+ }
+
+ if (!g_type_is_a (service_type, CAMEL_TYPE_SERVICE)) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_INVALID,
+ _("Invalid GType registered for protocol '%s'"),
+ protocol);
+ return NULL;
+ }
+
+ service = g_initable_new (
+ service_type, NULL, error,
+ "provider", provider, "session",
+ session, "uid", uid, NULL);
+
+ if (service != NULL) {
+ g_mutex_lock (&session->priv->services_lock);
+
+ g_hash_table_insert (
+ session->priv->services,
+ g_strdup (uid),
+ g_object_ref (service));
+
+ g_mutex_unlock (&session->priv->services_lock);
+ }
+
+ return service;
+}
+
+static void
+session_remove_service (CamelSession *session,
+ CamelService *service)
+{
+ const gchar *uid;
+
+ g_mutex_lock (&session->priv->services_lock);
+
+ uid = camel_service_get_uid (service);
+ g_hash_table_remove (session->priv->services, uid);
+
+ g_mutex_unlock (&session->priv->services_lock);
+}
+
+static gboolean
+session_authenticate_sync (CamelSession *session,
+ CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelServiceAuthType *authtype = NULL;
+ CamelAuthenticationResult result;
+ GError *local_error = NULL;
+
+ /* XXX This authenticate_sync() implementation serves only as
+ * a rough example and is not intended to be used as is.
+ *
+ * Any CamelSession subclass should override this method
+ * and implement a more complete authentication loop that
+ * handles user prompts and password storage.
+ */
+
+ g_warning (
+ "The default CamelSession.authenticate_sync() "
+ "method is not intended for production use.");
+
+ /* If a SASL mechanism was given and we can't find
+ * a CamelServiceAuthType for it, fail immediately. */
+ if (mechanism != NULL) {
+ authtype = camel_sasl_authtype (mechanism);
+ if (authtype == NULL) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("No support for %s authentication"),
+ mechanism);
+ return FALSE;
+ }
+ }
+
+ /* If the SASL mechanism does not involve a user
+ * password, then it gets one shot to authenticate. */
+ if (authtype != NULL && !authtype->need_password) {
+ result = camel_service_authenticate_sync (
+ service, mechanism, cancellable, error);
+ if (result == CAMEL_AUTHENTICATION_REJECTED)
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("%s authentication failed"), mechanism);
+ return (result == CAMEL_AUTHENTICATION_ACCEPTED);
+ }
+
+ /* Some SASL mechanisms can attempt to authenticate without a
+ * user password being provided (e.g. single-sign-on credentials),
+ * but can fall back to a user password. Handle that case next. */
+ if (mechanism != NULL) {
+ CamelProvider *provider;
+ CamelSasl *sasl;
+ const gchar *service_name;
+ gboolean success = FALSE;
+
+ provider = camel_service_get_provider (service);
+ service_name = provider->protocol;
+
+ /* XXX Would be nice if camel_sasl_try_empty_password_sync()
+ * returned CamelAuthenticationResult so it's easier to
+ * detect errors. */
+ sasl = camel_sasl_new (service_name, mechanism, service);
+ if (sasl != NULL) {
+ success = camel_sasl_try_empty_password_sync (
+ sasl, cancellable, &local_error);
+ g_object_unref (sasl);
+ }
+
+ if (success)
+ return TRUE;
+ }
+
+ /* Abort authentication if we got cancelled.
+ * Otherwise clear any errors and press on. */
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return FALSE;
+
+ g_clear_error (&local_error);
+
+retry:
+ /* XXX This is where things get bogus. In a real implementation you
+ * would want to fetch a stored password or prompt the user here.
+ * Password should be stashed using camel_service_set_password()
+ * before calling camel_service_authenticate_sync(). */
+
+ result = camel_service_authenticate_sync (
+ service, mechanism, cancellable, error);
+
+ if (result == CAMEL_AUTHENTICATION_REJECTED) {
+ /* XXX Request a different password here. */
+ goto retry;
+ }
+
+ if (result == CAMEL_AUTHENTICATION_ACCEPTED) {
+ /* XXX Possibly store the password here using
+ * GNOME Keyring or something equivalent. */
+ }
+
+ return (result == CAMEL_AUTHENTICATION_ACCEPTED);
+}
+
+static gboolean
+session_forward_to_sync (CamelSession *session,
+ CamelFolder *folder,
+ CamelMimeMessage *message,
+ const gchar *address,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error_literal (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Forwarding messages is not supported"));
+
+ return FALSE;
+}
+
+static void
+camel_session_class_init (CamelSessionClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelSessionPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = session_set_property;
+ object_class->get_property = session_get_property;
+ object_class->dispose = session_dispose;
+ object_class->finalize = session_finalize;
+
+ class->add_service = session_add_service;
+ class->remove_service = session_remove_service;
+
+ class->authenticate_sync = session_authenticate_sync;
+ class->forward_to_sync = session_forward_to_sync;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_JUNK_FILTER,
+ g_param_spec_object (
+ "junk-filter",
+ "Junk Filter",
+ "Classifies messages as junk or not junk",
+ CAMEL_TYPE_JUNK_FILTER,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_MAIN_CONTEXT,
+ g_param_spec_boxed (
+ "main-context",
+ "Main Context",
+ "The main loop context on "
+ "which to attach event sources",
+ G_TYPE_MAIN_CONTEXT,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_NETWORK_MONITOR,
+ g_param_spec_object (
+ "network-monitor",
+ "Network Monitor",
+ NULL,
+ G_TYPE_NETWORK_MONITOR,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ONLINE,
+ g_param_spec_boolean (
+ "online",
+ "Online",
+ "Whether the shell is online",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USER_DATA_DIR,
+ g_param_spec_string (
+ "user-data-dir",
+ "User Data Directory",
+ "User-specific base directory for mail data",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USER_CACHE_DIR,
+ g_param_spec_string (
+ "user-cache-dir",
+ "User Cache Directory",
+ "User-specific base directory for mail cache",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ signals[JOB_STARTED] = g_signal_new (
+ "job-started",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CamelSessionClass, job_started),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_CANCELLABLE);
+
+ signals[JOB_FINISHED] = g_signal_new (
+ "job-finished",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CamelSessionClass, job_finished),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_CANCELLABLE,
+ G_TYPE_POINTER);
+
+ /**
+ * CamelSession::user-alert:
+ * @session: the #CamelSession that received the signal
+ * @service: the #CamelService issuing the alert
+ * @type: the #CamelSessionAlertType
+ * @message: the alert message
+ *
+ * This purpose of this signal is to propagate a server-issued alert
+ * message from @service to a user interface. The @type hints at the
+ * severity of the alert message.
+ **/
+ signals[USER_ALERT] = g_signal_new (
+ "user-alert",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CamelSessionClass, user_alert),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 3,
+ CAMEL_TYPE_SERVICE,
+ CAMEL_TYPE_SESSION_ALERT_TYPE,
+ G_TYPE_STRING);
+}
+
+static void
+camel_session_init (CamelSession *session)
+{
+ GHashTable *services;
+
+ services = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+
+ session->priv = CAMEL_SESSION_GET_PRIVATE (session);
+
+ session->priv->services = services;
+ g_mutex_init (&session->priv->services_lock);
+ g_mutex_init (&session->priv->property_lock);
+ session->priv->junk_headers = NULL;
+
+ session->priv->main_context = g_main_context_ref_thread_default ();
+}
+
+/**
+ * camel_session_ref_main_context:
+ * @session: a #CamelSession
+ *
+ * Returns the #GMainContext on which event sources for @session are to
+ * be attached.
+ *
+ * Returns: a #GMainContext
+ *
+ * Since: 3.8
+ **/
+GMainContext *
+camel_session_ref_main_context (CamelSession *session)
+{
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+
+ return g_main_context_ref (session->priv->main_context);
+}
+
+/**
+ * camel_session_get_user_data_dir:
+ * @session: a #CamelSession
+ *
+ * Returns the base directory under which to store user-specific mail data.
+ *
+ * Returns: the base directory for mail data
+ *
+ * Since: 3.2
+ **/
+const gchar *
+camel_session_get_user_data_dir (CamelSession *session)
+{
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+
+ return session->priv->user_data_dir;
+}
+
+/**
+ * camel_session_get_user_cache_dir:
+ * @session: a #CamelSession
+ *
+ * Returns the base directory under which to store user-specific mail cache.
+ *
+ * Returns: the base directory for mail cache
+ *
+ * Since: 3.4
+ **/
+const gchar *
+camel_session_get_user_cache_dir (CamelSession *session)
+{
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+
+ return session->priv->user_cache_dir;
+}
+
+/**
+ * camel_session_set_network_monitor:
+ * @session: a #CamelSession
+ * @network_monitor: (nullable): a #GNetworkMonitor or %NULL
+ *
+ * Sets a network monitor instance for the @session. This can be used
+ * to override which #GNetworkMonitor should be used to check network
+ * availability and whether a server is reachable.
+ *
+ * Since: 3.22
+ **/
+void
+camel_session_set_network_monitor (CamelSession *session,
+ GNetworkMonitor *network_monitor)
+{
+ gboolean changed = FALSE;
+
+ g_return_if_fail (CAMEL_IS_SESSION (session));
+ if (network_monitor)
+ g_return_if_fail (G_IS_NETWORK_MONITOR (network_monitor));
+
+ g_mutex_lock (&session->priv->property_lock);
+
+ if (network_monitor != session->priv->network_monitor) {
+ g_clear_object (&session->priv->network_monitor);
+ session->priv->network_monitor = network_monitor ? g_object_ref (network_monitor) : NULL;
+
+ changed = TRUE;
+ }
+
+ g_mutex_unlock (&session->priv->property_lock);
+
+ if (changed)
+ g_object_notify (G_OBJECT (session), "network-monitor");
+}
+
+/**
+ * camel_session_ref_network_monitor:
+ * @session: a #CamelSession
+ *
+ * References a #GNetworkMonitor instance, which had been previously set
+ * by camel_session_set_network_monitor(). If none is set, then the default
+ * #GNetworkMonitor is returned, as provided by g_network_monitor_get_default().
+ * The returned pointer is referenced for thread safety, unref it with
+ * g_object_unref() when no longer needed.
+ *
+ * Returns: (transfer full): A referenced #GNetworkMonitor instance to use
+ * for network availability tests.
+ *
+ * Since:3.22
+ **/
+GNetworkMonitor *
+camel_session_ref_network_monitor (CamelSession *session)
+{
+ GNetworkMonitor *network_monitor;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+
+ g_mutex_lock (&session->priv->property_lock);
+
+ network_monitor = g_object_ref (session->priv->network_monitor ?
+ session->priv->network_monitor : g_network_monitor_get_default ());
+
+ g_mutex_unlock (&session->priv->property_lock);
+
+ return network_monitor;
+}
+
+/**
+ * camel_session_add_service:
+ * @session: a #CamelSession
+ * @uid: a unique identifier string
+ * @protocol: the service protocol
+ * @type: the service type
+ * @error: return location for a #GError, or %NULL
+ *
+ * Instantiates a new #CamelService for @session. The @uid identifies the
+ * service for future lookup. The @protocol indicates which #CamelProvider
+ * holds the #GType of the #CamelService subclass to instantiate. The @type
+ * explicitly designates the service as a #CamelStore or #CamelTransport.
+ *
+ * If the given @uid has already been added, the existing #CamelService
+ * with that @uid is returned regardless of whether it agrees with the
+ * given @protocol and @type.
+ *
+ * If no #CamelProvider is available to handle the given @protocol, or
+ * if the #CamelProvider does not specify a valid #GType for @type, the
+ * function sets @error and returns %NULL.
+ *
+ * The returned #CamelService is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: (transfer full): a #CamelService instance, or %NULL
+ *
+ * Since: 3.2
+ **/
+CamelService *
+camel_session_add_service (CamelSession *session,
+ const gchar *uid,
+ const gchar *protocol,
+ CamelProviderType type,
+ GError **error)
+{
+ CamelSessionClass *class;
+ CamelService *service;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+ g_return_val_if_fail (uid != NULL, NULL);
+ g_return_val_if_fail (protocol != NULL, NULL);
+
+ class = CAMEL_SESSION_GET_CLASS (session);
+ g_return_val_if_fail (class->add_service != NULL, NULL);
+
+ service = class->add_service (session, uid, protocol, type, error);
+ CAMEL_CHECK_GERROR (session, add_service, service != NULL, error);
+
+ return service;
+}
+
+/**
+ * camel_session_remove_service:
+ * @session: a #CamelSession
+ * @service: the #CamelService to remove
+ *
+ * Removes a #CamelService previously added by camel_session_add_service().
+ *
+ * Since: 3.2
+ **/
+void
+camel_session_remove_service (CamelSession *session,
+ CamelService *service)
+{
+ CamelSessionClass *class;
+
+ g_return_if_fail (CAMEL_IS_SESSION (session));
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ class = CAMEL_SESSION_GET_CLASS (session);
+ g_return_if_fail (class->remove_service != NULL);
+
+ class->remove_service (session, service);
+}
+
+/**
+ * camel_session_ref_service:
+ * @session: a #CamelSession
+ * @uid: a unique identifier string
+ *
+ * Looks up a #CamelService by its unique identifier string. The service
+ * must have been previously added using camel_session_add_service().
+ *
+ * The returned #CamelService is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: (transfer full): a #CamelService instance, or %NULL
+ *
+ * Since: 3.6
+ **/
+CamelService *
+camel_session_ref_service (CamelSession *session,
+ const gchar *uid)
+{
+ CamelService *service;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+ g_return_val_if_fail (uid != NULL, NULL);
+
+ g_mutex_lock (&session->priv->services_lock);
+
+ service = g_hash_table_lookup (session->priv->services, uid);
+
+ if (service != NULL)
+ g_object_ref (service);
+
+ g_mutex_unlock (&session->priv->services_lock);
+
+ return service;
+}
+
+/**
+ * camel_session_ref_service_by_url:
+ * @session: a #CamelSession
+ * @url: a #CamelURL
+ * @type: a #CamelProviderType
+ *
+ * Looks up a #CamelService by trying to match its #CamelURL against the
+ * given @url and then checking that the object is of the desired @type.
+ * The service must have been previously added using
+ * camel_session_add_service().
+ *
+ * The returned #CamelService is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Note this function is significantly slower than camel_session_ref_service().
+ *
+ * Returns: (transfer full): a #CamelService instance, or %NULL
+ *
+ * Since: 3.6
+ **/
+CamelService *
+camel_session_ref_service_by_url (CamelSession *session,
+ CamelURL *url,
+ CamelProviderType type)
+{
+ CamelService *match = NULL;
+ GList *list, *iter;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+ g_return_val_if_fail (url != NULL, NULL);
+
+ list = camel_session_list_services (session);
+
+ for (iter = list; iter != NULL; iter = g_list_next (iter)) {
+ CamelProvider *provider;
+ CamelService *service;
+ CamelURL *service_url;
+ gboolean url_equal;
+
+ service = CAMEL_SERVICE (iter->data);
+ provider = camel_service_get_provider (service);
+
+ if (provider == NULL)
+ continue;
+
+ if (provider->url_equal == NULL)
+ continue;
+
+ service_url = camel_service_new_camel_url (service);
+ url_equal = provider->url_equal (url, service_url);
+ camel_url_free (service_url);
+
+ if (!url_equal)
+ continue;
+
+ switch (type) {
+ case CAMEL_PROVIDER_STORE:
+ if (CAMEL_IS_STORE (service))
+ match = g_object_ref (service);
+ break;
+ case CAMEL_PROVIDER_TRANSPORT:
+ if (CAMEL_IS_TRANSPORT (service))
+ match = g_object_ref (service);
+ break;
+ default:
+ g_warn_if_reached ();
+ break;
+ }
+
+ if (match != NULL)
+ break;
+ }
+
+ g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+ return match;
+}
+
+/**
+ * camel_session_list_services:
+ * @session: a #CamelSession
+ *
+ * Returns a list of all #CamelService objects previously added using
+ * camel_session_add_service().
+ *
+ * The services returned in the list are referenced for thread-safety.
+ * They must each be unreferenced with g_object_unref() when finished
+ * with them. Free the returned list itself with g_list_free().
+ *
+ * An easy way to free the list property in one step is as follows:
+ *
+ * |[
+ * g_list_free_full (list, g_object_unref);
+ * ]|
+ *
+ * Returns: (element-type CamelService) (transfer full): an unsorted list of #CamelService objects
+ *
+ * Since: 3.2
+ **/
+GList *
+camel_session_list_services (CamelSession *session)
+{
+ GList *list;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+
+ g_mutex_lock (&session->priv->services_lock);
+
+ list = g_hash_table_get_values (session->priv->services);
+
+ g_list_foreach (list, (GFunc) g_object_ref, NULL);
+
+ g_mutex_unlock (&session->priv->services_lock);
+
+ return list;
+}
+
+/**
+ * camel_session_remove_services:
+ * @session: a #CamelSession
+ *
+ * Removes all #CamelService instances added by camel_session_add_service().
+ *
+ * This can be useful during application shutdown to ensure all #CamelService
+ * instances are freed properly, especially since #CamelSession instances are
+ * prone to reference cycles.
+ *
+ * Since: 3.2
+ **/
+void
+camel_session_remove_services (CamelSession *session)
+{
+ g_return_if_fail (CAMEL_IS_SESSION (session));
+
+ g_mutex_lock (&session->priv->services_lock);
+
+ g_hash_table_remove_all (session->priv->services);
+
+ g_mutex_unlock (&session->priv->services_lock);
+}
+
+/**
+ * camel_session_get_password:
+ * @session: a #CamelSession
+ * @service: the #CamelService this query is being made by
+ * @prompt: prompt to provide to user
+ * @item: an identifier, unique within this service, for the information
+ * @flags: %CAMEL_SESSION_PASSWORD_REPROMPT, the prompt should force a reprompt
+ * %CAMEL_SESSION_PASSWORD_SECRET, whether the password is secret
+ * %CAMEL_SESSION_PASSWORD_STATIC, the password is remembered externally
+ * @error: return location for a #GError, or %NULL
+ *
+ * This function is used by a #CamelService to ask the application and
+ * the user for a password or other authentication data.
+ *
+ * @service and @item together uniquely identify the piece of data the
+ * caller is concerned with.
+ *
+ * @prompt is a question to ask the user (if the application doesn't
+ * already have the answer cached). If %CAMEL_SESSION_PASSWORD_SECRET
+ * is set, the user's input will not be echoed back.
+ *
+ * If %CAMEL_SESSION_PASSWORD_STATIC is set, it means the password returned
+ * will be stored statically by the caller automatically, for the current
+ * session.
+ *
+ * The authenticator should set @error to %G_IO_ERROR_CANCELLED if
+ * the user did not provide the information. The caller must g_free()
+ * the information returned when it is done with it.
+ *
+ * Returns: the authentication information or %NULL
+ **/
+gchar *
+camel_session_get_password (CamelSession *session,
+ CamelService *service,
+ const gchar *prompt,
+ const gchar *item,
+ guint32 flags,
+ GError **error)
+{
+ CamelSessionClass *class;
+ gchar *password;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+ g_return_val_if_fail (prompt != NULL, NULL);
+ g_return_val_if_fail (item != NULL, NULL);
+
+ class = CAMEL_SESSION_GET_CLASS (session);
+ g_return_val_if_fail (class->get_password != NULL, NULL);
+
+ password = class->get_password (
+ session, service, prompt, item, flags, error);
+ CAMEL_CHECK_GERROR (session, get_password, password != NULL, error);
+
+ return password;
+}
+
+/**
+ * camel_session_forget_password:
+ * @session: a #CamelSession
+ * @service: the #CamelService rejecting the password
+ * @item: an identifier, unique within this service, for the information
+ * @error: return location for a #GError, or %NULL
+ *
+ * This function is used by a #CamelService to tell the application
+ * that the authentication information it provided via
+ * camel_session_get_password() was rejected by the service. If the
+ * application was caching this information, it should stop,
+ * and if the service asks for it again, it should ask the user.
+ *
+ * @service and @item identify the rejected authentication information,
+ * as with camel_session_get_password().
+ *
+ * Returns: %TRUE on success, %FALSE on failure
+ **/
+gboolean
+camel_session_forget_password (CamelSession *session,
+ CamelService *service,
+ const gchar *item,
+ GError **error)
+{
+ CamelSessionClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
+ g_return_val_if_fail (item != NULL, FALSE);
+
+ class = CAMEL_SESSION_GET_CLASS (session);
+ g_return_val_if_fail (class->forget_password, FALSE);
+
+ success = class->forget_password (session, service, item, error);
+ CAMEL_CHECK_GERROR (session, forget_password, success, error);
+
+ return success;
+}
+
+/**
+ * camel_session_trust_prompt:
+ * @session: a #CamelSession
+ * @service: a #CamelService
+ * @certificate: the peer's #GTlsCertificate
+ * @errors: the problems with @certificate
+ *
+ * Prompts the user whether to accept @certificate for @service. The
+ * set of flags given in @errors indicate why the @certificate failed
+ * validation.
+ *
+ * If an error occurs during prompting or if the user declines to respond,
+ * the function returns #CAMEL_CERT_TRUST_UNKNOWN and the certificate will
+ * be rejected.
+ *
+ * Returns: the user's trust level for @certificate
+ *
+ * Since: 3.8
+ **/
+CamelCertTrust
+camel_session_trust_prompt (CamelSession *session,
+ CamelService *service,
+ GTlsCertificate *certificate,
+ GTlsCertificateFlags errors)
+{
+ CamelSessionClass *class;
+
+ g_return_val_if_fail (
+ CAMEL_IS_SESSION (session), CAMEL_CERT_TRUST_UNKNOWN);
+ g_return_val_if_fail (
+ CAMEL_IS_SERVICE (service), CAMEL_CERT_TRUST_UNKNOWN);
+ g_return_val_if_fail (
+ G_IS_TLS_CERTIFICATE (certificate), CAMEL_CERT_TRUST_UNKNOWN);
+
+ class = CAMEL_SESSION_GET_CLASS (session);
+ g_return_val_if_fail (
+ class->trust_prompt != NULL, CAMEL_CERT_TRUST_UNKNOWN);
+
+ return class->trust_prompt (session, service, certificate, errors);
+}
+
+/**
+ * camel_session_user_alert:
+ * @session: a #CamelSession
+ * @service: a #CamelService
+ * @type: a #CamelSessionAlertType
+ * @message: the message for the user
+ *
+ * Emits a #CamelSession:user_alert signal from an idle source on the main
+ * loop. The idle source's priority is #G_PRIORITY_LOW.
+ *
+ * The purpose of the signal is to propagate a server-issued alert message
+ * from @service to a user interface. The @type hints at the nature of the
+ * alert message.
+ *
+ * Since: 3.12
+ */
+void
+camel_session_user_alert (CamelSession *session,
+ CamelService *service,
+ CamelSessionAlertType type,
+ const gchar *message)
+{
+ SignalClosure *signal_closure;
+
+ g_return_if_fail (CAMEL_IS_SESSION (session));
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+ g_return_if_fail (message != NULL);
+
+ signal_closure = g_slice_new0 (SignalClosure);
+ g_weak_ref_init (&signal_closure->session, session);
+ signal_closure->service = g_object_ref (service);
+ signal_closure->alert_type = type;
+ signal_closure->alert_message = g_strdup (message);
+
+ camel_session_idle_add (
+ session, G_PRIORITY_LOW,
+ session_emit_user_alert_cb,
+ signal_closure,
+ (GDestroyNotify) signal_closure_free);
+}
+
+/**
+ * camel_session_lookup_addressbook:
+ *
+ * Since: 2.22
+ **/
+gboolean
+camel_session_lookup_addressbook (CamelSession *session,
+ const gchar *name)
+{
+ CamelSessionClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+
+ class = CAMEL_SESSION_GET_CLASS (session);
+ g_return_val_if_fail (class->lookup_addressbook != NULL, FALSE);
+
+ return class->lookup_addressbook (session, name);
+}
+
+/**
+ * camel_session_get_online:
+ * @session: a #CamelSession
+ *
+ * Returns: whether or not @session is online
+ **/
+gboolean
+camel_session_get_online (CamelSession *session)
+{
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
+
+ return session->priv->online;
+}
+
+/**
+ * camel_session_set_online:
+ * @session: a #CamelSession
+ * @online: whether or not the session should be online
+ *
+ * Sets the online status of @session to @online.
+ **/
+void
+camel_session_set_online (CamelSession *session,
+ gboolean online)
+{
+ g_return_if_fail (CAMEL_IS_SESSION (session));
+
+ if (online == session->priv->online)
+ return;
+
+ session->priv->online = online;
+
+ g_object_notify (G_OBJECT (session), "online");
+}
+
+/**
+ * camel_session_get_filter_driver:
+ * @session: a #CamelSession
+ * @type: the type of filter (eg, "incoming")
+ * @error: return location for a #GError, or %NULL
+ *
+ * Returns:(transfer none): a filter driver, loaded with applicable rules
+ **/
+CamelFilterDriver *
+camel_session_get_filter_driver (CamelSession *session,
+ const gchar *type,
+ GError **error)
+{
+ CamelSessionClass *class;
+ CamelFilterDriver *driver;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+ g_return_val_if_fail (type != NULL, NULL);
+
+ class = CAMEL_SESSION_GET_CLASS (session);
+ g_return_val_if_fail (class->get_filter_driver != NULL, NULL);
+
+ driver = class->get_filter_driver (session, type, error);
+ CAMEL_CHECK_GERROR (session, get_filter_driver, driver != NULL, error);
+
+ return driver;
+}
+
+/**
+ * camel_session_get_junk_filter:
+ * @session: a #CamelSession
+ *
+ * Returns the #CamelJunkFilter instance used to classify messages as
+ * junk or not junk during filtering.
+ *
+ * Note that #CamelJunkFilter itself is just an interface. The application
+ * must implement the interface and install a #CamelJunkFilter instance for
+ * junk filtering to take place.
+ *
+ * Returns: (transfer none): a #CamelJunkFilter, or %NULL
+ *
+ * Since: 3.2
+ **/
+CamelJunkFilter *
+camel_session_get_junk_filter (CamelSession *session)
+{
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+
+ return session->priv->junk_filter;
+}
+
+/**
+ * camel_session_set_junk_filter:
+ * @session: a #CamelSession
+ * @junk_filter: a #CamelJunkFilter, or %NULL
+ *
+ * Installs the #CamelJunkFilter instance used to classify messages as
+ * junk or not junk during filtering.
+ *
+ * Note that #CamelJunkFilter itself is just an interface. The application
+ * must implement the interface and install a #CamelJunkFilter instance for
+ * junk filtering to take place.
+ *
+ * Since: 3.2
+ **/
+void
+camel_session_set_junk_filter (CamelSession *session,
+ CamelJunkFilter *junk_filter)
+{
+ g_return_if_fail (CAMEL_IS_SESSION (session));
+
+ if (junk_filter != NULL) {
+ g_return_if_fail (CAMEL_IS_JUNK_FILTER (junk_filter));
+ g_object_ref (junk_filter);
+ }
+
+ if (session->priv->junk_filter != NULL)
+ g_object_unref (session->priv->junk_filter);
+
+ session->priv->junk_filter = junk_filter;
+
+ g_object_notify (G_OBJECT (session), "junk-filter");
+}
+
+/**
+ * camel_session_idle_add:
+ * @session: a #CamelSession
+ * @priority: the priority of the idle source
+ * @function: a function to call
+ * @data: data to pass to @function
+ * @notify: function to call when the idle is removed, or %NULL
+ *
+ * Adds a function to be called whenever there are no higher priority events
+ * pending. If @function returns %FALSE it is automatically removed from the
+ * list of event sources and will not be called again.
+ *
+ * This internally creates a main loop source using g_idle_source_new()
+ * and attaches it to @session's own #CamelSession:main-context using
+ * g_source_attach().
+ *
+ * The @priority is typically in the range between %G_PRIORITY_DEFAULT_IDLE
+ * and %G_PRIORITY_HIGH_IDLE.
+ *
+ * Returns: the ID (greater than 0) of the event source
+ *
+ * Since: 3.6
+ **/
+guint
+camel_session_idle_add (CamelSession *session,
+ gint priority,
+ GSourceFunc function,
+ gpointer data,
+ GDestroyNotify notify)
+{
+ GMainContext *main_context;
+ GSource *source;
+ guint source_id;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), 0);
+ g_return_val_if_fail (function != NULL, 0);
+
+ main_context = camel_session_ref_main_context (session);
+
+ source = g_idle_source_new ();
+
+ if (priority != G_PRIORITY_DEFAULT_IDLE)
+ g_source_set_priority (source, priority);
+
+ g_source_set_callback (source, function, data, notify);
+
+ source_id = g_source_attach (source, main_context);
+
+ g_source_unref (source);
+
+ g_main_context_unref (main_context);
+
+ return source_id;
+}
+
+/**
+ * camel_session_submit_job:
+ * @session: a #CamelSession
+ * @description: human readable description of the job, shown to a user
+ * @callback: a #CamelSessionCallback
+ * @user_data: user data passed to the callback
+ * @notify: a #GDestroyNotify function
+ *
+ * This function provides a simple mechanism for providers to initiate
+ * low-priority background jobs. Jobs can be submitted from any thread,
+ * but execution of the jobs is always as follows:
+ *
+ * 1) The #CamelSession:job-started signal is emitted from the thread
+ * in which @session was created. This is typically the same thread
+ * that hosts the global default #GMainContext, or "main" thread.
+ *
+ * 2) The @callback function is invoked from a different thread where
+ * it's safe to call synchronous functions.
+ *
+ * 3) Once @callback has returned, the #CamelSesson:job-finished signal
+ * is emitted from the same thread as #CamelSession:job-started was
+ * emitted.
+ *
+ * 4) Finally if a @notify function was provided, it is invoked and
+ * passed @user_data so that @user_data can be freed.
+ *
+ * Since: 3.2
+ **/
+void
+camel_session_submit_job (CamelSession *session,
+ const gchar *description,
+ CamelSessionCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ JobData *job_data;
+
+ g_return_if_fail (CAMEL_IS_SESSION (session));
+ g_return_if_fail (description != NULL);
+ g_return_if_fail (callback != NULL);
+
+ job_data = g_slice_new0 (JobData);
+ job_data->session = g_object_ref (session);
+ job_data->cancellable = camel_operation_new ();
+ job_data->callback = callback;
+ job_data->user_data = user_data;
+ job_data->notify = notify;
+ job_data->main_context = NULL;
+ job_data->error = NULL;
+
+ camel_operation_push_message (job_data->cancellable, "%s", description);
+
+ camel_session_idle_add (
+ session, JOB_PRIORITY,
+ session_start_job_cb,
+ job_data, (GDestroyNotify) NULL);
+}
+
+/**
+ * camel_session_set_junk_headers:
+ * @session: a #CamelSession
+ * @headers: (array length=len):
+ * @values: (array):
+ * @len: the length of the headers and values arrays
+ *
+ * Since: 2.22
+ **/
+void
+camel_session_set_junk_headers (CamelSession *session,
+ const gchar **headers,
+ const gchar **values,
+ gint len)
+{
+ gint i;
+
+ g_return_if_fail (CAMEL_IS_SESSION (session));
+
+ if (session->priv->junk_headers) {
+ g_hash_table_remove_all (session->priv->junk_headers);
+ g_hash_table_destroy (session->priv->junk_headers);
+ }
+
+ session->priv->junk_headers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ for (i = 0; i < len; i++) {
+ g_hash_table_insert (session->priv->junk_headers, g_strdup (headers[i]), g_strdup (values[i]));
+ }
+}
+
+/**
+ * camel_session_get_junk_headers:
+ *
+ * Since: 2.22
+ * Returns: (element-type utf8 utf8) (transfer none):
+ **/
+const GHashTable *
+camel_session_get_junk_headers (CamelSession *session)
+{
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+
+ return session->priv->junk_headers;
+}
+
+/**
+ * camel_session_authenticate_sync:
+ * @session: a #CamelSession
+ * @service: a #CamelService
+ * @mechanism: (nullable): a SASL mechanism name, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Authenticates @service, which may involve repeated calls to
+ * camel_service_authenticate() or camel_service_authenticate_sync().
+ * A #CamelSession subclass is largely responsible for implementing this,
+ * and should handle things like user prompts and secure password storage.
+ * These issues are out-of-scope for Camel.
+ *
+ * If an error occurs, or if authentication is aborted, the function sets
+ * @error and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on failure
+ *
+ * Since: 3.4
+ **/
+gboolean
+camel_session_authenticate_sync (CamelSession *session,
+ CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSessionClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
+
+ class = CAMEL_SESSION_GET_CLASS (session);
+ g_return_val_if_fail (class->authenticate_sync != NULL, FALSE);
+
+ success = class->authenticate_sync (
+ session, service, mechanism, cancellable, error);
+ CAMEL_CHECK_GERROR (session, authenticate_sync, success, error);
+
+ return success;
+}
+
+/* Helper for camel_session_authenticate() */
+static void
+session_authenticate_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_session_authenticate_sync (
+ CAMEL_SESSION (source_object),
+ async_context->service,
+ async_context->auth_mechanism,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_session_authenticate:
+ * @session: a #CamelSession
+ * @service: a #CamelService
+ * @mechanism: (nullable): a SASL mechanism name, or %NULL
+ * @io_priority: the I/O priority for the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously authenticates @service, which may involve repeated calls
+ * to camel_service_authenticate() or camel_service_authenticate_sync().
+ * A #CamelSession subclass is largely responsible for implementing this,
+ * and should handle things like user prompts and secure password storage.
+ * These issues are out-of-scope for Camel.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_session_authenticate_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.4
+ **/
+void
+camel_session_authenticate (CamelSession *session,
+ CamelService *service,
+ const gchar *mechanism,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_SESSION (session));
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->service = g_object_ref (service);
+ async_context->auth_mechanism = g_strdup (mechanism);
+
+ task = g_task_new (session, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_session_authenticate);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, session_authenticate_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_session_authenticate_finish:
+ * @session: a #CamelSession
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_session_authenticate().
+ *
+ * If an error occurred, or if authentication was aborted, the function
+ * sets @error and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on failure
+ *
+ * Since: 3.4
+ **/
+gboolean
+camel_session_authenticate_finish (CamelSession *session,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, session), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_session_authenticate), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_session_forward_to_sync:
+ * @session: a #CamelSession
+ * @folder: the #CamelFolder where @message is located
+ * @message: the #CamelMimeMessage to forward
+ * @address: the recipient's email address
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Forwards @message in @folder to the email address(es) given by @address.
+ *
+ * If an error occurs, the function sets @error and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on failure
+ *
+ * Since: 3.6
+ **/
+gboolean
+camel_session_forward_to_sync (CamelSession *session,
+ CamelFolder *folder,
+ CamelMimeMessage *message,
+ const gchar *address,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSessionClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
+ g_return_val_if_fail (address != NULL, FALSE);
+
+ class = CAMEL_SESSION_GET_CLASS (session);
+ g_return_val_if_fail (class->forward_to_sync != NULL, FALSE);
+
+ success = class->forward_to_sync (
+ session, folder, message, address, cancellable, error);
+ CAMEL_CHECK_GERROR (session, forward_to_sync, success, error);
+
+ return success;
+}
+
+/* Helper for camel_session_forward_to() */
+static void
+session_forward_to_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_session_forward_to_sync (
+ CAMEL_SESSION (source_object),
+ async_context->folder,
+ async_context->message,
+ async_context->address,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_session_forward_to:
+ * @session: a #CamelSession
+ * @folder: the #CamelFolder where @message is located
+ * @message: the #CamelMimeMessage to forward
+ * @address: the recipient's email address
+ * @io_priority: the I/O priority for the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously forwards @message in @folder to the email address(s)
+ * given by @address.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_session_forward_to_finish() to get the result of the
+ * operation.
+ *
+ * Since: 3.6
+ **/
+void
+camel_session_forward_to (CamelSession *session,
+ CamelFolder *folder,
+ CamelMimeMessage *message,
+ const gchar *address,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_SESSION (session));
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
+ g_return_if_fail (address != NULL);
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->folder = g_object_ref (folder);
+ async_context->message = g_object_ref (message);
+ async_context->address = g_strdup (address);
+
+ task = g_task_new (session, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_session_forward_to);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, session_forward_to_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_session_forward_to_finish:
+ * @session: a #CamelSession
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_session_forward_to().
+ *
+ * If an error occurred, the function sets @error and returns %FALSE.
+ *
+ * Returns: %TRUE on success, %FALSE on failure
+ *
+ * Since: 3.6
+ **/
+gboolean
+camel_session_forward_to_finish (CamelSession *session,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, session), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_session_forward_to), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
diff --git a/src/camel/camel-session.h b/src/camel/camel-session.h
new file mode 100644
index 000000000..e837e0f37
--- /dev/null
+++ b/src/camel/camel-session.h
@@ -0,0 +1,260 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-session.h : Abstract class for an email session
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SESSION_H
+#define CAMEL_SESSION_H
+
+#include <camel/camel-enums.h>
+#include <camel/camel-filter-driver.h>
+#include <camel/camel-junk-filter.h>
+#include <camel/camel-msgport.h>
+#include <camel/camel-provider.h>
+#include <camel/camel-service.h>
+#include <camel/camel-certdb.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SESSION \
+ (camel_session_get_type ())
+#define CAMEL_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SESSION, CamelSession))
+#define CAMEL_SESSION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SESSION, CamelSessionClass))
+#define CAMEL_IS_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SESSION))
+#define CAMEL_IS_SESSION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SESSION))
+#define CAMEL_SESSION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SESSION, CamelSessionClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSession CamelSession;
+typedef struct _CamelSessionClass CamelSessionClass;
+typedef struct _CamelSessionPrivate CamelSessionPrivate;
+
+enum {
+ CAMEL_SESSION_PASSWORD_REPROMPT = 1 << 0,
+ CAMEL_SESSION_PASSWORD_SECRET = 1 << 2,
+ CAMEL_SESSION_PASSWORD_STATIC = 1 << 3,
+ CAMEL_SESSION_PASSPHRASE = 1 << 4
+};
+
+struct _CamelSession {
+ GObject parent;
+ CamelSessionPrivate *priv;
+};
+
+/**
+ * CamelSessionCallback:
+ * @session: a #CamelSession
+ * @cancellable: a #CamelOperation cast as a #GCancellable
+ * @user_data: data passed to camel_session_submit_job()
+ * @error: return location for a #GError
+ *
+ * This is the callback signature for jobs submitted to the CamelSession
+ * via camel_session_submit_job(). The @error pointer is always non-%NULL,
+ * so it's safe to dereference to check if a #GError has been set.
+ *
+ * Since: 3.2
+ **/
+typedef void (*CamelSessionCallback) (CamelSession *session,
+ GCancellable *cancellable,
+ gpointer user_data,
+ GError **error);
+
+struct _CamelSessionClass {
+ GObjectClass parent_class;
+
+ CamelService * (*add_service) (CamelSession *session,
+ const gchar *uid,
+ const gchar *protocol,
+ CamelProviderType type,
+ GError **error);
+ void (*remove_service) (CamelSession *session,
+ CamelService *service);
+ gchar * (*get_password) (CamelSession *session,
+ CamelService *service,
+ const gchar *prompt,
+ const gchar *item,
+ guint32 flags,
+ GError **error);
+ gboolean (*forget_password) (CamelSession *session,
+ CamelService *service,
+ const gchar *item,
+ GError **error);
+ CamelCertTrust (*trust_prompt) (CamelSession *session,
+ CamelService *service,
+ GTlsCertificate *certificate,
+ GTlsCertificateFlags errors);
+ CamelFilterDriver *
+ (*get_filter_driver) (CamelSession *session,
+ const gchar *type,
+ GError **error);
+ gboolean (*lookup_addressbook) (CamelSession *session,
+ const gchar *name);
+
+ /* Synchronous I/O Methods */
+ gboolean (*authenticate_sync) (CamelSession *session,
+ CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*forward_to_sync) (CamelSession *session,
+ CamelFolder *folder,
+ CamelMimeMessage *message,
+ const gchar *address,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* Reserved slots for methods. */
+ gpointer reserved_for_methods[4];
+
+ /* Signals */
+ void (*job_started) (CamelSession *session,
+ GCancellable *cancellable);
+ void (*job_finished) (CamelSession *session,
+ GCancellable *cancellable,
+ const GError *error);
+ void (*user_alert) (CamelSession *session,
+ CamelService *service,
+ CamelSessionAlertType type,
+ const gchar *message);
+};
+
+GType camel_session_get_type (void);
+GMainContext * camel_session_ref_main_context (CamelSession *session);
+const gchar * camel_session_get_user_data_dir (CamelSession *session);
+const gchar * camel_session_get_user_cache_dir
+ (CamelSession *session);
+void camel_session_set_network_monitor
+ (CamelSession *session,
+ GNetworkMonitor *network_monitor);
+GNetworkMonitor *
+ camel_session_ref_network_monitor
+ (CamelSession *session);
+CamelService * camel_session_add_service (CamelSession *session,
+ const gchar *uid,
+ const gchar *protocol,
+ CamelProviderType type,
+ GError **error);
+void camel_session_remove_service (CamelSession *session,
+ CamelService *service);
+CamelService * camel_session_ref_service (CamelSession *session,
+ const gchar *uid);
+CamelService * camel_session_ref_service_by_url
+ (CamelSession *session,
+ CamelURL *url,
+ CamelProviderType type);
+GList * camel_session_list_services (CamelSession *session);
+void camel_session_remove_services (CamelSession *session);
+gchar * camel_session_get_password (CamelSession *session,
+ CamelService *service,
+ const gchar *prompt,
+ const gchar *item,
+ guint32 flags,
+ GError **error);
+gboolean camel_session_forget_password (CamelSession *session,
+ CamelService *service,
+ const gchar *item,
+ GError **error);
+CamelCertTrust camel_session_trust_prompt (CamelSession *session,
+ CamelService *service,
+ GTlsCertificate *certificate,
+ GTlsCertificateFlags errors);
+void camel_session_user_alert (CamelSession *session,
+ CamelService *service,
+ CamelSessionAlertType type,
+ const gchar *message);
+gboolean camel_session_get_online (CamelSession *session);
+void camel_session_set_online (CamelSession *session,
+ gboolean online);
+CamelFilterDriver *
+ camel_session_get_filter_driver (CamelSession *session,
+ const gchar *type,
+ GError **error);
+CamelJunkFilter *
+ camel_session_get_junk_filter (CamelSession *session);
+void camel_session_set_junk_filter (CamelSession *session,
+ CamelJunkFilter *junk_filter);
+guint camel_session_idle_add (CamelSession *session,
+ gint priority,
+ GSourceFunc function,
+ gpointer data,
+ GDestroyNotify notify);
+void camel_session_submit_job (CamelSession *session,
+ const gchar *description,
+ CamelSessionCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify);
+const GHashTable *
+ camel_session_get_junk_headers (CamelSession *session);
+void camel_session_set_junk_headers (CamelSession *session,
+ const gchar **headers,
+ const gchar **values,
+ gint len);
+gboolean camel_session_lookup_addressbook (CamelSession *session,
+ const gchar *name);
+
+gboolean camel_session_authenticate_sync (CamelSession *session,
+ CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error);
+void camel_session_authenticate (CamelSession *session,
+ CamelService *service,
+ const gchar *mechanism,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_session_authenticate_finish
+ (CamelSession *session,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_session_forward_to_sync (CamelSession *session,
+ CamelFolder *folder,
+ CamelMimeMessage *message,
+ const gchar *address,
+ GCancellable *cancellable,
+ GError **error);
+void camel_session_forward_to (CamelSession *session,
+ CamelFolder *folder,
+ CamelMimeMessage *message,
+ const gchar *address,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_session_forward_to_finish (CamelSession *session,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_SESSION_H */
diff --git a/src/camel/camel-settings.c b/src/camel/camel-settings.c
new file mode 100644
index 000000000..fdeb8e768
--- /dev/null
+++ b/src/camel/camel-settings.c
@@ -0,0 +1,224 @@
+/*
+ * camel-settings.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-settings.h"
+
+#include <stdlib.h>
+
+/* Needed for CamelSettings <--> CamelURL conversions. */
+#include "camel-local-settings.h"
+#include "camel-network-settings.h"
+
+G_DEFINE_TYPE (CamelSettings, camel_settings, G_TYPE_OBJECT)
+
+static GParamSpec **
+settings_list_settings (CamelSettingsClass *class,
+ guint *n_settings)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ return g_object_class_list_properties (object_class, n_settings);
+}
+
+static CamelSettings *
+settings_clone (CamelSettings *settings)
+{
+ CamelSettingsClass *class;
+ GParamSpec **properties;
+ GParameter *parameters;
+ CamelSettings *clone;
+ guint ii, n_properties;
+
+ class = CAMEL_SETTINGS_GET_CLASS (settings);
+ properties = camel_settings_class_list_settings (class, &n_properties);
+
+ parameters = g_new0 (GParameter, n_properties);
+
+ for (ii = 0; ii < n_properties; ii++) {
+ parameters[ii].name = properties[ii]->name;
+ g_value_init (
+ &parameters[ii].value,
+ properties[ii]->value_type);
+ g_object_get_property (
+ G_OBJECT (settings),
+ parameters[ii].name,
+ &parameters[ii].value);
+ }
+
+ clone = g_object_newv (
+ G_OBJECT_TYPE (settings),
+ n_properties, parameters);
+
+ for (ii = 0; ii < n_properties; ii++)
+ g_value_unset (&parameters[ii].value);
+
+ g_free (parameters);
+ g_free (properties);
+
+ return clone;
+}
+
+static gboolean
+settings_equal (CamelSettings *settings_a,
+ CamelSettings *settings_b)
+{
+ CamelSettingsClass *class;
+ GParamSpec **properties;
+ GValue *value_a;
+ GValue *value_b;
+ guint ii, n_properties;
+ gboolean equal = TRUE;
+
+ /* Make sure both instances are of the same type. */
+ if (G_OBJECT_TYPE (settings_a) != G_OBJECT_TYPE (settings_b))
+ return FALSE;
+
+ value_a = g_slice_new0 (GValue);
+ value_b = g_slice_new0 (GValue);
+
+ class = CAMEL_SETTINGS_GET_CLASS (settings_a);
+ properties = camel_settings_class_list_settings (class, &n_properties);
+
+ for (ii = 0; equal && ii < n_properties; ii++) {
+ GParamSpec *pspec = properties[ii];
+
+ g_value_init (value_a, pspec->value_type);
+ g_value_init (value_b, pspec->value_type);
+
+ g_object_get_property (
+ G_OBJECT (settings_a),
+ pspec->name, value_a);
+
+ g_object_get_property (
+ G_OBJECT (settings_b),
+ pspec->name, value_b);
+
+ equal = (g_param_values_cmp (pspec, value_a, value_b) == 0);
+
+ g_value_unset (value_a);
+ g_value_unset (value_b);
+ }
+
+ g_free (properties);
+
+ g_slice_free (GValue, value_a);
+ g_slice_free (GValue, value_b);
+
+ return equal;
+}
+
+static void
+camel_settings_class_init (CamelSettingsClass *class)
+{
+ class->list_settings = settings_list_settings;
+ class->clone = settings_clone;
+ class->equal = settings_equal;
+}
+
+static void
+camel_settings_init (CamelSettings *settings)
+{
+}
+
+/**
+ * camel_settings_class_list_settings:
+ * @settings_class: a #CamelSettingsClass
+ * @n_settings: return location for the length of the returned array
+ *
+ * Returns an array of #GParamSpec for properties of @class which are
+ * considered to be settings. By default all properties are considered
+ * to be settings, but subclasses may wish to exclude certain properties.
+ * Free the returned array with g_free().
+ *
+ * Returns: (transfer full): an array of #GParamSpec which should be freed after use
+ *
+ * Since: 3.2
+ **/
+GParamSpec **
+camel_settings_class_list_settings (CamelSettingsClass *settings_class,
+ guint *n_settings)
+{
+ g_return_val_if_fail (CAMEL_IS_SETTINGS_CLASS (settings_class), NULL);
+ g_return_val_if_fail (settings_class->list_settings != NULL, NULL);
+
+ return settings_class->list_settings (settings_class, n_settings);
+}
+
+/**
+ * camel_settings_clone:
+ * @settings: a #CamelSettings
+ *
+ * Creates an copy of @settings, such that passing @settings and the
+ * copied instance to camel_settings_equal() would return %TRUE.
+ *
+ * By default, this creates a new settings instance with the same #GType
+ * as @settings, and copies all #GObject property values from @settings
+ * to the new instance.
+ *
+ * Returns: (transfer full): a newly-created copy of @settings
+ *
+ * Since: 3.2
+ **/
+CamelSettings *
+camel_settings_clone (CamelSettings *settings)
+{
+ CamelSettingsClass *class;
+ CamelSettings *clone;
+
+ g_return_val_if_fail (CAMEL_IS_SETTINGS (settings), NULL);
+
+ class = CAMEL_SETTINGS_GET_CLASS (settings);
+ g_return_val_if_fail (class->clone != NULL, NULL);
+
+ clone = class->clone (settings);
+
+ /* Make sure the documented invariant is satisfied. */
+ g_warn_if_fail (camel_settings_equal (settings, clone));
+
+ return clone;
+}
+
+/**
+ * camel_settings_equal:
+ * @settings_a: a #CamelSettings
+ * @settings_b: another #CamelSettings
+ *
+ * Returns %TRUE if @settings_a and @settings_b are equal.
+ *
+ * By default, equality requires both instances to have the same #GType
+ * with the same set of #GObject properties, and each property value in
+ * @settings_a is equal to the corresponding value in @settings_b.
+ *
+ * Returns: %TRUE if @settings_a and @settings_b are equal
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_settings_equal (CamelSettings *settings_a,
+ CamelSettings *settings_b)
+{
+ CamelSettingsClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_SETTINGS (settings_a), FALSE);
+ g_return_val_if_fail (CAMEL_IS_SETTINGS (settings_b), FALSE);
+
+ class = CAMEL_SETTINGS_GET_CLASS (settings_a);
+ g_return_val_if_fail (class->equal != NULL, FALSE);
+
+ return class->equal (settings_a, settings_b);
+}
+
diff --git a/src/camel/camel-settings.h b/src/camel/camel-settings.h
new file mode 100644
index 000000000..3781b8227
--- /dev/null
+++ b/src/camel/camel-settings.h
@@ -0,0 +1,87 @@
+/*
+ * camel-settings.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SETTINGS_H
+#define CAMEL_SETTINGS_H
+
+#include <glib-object.h>
+#include <camel/camel-url.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SETTINGS \
+ (camel_settings_get_type ())
+#define CAMEL_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SETTINGS, CamelSettings))
+#define CAMEL_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SETTINGS, CamelSettingsClass))
+#define CAMEL_IS_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SETTINGS))
+#define CAMEL_IS_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SETTINGS))
+#define CAMEL_SETTINGS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SETTINGS, CamelSettingsClass))
+
+G_BEGIN_DECLS
+
+/**
+ * CamelSettings:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.2
+ **/
+typedef struct _CamelSettings CamelSettings;
+typedef struct _CamelSettingsClass CamelSettingsClass;
+typedef struct _CamelSettingsPrivate CamelSettingsPrivate;
+
+struct _CamelSettings {
+ GObject parent;
+ CamelSettingsPrivate *priv;
+};
+
+struct _CamelSettingsClass {
+ GObjectClass parent_class;
+
+ GParamSpec ** (*list_settings) (CamelSettingsClass *klass,
+ guint *n_settings);
+
+ CamelSettings * (*clone) (CamelSettings *settings);
+ gboolean (*equal) (CamelSettings *settings_a,
+ CamelSettings *settings_b);
+};
+
+GType camel_settings_get_type (void) G_GNUC_CONST;
+GParamSpec ** camel_settings_class_list_settings
+ (CamelSettingsClass *settings_class,
+ guint *n_settings);
+CamelSettings * camel_settings_clone (CamelSettings *settings);
+gboolean camel_settings_equal (CamelSettings *settings_a,
+ CamelSettings *settings_b);
+
+G_END_DECLS
+
+#endif /* CAMEL_SETTINGS_H */
diff --git a/src/camel/camel-sexp.c b/src/camel/camel-sexp.c
new file mode 100644
index 000000000..41664c4c0
--- /dev/null
+++ b/src/camel/camel-sexp.c
@@ -0,0 +1,1856 @@
+/* A simple, extensible s-exp evaluation engine.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/*
+ * The following built-in s-exp's are supported:
+ *
+ * list = (and list*)
+ * perform an intersection of a number of lists, and return that.
+ *
+ * bool = (and bool*)
+ * perform a boolean AND of boolean values.
+ *
+ * list = (or list*)
+ * perform a union of a number of lists, returning the new list.
+ *
+ * bool = (or bool*)
+ * perform a boolean OR of boolean values.
+ *
+ * gint = (+ int*)
+ * Add integers.
+ *
+ * string = (+ string*)
+ * Concat strings.
+ *
+ * time_t = (+ time_t*)
+ * Add time_t values.
+ *
+ * gint = (- gint int*)
+ * Subtract integers from the first.
+ *
+ * time_t = (- time_t*)
+ * Subtract time_t values from the first.
+ *
+ * gint = (cast-int string|int|bool)
+ * Cast to an integer value.
+ *
+ * string = (cast-string string|int|bool)
+ * Cast to an string value.
+ *
+ * Comparison operators:
+ *
+ * bool = (< gint gint)
+ * bool = (> gint gint)
+ * bool = (= gint gint)
+ *
+ * bool = (< string string)
+ * bool = (> string string)
+ * bool = (= string string)
+ *
+ * bool = (< time_t time_t)
+ * bool = (> time_t time_t)
+ * bool = (= time_t time_t)
+ * Perform a comparision of 2 integers, 2 string values, or 2 time values.
+ *
+ * Function flow:
+ *
+ * type = (if bool function)
+ * type = (if bool function function)
+ * Choose a flow path based on a boolean value
+ *
+ * type = (begin func func func)
+ * Execute a sequence. The last function return is the return type.
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+
+#include "camel-sexp.h"
+
+#define p(x) /* parse debug */
+#define r(x) /* run debug */
+#define d(x) /* general debug */
+
+G_DEFINE_TYPE (CamelSExp, camel_sexp, G_TYPE_OBJECT)
+
+static CamelSExpTerm * parse_list (CamelSExp *sexp, gint gotbrace);
+static CamelSExpTerm * parse_value (CamelSExp *sexp);
+
+#ifdef TESTER
+static void parse_dump_term (CamelSExpTerm *term, gint depth);
+#endif
+
+typedef gboolean (CamelSGeneratorFunc) (gint argc,
+ CamelSExpResult **argv,
+ CamelSExpResult *result);
+typedef gboolean (CamelSOperatorFunc) (gint argc,
+ CamelSExpResult **argv,
+ CamelSExpResult *result);
+
+/* FIXME: constant _TIME_MAX used in different files, move it somewhere */
+#define _TIME_MAX ((time_t) INT_MAX) /* Max valid time_t */
+
+static const GScannerConfig scanner_config =
+{
+ ( (gchar *) " \t\r\n") /* cset_skip_characters */,
+ ( (gchar *) G_CSET_a_2_z
+ "_+-<=>?"
+ G_CSET_A_2_Z) /* cset_identifier_first */,
+ ( (gchar *) G_CSET_a_2_z
+ "_0123456789-<>?"
+ G_CSET_A_2_Z
+ G_CSET_LATINS
+ G_CSET_LATINC ) /* cset_identifier_nth */,
+ ( (gchar *) ";\n" ) /* cpair_comment_single */,
+
+ FALSE /* case_sensitive */,
+
+ TRUE /* skip_comment_multi */,
+ TRUE /* skip_comment_single */,
+ TRUE /* scan_comment_multi */,
+ TRUE /* scan_identifier */,
+ TRUE /* scan_identifier_1char */,
+ FALSE /* scan_identifier_NULL */,
+ TRUE /* scan_symbols */,
+ FALSE /* scan_binary */,
+ TRUE /* scan_octal */,
+ TRUE /* scan_float */,
+ TRUE /* scan_hex */,
+ FALSE /* scan_hex_dollar */,
+ TRUE /* scan_string_sq */,
+ TRUE /* scan_string_dq */,
+ TRUE /* numbers_2_int */,
+ FALSE /* int_2_float */,
+ FALSE /* identifier_2_string */,
+ TRUE /* char_2_token */,
+ FALSE /* symbol_2_token */,
+ FALSE /* scope_0_fallback */,
+};
+
+/**
+ * camel_sexp_fatal_error:
+ *
+ * Since: 3.4
+ **/
+void
+camel_sexp_fatal_error (CamelSExp *sexp,
+ const gchar *why,
+ ...)
+{
+ va_list args;
+
+ /* jumps back to the caller of sexp->failenv,
+ * only to be called from inside a callback */
+
+ if (sexp->error)
+ g_free (sexp->error);
+
+ va_start (args, why);
+ sexp->error = g_strdup_vprintf (why, args);
+ va_end (args);
+
+ longjmp (sexp->failenv, 1);
+}
+
+/**
+ * camel_sexp_error:
+ *
+ * Since: 3.4
+ **/
+const gchar *
+camel_sexp_error (CamelSExp *sexp)
+{
+ return sexp->error;
+}
+
+/**
+ * camel_sexp_result_new:
+ *
+ * Since: 3.4
+ **/
+CamelSExpResult *
+camel_sexp_result_new (CamelSExp *sexp,
+ gint type)
+{
+ CamelSExpResult *result;
+
+ result = camel_memchunk_alloc0 (sexp->result_chunks);
+ result->type = type;
+ result->occuring_start = 0;
+ result->occuring_end = _TIME_MAX;
+ result->time_generator = FALSE;
+
+ return result;
+}
+
+/**
+ * camel_sexp_result_free:
+ *
+ * Since: 3.4
+ **/
+void
+camel_sexp_result_free (CamelSExp *sexp,
+ CamelSExpResult *term)
+{
+ if (term == NULL)
+ return;
+
+ switch (term->type) {
+ case CAMEL_SEXP_RES_ARRAY_PTR:
+ g_ptr_array_free (term->value.ptrarray, TRUE);
+ break;
+ case CAMEL_SEXP_RES_BOOL:
+ case CAMEL_SEXP_RES_INT:
+ case CAMEL_SEXP_RES_TIME:
+ break;
+ case CAMEL_SEXP_RES_STRING:
+ g_free (term->value.string);
+ break;
+ case CAMEL_SEXP_RES_UNDEFINED:
+ break;
+ default:
+ g_return_if_reached ();
+ }
+ camel_memchunk_free (sexp->result_chunks, term);
+}
+
+/**
+ * camel_sexp_resultv_free:
+ *
+ * Since: 3.4
+ **/
+void
+camel_sexp_resultv_free (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv)
+{
+ gint i;
+
+ /* used in normal functions if they have to abort,
+ * and free their arguments */
+
+ for (i = 0; i < argc; i++) {
+ camel_sexp_result_free (sexp, argv[i]);
+ }
+}
+
+/* implementations for the builtin functions */
+
+/* we can only itereate a hashtable from a called function */
+struct IterData {
+ gint count;
+ GPtrArray *uids;
+};
+
+/* ok, store any values that are in all sets */
+static void
+htand (gchar *key,
+ gint value,
+ struct IterData *iter_data)
+{
+ if (value == iter_data->count) {
+ g_ptr_array_add (iter_data->uids, key);
+ }
+}
+
+/* or, store all unique values */
+static void
+htor (gchar *key,
+ gint value,
+ struct IterData *iter_data)
+{
+ g_ptr_array_add (iter_data->uids, key);
+}
+
+static CamelSExpResult *
+term_eval_and (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ gpointer data)
+{
+ CamelSExpResult *result, *r1;
+ GHashTable *ht = g_hash_table_new (g_str_hash, g_str_equal);
+ struct IterData lambdafoo;
+ gint type=-1;
+ gint bool = TRUE;
+ gint i;
+ const gchar *oper;
+
+ r (printf ("( and\n"));
+
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_UNDEFINED);
+
+ oper = "AND";
+ sexp->operators = g_slist_prepend (sexp->operators, (gpointer) oper);
+
+ for (i = 0; bool && i < argc; i++) {
+ r1 = camel_sexp_term_eval (sexp, argv[i]);
+ if (type == -1)
+ type = r1->type;
+ if (type != r1->type) {
+ camel_sexp_result_free (sexp, result);
+ camel_sexp_result_free (sexp, r1);
+ g_hash_table_destroy (ht);
+ camel_sexp_fatal_error (sexp, "Invalid types in AND");
+ } else if (r1->type == CAMEL_SEXP_RES_ARRAY_PTR) {
+ gchar **a1;
+ gint l1, j;
+
+ a1 = (gchar **) r1->value.ptrarray->pdata;
+ l1 = r1->value.ptrarray->len;
+ for (j = 0; j < l1; j++) {
+ gpointer ptr;
+ gint n;
+ ptr = g_hash_table_lookup (ht, a1[j]);
+ n = GPOINTER_TO_INT (ptr);
+ g_hash_table_insert (ht, a1[j], GINT_TO_POINTER (n + 1));
+ }
+ } else if (r1->type == CAMEL_SEXP_RES_BOOL) {
+ bool = bool && r1->value.boolean;
+ }
+ camel_sexp_result_free (sexp, r1);
+ }
+
+ if (type == CAMEL_SEXP_RES_ARRAY_PTR) {
+ lambdafoo.count = argc;
+ lambdafoo.uids = g_ptr_array_new ();
+ g_hash_table_foreach (ht, (GHFunc) htand, &lambdafoo);
+ result->type = CAMEL_SEXP_RES_ARRAY_PTR;
+ result->value.ptrarray = lambdafoo.uids;
+ } else if (type == CAMEL_SEXP_RES_BOOL) {
+ result->type = CAMEL_SEXP_RES_BOOL;
+ result->value.boolean = bool;
+ }
+
+ g_hash_table_destroy (ht);
+ sexp->operators = g_slist_remove (sexp->operators, oper);
+
+ return result;
+}
+
+static CamelSExpResult *
+term_eval_or (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ gpointer data)
+{
+ CamelSExpResult *result, *r1;
+ GHashTable *ht = g_hash_table_new (g_str_hash, g_str_equal);
+ struct IterData lambdafoo;
+ gint type = -1;
+ gint bool = FALSE;
+ gint i;
+ const gchar *oper;
+
+ r (printf ("(or \n"));
+
+ oper = "OR";
+ sexp->operators = g_slist_prepend (sexp->operators, (gpointer) oper);
+
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_UNDEFINED);
+
+ for (i = 0; !bool && i < argc; i++) {
+ r1 = camel_sexp_term_eval (sexp, argv[i]);
+ if (type == -1)
+ type = r1->type;
+ if (r1->type != type) {
+ camel_sexp_result_free (sexp, result);
+ camel_sexp_result_free (sexp, r1);
+ g_hash_table_destroy (ht);
+ camel_sexp_fatal_error (sexp, "Invalid types in OR");
+ } else if (r1->type == CAMEL_SEXP_RES_ARRAY_PTR) {
+ gchar **a1;
+ gint l1, j;
+
+ a1 = (gchar **) r1->value.ptrarray->pdata;
+ l1 = r1->value.ptrarray->len;
+ for (j = 0; j < l1; j++) {
+ g_hash_table_insert (ht, a1[j], (gpointer) 1);
+ }
+ } else if (r1->type == CAMEL_SEXP_RES_BOOL) {
+ bool |= r1->value.boolean;
+ }
+ camel_sexp_result_free (sexp, r1);
+ }
+
+ if (type == CAMEL_SEXP_RES_ARRAY_PTR) {
+ lambdafoo.count = argc;
+ lambdafoo.uids = g_ptr_array_new ();
+ g_hash_table_foreach (ht, (GHFunc) htor, &lambdafoo);
+ result->type = CAMEL_SEXP_RES_ARRAY_PTR;
+ result->value.ptrarray = lambdafoo.uids;
+ } else if (type == CAMEL_SEXP_RES_BOOL) {
+ result->type = CAMEL_SEXP_RES_BOOL;
+ result->value.boolean = bool;
+ }
+ g_hash_table_destroy (ht);
+
+ sexp->operators = g_slist_remove (sexp->operators, oper);
+ return result;
+}
+
+static CamelSExpResult *
+term_eval_not (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ gpointer data)
+{
+ gint res = TRUE;
+ CamelSExpResult *result;
+
+ if (argc > 0) {
+ if (argv[0]->type == CAMEL_SEXP_RES_BOOL
+ && argv[0]->value.boolean)
+ res = FALSE;
+ }
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ result->value.boolean = res;
+
+ return result;
+}
+
+/* this should support all arguments ...? */
+static CamelSExpResult *
+term_eval_lt (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ gpointer data)
+{
+ CamelSExpResult *result, *r1, *r2;
+
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_UNDEFINED);
+
+ if (argc == 2) {
+ r1 = camel_sexp_term_eval (sexp, argv[0]);
+ r2 = camel_sexp_term_eval (sexp, argv[1]);
+ if (r1->type != r2->type) {
+ camel_sexp_result_free (sexp, r1);
+ camel_sexp_result_free (sexp, r2);
+ camel_sexp_result_free (sexp, result);
+ camel_sexp_fatal_error (sexp, "Incompatible types in compare <");
+ } else if (r1->type == CAMEL_SEXP_RES_INT) {
+ result->type = CAMEL_SEXP_RES_BOOL;
+ result->value.boolean = r1->value.number < r2->value.number;
+ } else if (r1->type == CAMEL_SEXP_RES_TIME) {
+ result->type = CAMEL_SEXP_RES_BOOL;
+ result->value.boolean = r1->value.time < r2->value.time;
+ } else if (r1->type == CAMEL_SEXP_RES_STRING) {
+ result->type = CAMEL_SEXP_RES_BOOL;
+ result->value.boolean = strcmp (r1->value.string, r2->value.string) < 0;
+ }
+ camel_sexp_result_free (sexp, r1);
+ camel_sexp_result_free (sexp, r2);
+ }
+ return result;
+}
+
+/* this should support all arguments ...? */
+static CamelSExpResult *
+term_eval_gt (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ gpointer data)
+{
+ CamelSExpResult *result, *r1, *r2;
+
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_UNDEFINED);
+
+ if (argc == 2) {
+ r1 = camel_sexp_term_eval (sexp, argv[0]);
+ r2 = camel_sexp_term_eval (sexp, argv[1]);
+ if (r1->type != r2->type) {
+ camel_sexp_result_free (sexp, r1);
+ camel_sexp_result_free (sexp, r2);
+ camel_sexp_result_free (sexp, result);
+ camel_sexp_fatal_error (sexp, "Incompatible types in compare >");
+ } else if (r1->type == CAMEL_SEXP_RES_INT) {
+ result->type = CAMEL_SEXP_RES_BOOL;
+ result->value.boolean = r1->value.number > r2->value.number;
+ } else if (r1->type == CAMEL_SEXP_RES_TIME) {
+ result->type = CAMEL_SEXP_RES_BOOL;
+ result->value.boolean = r1->value.time > r2->value.time;
+ } else if (r1->type == CAMEL_SEXP_RES_STRING) {
+ result->type = CAMEL_SEXP_RES_BOOL;
+ result->value.boolean = strcmp (r1->value.string, r2->value.string) > 0;
+ }
+ camel_sexp_result_free (sexp, r1);
+ camel_sexp_result_free (sexp, r2);
+ }
+ return result;
+}
+
+/* this should support all arguments ...? */
+static CamelSExpResult *
+term_eval_eq (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ gpointer data)
+{
+ CamelSExpResult *result, *r1, *r2;
+
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+
+ if (argc == 2) {
+ r1 = camel_sexp_term_eval (sexp, argv[0]);
+ r2 = camel_sexp_term_eval (sexp, argv[1]);
+ if (r1->type != r2->type) {
+ result->value.boolean = FALSE;
+ } else if (r1->type == CAMEL_SEXP_RES_INT) {
+ result->value.boolean = r1->value.number == r2->value.number;
+ } else if (r1->type == CAMEL_SEXP_RES_BOOL) {
+ result->value.boolean = r1->value.boolean == r2->value.boolean;
+ } else if (r1->type == CAMEL_SEXP_RES_TIME) {
+ result->value.boolean = r1->value.time == r2->value.time;
+ } else if (r1->type == CAMEL_SEXP_RES_STRING) {
+ result->value.boolean = strcmp (r1->value.string, r2->value.string) == 0;
+ }
+ camel_sexp_result_free (sexp, r1);
+ camel_sexp_result_free (sexp, r2);
+ }
+ return result;
+}
+
+static CamelSExpResult *
+term_eval_plus (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *result = NULL;
+ gint type;
+ gint i;
+
+ if (argc > 0) {
+ type = argv[0]->type;
+ switch (type) {
+ case CAMEL_SEXP_RES_INT: {
+ gint total = argv[0]->value.number;
+ for (i = 1; i < argc && argv[i]->type == CAMEL_SEXP_RES_INT; i++) {
+ total += argv[i]->value.number;
+ }
+ if (i < argc) {
+ camel_sexp_resultv_free (sexp, argc, argv);
+ camel_sexp_fatal_error (sexp, "Invalid types in (+ ints)");
+ }
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT);
+ result->value.number = total;
+ break; }
+ case CAMEL_SEXP_RES_STRING: {
+ GString *string = g_string_new (argv[0]->value.string);
+ for (i = 1; i < argc && argv[i]->type == CAMEL_SEXP_RES_STRING; i++) {
+ g_string_append (string, argv[i]->value.string);
+ }
+ if (i < argc) {
+ camel_sexp_resultv_free (sexp, argc, argv);
+ camel_sexp_fatal_error (sexp, "Invalid types in (+ strings)");
+ }
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_STRING);
+ result->value.string = string->str;
+ g_string_free (string, FALSE);
+ break; }
+ case CAMEL_SEXP_RES_TIME: {
+ time_t total;
+
+ total = argv[0]->value.time;
+
+ for (i = 1; i < argc && argv[i]->type == CAMEL_SEXP_RES_TIME; i++)
+ total += argv[i]->value.time;
+
+ if (i < argc) {
+ camel_sexp_resultv_free (sexp, argc, argv);
+ camel_sexp_fatal_error (sexp, "Invalid types in (+ time_t)");
+ }
+
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_TIME);
+ result->value.time = total;
+ break; }
+ }
+ }
+
+ if (result == NULL) {
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT);
+ result->value.number = 0;
+ }
+
+ return result;
+}
+
+static CamelSExpResult *
+term_eval_sub (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *result = NULL;
+ gint type;
+ gint i;
+
+ if (argc > 0) {
+ type = argv[0]->type;
+ switch (type) {
+ case CAMEL_SEXP_RES_INT: {
+ gint total = argv[0]->value.number;
+ for (i = 1; i < argc && argv[i]->type == CAMEL_SEXP_RES_INT; i++) {
+ total -= argv[i]->value.number;
+ }
+ if (i < argc) {
+ camel_sexp_resultv_free (sexp, argc, argv);
+ camel_sexp_fatal_error (sexp, "Invalid types in -");
+ }
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT);
+ result->value.number = total;
+ break; }
+ case CAMEL_SEXP_RES_TIME: {
+ time_t total;
+
+ total = argv[0]->value.time;
+
+ for (i = 1; i < argc && argv[i]->type == CAMEL_SEXP_RES_TIME; i++)
+ total -= argv[i]->value.time;
+
+ if (i < argc) {
+ camel_sexp_resultv_free (sexp, argc, argv);
+ camel_sexp_fatal_error (sexp, "Invalid types in (- time_t)");
+ }
+
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_TIME);
+ result->value.time = total;
+ break; }
+ }
+ }
+
+ if (result == NULL) {
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT);
+ result->value.number = 0;
+ }
+ return result;
+}
+
+/* cast to gint */
+static CamelSExpResult *
+term_eval_castint (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *result;
+
+ if (argc != 1)
+ camel_sexp_fatal_error (sexp, "Incorrect argument count to (gint )");
+
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT);
+ switch (argv[0]->type) {
+ case CAMEL_SEXP_RES_INT:
+ result->value.number = argv[0]->value.number;
+ break;
+ case CAMEL_SEXP_RES_BOOL:
+ result->value.number = argv[0]->value.boolean != 0;
+ break;
+ case CAMEL_SEXP_RES_STRING:
+ result->value.number = strtoul (argv[0]->value.string, NULL, 10);
+ break;
+ default:
+ camel_sexp_result_free (sexp, result);
+ camel_sexp_fatal_error (sexp, "Invalid type in (cast-int )");
+ }
+
+ return result;
+}
+
+/* cast to string */
+static CamelSExpResult *
+term_eval_caststring (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ gpointer data)
+{
+ CamelSExpResult *result;
+
+ if (argc != 1)
+ camel_sexp_fatal_error (sexp, "Incorrect argument count to (cast-string )");
+
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_STRING);
+ switch (argv[0]->type) {
+ case CAMEL_SEXP_RES_INT:
+ result->value.string = g_strdup_printf ("%d", argv[0]->value.number);
+ break;
+ case CAMEL_SEXP_RES_BOOL:
+ result->value.string = g_strdup_printf ("%d", argv[0]->value.boolean != 0);
+ break;
+ case CAMEL_SEXP_RES_STRING:
+ result->value.string = g_strdup (argv[0]->value.string);
+ break;
+ default:
+ camel_sexp_result_free (sexp, result);
+ camel_sexp_fatal_error (sexp, "Invalid type in (gint )");
+ }
+
+ return result;
+}
+
+/* implements 'if' function */
+static CamelSExpResult *
+term_eval_if (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ gpointer data)
+{
+ CamelSExpResult *result;
+ gint doit;
+
+ if (argc >=2 && argc <= 3) {
+ result = camel_sexp_term_eval (sexp, argv[0]);
+ doit = (result->type == CAMEL_SEXP_RES_BOOL && result->value.boolean);
+ camel_sexp_result_free (sexp, result);
+ if (doit) {
+ return camel_sexp_term_eval (sexp, argv[1]);
+ } else if (argc > 2) {
+ return camel_sexp_term_eval (sexp, argv[2]);
+ }
+ }
+ return camel_sexp_result_new (sexp, CAMEL_SEXP_RES_UNDEFINED);
+}
+
+/* implements 'begin' statement */
+static CamelSExpResult *
+term_eval_begin (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ gpointer data)
+{
+ CamelSExpResult *result = NULL;
+ gint i;
+
+ for (i = 0; i < argc; i++) {
+ if (result != NULL)
+ camel_sexp_result_free (sexp, result);
+ result = camel_sexp_term_eval (sexp, argv[i]);
+ }
+ if (result != NULL)
+ return result;
+ else
+ return camel_sexp_result_new (sexp, CAMEL_SEXP_RES_UNDEFINED);
+}
+
+/**
+ * camel_sexp_term_eval:
+ *
+ * Since: 3.4
+ **/
+CamelSExpResult *
+camel_sexp_term_eval (CamelSExp *sexp,
+ CamelSExpTerm *term)
+{
+ CamelSExpResult *result = NULL;
+ gint i;
+ CamelSExpResult **argv;
+
+ /* this must only be called from inside term evaluation callbacks! */
+
+ g_return_val_if_fail (term != NULL, NULL);
+
+ r (printf ("eval term :\n"));
+ r (parse_dump_term (term, 0));
+
+ switch (term->type) {
+ case CAMEL_SEXP_TERM_STRING:
+ r (printf (" (string \"%s\")\n", term->value.string));
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_STRING);
+ /* erk, this shoul;dn't need to strdup this ... */
+ result->value.string = g_strdup (term->value.string);
+ break;
+ case CAMEL_SEXP_TERM_INT:
+ r (printf (" (gint %d)\n", term->value.number));
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT);
+ result->value.number = term->value.number;
+ break;
+ case CAMEL_SEXP_TERM_BOOL:
+ r (printf (" (gint %d)\n", term->value.number));
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ result->value.boolean = term->value.boolean;
+ break;
+ case CAMEL_SEXP_TERM_TIME:
+ r (printf (" (time_t %ld)\n", term->value.time));
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_TIME);
+ result->value.time = term->value.time;
+ break;
+ case CAMEL_SEXP_TERM_IFUNC:
+ if (term->value.func.sym && term->value.func.sym->f.ifunc)
+ result = term->value.func.sym->f.ifunc (sexp, term->value.func.termcount, term->value.func.terms, term->value.func.sym->data);
+ break;
+ case CAMEL_SEXP_TERM_FUNC:
+ /* first evaluate all arguments to result types */
+ argv = alloca (sizeof (argv[0]) * term->value.func.termcount);
+ for (i = 0; i < term->value.func.termcount; i++) {
+ argv[i] = camel_sexp_term_eval (sexp, term->value.func.terms[i]);
+ }
+ /* call the function */
+ if (term->value.func.sym->f.func)
+ result = term->value.func.sym->f.func (sexp, term->value.func.termcount, argv, term->value.func.sym->data);
+
+ camel_sexp_resultv_free (sexp, term->value.func.termcount, argv);
+ break;
+ default:
+ camel_sexp_fatal_error (sexp, "Unknown type in parse tree: %d", term->type);
+ }
+
+ if (result == NULL)
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_UNDEFINED);
+
+ return result;
+}
+
+#ifdef TESTER
+static void
+eval_dump_result (CamelSExpResult *result,
+ gint depth)
+{
+ gint i;
+
+ if (result == NULL) {
+ printf ("null result???\n");
+ return;
+ }
+
+ for (i = 0; i < depth; i++)
+ printf (" ");
+
+ switch (result->type) {
+ case CAMEL_SEXP_RES_ARRAY_PTR:
+ printf ("array pointers\n");
+ break;
+ case CAMEL_SEXP_RES_INT:
+ printf ("int: %d\n", result->value.number);
+ break;
+ case CAMEL_SEXP_RES_STRING:
+ printf ("string: '%s'\n", result->value.string);
+ break;
+ case CAMEL_SEXP_RES_BOOL:
+ printf ("bool: %c\n", result->value.boolean ? 't':'f');
+ break;
+ case CAMEL_SEXP_RES_TIME:
+ printf ("time_t: %ld\n", (glong) result->value.time);
+ break;
+ case CAMEL_SEXP_RES_UNDEFINED:
+ printf (" <undefined>\n");
+ break;
+ }
+ printf ("\n");
+}
+#endif
+
+#ifdef TESTER
+static void
+parse_dump_term (CamelSExpTerm *term,
+ gint depth)
+{
+ gint i;
+
+ if (t == NULL) {
+ printf ("null term??\n");
+ return;
+ }
+
+ for (i = 0; i < depth; i++)
+ printf (" ");
+
+ switch (term->type) {
+ case CAMEL_SEXP_TERM_STRING:
+ printf (" \"%s\"", term->value.string);
+ break;
+ case CAMEL_SEXP_TERM_INT:
+ printf (" %d", term->value.number);
+ break;
+ case CAMEL_SEXP_TERM_BOOL:
+ printf (" #%c", term->value.boolean ? 't':'f');
+ break;
+ case CAMEL_SEXP_TERM_TIME:
+ printf (" %ld", (glong) term->value.time);
+ break;
+ case CAMEL_SEXP_TERM_IFUNC:
+ case CAMEL_SEXP_TERM_FUNC:
+ printf (" (function %s\n", term->value.func.sym->name);
+ /*printf(" [%d] ", term->value.func.termcount);*/
+ for (i = 0; i < term->value.func.termcount; i++) {
+ parse_dump_term (term->value.func.terms[i], depth + 1);
+ }
+ for (i = 0; i < depth; i++)
+ printf (" ");
+ printf (" )");
+ break;
+ case CAMEL_SEXP_TERM_VAR:
+ printf (" (variable %s )\n", term->value.var->name);
+ break;
+ default:
+ printf ("unknown type: %d\n", term->type);
+ }
+
+ printf ("\n");
+}
+#endif
+
+static const gchar *time_functions[] = {
+ "time-now",
+ "make-time",
+ "time-add-day",
+ "time-day-begin",
+ "time-day-end"
+};
+
+static gboolean
+occur_in_time_range_generator (gint argc,
+ CamelSExpResult **argv,
+ CamelSExpResult *result)
+{
+ g_return_val_if_fail (result != NULL, FALSE);
+ g_return_val_if_fail (argc == 2 || argc == 3, FALSE);
+
+ if ((argv[0]->type != CAMEL_SEXP_RES_TIME) || (argv[1]->type != CAMEL_SEXP_RES_TIME))
+ return FALSE;
+
+ result->occuring_start = argv[0]->value.time;
+ result->occuring_end = argv[1]->value.time;
+
+ return TRUE;
+}
+
+static gboolean
+binary_generator (gint argc,
+ CamelSExpResult **argv,
+ CamelSExpResult *result)
+{
+ g_return_val_if_fail (result != NULL, FALSE);
+ g_return_val_if_fail (argc == 2, FALSE);
+
+ if ((argv[0]->type != CAMEL_SEXP_RES_TIME) || (argv[1]->type != CAMEL_SEXP_RES_TIME))
+ return FALSE;
+
+ result->occuring_start = argv[0]->value.time;
+ result->occuring_end = argv[1]->value.time;
+
+ return TRUE;
+}
+
+static gboolean
+unary_generator (gint argc,
+ CamelSExpResult **argv,
+ CamelSExpResult *result)
+{
+ /* unary generator with end time */
+ g_return_val_if_fail (result != NULL, FALSE);
+ g_return_val_if_fail (argc == 1, FALSE);
+
+ if (argv[0]->type != CAMEL_SEXP_RES_TIME)
+ return FALSE;
+
+ result->occuring_start = 0;
+ result->occuring_end = argv[0]->value.time;
+
+ return TRUE;
+}
+
+static const struct {
+ const gchar *name;
+ CamelSGeneratorFunc *func;
+} generators[] = {
+ {"occur-in-time-range?", occur_in_time_range_generator},
+ {"due-in-time-range?", binary_generator},
+ {"has-alarms-in-range?", binary_generator},
+ {"completed-before?", unary_generator},
+};
+
+static gboolean
+or_operator (gint argc,
+ CamelSExpResult **argv,
+ CamelSExpResult *result)
+{
+ gint ii;
+
+ /*
+ * A B A or B
+ * ---- ---- ------
+ * norm (0) norm (0) norm (0)
+ * gen (1) norm (0) norm (0)
+ * norm (0) gen (1) norm (0)
+ * gen (1) gen (1) gen*(1)
+ */
+
+ g_return_val_if_fail (result != NULL, FALSE);
+ g_return_val_if_fail (argc > 0, FALSE);
+
+ result->time_generator = TRUE;
+ for (ii = 0; ii < argc && result->time_generator; ii++) {
+ result->time_generator = argv[ii]->time_generator;
+ }
+
+ if (result->time_generator) {
+ result->occuring_start = argv[0]->occuring_start;
+ result->occuring_end = argv[0]->occuring_end;
+
+ for (ii = 1; ii < argc; ii++) {
+ result->occuring_start = MIN (result->occuring_start, argv[ii]->occuring_start);
+ result->occuring_end = MAX (result->occuring_end, argv[ii]->occuring_end);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+and_operator (gint argc,
+ CamelSExpResult **argv,
+ CamelSExpResult *result)
+{
+ gint ii;
+
+ /*
+ * A B A and B
+ * ---- ---- ------- -
+ * norm (0) norm (0) norm (0)
+ * gen (1) norm (0) gen (1)
+ * norm (0) gen (1) gen (1)
+ * gen (1) gen (1) gen (1)
+ * */
+
+ g_return_val_if_fail (result != NULL, FALSE);
+ g_return_val_if_fail (argc > 0, FALSE);
+
+ result->time_generator = FALSE;
+ for (ii = 0; ii < argc && !result->time_generator; ii++) {
+ result->time_generator = argv[ii]->time_generator;
+ }
+
+ if (result->time_generator) {
+ result->occuring_start = argv[0]->occuring_start;
+ result->occuring_end = argv[0]->occuring_end;
+
+ for (ii = 1; ii < argc; ii++) {
+ result->occuring_start = MAX (result->occuring_start, argv[ii]->occuring_start);
+ result->occuring_end = MIN (result->occuring_end, argv[ii]->occuring_end);
+ }
+ }
+
+ return TRUE;
+}
+
+static const struct {
+ const gchar *name;
+ CamelSOperatorFunc *func;
+} operators[] = {
+ {"or", or_operator},
+ {"and", and_operator}
+};
+
+static CamelSOperatorFunc *
+get_operator_function (const gchar *fname)
+{
+ gint i;
+
+ g_return_val_if_fail (fname != NULL, NULL);
+
+ for (i = 0; i < sizeof (operators) / sizeof (operators[0]); i++)
+ if (strcmp (operators[i].name, fname) == 0)
+ return operators[i].func;
+
+ return NULL;
+}
+
+static inline gboolean
+is_time_function (const gchar *fname)
+{
+ gint i;
+
+ g_return_val_if_fail (fname != NULL, FALSE);
+
+ for (i = 0; i < sizeof (time_functions) / sizeof (time_functions[0]); i++)
+ if (strcmp (time_functions[i], fname) == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static CamelSGeneratorFunc *
+get_generator_function (const gchar *fname)
+{
+ gint i;
+
+ g_return_val_if_fail (fname != NULL, NULL);
+
+ for (i = 0; i < sizeof (generators) / sizeof (generators[0]); i++)
+ if (strcmp (generators[i].name, fname) == 0)
+ return generators[i].func;
+
+ return NULL;
+}
+
+/* this must only be called from inside term evaluation callbacks! */
+static CamelSExpResult *
+camel_sexp_term_evaluate_occur_times (CamelSExp *sexp,
+ CamelSExpTerm *term,
+ time_t *start,
+ time_t *end)
+{
+ CamelSExpResult *result = NULL;
+ gint i, argc;
+ CamelSExpResult **argv;
+ gboolean ok = TRUE;
+
+ g_return_val_if_fail (term != NULL, NULL);
+ g_return_val_if_fail (start != NULL, NULL);
+ g_return_val_if_fail (end != NULL, NULL);
+
+ /*
+ printf ("eval term :\n");
+ parse_dump_term (t, 0);
+ */
+
+ switch (term->type) {
+ case CAMEL_SEXP_TERM_STRING:
+ r (printf (" (string \"%s\")\n", term->value.string));
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_STRING);
+ result->value.string = g_strdup (term->value.string);
+ break;
+ case CAMEL_SEXP_TERM_IFUNC:
+ case CAMEL_SEXP_TERM_FUNC:
+ {
+ CamelSGeneratorFunc *generator = NULL;
+ CamelSOperatorFunc *operator = NULL;
+
+ r (printf (" (function \"%s\"\n", term->value.func.sym->name));
+
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_UNDEFINED);
+ argc = term->value.func.termcount;
+ argv = alloca (sizeof (argv[0]) * argc);
+
+ for (i = 0; i < term->value.func.termcount; i++) {
+ argv[i] = camel_sexp_term_evaluate_occur_times (
+ sexp, term->value.func.terms[i], start, end);
+ }
+
+ if (is_time_function (term->value.func.sym->name)) {
+ /* evaluate time */
+ if (term->value.func.sym->f.func)
+ result = term->value.func.sym->f.func (sexp, term->value.func.termcount,
+ argv, term->value.func.sym->data);
+ } else if ((generator = get_generator_function (term->value.func.sym->name)) != NULL) {
+ /* evaluate generator function */
+ result->time_generator = TRUE;
+ ok = generator (argc, argv, result);
+ } else if ((operator = get_operator_function (term->value.func.sym->name)) != NULL)
+ /* evaluate operator function */
+ ok = operator (argc, argv, result);
+ else {
+ /* normal function: we need to scan all objects */
+ result->time_generator = FALSE;
+ }
+
+ camel_sexp_resultv_free (sexp, term->value.func.termcount, argv);
+ break;
+ }
+ case CAMEL_SEXP_TERM_INT:
+ case CAMEL_SEXP_TERM_BOOL:
+ case CAMEL_SEXP_TERM_TIME:
+ break;
+ default:
+ ok = FALSE;
+ break;
+ }
+
+ if (!ok)
+ camel_sexp_fatal_error (sexp, "Error in parse tree");
+
+ if (result == NULL)
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_UNDEFINED);
+
+ return result;
+}
+
+/*
+ PARSER
+*/
+
+static CamelSExpTerm *
+parse_term_new (CamelSExp *sexp,
+ gint type)
+{
+ CamelSExpTerm *term;
+
+ term = camel_memchunk_alloc0 (sexp->term_chunks);
+ term->type = type;
+
+ return term;
+}
+
+static void
+parse_term_free (CamelSExp *sexp,
+ CamelSExpTerm *term)
+{
+ gint i;
+
+ if (term == NULL) {
+ return;
+ }
+
+ switch (term->type) {
+ case CAMEL_SEXP_TERM_INT:
+ case CAMEL_SEXP_TERM_BOOL:
+ case CAMEL_SEXP_TERM_TIME:
+ case CAMEL_SEXP_TERM_VAR:
+ break;
+
+ case CAMEL_SEXP_TERM_STRING:
+ g_free (term->value.string);
+ break;
+
+ case CAMEL_SEXP_TERM_FUNC:
+ case CAMEL_SEXP_TERM_IFUNC:
+ for (i = 0; i < term->value.func.termcount; i++) {
+ parse_term_free (sexp, term->value.func.terms[i]);
+ }
+ g_free (term->value.func.terms);
+ break;
+
+ default:
+ printf ("parse_term_free: unknown type: %d\n", term->type);
+ }
+ camel_memchunk_free (sexp->term_chunks, term);
+}
+
+static CamelSExpTerm **
+parse_values (CamelSExp *sexp,
+ gint *len)
+{
+ gint token;
+ CamelSExpTerm **terms;
+ gint i, size = 0;
+ GScanner *gs = sexp->scanner;
+ GSList *list = NULL, *l;
+
+ p (printf ("parsing values\n"));
+
+ while ( (token = g_scanner_peek_next_token (gs)) != G_TOKEN_EOF
+ && token != ')') {
+ list = g_slist_prepend (list, parse_value (sexp));
+ size++;
+ }
+
+ /* go over the list, and put them backwards into the term array */
+ terms = g_malloc (size * sizeof (*terms));
+ l = list;
+ for (i = size - 1; i >= 0; i--) {
+ g_return_val_if_fail (l, NULL);
+ g_return_val_if_fail (l->data, NULL);
+ terms[i] = l->data;
+ l = g_slist_next (l);
+ }
+ g_slist_free (list);
+
+ p (printf ("found %d subterms\n", size));
+ *len = size;
+
+ p (printf ("done parsing values\n"));
+ return terms;
+}
+
+/**
+ * camel_sexp_parse_value:
+ *
+ * Since: 3.4
+ **/
+CamelSExpTerm *
+camel_sexp_parse_value (CamelSExp *sexp)
+{
+ return parse_value (sexp);
+}
+
+static CamelSExpTerm *
+parse_value (CamelSExp *sexp)
+{
+ gint token, negative = FALSE;
+ CamelSExpTerm *term = NULL;
+ GScanner *gs = sexp->scanner;
+ CamelSExpSymbol *sym;
+
+ p (printf ("parsing value\n"));
+
+ token = g_scanner_get_next_token (gs);
+ switch (token) {
+ case G_TOKEN_EOF:
+ break;
+ case G_TOKEN_LEFT_PAREN:
+ p (printf ("got brace, its a list!\n"));
+ return parse_list (sexp, TRUE);
+ case G_TOKEN_STRING:
+ p (printf ("got string '%s'\n", g_scanner_cur_value (gs).v_string));
+ term = parse_term_new (sexp, CAMEL_SEXP_TERM_STRING);
+ term->value.string = g_strdup (g_scanner_cur_value (gs).v_string);
+ break;
+ case '-':
+ p (printf ("got negative int?\n"));
+ token = g_scanner_get_next_token (gs);
+ if (token != G_TOKEN_INT) {
+ camel_sexp_fatal_error (sexp, "Invalid format for a integer value");
+ return NULL;
+ }
+
+ negative = TRUE;
+ /* fall through... */
+ case G_TOKEN_INT:
+ term = parse_term_new (sexp, CAMEL_SEXP_TERM_INT);
+ term->value.number = g_scanner_cur_value (gs).v_int;
+ if (negative)
+ term->value.number = -term->value.number;
+ p (printf ("got gint %d\n", term->value.number));
+ break;
+ case '#': {
+ gchar *str;
+
+ p (printf ("got bool?\n"));
+ token = g_scanner_get_next_token (gs);
+ if (token != G_TOKEN_IDENTIFIER) {
+ camel_sexp_fatal_error (sexp, "Invalid format for a boolean value");
+ return NULL;
+ }
+
+ str = g_scanner_cur_value (gs).v_identifier;
+
+ g_return_val_if_fail (str != NULL, NULL);
+ if (!(strlen (str) == 1 && (str[0] == 't' || str[0] == 'f'))) {
+ camel_sexp_fatal_error (sexp, "Invalid format for a boolean value");
+ return NULL;
+ }
+
+ term = parse_term_new (sexp, CAMEL_SEXP_TERM_BOOL);
+ term->value.boolean = (str[0] == 't');
+ break; }
+ case G_TOKEN_SYMBOL:
+ sym = g_scanner_cur_value (gs).v_symbol;
+ p (printf ("got symbol '%s'\n", sym->name));
+ switch (sym->type) {
+ case CAMEL_SEXP_TERM_FUNC:
+ case CAMEL_SEXP_TERM_IFUNC:
+ /* this is basically invalid, since we can't use function
+ * pointers, but let the runtime catch it ... */
+ term = parse_term_new (sexp, sym->type);
+ term->value.func.sym = sym;
+ term->value.func.terms = parse_values (sexp, &term->value.func.termcount);
+ break;
+ case CAMEL_SEXP_TERM_VAR:
+ term = parse_term_new (sexp, sym->type);
+ term->value.var = sym;
+ break;
+ default:
+ camel_sexp_fatal_error (sexp, "Invalid symbol type: %s: %d", sym->name, sym->type);
+ }
+ break;
+ case G_TOKEN_IDENTIFIER:
+ p (printf ("got unknown identifider '%s'\n", g_scanner_cur_value (gs).v_identifier));
+ camel_sexp_fatal_error (sexp, "Unknown identifier: %s", g_scanner_cur_value (gs).v_identifier);
+ break;
+ default:
+ camel_sexp_fatal_error (sexp, "Unexpected token encountered: %d", token);
+ }
+ p (printf ("done parsing value\n"));
+
+ return term;
+}
+
+/* FIXME: this needs some robustification */
+static CamelSExpTerm *
+parse_list (CamelSExp *sexp,
+ gint gotbrace)
+{
+ gint token;
+ CamelSExpTerm *term = NULL;
+ GScanner *gs = sexp->scanner;
+
+ p (printf ("parsing list\n"));
+ if (gotbrace)
+ token = '(';
+ else
+ token = g_scanner_get_next_token (gs);
+ if (token =='(') {
+ token = g_scanner_get_next_token (gs);
+ switch (token) {
+ case G_TOKEN_SYMBOL: {
+ CamelSExpSymbol *sym;
+
+ sym = g_scanner_cur_value (gs).v_symbol;
+ p (printf ("got funciton: %s\n", sym->name));
+ term = parse_term_new (sexp, sym->type);
+ p (printf ("created new list %p\n", t));
+ /* if we have a variable, find out its base type */
+ while (sym->type == CAMEL_SEXP_TERM_VAR) {
+ sym = ((CamelSExpTerm *)(sym->data))->value.var;
+ }
+ if (sym->type == CAMEL_SEXP_TERM_FUNC
+ || sym->type == CAMEL_SEXP_TERM_IFUNC) {
+ term->value.func.sym = sym;
+ term->value.func.terms = parse_values (sexp, &term->value.func.termcount);
+ } else {
+ parse_term_free (sexp, term);
+ camel_sexp_fatal_error (sexp, "Trying to call variable as function: %s", sym->name);
+ }
+ break; }
+ case G_TOKEN_IDENTIFIER:
+ camel_sexp_fatal_error (sexp, "Unknown identifier: %s", g_scanner_cur_value (gs).v_identifier);
+ break;
+ case G_TOKEN_LEFT_PAREN:
+ return parse_list (sexp, TRUE);
+ default:
+ camel_sexp_fatal_error (sexp, "Unexpected token encountered: %d", token);
+ }
+ token = g_scanner_get_next_token (gs);
+ if (token != ')') {
+ camel_sexp_fatal_error (sexp, "Missing ')'");
+ }
+ } else {
+ camel_sexp_fatal_error (sexp, "Missing '('");
+ }
+
+ p (printf ("returning list %p\n", term));
+
+ return term;
+}
+
+static void
+free_symbol (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ CamelSExpSymbol *sym = value;
+
+ g_free (sym->name);
+ g_free (sym);
+}
+
+static void
+camel_sexp_finalize (GObject *object)
+{
+ CamelSExp *sexp = (CamelSExp *) object;
+
+ if (sexp->tree) {
+ parse_term_free (sexp, sexp->tree);
+ sexp->tree = NULL;
+ }
+
+ camel_memchunk_destroy (sexp->term_chunks);
+ camel_memchunk_destroy (sexp->result_chunks);
+
+ g_scanner_scope_foreach_symbol (sexp->scanner, 0, free_symbol, NULL);
+ g_scanner_destroy (sexp->scanner);
+
+ g_free (sexp->error);
+ sexp->error = NULL;
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_sexp_parent_class)->finalize (object);
+}
+
+static void
+camel_sexp_class_init (CamelSExpClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = camel_sexp_finalize;
+}
+
+/* 'builtin' functions */
+static const struct {
+ const gchar *name;
+ CamelSExpFunc func;
+ gint type; /* set to 1 if a function can perform shortcut
+ * evaluation, or doesn't execute everything,
+ * 0 otherwise */
+} symbols[] = {
+ { "and", (CamelSExpFunc) term_eval_and, 1 },
+ { "or", (CamelSExpFunc) term_eval_or, 1 },
+ { "not", (CamelSExpFunc) term_eval_not, 0 },
+ { "<", (CamelSExpFunc) term_eval_lt, 1 },
+ { ">", (CamelSExpFunc) term_eval_gt, 1 },
+ { "=", (CamelSExpFunc) term_eval_eq, 1 },
+ { "+", (CamelSExpFunc) term_eval_plus, 0 },
+ { "-", (CamelSExpFunc) term_eval_sub, 0 },
+ { "cast-int", (CamelSExpFunc) term_eval_castint, 0 },
+ { "cast-string", (CamelSExpFunc) term_eval_caststring, 0 },
+ { "if", (CamelSExpFunc) term_eval_if, 1 },
+ { "begin", (CamelSExpFunc) term_eval_begin, 1 },
+};
+
+static void
+camel_sexp_init (CamelSExp *sexp)
+{
+ gint i;
+
+ sexp->scanner = g_scanner_new (&scanner_config);
+ sexp->term_chunks = camel_memchunk_new (16, sizeof (CamelSExpTerm));
+ sexp->result_chunks = camel_memchunk_new (16, sizeof (CamelSExpResult));
+
+ /* load in builtin symbols? */
+ for (i = 0; i < G_N_ELEMENTS (symbols); i++) {
+ if (symbols[i].type == 1) {
+ camel_sexp_add_ifunction (
+ sexp, 0, symbols[i].name,
+ (CamelSExpIFunc) symbols[i].func,
+ (gpointer) &symbols[i]);
+ } else {
+ camel_sexp_add_function (
+ sexp, 0, symbols[i].name,
+ symbols[i].func,
+ (gpointer) &symbols[i]);
+ }
+ }
+}
+
+/**
+ * camel_sexp_new:
+ *
+ * Since: 3.4
+ **/
+CamelSExp *
+camel_sexp_new (void)
+{
+ return g_object_new (CAMEL_TYPE_SEXP, NULL);
+}
+
+/**
+ * camel_sexp_add_function:
+ * @func: (scope call):
+ *
+ * Since: 3.4
+ **/
+void
+camel_sexp_add_function (CamelSExp *sexp,
+ guint scope,
+ const gchar *name,
+ CamelSExpFunc func,
+ gpointer user_data)
+{
+ CamelSExpSymbol *sym;
+
+ g_return_if_fail (CAMEL_IS_SEXP (sexp));
+ g_return_if_fail (name != NULL);
+
+ camel_sexp_remove_symbol (sexp, scope, name);
+
+ sym = g_malloc0 (sizeof (*sym));
+ sym->name = g_strdup (name);
+ sym->f.func = func;
+ sym->type = CAMEL_SEXP_TERM_FUNC;
+ sym->data = user_data;
+
+ g_scanner_scope_add_symbol (sexp->scanner, scope, sym->name, sym);
+}
+
+/**
+ * camel_sexp_add_ifunction:
+ * @func: (scope call):
+ *
+ * Since: 3.4
+ **/
+void
+camel_sexp_add_ifunction (CamelSExp *sexp,
+ guint scope,
+ const gchar *name,
+ CamelSExpIFunc ifunc,
+ gpointer user_data)
+{
+ CamelSExpSymbol *sym;
+
+ g_return_if_fail (CAMEL_IS_SEXP (sexp));
+ g_return_if_fail (name != NULL);
+
+ camel_sexp_remove_symbol (sexp, scope, name);
+
+ sym = g_malloc0 (sizeof (*sym));
+ sym->name = g_strdup (name);
+ sym->f.ifunc = ifunc;
+ sym->type = CAMEL_SEXP_TERM_IFUNC;
+ sym->data = user_data;
+
+ g_scanner_scope_add_symbol (sexp->scanner, scope, sym->name, sym);
+}
+
+/**
+ * camel_sexp_add_variable:
+ *
+ * Since: 3.4
+ **/
+void
+camel_sexp_add_variable (CamelSExp *sexp,
+ guint scope,
+ gchar *name,
+ CamelSExpTerm *value)
+{
+ CamelSExpSymbol *sym;
+
+ g_return_if_fail (CAMEL_IS_SEXP (sexp));
+ g_return_if_fail (name != NULL);
+
+ sym = g_malloc0 (sizeof (*sym));
+ sym->name = g_strdup (name);
+ sym->type = CAMEL_SEXP_TERM_VAR;
+ sym->data = value;
+
+ g_scanner_scope_add_symbol (sexp->scanner, scope, sym->name, sym);
+}
+
+/**
+ * camel_sexp_remove_symbol:
+ *
+ * Since: 3.4
+ **/
+void
+camel_sexp_remove_symbol (CamelSExp *sexp,
+ guint scope,
+ const gchar *name)
+{
+ gint oldscope;
+ CamelSExpSymbol *sym;
+
+ g_return_if_fail (CAMEL_IS_SEXP (sexp));
+ g_return_if_fail (name != NULL);
+
+ oldscope = g_scanner_set_scope (sexp->scanner, scope);
+ sym = g_scanner_lookup_symbol (sexp->scanner, name);
+ g_scanner_scope_remove_symbol (sexp->scanner, scope, name);
+ g_scanner_set_scope (sexp->scanner, oldscope);
+ if (sym != NULL) {
+ g_free (sym->name);
+ g_free (sym);
+ }
+}
+
+/**
+ * camel_sexp_set_scope:
+ *
+ * Since: 3.4
+ **/
+gint
+camel_sexp_set_scope (CamelSExp *sexp,
+ guint scope)
+{
+ g_return_val_if_fail (CAMEL_IS_SEXP (sexp), 0);
+
+ return g_scanner_set_scope (sexp->scanner, scope);
+}
+
+/**
+ * camel_sexp_input_text:
+ *
+ * Since: 3.4
+ **/
+void
+camel_sexp_input_text (CamelSExp *sexp,
+ const gchar *text,
+ gint len)
+{
+ g_return_if_fail (CAMEL_IS_SEXP (sexp));
+ g_return_if_fail (text != NULL);
+
+ g_scanner_input_text (sexp->scanner, text, len);
+}
+
+/**
+ * camel_sexp_input_file:
+ *
+ * Since: 3.4
+ **/
+void
+camel_sexp_input_file (CamelSExp *sexp,
+ gint fd)
+{
+ g_return_if_fail (CAMEL_IS_SEXP (sexp));
+
+ g_scanner_input_file (sexp->scanner, fd);
+}
+
+/**
+ * camel_sexp_parse:
+ *
+ * Since: 3.4
+ **/
+gint
+camel_sexp_parse (CamelSExp *sexp)
+{
+ g_return_val_if_fail (CAMEL_IS_SEXP (sexp), -1);
+
+ if (setjmp (sexp->failenv)) {
+ g_warning ("Error in parsing: %s", sexp->error);
+ return -1;
+ }
+
+ if (sexp->tree)
+ parse_term_free (sexp, sexp->tree);
+
+ sexp->tree = parse_value (sexp);
+
+ return 0;
+}
+
+/**
+ * camel_sexp_eval:
+ *
+ * Since: 3.4
+ **/
+CamelSExpResult *
+camel_sexp_eval (CamelSExp *sexp)
+{
+ g_return_val_if_fail (CAMEL_IS_SEXP (sexp), NULL);
+ g_return_val_if_fail (sexp->tree != NULL, NULL);
+
+ if (setjmp (sexp->failenv)) {
+ g_warning ("Error in execution: %s", sexp->error);
+ return NULL;
+ }
+
+ return camel_sexp_term_eval (sexp, sexp->tree);
+}
+
+/**
+ * e_cal_backend_sexp_evaluate_occur_times:
+ * @f: An #CamelSExp object.
+ * @start: Start of the time window will be stored here.
+ * @end: End of the time window will be stored here.
+ *
+ * Determines biggest time window given by expressions "occur-in-range" in sexp.
+ *
+ * Since: 3.4
+ */
+gboolean
+camel_sexp_evaluate_occur_times (CamelSExp *sexp,
+ time_t *start,
+ time_t *end)
+{
+ CamelSExpResult *result;
+ gboolean generator;
+ g_return_val_if_fail (CAMEL_IS_SEXP (sexp), FALSE);
+ g_return_val_if_fail (sexp->tree != NULL, FALSE);
+ g_return_val_if_fail (start != NULL, FALSE);
+ g_return_val_if_fail (end != NULL, FALSE);
+
+ *start = *end = -1;
+
+ if (setjmp (sexp->failenv)) {
+ g_warning ("Error in execution: %s", sexp->error);
+ return FALSE;
+ }
+
+ result = camel_sexp_term_evaluate_occur_times (
+ sexp, sexp->tree, start, end);
+ generator = result->time_generator;
+
+ if (generator) {
+ *start = result->occuring_start;
+ *end = result->occuring_end;
+ }
+
+ camel_sexp_result_free (sexp, result);
+
+ return generator;
+}
+
+/**
+ * camel_sexp_encode_bool:
+ * @string:
+ * @v_bool:
+ *
+ * Encode a bool into an s-expression @string. Bools are
+ * encoded using #t #f syntax.
+ *
+ * Since: 3.4
+ **/
+void
+camel_sexp_encode_bool (GString *string,
+ gboolean v_bool)
+{
+ if (v_bool)
+ g_string_append (string, " #t");
+ else
+ g_string_append (string, " #f");
+}
+
+/**
+ * camel_sexp_encode_string:
+ * @string: Destination string.
+ * @v_string: String expression.
+ *
+ * Add a c string @v_string to the s-expression stored in
+ * the gstring @s. Quotes are added, and special characters
+ * are escaped appropriately.
+ *
+ * Since: 3.4
+ **/
+void
+camel_sexp_encode_string (GString *string,
+ const gchar *v_string)
+{
+ gchar c;
+ const gchar *p;
+
+ if (v_string == NULL)
+ p = "";
+ else
+ p = v_string;
+ g_string_append (string, " \"");
+ while ((c = *p++)) {
+ if (c == '\\' || c == '\"' || c == '\'')
+ g_string_append_c (string, '\\');
+ g_string_append_c (string, c);
+ }
+ g_string_append (string, "\"");
+}
+
+#ifdef TESTER
+gint
+main (gint argc,
+ gchar **argv)
+{
+ CamelSExp *sexp;
+ gchar *t = "(+ \"foo\" \"\\\"\" \"bar\" \"\\\\ blah \\x \")";
+ CamelSExpResult *result;
+
+ sexp = camel_sexp_new ();
+
+ camel_sexp_add_variable (sexp, 0, "test", NULL);
+
+ if (argc < 2 || !argv[1])
+ return;
+
+ camel_sexp_input_text (sexp, t, t);
+ camel_sexp_parse (sexp);
+
+ if (sexp->tree)
+ parse_dump_term (sexp->tree, 0);
+
+ result = camel_sexp_eval (sexp);
+ if (result) {
+ eval_dump_result (result, 0);
+ } else {
+ printf ("no result?|\n");
+ }
+
+ return 0;
+}
+#endif
diff --git a/src/camel/camel-sexp.h b/src/camel/camel-sexp.h
new file mode 100644
index 000000000..e0d824a4a
--- /dev/null
+++ b/src/camel/camel-sexp.h
@@ -0,0 +1,254 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SEXP_H
+#define CAMEL_SEXP_H
+
+#include <setjmp.h>
+#include <time.h>
+#include <glib.h>
+
+#include <glib-object.h>
+
+#include <camel/camel-memchunk.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SEXP \
+ (camel_sexp_get_type ())
+#define CAMEL_SEXP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SEXP, CamelSExp))
+#define CAMEL_SEXP_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SEXP, CamelSExpClass))
+#define CAMEL_IS_SEXP(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SEXP))
+#define CAMEL_IS_SEXP_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SEXP))
+#define CAMEL_SEXP_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SEXP, CamelSExpClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSExp CamelSExp;
+typedef struct _CamelSExpClass CamelSExpClass;
+
+typedef struct _CamelSExpSymbol CamelSExpSymbol;
+typedef struct _CamelSExpResult CamelSExpResult;
+typedef struct _CamelSExpTerm CamelSExpTerm;
+
+/**
+ * CamelSExpResultType:
+ *
+ * Since: 3.4
+ **/
+typedef enum {
+ CAMEL_SEXP_RES_ARRAY_PTR, /* type is a ptrarray, what it points to is implementation dependant */
+ CAMEL_SEXP_RES_INT, /* type is a number */
+ CAMEL_SEXP_RES_STRING, /* type is a pointer to a single string */
+ CAMEL_SEXP_RES_BOOL, /* boolean type */
+ CAMEL_SEXP_RES_TIME, /* time_t type */
+ CAMEL_SEXP_RES_UNDEFINED /* unknown type */
+} CamelSExpResultType;
+
+/**
+ * CamelSExpResult:
+ *
+ * Since: 3.4
+ **/
+struct _CamelSExpResult {
+ CamelSExpResultType type;
+ union {
+ GPtrArray *ptrarray;
+ gint number;
+ gchar *string;
+ gint boolean;
+ time_t time;
+ } value;
+ gboolean time_generator;
+ time_t occuring_start;
+ time_t occuring_end;
+};
+
+/**
+ * CamelSExpFunc:
+ *
+ * Since: 3.4
+ **/
+typedef CamelSExpResult *
+ (*CamelSExpFunc) (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ gpointer data);
+
+/**
+ * CamelSExpIFunc:
+ *
+ * Since: 3.4
+ **/
+typedef CamelSExpResult *
+ (*CamelSExpIFunc) (CamelSExp *sexp, gint argc,
+ CamelSExpTerm **argv,
+ gpointer data);
+
+/**
+ * CamelSExpTermType:
+ *
+ * Since: 3.4
+ **/
+typedef enum {
+ CAMEL_SEXP_TERM_INT, /* integer literal */
+ CAMEL_SEXP_TERM_BOOL, /* boolean literal */
+ CAMEL_SEXP_TERM_STRING, /* string literal */
+ CAMEL_SEXP_TERM_TIME, /* time_t literal (number of seconds past the epoch) */
+ CAMEL_SEXP_TERM_FUNC, /* normal function, arguments are evaluated before calling */
+ CAMEL_SEXP_TERM_IFUNC, /* immediate function, raw terms are arguments */
+ CAMEL_SEXP_TERM_VAR /* variable reference */
+} CamelSExpTermType;
+
+/**
+ * CamelSExpSymbol:
+ *
+ * Since: 3.4
+ **/
+struct _CamelSExpSymbol {
+ gint type; /* TERM_FUNC or TERM_VAR */
+ gchar *name;
+ gpointer data;
+ union {
+ CamelSExpFunc func;
+ CamelSExpIFunc ifunc;
+ } f;
+};
+
+/**
+ * CamelSExpTerm:
+ *
+ * Since: 3.4
+ **/
+struct _CamelSExpTerm {
+ CamelSExpTermType type;
+ union {
+ gchar *string;
+ gint number;
+ gint boolean;
+ time_t time;
+ struct {
+ CamelSExpSymbol *sym;
+ CamelSExpTerm **terms;
+ gint termcount;
+ } func;
+ CamelSExpSymbol *var;
+ } value;
+};
+
+/**
+ * CamelSExp:
+ *
+ * Since: 3.4
+ **/
+struct _CamelSExp {
+ GObject parent;
+ GScanner *scanner; /* for parsing text version */
+ CamelSExpTerm *tree; /* root of expression tree */
+
+ /* private stuff */
+ jmp_buf failenv;
+ gchar *error;
+ GSList *operators;
+
+ /* TODO: may also need a pool allocator for term strings,
+ * so we dont lose them in error conditions? */
+ CamelMemChunk *term_chunks;
+ CamelMemChunk *result_chunks;
+};
+
+struct _CamelSExpClass {
+ GObjectClass parent_class;
+};
+
+GType camel_sexp_get_type (void) G_GNUC_CONST;
+CamelSExp * camel_sexp_new (void);
+void camel_sexp_add_function (CamelSExp *sexp,
+ guint scope,
+ const gchar *name,
+ CamelSExpFunc func,
+ gpointer user_data);
+void camel_sexp_add_ifunction (CamelSExp *sexp,
+ guint scope,
+ const gchar *name,
+ CamelSExpIFunc func,
+ gpointer user_data);
+void camel_sexp_add_variable (CamelSExp *sexp,
+ guint scope,
+ gchar *name,
+ CamelSExpTerm *value);
+void camel_sexp_remove_symbol (CamelSExp *sexp,
+ guint scope,
+ const gchar *name);
+gint camel_sexp_set_scope (CamelSExp *sexp,
+ guint scope);
+void camel_sexp_input_text (CamelSExp *sexp,
+ const gchar *text,
+ gint len);
+void camel_sexp_input_file (CamelSExp *sexp,
+ gint fd);
+gint camel_sexp_parse (CamelSExp *sexp);
+CamelSExpResult *
+ camel_sexp_eval (CamelSExp *sexp);
+CamelSExpResult *
+ camel_sexp_term_eval (CamelSExp *sexp,
+ CamelSExpTerm *term);
+CamelSExpResult *
+ camel_sexp_result_new (CamelSExp *sexp,
+ gint type);
+void camel_sexp_result_free (CamelSExp *sexp,
+ CamelSExpResult *term);
+
+/* used in normal functions if they have to abort, to free their arguments */
+void camel_sexp_resultv_free (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv);
+
+/* utility functions for creating s-exp strings. */
+void camel_sexp_encode_bool (GString *string,
+ gboolean v_bool);
+void camel_sexp_encode_string (GString *string,
+ const gchar *v_string);
+
+/* only to be called from inside a callback to signal a fatal execution error */
+void camel_sexp_fatal_error (CamelSExp *sexp,
+ const gchar *why,
+ ...) G_GNUC_NORETURN;
+
+/* return the error string */
+const gchar * camel_sexp_error (CamelSExp *sexp);
+
+CamelSExpTerm * camel_sexp_parse_value (CamelSExp *sexp);
+
+gboolean camel_sexp_evaluate_occur_times (CamelSExp *sexp,
+ time_t *start,
+ time_t *end);
+
+G_END_DECLS
+
+#endif /* CAMEL_SEXP_H */
diff --git a/src/camel/camel-smime-context.c b/src/camel/camel-smime-context.c
new file mode 100644
index 000000000..1e18a1d44
--- /dev/null
+++ b/src/camel/camel-smime-context.c
@@ -0,0 +1,1437 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * 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 (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#ifdef ENABLE_SMIME
+
+#include "nss.h"
+#include <cms.h>
+#include <cert.h>
+#include <certdb.h>
+#include <pkcs11.h>
+#include <smime.h>
+#include <secerr.h>
+#include <pkcs11t.h>
+#include <pk11func.h>
+#include <secoid.h>
+
+#include <errno.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-data-wrapper.h"
+#include "camel-mime-filter-basic.h"
+#include "camel-mime-filter-canon.h"
+#include "camel-mime-part.h"
+#include "camel-multipart-signed.h"
+#include "camel-operation.h"
+#include "camel-session.h"
+#include "camel-smime-context.h"
+#include "camel-stream-filter.h"
+#include "camel-stream-fs.h"
+#include "camel-stream-mem.h"
+
+#define d(x)
+
+#define CAMEL_SMIME_CONTEXT_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SMIME_CONTEXT, CamelSMIMEContextPrivate))
+
+struct _CamelSMIMEContextPrivate {
+ CERTCertDBHandle *certdb;
+
+ gchar *encrypt_key;
+ CamelSMIMESign sign_mode;
+
+ gint password_tries;
+ guint send_encrypt_key_prefs : 1;
+};
+
+G_DEFINE_TYPE (CamelSMIMEContext, camel_smime_context, CAMEL_TYPE_CIPHER_CONTEXT)
+
+static void
+smime_cert_data_free (gpointer cert_data)
+{
+ g_return_if_fail (cert_data != NULL);
+
+ CERT_DestroyCertificate (cert_data);
+}
+
+static gpointer
+smime_cert_data_clone (gpointer cert_data)
+{
+ g_return_val_if_fail (cert_data != NULL, NULL);
+
+ return CERT_DupCertificate (cert_data);
+}
+
+/* used for decode content callback, for streaming decode */
+static void
+sm_write_stream (gpointer arg,
+ const gchar *buf,
+ gulong len)
+{
+ camel_stream_write ((CamelStream *) arg, buf, len, NULL, NULL);
+}
+
+static PK11SymKey *
+sm_decrypt_key (gpointer arg,
+ SECAlgorithmID *algid)
+{
+ printf ("Decrypt key called\n");
+ return (PK11SymKey *) arg;
+}
+
+static const gchar *
+nss_error_to_string (glong errorcode)
+{
+#define cs(a,b) case a: return b;
+
+ switch (errorcode) {
+ cs (SEC_ERROR_IO, "An I/O error occurred during security authorization.")
+ cs (SEC_ERROR_LIBRARY_FAILURE, "security library failure.")
+ cs (SEC_ERROR_BAD_DATA, "security library: received bad data.")
+ cs (SEC_ERROR_OUTPUT_LEN, "security library: output length error.")
+ cs (SEC_ERROR_INPUT_LEN, "security library has experienced an input length error.")
+ cs (SEC_ERROR_INVALID_ARGS, "security library: invalid arguments.")
+ cs (SEC_ERROR_INVALID_ALGORITHM, "security library: invalid algorithm.")
+ cs (SEC_ERROR_INVALID_AVA, "security library: invalid AVA.")
+ cs (SEC_ERROR_INVALID_TIME, "Improperly formatted time string.")
+ cs (SEC_ERROR_BAD_DER, "security library: improperly formatted DER-encoded message.")
+ cs (SEC_ERROR_BAD_SIGNATURE, "Peer's certificate has an invalid signature.")
+ cs (SEC_ERROR_EXPIRED_CERTIFICATE, "Peer's Certificate has expired.")
+ cs (SEC_ERROR_REVOKED_CERTIFICATE, "Peer's Certificate has been revoked.")
+ cs (SEC_ERROR_UNKNOWN_ISSUER, "Peer's Certificate issuer is not recognized.")
+ cs (SEC_ERROR_BAD_KEY, "Peer's public key is invalid.")
+ cs (SEC_ERROR_BAD_PASSWORD, "The security password entered is incorrect.")
+ cs (SEC_ERROR_RETRY_PASSWORD, "New password entered incorrectly. Please try again.")
+ cs (SEC_ERROR_NO_NODELOCK, "security library: no nodelock.")
+ cs (SEC_ERROR_BAD_DATABASE, "security library: bad database.")
+ cs (SEC_ERROR_NO_MEMORY, "security library: memory allocation failure.")
+ cs (SEC_ERROR_UNTRUSTED_ISSUER, "Peer's certificate issuer has been marked as not trusted by the user.")
+ cs (SEC_ERROR_UNTRUSTED_CERT, "Peer's certificate has been marked as not trusted by the user.")
+ cs (SEC_ERROR_DUPLICATE_CERT, "Certificate already exists in your database.")
+ cs (SEC_ERROR_DUPLICATE_CERT_NAME, "Downloaded certificate's name duplicates one already in your database.")
+ cs (SEC_ERROR_ADDING_CERT, "Error adding certificate to database.")
+ cs (SEC_ERROR_FILING_KEY, "Error refiling the key for this certificate.")
+ cs (SEC_ERROR_NO_KEY, "The private key for this certificate cannot be found in key database")
+ cs (SEC_ERROR_CERT_VALID, "This certificate is valid.")
+ cs (SEC_ERROR_CERT_NOT_VALID, "This certificate is not valid.")
+ cs (SEC_ERROR_CERT_NO_RESPONSE, "Cert Library: No Response")
+ cs (SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE, "The certificate issuer's certificate has expired. Check your system date and time.")
+ cs (SEC_ERROR_CRL_EXPIRED, "The CRL for the certificate's issuer has expired. Update it or check your system date and time.")
+ cs (SEC_ERROR_CRL_BAD_SIGNATURE, "The CRL for the certificate's issuer has an invalid signature.")
+ cs (SEC_ERROR_CRL_INVALID, "New CRL has an invalid format.")
+ cs (SEC_ERROR_EXTENSION_VALUE_INVALID, "Certificate extension value is invalid.")
+ cs (SEC_ERROR_EXTENSION_NOT_FOUND, "Certificate extension not found.")
+ cs (SEC_ERROR_CA_CERT_INVALID, "Issuer certificate is invalid.")
+ cs (SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID, "Certificate path length constraint is invalid.")
+ cs (SEC_ERROR_CERT_USAGES_INVALID, "Certificate usages field is invalid.")
+ cs (SEC_INTERNAL_ONLY, "**Internal ONLY module**")
+ cs (SEC_ERROR_INVALID_KEY, "The key does not support the requested operation.")
+ cs (SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION, "Certificate contains unknown critical extension.")
+ cs (SEC_ERROR_OLD_CRL, "New CRL is not later than the current one.")
+ cs (SEC_ERROR_NO_EMAIL_CERT, "Not encrypted or signed: you do not yet have an email certificate.")
+ cs (SEC_ERROR_NO_RECIPIENT_CERTS_QUERY, "Not encrypted: you do not have certificates for each of the recipients.")
+ cs (SEC_ERROR_NOT_A_RECIPIENT, "Cannot decrypt: you are not a recipient, or matching certificate and private key not found.")
+ cs (SEC_ERROR_PKCS7_KEYALG_MISMATCH, "Cannot decrypt: key encryption algorithm does not match your certificate.")
+ cs (SEC_ERROR_PKCS7_BAD_SIGNATURE, "Signature verification failed: no signer found, too many signers found, or improper or corrupted data.")
+ cs (SEC_ERROR_UNSUPPORTED_KEYALG, "Unsupported or unknown key algorithm.")
+ cs (SEC_ERROR_DECRYPTION_DISALLOWED, "Cannot decrypt: encrypted using a disallowed algorithm or key size.")
+ cs (XP_SEC_FORTEZZA_BAD_CARD, "Fortezza card has not been properly initialized. Please remove it and return it to your issuer.")
+ cs (XP_SEC_FORTEZZA_NO_CARD, "No Fortezza cards Found")
+ cs (XP_SEC_FORTEZZA_NONE_SELECTED, "No Fortezza card selected")
+ cs (XP_SEC_FORTEZZA_MORE_INFO, "Please select a personality to get more info on")
+ cs (XP_SEC_FORTEZZA_PERSON_NOT_FOUND, "Personality not found")
+ cs (XP_SEC_FORTEZZA_NO_MORE_INFO, "No more information on that Personality")
+ cs (XP_SEC_FORTEZZA_BAD_PIN, "Invalid Pin")
+ cs (XP_SEC_FORTEZZA_PERSON_ERROR, "Couldn't initialize Fortezza personalities.")
+ cs (SEC_ERROR_NO_KRL, "No KRL for this site's certificate has been found.")
+ cs (SEC_ERROR_KRL_EXPIRED, "The KRL for this site's certificate has expired.")
+ cs (SEC_ERROR_KRL_BAD_SIGNATURE, "The KRL for this site's certificate has an invalid signature.")
+ cs (SEC_ERROR_REVOKED_KEY, "The key for this site's certificate has been revoked.")
+ cs (SEC_ERROR_KRL_INVALID, "New KRL has an invalid format.")
+ cs (SEC_ERROR_NEED_RANDOM, "security library: need random data.")
+ cs (SEC_ERROR_NO_MODULE, "security library: no security module can perform the requested operation.")
+ cs (SEC_ERROR_NO_TOKEN, "The security card or token does not exist, needs to be initialized, or has been removed.")
+ cs (SEC_ERROR_READ_ONLY, "security library: read-only database.")
+ cs (SEC_ERROR_NO_SLOT_SELECTED, "No slot or token was selected.")
+ cs (SEC_ERROR_CERT_NICKNAME_COLLISION, "A certificate with the same nickname already exists.")
+ cs (SEC_ERROR_KEY_NICKNAME_COLLISION, "A key with the same nickname already exists.")
+ cs (SEC_ERROR_SAFE_NOT_CREATED, "error while creating safe object")
+ cs (SEC_ERROR_BAGGAGE_NOT_CREATED, "error while creating baggage object")
+ cs (XP_JAVA_REMOVE_PRINCIPAL_ERROR, "Couldn't remove the principal")
+ cs (XP_JAVA_DELETE_PRIVILEGE_ERROR, "Couldn't delete the privilege")
+ cs (XP_JAVA_CERT_NOT_EXISTS_ERROR, "This principal doesn't have a certificate")
+ cs (SEC_ERROR_BAD_EXPORT_ALGORITHM, "Required algorithm is not allowed.")
+ cs (SEC_ERROR_EXPORTING_CERTIFICATES, "Error attempting to export certificates.")
+ cs (SEC_ERROR_IMPORTING_CERTIFICATES, "Error attempting to import certificates.")
+ cs (SEC_ERROR_PKCS12_DECODING_PFX, "Unable to import. Decoding error. File not valid.")
+ cs (SEC_ERROR_PKCS12_INVALID_MAC, "Unable to import. Invalid MAC. Incorrect password or corrupt file.")
+ cs (SEC_ERROR_PKCS12_UNSUPPORTED_MAC_ALGORITHM, "Unable to import. MAC algorithm not supported.")
+ cs (SEC_ERROR_PKCS12_UNSUPPORTED_TRANSPORT_MODE, "Unable to import. Only password integrity and privacy modes supported.")
+ cs (SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE, "Unable to import. File structure is corrupt.")
+ cs (SEC_ERROR_PKCS12_UNSUPPORTED_PBE_ALGORITHM, "Unable to import. Encryption algorithm not supported.")
+ cs (SEC_ERROR_PKCS12_UNSUPPORTED_VERSION, "Unable to import. File version not supported.")
+ cs (SEC_ERROR_PKCS12_PRIVACY_PASSWORD_INCORRECT, "Unable to import. Incorrect privacy password.")
+ cs (SEC_ERROR_PKCS12_CERT_COLLISION, "Unable to import. Same nickname already exists in database.")
+ cs (SEC_ERROR_USER_CANCELLED, "The user pressed cancel.")
+ cs (SEC_ERROR_PKCS12_DUPLICATE_DATA, "Not imported, already in database.")
+ cs (SEC_ERROR_MESSAGE_SEND_ABORTED, "Message not sent.")
+ cs (SEC_ERROR_INADEQUATE_KEY_USAGE, "Certificate key usage inadequate for attempted operation.")
+ cs (SEC_ERROR_INADEQUATE_CERT_TYPE, "Certificate type not approved for application.")
+ cs (SEC_ERROR_CERT_ADDR_MISMATCH, "Address in signing certificate does not match address in message headers.")
+ cs (SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY, "Unable to import. Error attempting to import private key.")
+ cs (SEC_ERROR_PKCS12_IMPORTING_CERT_CHAIN, "Unable to import. Error attempting to import certificate chain.")
+ cs (SEC_ERROR_PKCS12_UNABLE_TO_LOCATE_OBJECT_BY_NAME, "Unable to export. Unable to locate certificate or key by nickname.")
+ cs (SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY, "Unable to export. Private Key could not be located and exported.")
+ cs (SEC_ERROR_PKCS12_UNABLE_TO_WRITE, "Unable to export. Unable to write the export file.")
+ cs (SEC_ERROR_PKCS12_UNABLE_TO_READ, "Unable to import. Unable to read the import file.")
+ cs (SEC_ERROR_PKCS12_KEY_DATABASE_NOT_INITIALIZED, "Unable to export. Key database corrupt or deleted.")
+ cs (SEC_ERROR_KEYGEN_FAIL, "Unable to generate public/private key pair.")
+ cs (SEC_ERROR_INVALID_PASSWORD, "Password entered is invalid. Please pick a different one.")
+ cs (SEC_ERROR_RETRY_OLD_PASSWORD, "Old password entered incorrectly. Please try again.")
+ cs (SEC_ERROR_BAD_NICKNAME, "Certificate nickname already in use.")
+ cs (SEC_ERROR_NOT_FORTEZZA_ISSUER, "Peer FORTEZZA chain has a non-FORTEZZA Certificate.")
+ cs (SEC_ERROR_CANNOT_MOVE_SENSITIVE_KEY, "A sensitive key cannot be moved to the slot where it is needed.")
+ cs (SEC_ERROR_JS_INVALID_MODULE_NAME, "Invalid module name.")
+ cs (SEC_ERROR_JS_INVALID_DLL, "Invalid module path/filename")
+ cs (SEC_ERROR_JS_ADD_MOD_FAILURE, "Unable to add module")
+ cs (SEC_ERROR_JS_DEL_MOD_FAILURE, "Unable to delete module")
+ cs (SEC_ERROR_OLD_KRL, "New KRL is not later than the current one.")
+ cs (SEC_ERROR_CKL_CONFLICT, "New CKL has different issuer than current CKL. Delete current CKL.")
+ cs (SEC_ERROR_CERT_NOT_IN_NAME_SPACE, "The Certifying Authority for this certificate is not permitted to issue a certificate with this name.")
+ cs (SEC_ERROR_KRL_NOT_YET_VALID, "The key revocation list for this certificate is not yet valid.")
+ cs (SEC_ERROR_CRL_NOT_YET_VALID, "The certificate revocation list for this certificate is not yet valid.")
+ cs (SEC_ERROR_UNKNOWN_CERT, "The requested certificate could not be found.")
+ cs (SEC_ERROR_UNKNOWN_SIGNER, "The signer's certificate could not be found.")
+ cs (SEC_ERROR_CERT_BAD_ACCESS_LOCATION, "The location for the certificate status server has invalid format.")
+ cs (SEC_ERROR_OCSP_UNKNOWN_RESPONSE_TYPE, "The OCSP response cannot be fully decoded; it is of an unknown type.")
+ cs (SEC_ERROR_OCSP_BAD_HTTP_RESPONSE, "The OCSP server returned unexpected/invalid HTTP data.")
+ cs (SEC_ERROR_OCSP_MALFORMED_REQUEST, "The OCSP server found the request to be corrupted or improperly formed.")
+ cs (SEC_ERROR_OCSP_SERVER_ERROR, "The OCSP server experienced an internal error.")
+ cs (SEC_ERROR_OCSP_TRY_SERVER_LATER, "The OCSP server suggests trying again later.")
+ cs (SEC_ERROR_OCSP_REQUEST_NEEDS_SIG, "The OCSP server requires a signature on this request.")
+ cs (SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST, "The OCSP server has refused this request as unauthorized.")
+ cs (SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS, "The OCSP server returned an unrecognizable status.")
+ cs (SEC_ERROR_OCSP_UNKNOWN_CERT, "The OCSP server has no status for the certificate.")
+ cs (SEC_ERROR_OCSP_NOT_ENABLED, "You must enable OCSP before performing this operation.")
+ cs (SEC_ERROR_OCSP_NO_DEFAULT_RESPONDER, "You must set the OCSP default responder before performing this operation.")
+ cs (SEC_ERROR_OCSP_MALFORMED_RESPONSE, "The response from the OCSP server was corrupted or improperly formed.")
+ cs (SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE, "The signer of the OCSP response is not authorized to give status for this certificate.")
+ cs (SEC_ERROR_OCSP_FUTURE_RESPONSE, "The OCSP response is not yet valid (contains a date in the future).")
+ cs (SEC_ERROR_OCSP_OLD_RESPONSE, "The OCSP response contains out-of-date information.")
+ cs (SEC_ERROR_DIGEST_NOT_FOUND, "The CMS or PKCS #7 Digest was not found in signed message.")
+ cs (SEC_ERROR_UNSUPPORTED_MESSAGE_TYPE, "The CMS or PKCS #7 Message type is unsupported.")
+ cs (SEC_ERROR_MODULE_STUCK, "PKCS #11 module could not be removed because it is still in use.")
+ cs (SEC_ERROR_BAD_TEMPLATE, "Could not decode ASN.1 data. Specified template was invalid.")
+ cs (SEC_ERROR_CRL_NOT_FOUND, "No matching CRL was found.")
+ cs (SEC_ERROR_REUSED_ISSUER_AND_SERIAL, "You are attempting to import a cert with the same issuer/serial as an existing cert, but that is not the same cert.")
+ cs (SEC_ERROR_BUSY, "NSS could not shutdown. Objects are still in use.")
+ cs (SEC_ERROR_EXTRA_INPUT, "DER-encoded message contained extra unused data.")
+ cs (SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE, "Unsupported elliptic curve.")
+ cs (SEC_ERROR_UNSUPPORTED_EC_POINT_FORM, "Unsupported elliptic curve point form.")
+ cs (SEC_ERROR_UNRECOGNIZED_OID, "Unrecognized Object Identifier.")
+ cs (SEC_ERROR_OCSP_INVALID_SIGNING_CERT, "Invalid OCSP signing certificate in OCSP response.")
+ cs (SEC_ERROR_REVOKED_CERTIFICATE_CRL, "Certificate is revoked in issuer's certificate revocation list.")
+ cs (SEC_ERROR_REVOKED_CERTIFICATE_OCSP, "Issuer's OCSP responder reports certificate is revoked.")
+ cs (SEC_ERROR_CRL_INVALID_VERSION, "Issuer's Certificate Revocation List has an unknown version number.")
+ cs (SEC_ERROR_CRL_V1_CRITICAL_EXTENSION, "Issuer's V1 Certificate Revocation List has a critical extension.")
+ cs (SEC_ERROR_CRL_UNKNOWN_CRITICAL_EXTENSION, "Issuer's V2 Certificate Revocation List has an unknown critical extension.")
+ cs (SEC_ERROR_UNKNOWN_OBJECT_TYPE, "Unknown object type specified.")
+ cs (SEC_ERROR_INCOMPATIBLE_PKCS11, "PKCS #11 driver violates the spec in an incompatible way.")
+ cs (SEC_ERROR_NO_EVENT, "No new slot event is available at this time.")
+ cs (SEC_ERROR_CRL_ALREADY_EXISTS, "CRL already exists.")
+ cs (SEC_ERROR_NOT_INITIALIZED, "NSS is not initialized.")
+ cs (SEC_ERROR_TOKEN_NOT_LOGGED_IN, "The operation failed because the PKCS#11 token is not logged in.")
+ cs (SEC_ERROR_OCSP_RESPONDER_CERT_INVALID, "Configured OCSP responder's certificate is invalid.")
+ cs (SEC_ERROR_OCSP_BAD_SIGNATURE, "OCSP response has an invalid signature.")
+
+ #if defined (NSS_VMAJOR) && defined (NSS_VMINOR) && defined (NSS_VPATCH) && (NSS_VMAJOR > 3 || (NSS_VMAJOR == 3 && NSS_VMINOR > 12) || (NSS_VMAJOR == 3 && NSS_VMINOR == 12 && NSS_VPATCH >= 2))
+ cs (SEC_ERROR_OUT_OF_SEARCH_LIMITS, "Cert validation search is out of search limits")
+ cs (SEC_ERROR_INVALID_POLICY_MAPPING, "Policy mapping contains anypolicy")
+ cs (SEC_ERROR_POLICY_VALIDATION_FAILED, "Cert chain fails policy validation")
+ cs (SEC_ERROR_UNKNOWN_AIA_LOCATION_TYPE, "Unknown location type in cert AIA extension")
+ cs (SEC_ERROR_BAD_HTTP_RESPONSE, "Server returned bad HTTP response")
+ cs (SEC_ERROR_BAD_LDAP_RESPONSE, "Server returned bad LDAP response")
+ cs (SEC_ERROR_FAILED_TO_ENCODE_DATA, "Failed to encode data with ASN1 encoder")
+ cs (SEC_ERROR_BAD_INFO_ACCESS_LOCATION, "Bad information access location in cert extension")
+ cs (SEC_ERROR_LIBPKIX_INTERNAL, "Libpkix internal error occurred during cert validation.")
+ cs (SEC_ERROR_PKCS11_GENERAL_ERROR, "A PKCS #11 module returned CKR_GENERAL_ERROR, indicating that an unrecoverable error has occurred.")
+ cs (SEC_ERROR_PKCS11_FUNCTION_FAILED, "A PKCS #11 module returned CKR_FUNCTION_FAILED, indicating that the requested function could not be performed. Trying the same operation again might succeed.")
+ cs (SEC_ERROR_PKCS11_DEVICE_ERROR, "A PKCS #11 module returned CKR_DEVICE_ERROR, indicating that a problem has occurred with the token or slot.")
+ #endif
+ }
+
+ #undef cs
+
+ return NULL;
+}
+
+static void
+set_nss_error (GError **error,
+ const gchar *def_error)
+{
+ glong err_code;
+
+ g_return_if_fail (def_error != NULL);
+
+ err_code = PORT_GetError ();
+
+ if (!err_code) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ "%s", def_error);
+ } else {
+ const gchar *err_str;
+
+ err_str = nss_error_to_string (err_code);
+ if (!err_str)
+ err_str = "Uknown error.";
+
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ "%s (%d) - %s", err_str, (gint) err_code, def_error);
+ }
+}
+
+static NSSCMSMessage *
+sm_signing_cmsmessage (CamelSMIMEContext *context,
+ const gchar *nick,
+ SECOidTag *hash,
+ gint detached,
+ GError **error)
+{
+ CamelSMIMEContextPrivate *p = context->priv;
+ NSSCMSMessage *cmsg = NULL;
+ NSSCMSContentInfo *cinfo;
+ NSSCMSSignedData *sigd;
+ NSSCMSSignerInfo *signerinfo;
+ CERTCertificate *cert= NULL, *ekpcert = NULL;
+
+ g_return_val_if_fail (hash != NULL, NULL);
+
+ if ((cert = CERT_FindUserCertByUsage (p->certdb,
+ (gchar *) nick,
+ certUsageEmailSigner,
+ PR_TRUE,
+ NULL)) == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot find certificate for '%s'"), nick);
+ return NULL;
+ }
+
+ if (*hash == SEC_OID_UNKNOWN) {
+ /* use signature algorithm from the certificate */
+ switch (SECOID_GetAlgorithmTag (&cert->signature)) {
+ case SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION:
+ *hash = SEC_OID_SHA256;
+ break;
+ case SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION:
+ *hash = SEC_OID_SHA384;
+ break;
+ case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION:
+ *hash = SEC_OID_SHA512;
+ break;
+ case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION:
+ *hash = SEC_OID_MD5;
+ break;
+ case SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION:
+ default:
+ *hash = SEC_OID_SHA1;
+ break;
+ }
+ }
+
+ cmsg = NSS_CMSMessage_Create (NULL); /* create a message on its own pool */
+ if (cmsg == NULL) {
+ set_nss_error (error, _("Cannot create CMS message"));
+ goto fail;
+ }
+
+ if ((sigd = NSS_CMSSignedData_Create (cmsg)) == NULL) {
+ set_nss_error (error, _("Cannot create CMS signed data"));
+ goto fail;
+ }
+
+ cinfo = NSS_CMSMessage_GetContentInfo (cmsg);
+ if (NSS_CMSContentInfo_SetContent_SignedData (cmsg, cinfo, sigd) != SECSuccess) {
+ set_nss_error (error, _("Cannot attach CMS signed data"));
+ 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) {
+ set_nss_error (error, _("Cannot attach CMS data"));
+ goto fail;
+ }
+
+ signerinfo = NSS_CMSSignerInfo_Create (cmsg, cert, *hash);
+ if (signerinfo == NULL) {
+ set_nss_error (error, _("Cannot create CMS Signer information"));
+ goto fail;
+ }
+
+ /* we want the cert chain included for this one */
+ if (NSS_CMSSignerInfo_IncludeCerts (signerinfo, NSSCMSCM_CertChain, certUsageEmailSigner) != SECSuccess) {
+ set_nss_error (error, _("Cannot find certificate chain"));
+ goto fail;
+ }
+
+ /* SMIME RFC says signing time should always be added */
+ if (NSS_CMSSignerInfo_AddSigningTime (signerinfo, PR_Now ()) != SECSuccess) {
+ set_nss_error (error, _("Cannot add CMS Signing time"));
+ 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_TRUE, NULL)) == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Encryption certificate 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, (gchar *) nick,
+ certUsageEmailRecipient, PR_TRUE, NULL)) == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Encryption certificate for '%s' does not exist"), nick);
+ goto fail;
+ }
+ enccert = ekpcert;
+ }
+
+ if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs (signerinfo, enccert, p->certdb) != SECSuccess) {
+ set_nss_error (error, _("Cannot add SMIMEEncKeyPrefs attribute"));
+ goto fail;
+ }
+
+ if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs (signerinfo, enccert, p->certdb) != SECSuccess) {
+ set_nss_error (error, _("Cannot add MS SMIMEEncKeyPrefs attribute"));
+ goto fail;
+ }
+
+ if (ekpcert != NULL && NSS_CMSSignedData_AddCertificate (sigd, ekpcert) != SECSuccess) {
+ set_nss_error (error, _("Cannot add encryption certificate"));
+ goto fail;
+ }
+ }
+
+ if (NSS_CMSSignedData_AddSignerInfo (sigd, signerinfo) != SECSuccess) {
+ set_nss_error (error, _("Cannot add CMS Signer information"));
+ 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 const gchar *
+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:
+ /* Translators: A fallback message when couldn't verify an SMIME signature */
+ 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,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *) context)->priv;
+ NSSCMSSignedData *sigd = NULL;
+#if 0
+ NSSCMSEnvelopedData *envd;
+ NSSCMSEncryptedData *encd;
+#endif
+ SECAlgorithmID **digestalgs;
+ NSSCMSDigestContext *digcx;
+ gint count, i, nsigners, j;
+ SECItem **digests;
+ PLArenaPool *poolp = NULL;
+ CamelStream *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);
+ GByteArray *buffer;
+ gint which_digest;
+
+ switch (typetag) {
+ case SEC_OID_PKCS7_SIGNED_DATA:
+ sigd = (NSSCMSSignedData *) NSS_CMSContentInfo_GetContent (cinfo);
+ if (sigd == NULL) {
+ set_nss_error (error, _("No signed data in signature"));
+ goto fail;
+ }
+
+ if (extstream == NULL) {
+ set_nss_error (error, _("Digests missing from enveloped data"));
+ goto fail;
+ }
+
+ if ((poolp = PORT_NewArena (1024)) == NULL) {
+ set_nss_error (error, g_strerror (ENOMEM));
+ goto fail;
+ }
+
+ digestalgs = NSS_CMSSignedData_GetDigestAlgs (sigd);
+
+ digcx = NSS_CMSDigestContext_StartMultiple (digestalgs);
+ if (digcx == NULL) {
+ set_nss_error (error, _("Cannot calculate digests"));
+ goto fail;
+ }
+
+ buffer = g_byte_array_new ();
+ mem = camel_stream_mem_new_with_byte_array (buffer);
+ camel_stream_write_to_stream (extstream, mem, cancellable, NULL);
+ NSS_CMSDigestContext_Update (digcx, buffer->data, buffer->len);
+ g_object_unref (mem);
+
+ if (NSS_CMSDigestContext_FinishMultiple (digcx, poolp, &digests) != SECSuccess) {
+ set_nss_error (error, _("Cannot calculate digests"));
+ goto fail;
+ }
+
+ for (which_digest = 0; digests[which_digest] != NULL; which_digest++) {
+ SECOidData *digest_alg = SECOID_FindOID (&digestalgs[which_digest]->algorithm);
+ if (digest_alg == NULL) {
+ set_nss_error (error, _("Cannot set message digests"));
+ goto fail;
+ }
+ if (NSS_CMSSignedData_SetDigestValue (sigd, digest_alg->offset, digests[which_digest]) != SECSuccess) {
+ set_nss_error (error, _("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) {
+ set_nss_error (error, _("Certificate import failed"));
+ goto fail;
+ }
+
+ if (NSS_CMSSignedData_ImportCerts (sigd, p->certdb, certUsageEmailRecipient, PR_TRUE) != SECSuccess) {
+ set_nss_error (error, _("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 is the only message, cannot verify certificates"));
+ } else {
+ status = NSSCMSVS_GoodSignature;
+ g_string_printf (description, _("Certificate is the only message, certificates imported and verified"));
+ }
+ } else {
+ if (!NSS_CMSSignedData_HasDigests (sigd)) {
+ set_nss_error (error, _("Cannot find signature digests"));
+ goto fail;
+ }
+
+ for (j = 0; j < nsigners; j++) {
+ NSSCMSSignerInfo *si;
+ gchar *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_ex (
+ valid, CAMEL_CIPHER_VALIDITY_SIGN, cn, em,
+ smime_cert_data_clone (NSS_CMSSignerInfo_GetSigningCertificate (si, p->certdb)),
+ smime_cert_data_free, smime_cert_data_clone);
+
+ 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:
+ /* FIXME Do something with this? */
+ /*envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent (cinfo);*/
+ break;
+ case SEC_OID_PKCS7_ENCRYPTED_DATA:
+ /* FIXME Do something with this? */
+ /*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 const gchar *
+smime_context_hash_to_id (CamelCipherContext *context,
+ CamelCipherHash hash)
+{
+ switch (hash) {
+ /* Support registered IANA hash function textual names.
+ * http://www.iana.org/assignments/hash-function-text-names */
+ case CAMEL_CIPHER_HASH_MD5:
+ return "md5";
+ case CAMEL_CIPHER_HASH_SHA1:
+ case CAMEL_CIPHER_HASH_DEFAULT:
+ return "sha-1";
+ case CAMEL_CIPHER_HASH_SHA256:
+ return "sha-256";
+ case CAMEL_CIPHER_HASH_SHA384:
+ return "sha-384";
+ case CAMEL_CIPHER_HASH_SHA512:
+ return "sha-512";
+ default:
+ return NULL;
+ }
+}
+
+static CamelCipherHash
+smime_context_id_to_hash (CamelCipherContext *context,
+ const gchar *id)
+{
+ if (id != NULL) {
+ /* Support registered IANA hash function textual names.
+ * http://www.iana.org/assignments/hash-function-text-names */
+ if (g_str_equal (id, "md5"))
+ return CAMEL_CIPHER_HASH_MD5;
+ if (g_str_equal (id, "sha-1"))
+ return CAMEL_CIPHER_HASH_SHA1;
+ if (g_str_equal (id, "sha-256"))
+ return CAMEL_CIPHER_HASH_SHA256;
+ if (g_str_equal (id, "sha-384"))
+ return CAMEL_CIPHER_HASH_SHA384;
+ if (g_str_equal (id, "sha-512"))
+ return CAMEL_CIPHER_HASH_SHA512;
+
+ /* Non-standard names. */
+ if (g_str_equal (id, "sha1"))
+ return CAMEL_CIPHER_HASH_SHA1;
+ if (g_str_equal (id, "sha256"))
+ return CAMEL_CIPHER_HASH_SHA256;
+ if (g_str_equal (id, "sha384"))
+ return CAMEL_CIPHER_HASH_SHA384;
+ if (g_str_equal (id, "sha512"))
+ return CAMEL_CIPHER_HASH_SHA512;
+ }
+
+ return CAMEL_CIPHER_HASH_DEFAULT;
+}
+
+static CamelCipherHash
+get_hash_from_oid (SECOidTag oidTag)
+{
+ switch (oidTag) {
+ case SEC_OID_SHA1:
+ return CAMEL_CIPHER_HASH_SHA1;
+ case SEC_OID_SHA256:
+ return CAMEL_CIPHER_HASH_SHA256;
+ case SEC_OID_SHA384:
+ return CAMEL_CIPHER_HASH_SHA384;
+ case SEC_OID_SHA512:
+ return CAMEL_CIPHER_HASH_SHA512;
+ case SEC_OID_MD5:
+ return CAMEL_CIPHER_HASH_MD5;
+ default:
+ break;
+ }
+
+ return CAMEL_CIPHER_HASH_DEFAULT;
+}
+
+static gboolean
+smime_context_sign_sync (CamelCipherContext *context,
+ const gchar *userid,
+ CamelCipherHash hash,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelCipherContextClass *class;
+ NSSCMSMessage *cmsg;
+ CamelStream *ostream, *istream;
+ GByteArray *buffer;
+ SECOidTag sechash;
+ NSSCMSEncoderContext *enc;
+ CamelDataWrapper *dw;
+ CamelContentType *ct;
+ gboolean success = FALSE;
+
+ class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context);
+
+ switch (hash) {
+ case CAMEL_CIPHER_HASH_DEFAULT:
+ default:
+ sechash = SEC_OID_UNKNOWN;
+ break;
+ case CAMEL_CIPHER_HASH_SHA1:
+ sechash = SEC_OID_SHA1;
+ break;
+ case CAMEL_CIPHER_HASH_SHA256:
+ sechash = SEC_OID_SHA256;
+ break;
+ case CAMEL_CIPHER_HASH_SHA384:
+ sechash = SEC_OID_SHA384;
+ break;
+ case CAMEL_CIPHER_HASH_SHA512:
+ sechash = SEC_OID_SHA512;
+ 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, error);
+ if (cmsg == NULL)
+ return FALSE;
+
+ ostream = camel_stream_mem_new ();
+
+ /* FIXME: stream this, we stream output at least */
+ buffer = g_byte_array_new ();
+ istream = camel_stream_mem_new_with_byte_array (buffer);
+
+ if (camel_cipher_canonical_to_stream (
+ ipart, CAMEL_MIME_FILTER_CANON_STRIP |
+ CAMEL_MIME_FILTER_CANON_CRLF |
+ CAMEL_MIME_FILTER_CANON_FROM,
+ istream, cancellable, error) == -1) {
+ g_prefix_error (
+ error, _("Could not generate signing data: "));
+ 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) {
+ set_nss_error (error, _("Cannot create encoder context"));
+ goto fail;
+ }
+
+ if (NSS_CMSEncoder_Update (enc, (gchar *) buffer->data, buffer->len) != SECSuccess) {
+ NSS_CMSEncoder_Cancel (enc);
+ set_nss_error (error, _("Failed to add data to CMS encoder"));
+ goto fail;
+ }
+
+ if (NSS_CMSEncoder_Finish (enc) != SECSuccess) {
+ set_nss_error (error, _("Failed to encode data"));
+ goto fail;
+ }
+
+ success = TRUE;
+
+ dw = camel_data_wrapper_new ();
+ g_seekable_seek (G_SEEKABLE (ostream), 0, G_SEEK_SET, NULL, NULL);
+ camel_data_wrapper_construct_from_stream_sync (
+ dw, ostream, cancellable, NULL);
+ 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 ((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_context_hash_to_id (context, get_hash_from_oid (sechash)));
+ camel_content_type_set_param (ct, "protocol", class->sign_protocol);
+ camel_data_wrapper_set_mime_type_field ((CamelDataWrapper *) mps, ct);
+ camel_content_type_unref (ct);
+ camel_multipart_set_boundary ((CamelMultipart *) mps, NULL);
+
+ camel_multipart_signed_set_signature (mps, sigpart);
+ camel_multipart_signed_set_content_stream (mps, istream);
+
+ g_object_unref (sigpart);
+
+ g_seekable_seek (
+ G_SEEKABLE (istream), 0,
+ G_SEEK_SET, NULL, NULL);
+
+ camel_medium_set_content ((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 ((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);
+ }
+
+ g_object_unref (dw);
+fail:
+ g_object_unref (ostream);
+ g_object_unref (istream);
+
+ return success;
+}
+
+static CamelCipherValidity *
+smime_context_verify_sync (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelCipherContextClass *class;
+ NSSCMSDecoderContext *dec;
+ NSSCMSMessage *cmsg;
+ CamelStream *mem;
+ CamelStream *constream = NULL;
+ CamelCipherValidity *valid = NULL;
+ CamelContentType *ct;
+ const gchar *tmp;
+ CamelMimePart *sigpart;
+ CamelDataWrapper *dw;
+ GByteArray *buffer;
+
+ class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context);
+
+ dw = camel_medium_get_content ((CamelMedium *) ipart);
+ ct = dw->mime_type;
+
+ /* FIXME: we should stream this to the decoder */
+ buffer = g_byte_array_new ();
+ mem = camel_stream_mem_new_with_byte_array (buffer);
+
+ 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, class->sign_protocol) != 0
+ && g_ascii_strcasecmp (tmp, "application/pkcs7-signature") != 0)) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot verify message signature: "
+ "Incorrect message format"));
+ goto fail;
+ }
+
+ constream = camel_multipart_signed_get_content_stream (
+ (CamelMultipartSigned *) mps, error);
+ if (constream == NULL)
+ goto fail;
+
+ sigpart = camel_multipart_get_part (mps, CAMEL_MULTIPART_SIGNED_SIGNATURE);
+ if (sigpart == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot verify message signature: "
+ "Incorrect message format"));
+ goto fail;
+ }
+ } else if (camel_content_type_is (ct, "application", "x-pkcs7-mime")) {
+ sigpart = ipart;
+ } else {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("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_sync (
+ camel_medium_get_content (
+ CAMEL_MEDIUM (sigpart)), mem, cancellable, NULL);
+ if (NSS_CMSDecoder_Update (dec, (gchar *) buffer->data, buffer->len) != SECSuccess) {
+ g_warning ("%s: Failed to call NSS_CMSDecoder_Update", G_STRFUNC);
+ }
+ cmsg = NSS_CMSDecoder_Finish (dec);
+ if (cmsg == NULL) {
+ set_nss_error (error, _("Decoder failed"));
+ goto fail;
+ }
+
+ valid = sm_verify_cmsg (context, cmsg, constream, cancellable, error);
+
+ NSS_CMSMessage_Destroy (cmsg);
+fail:
+ g_object_unref (mem);
+ if (constream)
+ g_object_unref (constream);
+
+ return valid;
+}
+
+static gboolean
+smime_context_encrypt_sync (CamelCipherContext *context,
+ const gchar *userid,
+ GPtrArray *recipients,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *) context)->priv;
+ /*NSSCMSRecipientInfo **recipient_infos;*/
+ CERTCertificate **recipient_certs = NULL;
+ NSSCMSContentInfo *cinfo;
+ PK11SymKey *bulkkey = NULL;
+ SECOidTag bulkalgtag;
+ gint bulkkeysize, i;
+ CK_MECHANISM_TYPE type;
+ PK11SlotInfo *slot;
+ PLArenaPool *poolp;
+ NSSCMSMessage *cmsg = NULL;
+ NSSCMSEnvelopedData *envd;
+ NSSCMSEncoderContext *enc = NULL;
+ CamelStream *mem;
+ CamelStream *ostream = NULL;
+ CamelDataWrapper *dw;
+ CamelContentType *ct;
+ GByteArray *buffer;
+
+ poolp = PORT_NewArena (1024);
+ if (poolp == NULL) {
+ set_nss_error (error, g_strerror (ENOMEM));
+ return FALSE;
+ }
+
+ /* Lookup all recipients certs, for later working */
+ recipient_certs = (CERTCertificate **) PORT_ArenaZAlloc (poolp, sizeof (recipient_certs[0]) * (recipients->len + 1));
+ if (recipient_certs == NULL) {
+ set_nss_error (error, 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) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot find certificate for '%s'"),
+ (gchar *) recipients->pdata[i]);
+ goto fail;
+ }
+ }
+
+ /* Find a common algorithm, probably 3DES anyway ... */
+ if (NSS_SMIMEUtil_FindBulkAlgForRecipients (recipient_certs, &bulkalgtag, &bulkkeysize) != SECSuccess) {
+ set_nss_error (error, _("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) {
+ set_nss_error (error, _("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) {
+ set_nss_error (error, _("Cannot create CMS Message"));
+ goto fail;
+ }
+
+ envd = NSS_CMSEnvelopedData_Create (cmsg, bulkalgtag, bulkkeysize);
+ if (envd == NULL) {
+ set_nss_error (error, _("Cannot create CMS Enveloped data"));
+ goto fail;
+ }
+
+ cinfo = NSS_CMSMessage_GetContentInfo (cmsg);
+ if (NSS_CMSContentInfo_SetContent_EnvelopedData (cmsg, cinfo, envd) != SECSuccess) {
+ set_nss_error (error, _("Cannot attach CMS Enveloped data"));
+ goto fail;
+ }
+
+ cinfo = NSS_CMSEnvelopedData_GetContentInfo (envd);
+ if (NSS_CMSContentInfo_SetContent_Data (cmsg, cinfo, NULL, PR_FALSE) != SECSuccess) {
+ set_nss_error (error, _("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) {
+ set_nss_error (error, _("Cannot create CMS Recipient information"));
+ goto fail;
+ }
+
+ if (NSS_CMSEnvelopedData_AddRecipient (envd, ri) != SECSuccess) {
+ set_nss_error (error, _("Cannot add CMS Recipient information"));
+ 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) {
+ set_nss_error (error, _("Cannot create encoder context"));
+ goto fail;
+ }
+
+ /* FIXME: Stream the input */
+ buffer = g_byte_array_new ();
+ mem = camel_stream_mem_new_with_byte_array (buffer);
+ camel_cipher_canonical_to_stream (ipart, CAMEL_MIME_FILTER_CANON_CRLF, mem, NULL, NULL);
+ if (NSS_CMSEncoder_Update (enc, (gchar *) buffer->data, buffer->len) != SECSuccess) {
+ NSS_CMSEncoder_Cancel (enc);
+ g_object_unref (mem);
+ set_nss_error (error, _("Failed to add data to encoder"));
+ goto fail;
+ }
+ g_object_unref (mem);
+
+ if (NSS_CMSEncoder_Finish (enc) != SECSuccess) {
+ set_nss_error (error, _("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_sync (
+ dw, ostream, NULL, NULL);
+ g_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 ((CamelMedium *) opart, dw);
+ g_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 TRUE;
+
+fail:
+ if (ostream)
+ g_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 FALSE;
+}
+
+static CamelCipherValidity *
+smime_context_decrypt_sync (CamelCipherContext *context,
+ CamelMimePart *ipart,
+ CamelMimePart *opart,
+ GCancellable *cancellable,
+ GError **error)
+{
+ NSSCMSDecoderContext *dec;
+ NSSCMSMessage *cmsg;
+ CamelStream *istream;
+ CamelStream *ostream;
+ CamelCipherValidity *valid = NULL;
+ GByteArray *buffer;
+
+ /* 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 (CAMEL_STREAM_MEM (ostream));
+
+ /* FIXME: stream this to the decoder incrementally */
+ buffer = g_byte_array_new ();
+ istream = camel_stream_mem_new_with_byte_array (buffer);
+ if (!camel_data_wrapper_decode_to_stream_sync (
+ camel_medium_get_content (CAMEL_MEDIUM (ipart)),
+ istream, cancellable, error)) {
+ g_object_unref (istream);
+ goto fail;
+ }
+
+ g_seekable_seek (G_SEEKABLE (istream), 0, G_SEEK_SET, NULL, NULL);
+
+ dec = NSS_CMSDecoder_Start (
+ NULL,
+ sm_write_stream, ostream, /* content callback */
+ NULL, NULL,
+ NULL, NULL); /* decrypt key callback */
+
+ if (NSS_CMSDecoder_Update (dec, (gchar *) buffer->data, buffer->len) != SECSuccess) {
+ cmsg = NULL;
+ } else {
+ cmsg = NSS_CMSDecoder_Finish (dec);
+ }
+
+ g_object_unref (istream);
+
+ if (cmsg == NULL) {
+ set_nss_error (error, _("Decoder failed"));
+ goto fail;
+ }
+
+#if 0
+ /* not sure if we really care about this? */
+ if (!NSS_CMSMessage_IsEncrypted (cmsg)) {
+ set_nss_error (ex, _("S/MIME Decrypt: No encrypted content found"));
+ NSS_CMSMessage_Destroy (cmsg);
+ goto fail;
+ }
+#endif
+
+ g_seekable_seek (G_SEEKABLE (ostream), 0, G_SEEK_SET, NULL, NULL);
+
+ camel_data_wrapper_construct_from_stream_sync (
+ CAMEL_DATA_WRAPPER (opart), ostream, NULL, NULL);
+
+ if (NSS_CMSMessage_IsSigned (cmsg)) {
+ g_seekable_seek (
+ G_SEEKABLE (ostream), 0, G_SEEK_SET, NULL, NULL);
+ valid = sm_verify_cmsg (
+ context, cmsg, ostream, cancellable, error);
+ } 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:
+ g_object_unref (ostream);
+
+ return valid;
+}
+
+static void
+camel_smime_context_class_init (CamelSMIMEContextClass *class)
+{
+ CamelCipherContextClass *cipher_context_class;
+
+ g_type_class_add_private (class, sizeof (CamelSMIMEContextPrivate));
+
+ cipher_context_class = CAMEL_CIPHER_CONTEXT_CLASS (class);
+ cipher_context_class->sign_protocol = "application/x-pkcs7-signature";
+ cipher_context_class->encrypt_protocol = "application/x-pkcs7-mime";
+ cipher_context_class->key_protocol = "application/x-pkcs7-signature";
+ cipher_context_class->hash_to_id = smime_context_hash_to_id;
+ cipher_context_class->id_to_hash = smime_context_id_to_hash;
+ cipher_context_class->sign_sync = smime_context_sign_sync;
+ cipher_context_class->verify_sync = smime_context_verify_sync;
+ cipher_context_class->encrypt_sync = smime_context_encrypt_sync;
+ cipher_context_class->decrypt_sync = smime_context_decrypt_sync;
+}
+
+static void
+camel_smime_context_init (CamelSMIMEContext *smime_context)
+{
+ smime_context->priv = CAMEL_SMIME_CONTEXT_GET_PRIVATE (smime_context);
+ smime_context->priv->certdb = CERT_GetDefaultCertDB ();
+ smime_context->priv->sign_mode = CAMEL_SMIME_SIGN_CLEARSIGN;
+ smime_context->priv->password_tries = 0;
+}
+
+/**
+ * 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)
+{
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+
+ return g_object_new (
+ CAMEL_TYPE_SMIME_CONTEXT,
+ "session", session, NULL);
+}
+
+void
+camel_smime_context_set_encrypt_key (CamelSMIMEContext *context,
+ gboolean use,
+ const gchar *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,
+ CamelSMIMESign 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)
+{
+ CamelCipherContextClass *class;
+ guint32 flags = 0;
+ CamelContentType *ct;
+ const gchar *tmp;
+
+ if (!part)
+ return flags;
+
+ class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context);
+
+ 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, class->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")) {
+ CamelStream *istream;
+ NSSCMSMessage *cmsg;
+ NSSCMSDecoderContext *dec;
+ GByteArray *buffer;
+
+ /* FIXME: stream this to the decoder incrementally */
+ buffer = g_byte_array_new ();
+ istream = camel_stream_mem_new_with_byte_array (buffer);
+
+ /* FIXME Pass a GCancellable and GError here. */
+ camel_data_wrapper_decode_to_stream_sync (
+ camel_medium_get_content ((CamelMedium *) part),
+ istream, NULL, NULL);
+
+ g_seekable_seek (
+ G_SEEKABLE (istream), 0, G_SEEK_SET, NULL, NULL);
+
+ dec = NSS_CMSDecoder_Start (
+ NULL,
+ NULL, NULL,
+ NULL, NULL, /* password callback */
+ NULL, NULL); /* decrypt key callback */
+
+ NSS_CMSDecoder_Update (dec, (gchar *) buffer->data, buffer->len);
+ g_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;
+}
+
+#endif /* ENABLE_SMIME */
diff --git a/src/camel/camel-smime-context.h b/src/camel/camel-smime-context.h
new file mode 100644
index 000000000..b1dd0e977
--- /dev/null
+++ b/src/camel/camel-smime-context.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SMIME_CONTEXT_H
+#define CAMEL_SMIME_CONTEXT_H
+
+#include <camel/camel-cipher-context.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SMIME_CONTEXT \
+ (camel_smime_context_get_type ())
+#define CAMEL_SMIME_CONTEXT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SMIME_CONTEXT, CamelSMIMEContext))
+#define CAMEL_SMIME_CONTEXT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SMIME_CONTEXT, CamelSMIMEContextClass))
+#define CAMEL_IS_SMIME_CONTEXT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SMIME_CONTEXT))
+#define CAMEL_IS_SMIME_CONTEXT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SMIME_CONTEXT))
+#define CAMEL_SMIME_CONTEXT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SMIME_CONTEXT, CamelSMIMEContextClass))
+
+G_BEGIN_DECLS
+
+typedef enum _camel_smime_sign_t {
+ CAMEL_SMIME_SIGN_CLEARSIGN,
+ CAMEL_SMIME_SIGN_ENVELOPED
+} CamelSMIMESign;
+
+typedef enum _camel_smime_describe_t {
+ CAMEL_SMIME_SIGNED = 1 << 0,
+ CAMEL_SMIME_ENCRYPTED = 1 << 1,
+ CAMEL_SMIME_CERTS = 1 << 2,
+ CAMEL_SMIME_CRLS = 1 << 3
+} CamelSMIMEDescribe;
+
+typedef struct _CamelSMIMEContext CamelSMIMEContext;
+typedef struct _CamelSMIMEContextClass CamelSMIMEContextClass;
+typedef struct _CamelSMIMEContextPrivate CamelSMIMEContextPrivate;
+
+struct _CamelSMIMEContext {
+ CamelCipherContext parent;
+ CamelSMIMEContextPrivate *priv;
+};
+
+struct _CamelSMIMEContextClass {
+ CamelCipherContextClass parent_class;
+};
+
+GType camel_smime_context_get_type (void);
+
+CamelCipherContext *camel_smime_context_new (CamelSession *session);
+
+/* nick to use for SMIMEEncKeyPrefs attribute for signed data */
+void camel_smime_context_set_encrypt_key (CamelSMIMEContext *context, gboolean use, const gchar *key);
+/* set signing mode, clearsigned multipart/signed or enveloped */
+void camel_smime_context_set_sign_mode (CamelSMIMEContext *context, CamelSMIMESign type);
+
+guint32 camel_smime_context_describe_part (CamelSMIMEContext *context, struct _CamelMimePart *part);
+
+G_END_DECLS
+
+#endif /* CAMEL_SMIME_CONTEXT_H */
diff --git a/src/camel/camel-store-settings.c b/src/camel/camel-store-settings.c
new file mode 100644
index 000000000..884b8287c
--- /dev/null
+++ b/src/camel/camel-store-settings.c
@@ -0,0 +1,144 @@
+/*
+ * camel-store-settings.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-store-settings.h"
+
+#define CAMEL_STORE_SETTINGS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_STORE_SETTINGS, CamelStoreSettingsPrivate))
+
+struct _CamelStoreSettingsPrivate {
+ gboolean filter_inbox;
+};
+
+enum {
+ PROP_0,
+ PROP_FILTER_INBOX
+};
+
+G_DEFINE_TYPE (
+ CamelStoreSettings,
+ camel_store_settings,
+ CAMEL_TYPE_SETTINGS)
+
+static void
+store_settings_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_FILTER_INBOX:
+ camel_store_settings_set_filter_inbox (
+ CAMEL_STORE_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+store_settings_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_FILTER_INBOX:
+ g_value_set_boolean (
+ value,
+ camel_store_settings_get_filter_inbox (
+ CAMEL_STORE_SETTINGS (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+camel_store_settings_class_init (CamelStoreSettingsClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelStoreSettingsPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = store_settings_set_property;
+ object_class->get_property = store_settings_get_property;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FILTER_INBOX,
+ g_param_spec_boolean (
+ "filter-inbox",
+ "Filter Inbox",
+ "Whether to filter new messages in Inbox",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_store_settings_init (CamelStoreSettings *settings)
+{
+ settings->priv = CAMEL_STORE_SETTINGS_GET_PRIVATE (settings);
+}
+
+/**
+ * camel_store_settings_get_filter_inbox:
+ * @settings: a #CamelStoreSettings
+ *
+ * Returns whether to automatically apply filters to newly arrived messages
+ * in the store's Inbox folder (assuming it has an Inbox folder).
+ *
+ * Returns: whether to filter new messages in Inbox
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_store_settings_get_filter_inbox (CamelStoreSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE_SETTINGS (settings), FALSE);
+
+ return settings->priv->filter_inbox;
+}
+
+/**
+ * camel_store_settings_set_filter_inbox:
+ * @settings: a #CamelStoreSettings
+ * @filter_inbox: whether to filter new messages in Inbox
+ *
+ * Sets whether to automatically apply filters to newly arrived messages
+ * in the store's Inbox folder (assuming it has an Inbox folder).
+ *
+ * Since: 3.2
+ **/
+void
+camel_store_settings_set_filter_inbox (CamelStoreSettings *settings,
+ gboolean filter_inbox)
+{
+ g_return_if_fail (CAMEL_IS_STORE_SETTINGS (settings));
+
+ if (settings->priv->filter_inbox == filter_inbox)
+ return;
+
+ settings->priv->filter_inbox = filter_inbox;
+
+ g_object_notify (G_OBJECT (settings), "filter-inbox");
+}
diff --git a/src/camel/camel-store-settings.h b/src/camel/camel-store-settings.h
new file mode 100644
index 000000000..a743cb136
--- /dev/null
+++ b/src/camel/camel-store-settings.h
@@ -0,0 +1,78 @@
+/*
+ * camel-store-settings.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_STORE_SETTINGS_H
+#define CAMEL_STORE_SETTINGS_H
+
+#include <camel/camel-settings.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_STORE_SETTINGS \
+ (camel_store_settings_get_type ())
+#define CAMEL_STORE_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_STORE_SETTINGS, CamelStoreSettings))
+#define CAMEL_STORE_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_STORE_SETTINGS, CamelStoreSettingsClass))
+#define CAMEL_IS_STORE_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_STORE_SETTINGS))
+#define CAMEL_IS_STORE_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_STORE_SETTINGS))
+#define CAMEL_STORE_SETTINGS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_STORE_SETTINGS, CamelStoreSettingsClass))
+
+G_BEGIN_DECLS
+
+/**
+ * CamelStoreSettings:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.2
+ **/
+typedef struct _CamelStoreSettings CamelStoreSettings;
+typedef struct _CamelStoreSettingsClass CamelStoreSettingsClass;
+typedef struct _CamelStoreSettingsPrivate CamelStoreSettingsPrivate;
+
+struct _CamelStoreSettings {
+ CamelSettings parent;
+ CamelStoreSettingsPrivate *priv;
+};
+
+struct _CamelStoreSettingsClass {
+ CamelSettingsClass parent_class;
+};
+
+GType camel_store_settings_get_type (void) G_GNUC_CONST;
+gboolean camel_store_settings_get_filter_inbox
+ (CamelStoreSettings *settings);
+void camel_store_settings_set_filter_inbox
+ (CamelStoreSettings *settings,
+ gboolean filter_inbox);
+
+G_END_DECLS
+
+#endif /* CAMEL_STORE_SETTINGS_H */
diff --git a/src/camel/camel-store-summary.c b/src/camel/camel-store-summary.c
new file mode 100644
index 000000000..23527bcaf
--- /dev/null
+++ b/src/camel/camel-store-summary.c
@@ -0,0 +1,1047 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gstdio.h>
+
+#include "camel-file-utils.h"
+#include "camel-store-summary.h"
+#include "camel-folder-summary.h"
+#include "camel-url.h"
+#include "camel-win32.h"
+
+#define d(x)
+#define io(x) /* io debug */
+
+/* possible versions, for versioning changes */
+#define CAMEL_STORE_SUMMARY_VERSION_0 (1)
+#define CAMEL_STORE_SUMMARY_VERSION_2 (2)
+
+/* current version */
+#define CAMEL_STORE_SUMMARY_VERSION (2)
+
+#define CAMEL_STORE_SUMMARY_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_STORE_SUMMARY, CamelStoreSummaryPrivate))
+
+struct _CamelStoreSummaryPrivate {
+ GRecMutex summary_lock; /* for the summary hashtable/array */
+ GRecMutex io_lock; /* load/save lock, for access to saved_count, etc */
+
+ gboolean dirty; /* summary has unsaved changes */
+
+ gchar *summary_path;
+
+ /* header info */
+ guint32 version; /* version of base part of file */
+ guint32 count; /* how many were saved/loaded */
+ time_t time; /* timestamp for this summary (for implementors to use) */
+
+ GHashTable *folder_summaries; /* CamelFolderSummary->path; doesn't add reference to CamelFolderSummary */
+
+ guint scheduled_save_id;
+};
+
+G_DEFINE_TYPE (CamelStoreSummary, camel_store_summary, G_TYPE_OBJECT)
+
+static void
+store_summary_finalize (GObject *object)
+{
+ CamelStoreSummary *summary = CAMEL_STORE_SUMMARY (object);
+ guint ii;
+
+ for (ii = 0; ii < summary->folders->len; ii++) {
+ CamelStoreInfo *info;
+
+ info = g_ptr_array_index (summary->folders, ii);
+ camel_store_summary_info_unref (summary, info);
+ }
+
+ g_ptr_array_free (summary->folders, TRUE);
+ g_hash_table_destroy (summary->folders_path);
+ g_hash_table_destroy (summary->priv->folder_summaries);
+
+ g_free (summary->priv->summary_path);
+
+ g_rec_mutex_clear (&summary->priv->summary_lock);
+ g_rec_mutex_clear (&summary->priv->io_lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_store_summary_parent_class)->finalize (object);
+}
+
+static void
+store_summary_dispose (GObject *object)
+{
+ CamelStoreSummary *summary = CAMEL_STORE_SUMMARY (object);
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+
+ if (summary->priv->scheduled_save_id != 0) {
+ g_source_remove (summary->priv->scheduled_save_id);
+ summary->priv->scheduled_save_id = 0;
+ camel_store_summary_save (summary);
+ }
+
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+
+ G_OBJECT_CLASS (camel_store_summary_parent_class)->dispose (object);
+}
+
+static gint
+store_summary_summary_header_load (CamelStoreSummary *summary,
+ FILE *in)
+{
+ gint32 version, flags, count;
+ time_t time;
+
+ fseek (in, 0, SEEK_SET);
+
+ io (printf ("Loading header\n"));
+
+ /* XXX The flags value is legacy; not used for anything. */
+ if (camel_file_util_decode_fixed_int32 (in, &version) == -1
+ || camel_file_util_decode_fixed_int32 (in, &flags) == -1
+ || camel_file_util_decode_time_t (in, &time) == -1
+ || camel_file_util_decode_fixed_int32 (in, &count) == -1) {
+ return -1;
+ }
+
+ summary->priv->time = time;
+ summary->priv->count = count;
+ summary->priv->version = version;
+
+ if (version < CAMEL_STORE_SUMMARY_VERSION_0) {
+ g_warning ("Store summary header version too low");
+ return -1;
+ }
+
+ return 0;
+}
+
+static gint
+store_summary_summary_header_save (CamelStoreSummary *summary,
+ FILE *out)
+{
+ fseek (out, 0, SEEK_SET);
+
+ io (printf ("Savining header\n"));
+
+ /* always write latest version */
+ camel_file_util_encode_fixed_int32 (out, CAMEL_STORE_SUMMARY_VERSION);
+ camel_file_util_encode_fixed_int32 (out, 0); /* flags (unused) */
+ camel_file_util_encode_time_t (out, summary->priv->time);
+
+ return camel_file_util_encode_fixed_int32 (
+ out, camel_store_summary_count (summary));
+}
+
+static CamelStoreInfo *
+store_summary_store_info_new (CamelStoreSummary *summary,
+ const gchar *path)
+{
+ CamelStoreInfo *info;
+
+ info = camel_store_summary_info_new (summary);
+
+ info->path = g_strdup (path);
+ info->unread = CAMEL_STORE_INFO_FOLDER_UNKNOWN;
+ info->total = CAMEL_STORE_INFO_FOLDER_UNKNOWN;
+
+ return info;
+}
+
+static CamelStoreInfo *
+store_summary_store_info_load (CamelStoreSummary *summary,
+ FILE *in)
+{
+ CamelStoreInfo *info;
+
+ info = camel_store_summary_info_new (summary);
+
+ io (printf ("Loading folder info\n"));
+
+ if (camel_file_util_decode_string (in, &info->path) == -1 ||
+ camel_file_util_decode_uint32 (in, &info->flags) == -1 ||
+ camel_file_util_decode_uint32 (in, &info->unread) == -1 ||
+ camel_file_util_decode_uint32 (in, &info->total) == -1) {
+ camel_store_summary_info_unref (summary, info);
+
+ return NULL;
+ }
+
+ if (!ferror (in))
+ return info;
+
+ camel_store_summary_info_unref (summary, info);
+
+ return NULL;
+}
+
+static gint
+store_summary_store_info_save (CamelStoreSummary *summary,
+ FILE *out,
+ CamelStoreInfo *info)
+{
+ io (printf ("Saving folder info\n"));
+
+ if (camel_file_util_encode_string (out, camel_store_info_path (summary, info)) == -1 ||
+ camel_file_util_encode_uint32 (out, info->flags) == -1 ||
+ camel_file_util_encode_uint32 (out, info->unread) == -1 ||
+ camel_file_util_encode_uint32 (out, info->total) == -1)
+ return -1;
+
+ return ferror (out);
+}
+
+static void
+store_summary_store_info_free (CamelStoreSummary *summary,
+ CamelStoreInfo *info)
+{
+ CamelStoreSummaryClass *class;
+
+ class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
+
+ g_free (info->path);
+ g_slice_free1 (class->store_info_size, info);
+}
+
+static void
+store_summary_store_info_set_string (CamelStoreSummary *summary,
+ CamelStoreInfo *info,
+ gint type,
+ const gchar *str)
+{
+ switch (type) {
+ case CAMEL_STORE_INFO_PATH:
+ g_hash_table_remove (summary->folders_path, (gchar *) camel_store_info_path (summary, info));
+ g_free (info->path);
+ info->path = g_strdup (str);
+ g_hash_table_insert (summary->folders_path, (gchar *) camel_store_info_path (summary, info), info);
+ summary->priv->dirty = TRUE;
+ break;
+ }
+}
+
+static void
+camel_store_summary_class_init (CamelStoreSummaryClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelStoreSummaryPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = store_summary_dispose;
+ object_class->finalize = store_summary_finalize;
+
+ class->store_info_size = sizeof (CamelStoreInfo);
+ class->summary_header_load = store_summary_summary_header_load;
+ class->summary_header_save = store_summary_summary_header_save;
+ class->store_info_new = store_summary_store_info_new;
+ class->store_info_load = store_summary_store_info_load;
+ class->store_info_save = store_summary_store_info_save;
+ class->store_info_free = store_summary_store_info_free;
+ class->store_info_set_string = store_summary_store_info_set_string;
+}
+
+static void
+camel_store_summary_init (CamelStoreSummary *summary)
+{
+ summary->priv = CAMEL_STORE_SUMMARY_GET_PRIVATE (summary);
+
+ summary->priv->version = CAMEL_STORE_SUMMARY_VERSION;
+
+ summary->folders = g_ptr_array_new ();
+ summary->folders_path = g_hash_table_new (g_str_hash, g_str_equal);
+ summary->priv->folder_summaries = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
+ summary->priv->scheduled_save_id = 0;
+
+ g_rec_mutex_init (&summary->priv->summary_lock);
+ g_rec_mutex_init (&summary->priv->io_lock);
+}
+
+/**
+ * camel_store_summary_new:
+ *
+ * Create a new #CamelStoreSummary object.
+ *
+ * Returns: a new #CamelStoreSummary object
+ **/
+CamelStoreSummary *
+camel_store_summary_new (void)
+{
+ return g_object_new (CAMEL_TYPE_STORE_SUMMARY, NULL);
+}
+
+/**
+ * camel_store_summary_set_filename:
+ * @summary: a #CamelStoreSummary
+ * @filename: a filename
+ *
+ * Set the filename where the summary will be loaded to/saved from.
+ **/
+void
+camel_store_summary_set_filename (CamelStoreSummary *summary,
+ const gchar *name)
+{
+ g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+
+ g_free (summary->priv->summary_path);
+ summary->priv->summary_path = g_strdup (name);
+
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+}
+
+/**
+ * camel_store_summary_count:
+ * @summary: a #CamelStoreSummary object
+ *
+ * Get the number of summary items stored in this summary.
+ *
+ * Returns: the number of items gint he summary.
+ **/
+gint
+camel_store_summary_count (CamelStoreSummary *summary)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), -1);
+
+ return summary->folders->len;
+}
+
+/**
+ * camel_store_summary_array:
+ * @summary: a #CamelStoreSummary object
+ *
+ * Obtain a copy of the summary array. This is done atomically,
+ * so cannot contain empty entries.
+ *
+ * It must be freed using camel_store_summary_array_free().
+ *
+ * Returns: (element-type CamelStoreInfo) (transfer full): the summary array
+ **/
+GPtrArray *
+camel_store_summary_array (CamelStoreSummary *summary)
+{
+ CamelStoreInfo *info;
+ GPtrArray *res;
+ gint i;
+
+ g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+
+ res = g_ptr_array_sized_new (summary->folders->len);
+ for (i = 0; i < summary->folders->len; i++) {
+ info = g_ptr_array_index (summary->folders, i);
+ camel_store_summary_info_ref (summary, info);
+ g_ptr_array_add (res, info);
+ }
+
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+
+ return res;
+}
+
+/**
+ * camel_store_summary_array_free:
+ * @summary: a #CamelStoreSummary object
+ * @array: (element-type CamelStoreInfo): the summary array as gotten from camel_store_summary_array()
+ *
+ * Free the folder summary array.
+ **/
+void
+camel_store_summary_array_free (CamelStoreSummary *summary,
+ GPtrArray *array)
+{
+ gint i;
+
+ g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
+ g_return_if_fail (array != NULL);
+
+ for (i = 0; i < array->len; i++)
+ camel_store_summary_info_unref (summary, array->pdata[i]);
+
+ g_ptr_array_free (array, TRUE);
+}
+
+/**
+ * camel_store_summary_path:
+ * @summary: a #CamelStoreSummary object
+ * @path: path to the item
+ *
+ * Retrieve a summary item by path name.
+ *
+ * The returned #CamelStoreInfo is referenced for thread-safety and should be
+ * unreferenced with camel_store_summary_info_unref() when finished with it.
+ *
+ * Returns: the summary item, or %NULL if the @path name is not
+ * available
+ **/
+CamelStoreInfo *
+camel_store_summary_path (CamelStoreSummary *summary,
+ const gchar *path)
+{
+ CamelStoreInfo *info;
+
+ g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
+ g_return_val_if_fail (path != NULL, NULL);
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+
+ info = g_hash_table_lookup (summary->folders_path, path);
+
+ if (info != NULL)
+ camel_store_summary_info_ref (summary, info);
+
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+
+ return info;
+}
+
+/**
+ * camel_store_summary_load:
+ * @summary: a #CamelStoreSummary object
+ *
+ * Load the summary off disk.
+ *
+ * Returns: %0 on success or %-1 on fail
+ **/
+gint
+camel_store_summary_load (CamelStoreSummary *summary)
+{
+ CamelStoreSummaryClass *class;
+ CamelStoreInfo *info;
+ FILE *in;
+ gint i;
+
+ g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), -1);
+ g_return_val_if_fail (summary->priv->summary_path != NULL, -1);
+
+ class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
+ g_return_val_if_fail (class->store_info_load != NULL, -1);
+
+ in = g_fopen (summary->priv->summary_path, "rb");
+ if (in == NULL)
+ return -1;
+
+ g_rec_mutex_lock (&summary->priv->io_lock);
+
+ if (class->summary_header_load (summary, in) == -1)
+ goto error;
+
+ /* now read in each message ... */
+ for (i = 0; i < summary->priv->count; i++) {
+ info = class->store_info_load (summary, in);
+
+ if (info == NULL)
+ goto error;
+
+ camel_store_summary_add (summary, info);
+ }
+
+ g_rec_mutex_unlock (&summary->priv->io_lock);
+
+ if (fclose (in) != 0)
+ return -1;
+
+ summary->priv->dirty = FALSE;
+
+ return 0;
+
+error:
+ i = ferror (in);
+ g_warning ("Cannot load summary file '%s': %s", summary->priv->summary_path, i == 0 ? "Unknown error" : g_strerror (i));
+ g_rec_mutex_unlock (&summary->priv->io_lock);
+ fclose (in);
+ summary->priv->dirty = FALSE;
+ errno = i;
+
+ return -1;
+}
+
+/**
+ * camel_store_summary_save:
+ * @summary: a #CamelStoreSummary object
+ *
+ * Writes the summary to disk. The summary is only written if changes
+ * have occurred.
+ *
+ * Returns: %0 on succes or %-1 on fail
+ **/
+gint
+camel_store_summary_save (CamelStoreSummary *summary)
+{
+ CamelStoreSummaryClass *class;
+ CamelStoreInfo *info;
+ FILE *out;
+ gint fd;
+ gint i;
+ guint32 count;
+
+ g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), -1);
+ g_return_val_if_fail (summary->priv->summary_path != NULL, -1);
+
+ class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
+ g_return_val_if_fail (class->summary_header_save != NULL, -1);
+
+ io (printf ("** saving summary\n"));
+
+ if (!summary->priv->dirty) {
+ io (printf ("** summary clean no save\n"));
+ return 0;
+ }
+
+ fd = g_open (
+ summary->priv->summary_path,
+ O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600);
+ if (fd == -1) {
+ io (printf ("** open error: %s\n", g_strerror (errno)));
+ return -1;
+ }
+
+ out = fdopen (fd, "wb");
+ if (out == NULL) {
+ i = errno;
+ printf ("** fdopen error: %s\n", g_strerror (errno));
+ close (fd);
+ errno = i;
+ return -1;
+ }
+
+ io (printf ("saving header\n"));
+
+ g_rec_mutex_lock (&summary->priv->io_lock);
+
+ if (class->summary_header_save (summary, out) == -1) {
+ i = errno;
+ fclose (out);
+ g_rec_mutex_unlock (&summary->priv->io_lock);
+ errno = i;
+ return -1;
+ }
+
+ /* now write out each message ... */
+
+ /* FIXME: Locking? */
+
+ count = summary->folders->len;
+ for (i = 0; i < count; i++) {
+ info = summary->folders->pdata[i];
+ class->store_info_save (summary, out, info);
+ }
+
+ g_rec_mutex_unlock (&summary->priv->io_lock);
+
+ if (fflush (out) != 0 || fsync (fileno (out)) == -1) {
+ i = errno;
+ fclose (out);
+ errno = i;
+ return -1;
+ }
+
+ if (fclose (out) != 0)
+ return -1;
+
+ summary->priv->dirty = FALSE;
+
+ return 0;
+}
+
+/**
+ * camel_store_summary_add:
+ * @summary: a #CamelStoreSummary object
+ * @info: a #CamelStoreInfo
+ *
+ * Adds a new @info record to the summary. If @info->uid is %NULL,
+ * then a new uid is automatically re-assigned by calling
+ * camel_store_summary_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_store_summary_add (CamelStoreSummary *summary,
+ CamelStoreInfo *info)
+{
+ g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
+
+ if (info == NULL)
+ return;
+
+ if (camel_store_info_path (summary, info) == NULL) {
+ g_warning ("Trying to add a folder info with missing required path name\n");
+ return;
+ }
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+
+ g_ptr_array_add (summary->folders, info);
+ g_hash_table_insert (summary->folders_path, (gchar *) camel_store_info_path (summary, info), info);
+ summary->priv->dirty = TRUE;
+
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+}
+
+/**
+ * camel_store_summary_add_from_path:
+ * @summary: a #CamelStoreSummary object
+ * @path: item path
+ *
+ * Build a new info record based on the name, and add it to the summary.
+ *
+ * Returns: the newly added record
+ **/
+CamelStoreInfo *
+camel_store_summary_add_from_path (CamelStoreSummary *summary,
+ const gchar *path)
+{
+ CamelStoreInfo *info;
+
+ g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
+ g_return_val_if_fail (path != NULL, NULL);
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+
+ info = g_hash_table_lookup (summary->folders_path, path);
+ if (info != NULL) {
+ g_warning ("Trying to add folder '%s' to summary that already has it", path);
+ info = NULL;
+ } else {
+ CamelStoreSummaryClass *class;
+
+ class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
+ g_return_val_if_fail (class->store_info_new != NULL, NULL);
+
+ info = class->store_info_new (summary, path);
+
+ g_ptr_array_add (summary->folders, info);
+ g_hash_table_insert (summary->folders_path, (gchar *) camel_store_info_path (summary, info), info);
+ summary->priv->dirty = TRUE;
+ }
+
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+
+ return info;
+}
+
+/**
+ * camel_store_summary_info_ref:
+ * @summary: a #CamelStoreSummary object
+ * @info: a #CamelStoreInfo
+ *
+ * Add an extra reference to @info.
+ *
+ * Returns: the @info argument
+ **/
+CamelStoreInfo *
+camel_store_summary_info_ref (CamelStoreSummary *summary,
+ CamelStoreInfo *info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+ g_return_val_if_fail (info->refcount > 0, NULL);
+
+ g_atomic_int_inc (&info->refcount);
+
+ return info;
+}
+
+/**
+ * camel_store_summary_info_unref:
+ * @summary: a #CamelStoreSummary object
+ * @info: a #CamelStoreInfo
+ *
+ * Unref and potentially free @info, and all associated memory.
+ **/
+void
+camel_store_summary_info_unref (CamelStoreSummary *summary,
+ CamelStoreInfo *info)
+{
+ g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (info->refcount > 0);
+
+ if (g_atomic_int_dec_and_test (&info->refcount)) {
+ CamelStoreSummaryClass *class;
+
+ class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
+ g_return_if_fail (class->store_info_free != NULL);
+
+ class->store_info_free (summary, info);
+ }
+}
+
+/**
+ * camel_store_summary_touch:
+ * @summary: a #CamelStoreSummary object
+ *
+ * Mark the summary as changed, so that a save will force it to be
+ * written back to disk.
+ **/
+void
+camel_store_summary_touch (CamelStoreSummary *summary)
+{
+ g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+ summary->priv->dirty = TRUE;
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+}
+
+/**
+ * camel_store_summary_remove:
+ * @summary: a #CamelStoreSummary object
+ * @info: a #CamelStoreInfo
+ *
+ * Remove a specific @info record from the summary.
+ **/
+void
+camel_store_summary_remove (CamelStoreSummary *summary,
+ CamelStoreInfo *info)
+{
+ g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
+ g_return_if_fail (info != NULL);
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+ g_hash_table_remove (summary->folders_path, camel_store_info_path (summary, info));
+ g_ptr_array_remove (summary->folders, info);
+ summary->priv->dirty = TRUE;
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+
+ camel_store_summary_info_unref (summary, info);
+}
+
+/**
+ * camel_store_summary_remove_path:
+ * @summary: a #CamelStoreSummary object
+ * @path: item path
+ *
+ * Remove a specific info record from the summary, by @path.
+ **/
+void
+camel_store_summary_remove_path (CamelStoreSummary *summary,
+ const gchar *path)
+{
+ CamelStoreInfo *oldinfo;
+ gchar *oldpath;
+
+ g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
+ g_return_if_fail (path != NULL);
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+ if (g_hash_table_lookup_extended (summary->folders_path, path, (gpointer) &oldpath, (gpointer) &oldinfo)) {
+ /* make sure it doesn't vanish while we're removing it */
+ camel_store_summary_info_ref (summary, oldinfo);
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+ camel_store_summary_remove (summary, oldinfo);
+ camel_store_summary_info_unref (summary, oldinfo);
+ } else {
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+ }
+}
+
+/**
+ * camel_store_summary_info_new:
+ * @summary: a #CamelStoreSummary object
+ *
+ * Allocate a new #CamelStoreInfo, suitable for adding to this
+ * summary.
+ *
+ * Returns: the newly allocated #CamelStoreInfo
+ **/
+CamelStoreInfo *
+camel_store_summary_info_new (CamelStoreSummary *summary)
+{
+ CamelStoreSummaryClass *class;
+ CamelStoreInfo *info;
+
+ g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
+
+ class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
+ g_return_val_if_fail (class->store_info_size > 0, NULL);
+
+ info = g_slice_alloc0 (class->store_info_size);
+ info->refcount = 1;
+
+ return info;
+}
+
+/**
+ * camel_store_info_set_string:
+ * @summary: a #CamelStoreSummary object
+ * @info: a #CamelStoreInfo
+ * @type: specific string being set
+ * @value: string value to set
+ *
+ * Set a specific string on the @info.
+ **/
+void
+camel_store_info_set_string (CamelStoreSummary *summary,
+ CamelStoreInfo *info,
+ gint type,
+ const gchar *value)
+{
+ CamelStoreSummaryClass *class;
+
+ g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
+ g_return_if_fail (info != NULL);
+
+ class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
+ g_return_if_fail (class->store_info_set_string != NULL);
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+
+ class->store_info_set_string (summary, info, type, value);
+
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+}
+
+/**
+ * camel_store_info_path:
+ * @summary: a #CamelStoreSummary
+ * @info: a #CamelStoreInfo
+ *
+ * Returns the path string from @info.
+ *
+ * Returns: the path string from @info
+ **/
+const gchar *
+camel_store_info_path (CamelStoreSummary *summary,
+ CamelStoreInfo *info)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
+ g_return_val_if_fail (info != NULL, NULL);
+
+ /* XXX Not thread-safe; should return a duplicate. */
+ return info->path;
+}
+
+/**
+ * camel_store_info_name:
+ * @summary: a #CamelStoreSummary
+ * @info: a #CamelStoreInfo
+ *
+ * Returns the last segment of the path string from @info.
+ *
+ * Returns: the last segment of the path string from @info
+ **/
+const gchar *
+camel_store_info_name (CamelStoreSummary *summary,
+ CamelStoreInfo *info)
+{
+ const gchar *cp;
+
+ g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
+ g_return_val_if_fail (info != NULL, NULL);
+
+ cp = strrchr (info->path, '/');
+
+ /* XXX Not thread-safe; should return a duplicate. */
+ return (cp != NULL) ? cp + 1 : info->path;
+}
+
+static gboolean
+store_summary_save_timeout (gpointer user_data)
+{
+ CamelStoreSummary *summary = CAMEL_STORE_SUMMARY (user_data);
+
+ g_return_val_if_fail (summary != NULL, FALSE);
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+
+ if (summary->priv->scheduled_save_id) {
+ summary->priv->scheduled_save_id = 0;
+ camel_store_summary_save (summary);
+ }
+
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+
+ return FALSE;
+}
+
+static void
+store_summary_schedule_save (CamelStoreSummary *summary)
+{
+ g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
+
+ if (summary->priv->scheduled_save_id != 0)
+ g_source_remove (summary->priv->scheduled_save_id);
+
+ summary->priv->scheduled_save_id = g_timeout_add_seconds (
+ 5, store_summary_save_timeout, summary);
+ g_source_set_name_by_id (
+ summary->priv->scheduled_save_id,
+ "[camel] store_summary_save_timeout");
+}
+
+static void
+store_summary_sync_folder_summary_count_cb (CamelFolderSummary *folder_summary,
+ GParamSpec *param,
+ CamelStoreSummary *summary)
+{
+ gint new_count;
+ const gchar *path;
+ CamelStoreInfo *si;
+
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary));
+ g_return_if_fail (param != NULL);
+ g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
+
+ path = g_hash_table_lookup (summary->priv->folder_summaries, folder_summary);
+ g_return_if_fail (path != NULL);
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+ si = camel_store_summary_path (summary, path);
+ if (!si) {
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+ g_warning ("%s: Store summary %p doesn't hold path '%s'", G_STRFUNC, summary, path);
+ return;
+ }
+
+ if (g_strcmp0 (g_param_spec_get_name (param), "saved-count") == 0) {
+ new_count = camel_folder_summary_get_saved_count (folder_summary);
+ if (si->total != new_count) {
+ si->total = new_count;
+ camel_store_summary_touch (summary);
+ store_summary_schedule_save (summary);
+ }
+ } else if (g_strcmp0 (g_param_spec_get_name (param), "unread-count") == 0) {
+ new_count = camel_folder_summary_get_unread_count (folder_summary);
+ if (si->unread != new_count) {
+ si->unread = new_count;
+ camel_store_summary_touch (summary);
+ store_summary_schedule_save (summary);
+ }
+ } else {
+ g_warn_if_reached ();
+ }
+
+ camel_store_summary_info_unref (summary, si);
+
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+}
+
+/**
+ * camel_store_summary_connect_folder_summary:
+ * @summary: a #CamelStoreSummary object
+ * @path: used path for @folder_summary
+ * @folder_summary: a #CamelFolderSummary object
+ *
+ * Connects listeners for count changes on @folder_summary to keep
+ * CamelStoreInfo.total and CamelStoreInfo.unread in sync transparently.
+ * The @folder_summary is stored in @summary as @path. Use
+ * camel_store_summary_disconnect_folder_summary() to disconnect from
+ * listening.
+ *
+ * Returns: Whether successfully connect callbacks for count change
+ * notifications.
+ *
+ * Since: 3.4
+ **/
+gboolean
+camel_store_summary_connect_folder_summary (CamelStoreSummary *summary,
+ const gchar *path,
+ CamelFolderSummary *folder_summary)
+{
+ CamelStoreInfo *si;
+
+ g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), FALSE);
+ g_return_val_if_fail (path != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary), FALSE);
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+
+ si = camel_store_summary_path (summary, path);
+ if (!si) {
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+ g_warning ("%s: Store summary %p doesn't hold path '%s'", G_STRFUNC, summary, path);
+ return FALSE;
+ }
+
+ camel_store_summary_info_unref (summary, si);
+
+ if (g_hash_table_lookup (summary->priv->folder_summaries, folder_summary)) {
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+ g_warning ("%s: Store summary %p already listens on folder summary %p", G_STRFUNC, summary, folder_summary);
+ return FALSE;
+ }
+
+ g_hash_table_insert (summary->priv->folder_summaries, folder_summary, g_strdup (path));
+ g_signal_connect (folder_summary, "notify::saved-count", G_CALLBACK (store_summary_sync_folder_summary_count_cb), summary);
+ g_signal_connect (folder_summary, "notify::unread-count", G_CALLBACK (store_summary_sync_folder_summary_count_cb), summary);
+
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+
+ return TRUE;
+}
+
+/**
+ * camel_store_summary_disconnect_folder_summary:
+ * @summary: a #CamelStoreSummary object
+ * @folder_summary: a #CamelFolderSummary object
+ *
+ * Diconnects count change listeners previously connected
+ * by camel_store_summary_connect_folder_summary().
+ *
+ * Returns: Whether such connection existed and whether was successfully
+ * removed.
+ *
+ * Since: 3.4
+ **/
+gboolean
+camel_store_summary_disconnect_folder_summary (CamelStoreSummary *summary,
+ CamelFolderSummary *folder_summary)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), FALSE);
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary), FALSE);
+
+ g_rec_mutex_lock (&summary->priv->summary_lock);
+
+ if (!g_hash_table_lookup (summary->priv->folder_summaries, folder_summary)) {
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+ g_warning ("%s: Store summary %p is not connected to folder summary %p", G_STRFUNC, summary, folder_summary);
+ return FALSE;
+ }
+
+ g_signal_handlers_disconnect_by_func (folder_summary, G_CALLBACK (store_summary_sync_folder_summary_count_cb), summary);
+ g_hash_table_remove (summary->priv->folder_summaries, folder_summary);
+
+ if (summary->priv->scheduled_save_id != 0) {
+ g_source_remove (summary->priv->scheduled_save_id);
+ summary->priv->scheduled_save_id = 0;
+ }
+
+ camel_store_summary_save (summary);
+
+ g_rec_mutex_unlock (&summary->priv->summary_lock);
+
+ return TRUE;
+}
diff --git a/src/camel/camel-store-summary.h b/src/camel/camel-store-summary.h
new file mode 100644
index 000000000..68e1557f4
--- /dev/null
+++ b/src/camel/camel-store-summary.h
@@ -0,0 +1,185 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_STORE_SUMMARY_H
+#define CAMEL_STORE_SUMMARY_H
+
+#include <stdio.h>
+
+#include <camel/camel-enums.h>
+#include <camel/camel-mime-parser.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_STORE_SUMMARY \
+ (camel_store_summary_get_type ())
+#define CAMEL_STORE_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_STORE_SUMMARY, CamelStoreSummary))
+#define CAMEL_STORE_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_STORE_SUMMARY, CamelStoreSummaryClass))
+#define CAMEL_IS_STORE_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_STORE_SUMMARY))
+#define CAMEL_IS_STORE_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_STORE_SUMMARY))
+#define CAMEL_STORE_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_STORE_SUMMARY, CamelStoreSummaryClass))
+
+G_BEGIN_DECLS
+
+struct _CamelFolderSummary;
+
+typedef struct _CamelStoreSummary CamelStoreSummary;
+typedef struct _CamelStoreSummaryClass CamelStoreSummaryClass;
+typedef struct _CamelStoreSummaryPrivate CamelStoreSummaryPrivate;
+
+typedef struct _CamelStoreInfo CamelStoreInfo;
+
+#define CAMEL_STORE_INFO_FOLDER_UNKNOWN (~0)
+
+enum {
+ CAMEL_STORE_INFO_PATH = 0,
+ CAMEL_STORE_INFO_LAST
+};
+
+struct _CamelStoreInfo {
+ volatile gint refcount;
+ gchar *path;
+ guint32 flags;
+ guint32 unread;
+ guint32 total;
+};
+
+struct _CamelStoreSummary {
+ GObject parent;
+ CamelStoreSummaryPrivate *priv;
+
+ GPtrArray *folders; /* CamelStoreInfo's */
+ GHashTable *folders_path; /* CamelStoreInfo's by path name */
+};
+
+struct _CamelStoreSummaryClass {
+ GObjectClass parent_class;
+
+ /* size of memory objects */
+ gsize store_info_size;
+
+ /* load/save the global info */
+ gint (*summary_header_load) (CamelStoreSummary *summary,
+ FILE *file);
+ gint (*summary_header_save) (CamelStoreSummary *summary,
+ FILE *file);
+
+ /* create/save/load an individual message info */
+ CamelStoreInfo *
+ (*store_info_new) (CamelStoreSummary *summary,
+ const gchar *path);
+ CamelStoreInfo *
+ (*store_info_load) (CamelStoreSummary *summary,
+ FILE *file);
+ gint (*store_info_save) (CamelStoreSummary *summary,
+ FILE *file,
+ CamelStoreInfo *info);
+ void (*store_info_free) (CamelStoreSummary *summary,
+ CamelStoreInfo *info);
+
+ /* virtualise access methods */
+ void (*store_info_set_string)
+ (CamelStoreSummary *summary,
+ CamelStoreInfo *info,
+ gint type,
+ const gchar *value);
+};
+
+GType camel_store_summary_get_type (void) G_GNUC_CONST;
+CamelStoreSummary *
+ camel_store_summary_new (void);
+void camel_store_summary_set_filename
+ (CamelStoreSummary *summary,
+ const gchar *filename);
+
+/* load/save the summary in its entirety */
+gint camel_store_summary_load (CamelStoreSummary *summary);
+gint camel_store_summary_save (CamelStoreSummary *summary);
+
+/* set the dirty bit on the summary */
+void camel_store_summary_touch (CamelStoreSummary *summary);
+
+/* add a new raw summary item */
+void camel_store_summary_add (CamelStoreSummary *summary,
+ CamelStoreInfo *info);
+
+/* build/add raw summary items */
+CamelStoreInfo *
+ camel_store_summary_add_from_path
+ (CamelStoreSummary *summary,
+ const gchar *path);
+
+/* Just build raw summary items */
+CamelStoreInfo *
+ camel_store_summary_info_new (CamelStoreSummary *summary);
+CamelStoreInfo *
+ camel_store_summary_info_ref (CamelStoreSummary *summary,
+ CamelStoreInfo *info);
+void camel_store_summary_info_unref (CamelStoreSummary *summary,
+ CamelStoreInfo *info);
+
+/* removes a summary item */
+void camel_store_summary_remove (CamelStoreSummary *summary,
+ CamelStoreInfo *info);
+void camel_store_summary_remove_path (CamelStoreSummary *summary,
+ const gchar *path);
+
+/* lookup functions */
+gint camel_store_summary_count (CamelStoreSummary *summary);
+CamelStoreInfo *
+ camel_store_summary_path (CamelStoreSummary *summary,
+ const gchar *path);
+GPtrArray * camel_store_summary_array (CamelStoreSummary *summary);
+void camel_store_summary_array_free (CamelStoreSummary *summary,
+ GPtrArray *array);
+
+void camel_store_info_set_string (CamelStoreSummary *summary,
+ CamelStoreInfo *info,
+ gint type,
+ const gchar *value);
+
+const gchar * camel_store_info_path (CamelStoreSummary *summary,
+ CamelStoreInfo *info);
+const gchar * camel_store_info_name (CamelStoreSummary *summary,
+ CamelStoreInfo *info);
+
+gboolean camel_store_summary_connect_folder_summary
+ (CamelStoreSummary *summary,
+ const gchar *path,
+ struct _CamelFolderSummary *folder_summary);
+gboolean camel_store_summary_disconnect_folder_summary
+ (CamelStoreSummary *summary,
+ struct _CamelFolderSummary *folder_summary);
+
+G_END_DECLS
+
+#endif /* CAMEL_STORE_SUMMARY_H */
diff --git a/src/camel/camel-store.c b/src/camel/camel-store.c
new file mode 100644
index 000000000..4d4f11202
--- /dev/null
+++ b/src/camel/camel-store.c
@@ -0,0 +1,3141 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-store.c : Abstract class for an email store
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Dan Winship <danw@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-async-closure.h"
+#include "camel-db.h"
+#include "camel-debug.h"
+#include "camel-folder.h"
+#include "camel-network-service.h"
+#include "camel-offline-store.h"
+#include "camel-session.h"
+#include "camel-store.h"
+#include "camel-store-settings.h"
+#include "camel-subscribable.h"
+#include "camel-vtrash-folder.h"
+
+#define d(x)
+#define w(x)
+
+#define CAMEL_STORE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_STORE, CamelStorePrivate))
+
+typedef struct _AsyncContext AsyncContext;
+typedef struct _SignalClosure SignalClosure;
+
+struct _CamelStorePrivate {
+ GMutex signal_emission_lock;
+ gboolean folder_info_stale_scheduled;
+ volatile gint maintenance_lock;
+};
+
+struct _AsyncContext {
+ gchar *folder_name_1;
+ gchar *folder_name_2;
+ gboolean expunge;
+ guint32 flags;
+ GHashTable *save_setup;
+};
+
+struct _SignalClosure {
+ GWeakRef store;
+ CamelFolder *folder;
+ CamelFolderInfo *folder_info;
+ gchar *folder_name;
+};
+
+enum {
+ FOLDER_CREATED,
+ FOLDER_DELETED,
+ FOLDER_INFO_STALE,
+ FOLDER_OPENED,
+ FOLDER_RENAMED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+static GInitableIface *parent_initable_interface;
+
+/* Forward Declarations */
+static void camel_store_initable_init (GInitableIface *iface);
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (
+ CamelStore, camel_store, CAMEL_TYPE_SERVICE,
+ G_IMPLEMENT_INTERFACE (
+ G_TYPE_INITABLE, camel_store_initable_init))
+
+G_DEFINE_BOXED_TYPE (CamelFolderInfo,
+ camel_folder_info,
+ camel_folder_info_clone,
+ camel_folder_info_free)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+ if (async_context->save_setup) {
+ g_hash_table_destroy (async_context->save_setup);
+ async_context->save_setup = NULL;
+ }
+
+ g_free (async_context->folder_name_1);
+ g_free (async_context->folder_name_2);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static void
+signal_closure_free (SignalClosure *signal_closure)
+{
+ g_weak_ref_clear (&signal_closure->store);
+
+ if (signal_closure->folder != NULL)
+ g_object_unref (signal_closure->folder);
+
+ if (signal_closure->folder_info != NULL)
+ camel_folder_info_free (signal_closure->folder_info);
+
+ g_free (signal_closure->folder_name);
+
+ g_slice_free (SignalClosure, signal_closure);
+}
+
+static gboolean
+store_emit_folder_created_cb (gpointer user_data)
+{
+ SignalClosure *signal_closure = user_data;
+ CamelStore *store;
+
+ store = g_weak_ref_get (&signal_closure->store);
+
+ if (store != NULL) {
+ g_signal_emit (
+ store,
+ signals[FOLDER_CREATED], 0,
+ signal_closure->folder_info);
+ g_object_unref (store);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+store_emit_folder_deleted_cb (gpointer user_data)
+{
+ SignalClosure *signal_closure = user_data;
+ CamelStore *store;
+
+ store = g_weak_ref_get (&signal_closure->store);
+
+ if (store != NULL) {
+ g_signal_emit (
+ store,
+ signals[FOLDER_DELETED], 0,
+ signal_closure->folder_info);
+ g_object_unref (store);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+store_emit_folder_opened_cb (gpointer user_data)
+{
+ SignalClosure *signal_closure = user_data;
+ CamelStore *store;
+
+ store = g_weak_ref_get (&signal_closure->store);
+
+ if (store != NULL) {
+ g_signal_emit (
+ store,
+ signals[FOLDER_OPENED], 0,
+ signal_closure->folder);
+ g_object_unref (store);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+store_emit_folder_renamed_cb (gpointer user_data)
+{
+ SignalClosure *signal_closure = user_data;
+ CamelStore *store;
+
+ store = g_weak_ref_get (&signal_closure->store);
+
+ if (store != NULL) {
+ g_signal_emit (
+ store,
+ signals[FOLDER_RENAMED], 0,
+ signal_closure->folder_name,
+ signal_closure->folder_info);
+ g_object_unref (store);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+store_emit_folder_info_stale_cb (gpointer user_data)
+{
+ SignalClosure *signal_closure = user_data;
+ CamelStore *store;
+
+ store = g_weak_ref_get (&signal_closure->store);
+
+ if (store != NULL) {
+ g_mutex_lock (&store->priv->signal_emission_lock);
+ store->priv->folder_info_stale_scheduled = FALSE;
+ g_mutex_unlock (&store->priv->signal_emission_lock);
+
+ g_signal_emit (store, signals[FOLDER_INFO_STALE], 0);
+
+ g_object_unref (store);
+ }
+
+ return FALSE;
+}
+
+/*
+ * ignore_no_such_table_exception:
+ * Clears the error 'error' when it's the 'no such table' error.
+ */
+static void
+ignore_no_such_table_exception (GError **error)
+{
+ if (error == NULL || *error == NULL)
+ return;
+
+ if (g_ascii_strncasecmp ((*error)->message, "no such table", 13) == 0)
+ g_clear_error (error);
+}
+
+/* deletes folder/removes it from the folder cache, if it's there */
+static void
+cs_delete_cached_folder (CamelStore *store,
+ const gchar *folder_name)
+{
+ CamelFolder *folder;
+ CamelVeeFolder *vfolder;
+
+ if (store->folders == NULL)
+ return;
+
+ folder = camel_object_bag_get (store->folders, folder_name);
+ if (folder == NULL)
+ return;
+
+ if (store->flags & CAMEL_STORE_VTRASH) {
+ vfolder = camel_object_bag_get (
+ store->folders, CAMEL_VTRASH_NAME);
+ if (vfolder != NULL) {
+ camel_vee_folder_remove_folder (vfolder, folder, NULL);
+ g_object_unref (vfolder);
+ }
+ }
+
+ if (store->flags & CAMEL_STORE_VJUNK) {
+ vfolder = camel_object_bag_get (
+ store->folders, CAMEL_VJUNK_NAME);
+ if (vfolder != NULL) {
+ camel_vee_folder_remove_folder (vfolder, folder, NULL);
+ g_object_unref (vfolder);
+ }
+ }
+
+ camel_folder_delete (folder);
+
+ camel_object_bag_remove (store->folders, folder);
+ g_object_unref (folder);
+}
+
+static CamelFolder *
+store_get_special (CamelStore *store,
+ CamelVTrashFolderType type)
+{
+ CamelFolder *folder;
+ GPtrArray *folders;
+ gint 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], NULL);
+ g_object_unref (folders->pdata[i]);
+ }
+ g_ptr_array_free (folders, TRUE);
+
+ return folder;
+}
+
+static gboolean
+store_maybe_connect_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelService *service;
+ CamelServiceConnectionStatus status;
+ CamelSession *session;
+ gboolean connect = FALSE;
+ gboolean success = TRUE;
+
+ /* This is meant to recover from dropped connections
+ * when the CamelService is online but disconnected. */
+
+ service = CAMEL_SERVICE (store);
+ session = camel_service_ref_session (service);
+ status = camel_service_get_connection_status (service);
+ connect = session && camel_session_get_online (session) && (status != CAMEL_SERVICE_CONNECTED);
+ g_clear_object (&session);
+
+ if (connect && CAMEL_IS_NETWORK_SERVICE (store)) {
+ /* Disregard errors here. Just want to
+ * know whether to attempt a connection. */
+ connect = camel_network_service_can_reach_sync (
+ CAMEL_NETWORK_SERVICE (service), cancellable, NULL);
+ }
+
+ if (connect && CAMEL_IS_OFFLINE_STORE (store)) {
+ CamelOfflineStore *offline_store;
+
+ offline_store = CAMEL_OFFLINE_STORE (store);
+ if (!camel_offline_store_get_online (offline_store))
+ connect = FALSE;
+ }
+
+ if (connect) {
+ success = camel_service_connect_sync (
+ service, cancellable, error);
+ }
+
+ return success;
+}
+
+static void
+store_finalize (GObject *object)
+{
+ CamelStore *store = CAMEL_STORE (object);
+
+ if (store->folders != NULL)
+ camel_object_bag_destroy (store->folders);
+
+ if (store->cdb_r != NULL) {
+ camel_db_close (store->cdb_r);
+ store->cdb_r = NULL;
+ store->cdb_w = NULL;
+ }
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_store_parent_class)->finalize (object);
+}
+
+static void
+store_constructed (GObject *object)
+{
+ CamelStore *store;
+ CamelStoreClass *class;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (camel_store_parent_class)->constructed (object);
+
+ store = CAMEL_STORE (object);
+ class = CAMEL_STORE_GET_CLASS (store);
+
+ g_return_if_fail (class->hash_folder_name != NULL);
+ g_return_if_fail (class->equal_folder_name != NULL);
+
+ store->folders = camel_object_bag_new (
+ class->hash_folder_name,
+ class->equal_folder_name,
+ (CamelCopyFunc) g_strdup, g_free);
+}
+
+static gboolean
+store_can_refresh_folder (CamelStore *store,
+ CamelFolderInfo *info,
+ GError **error)
+{
+ return ((info->flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_INBOX);
+}
+
+static CamelFolder *
+store_get_inbox_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStoreClass *class;
+ CamelFolder *folder;
+
+ class = CAMEL_STORE_GET_CLASS (store);
+ g_return_val_if_fail (class->get_folder_sync != NULL, NULL);
+
+ /* Assume the inbox's name is "inbox" and open with default flags. */
+ folder = class->get_folder_sync (store, "inbox", 0, cancellable, error);
+ CAMEL_CHECK_GERROR (store, get_folder_sync, folder != NULL, error);
+
+ return folder;
+}
+
+static CamelFolder *
+store_get_junk_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return store_get_special (store, CAMEL_VTRASH_FOLDER_JUNK);
+}
+
+static CamelFolder *
+store_get_trash_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return store_get_special (store, CAMEL_VTRASH_FOLDER_TRASH);
+}
+
+static gboolean
+store_synchronize_sync (CamelStore *store,
+ gboolean expunge,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GPtrArray *folders;
+ gboolean success = TRUE;
+ gint ii;
+ GError *local_error = NULL;
+
+ if (expunge) {
+ /* ensure all folders are used when expunging */
+ CamelFolderInfo *root, *fi;
+
+ (void) g_atomic_int_add (&store->priv->maintenance_lock, 1);
+
+ folders = g_ptr_array_new ();
+ root = camel_store_get_folder_info_sync (
+ store, NULL,
+ CAMEL_STORE_FOLDER_INFO_RECURSIVE |
+ CAMEL_STORE_FOLDER_INFO_SUBSCRIBED |
+ CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL,
+ NULL, NULL);
+ fi = root;
+ while (fi != NULL) {
+ CamelFolderInfo *next;
+
+ if ((fi->flags & CAMEL_FOLDER_NOSELECT) == 0) {
+ CamelFolder *folder;
+
+ folder = camel_store_get_folder_sync (
+ store, fi->full_name, 0, NULL, NULL);
+ if (folder != NULL)
+ g_ptr_array_add (folders, folder);
+ }
+
+ /* pick the next */
+ next = fi->child;
+ if (next == NULL)
+ next = fi->next;
+ if (next == NULL) {
+ next = fi->parent;
+ while (next != NULL) {
+ if (next->next != NULL) {
+ next = next->next;
+ break;
+ }
+
+ next = next->parent;
+ }
+ }
+
+ fi = next;
+ }
+
+ camel_folder_info_free (root);
+ } else {
+ /* sync only folders opened until now */
+ folders = camel_object_bag_list (store->folders);
+ }
+
+ /* 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. */
+ for (ii = 0; ii < folders->len; ii++) {
+ CamelFolder *folder = folders->pdata[ii];
+
+ if (folder->summary)
+ camel_folder_summary_save_to_db (folder->summary, NULL);
+
+ if (!CAMEL_IS_VEE_FOLDER (folder) && local_error == NULL) {
+ camel_folder_synchronize_sync (
+ folder, expunge, cancellable, &local_error);
+ ignore_no_such_table_exception (&local_error);
+ }
+ g_object_unref (folder);
+ }
+
+ /* Unlock it before the call, thus it's actually done. */
+ if (expunge)
+ (void) g_atomic_int_add (&store->priv->maintenance_lock, -1);
+
+ if (!local_error && expunge) {
+ camel_store_maybe_run_db_maintenance (store, &local_error);
+ }
+
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ success = FALSE;
+ }
+
+ g_ptr_array_free (folders, TRUE);
+
+ return success;
+}
+
+static gboolean
+store_initial_setup_sync (CamelStore *store,
+ GHashTable *out_save_setup,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return TRUE;
+}
+
+static gboolean
+store_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *store;
+ CamelService *service;
+ const gchar *user_dir;
+ gchar *filename;
+
+ store = CAMEL_STORE (initable);
+
+ /* Chain up to parent interface's init() method. */
+ if (!parent_initable_interface->init (initable, cancellable, error))
+ return FALSE;
+
+ service = CAMEL_SERVICE (initable);
+ if ((store->flags & CAMEL_STORE_USE_CACHE_DIR) != 0)
+ user_dir = camel_service_get_user_cache_dir (service);
+ else
+ user_dir = camel_service_get_user_data_dir (service);
+
+ if (g_mkdir_with_parents (user_dir, S_IRWXU) == -1) {
+ g_set_error_literal (
+ error, G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ g_strerror (errno));
+ return FALSE;
+ }
+
+ /* This is for reading from the store */
+ filename = g_build_filename (user_dir, CAMEL_DB_FILE, NULL);
+ store->cdb_r = camel_db_open (filename, error);
+ g_free (filename);
+
+ if (store->cdb_r == NULL)
+ return FALSE;
+
+ if (camel_db_create_folders_table (store->cdb_r, error))
+ return FALSE;
+
+ /* keep cb_w to not break the ABI */
+ store->cdb_w = store->cdb_r;
+
+ return TRUE;
+}
+
+static void
+camel_store_class_init (CamelStoreClass *class)
+{
+ GObjectClass *object_class;
+ CamelServiceClass *service_class;
+
+ g_type_class_add_private (class, sizeof (CamelStorePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = store_finalize;
+ object_class->constructed = store_constructed;
+
+ service_class = CAMEL_SERVICE_CLASS (class);
+ service_class->settings_type = CAMEL_TYPE_STORE_SETTINGS;
+
+ class->hash_folder_name = g_str_hash;
+ class->equal_folder_name = g_str_equal;
+ class->can_refresh_folder = store_can_refresh_folder;
+
+ class->get_inbox_folder_sync = store_get_inbox_folder_sync;
+ class->get_junk_folder_sync = store_get_junk_folder_sync;
+ class->get_trash_folder_sync = store_get_trash_folder_sync;
+ class->synchronize_sync = store_synchronize_sync;
+ class->initial_setup_sync = store_initial_setup_sync;
+
+ signals[FOLDER_CREATED] = g_signal_new (
+ "folder-created",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CamelStoreClass, folder_created),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ signals[FOLDER_DELETED] = g_signal_new (
+ "folder-deleted",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CamelStoreClass, folder_deleted),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ /**
+ * CamelStore::folder-info-stale:
+ * @store: the #CamelStore that received the signal
+ *
+ * This signal indicates significant changes have occurred to
+ * the folder hierarchy of @store, and that previously fetched
+ * #CamelFolderInfo data should be considered stale.
+ *
+ * Applications should handle this signal by replacing cached
+ * #CamelFolderInfo data for @store with fresh data by way of
+ * camel_store_get_folder_info().
+ *
+ * More often than not this signal will be emitted as a result of
+ * user preference changes rather than actual server-side changes.
+ * For example, a user may change a preference that reveals a set
+ * of folders previously hidden from view, or that alters whether
+ * to augment the @store with virtual Junk and Trash folders.
+ **/
+ signals[FOLDER_INFO_STALE] = g_signal_new (
+ "folder-info-stale",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CamelStoreClass, folder_info_stale),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals[FOLDER_OPENED] = g_signal_new (
+ "folder-opened",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CamelStoreClass, folder_opened),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ CAMEL_TYPE_FOLDER);
+
+ signals[FOLDER_RENAMED] = g_signal_new (
+ "folder-renamed",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CamelStoreClass, folder_renamed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_POINTER);
+}
+
+static void
+camel_store_initable_init (GInitableIface *iface)
+{
+ parent_initable_interface = g_type_interface_peek_parent (iface);
+
+ iface->init = store_initable_init;
+}
+
+static void
+camel_store_init (CamelStore *store)
+{
+ store->priv = CAMEL_STORE_GET_PRIVATE (store);
+
+ /* Default CamelStore capabilities:
+ *
+ * - Include a virtual Junk folder.
+ * - Include a virtual Trash folder.
+ * - Allow creating/deleting/renaming folders.
+ */
+ store->flags =
+ CAMEL_STORE_VJUNK |
+ CAMEL_STORE_VTRASH |
+ CAMEL_STORE_CAN_EDIT_FOLDERS;
+
+ store->mode = CAMEL_STORE_READ | CAMEL_STORE_WRITE;
+ store->priv->maintenance_lock = 0;
+}
+
+G_DEFINE_QUARK (camel-store-error-quark, camel_store_error)
+
+/**
+ * camel_store_folder_created:
+ * @store: a #CamelStore
+ * @folder_info: information about the created folder
+ *
+ * Emits the #CamelStore::folder-created signal from an idle source on
+ * the main loop. The idle source's priority is #G_PRIORITY_HIGH_IDLE.
+ *
+ * This function is only intended for Camel providers.
+ *
+ * Since: 2.32
+ **/
+void
+camel_store_folder_created (CamelStore *store,
+ CamelFolderInfo *folder_info)
+{
+ CamelSession *session;
+ SignalClosure *signal_closure;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+ g_return_if_fail (folder_info != NULL);
+
+ session = camel_service_ref_session (CAMEL_SERVICE (store));
+ if (!session)
+ return;
+
+ signal_closure = g_slice_new0 (SignalClosure);
+ g_weak_ref_init (&signal_closure->store, store);
+ signal_closure->folder_info = camel_folder_info_clone (folder_info);
+
+ /* Prioritize ahead of GTK+ redraws. */
+ camel_session_idle_add (
+ session, G_PRIORITY_HIGH_IDLE,
+ store_emit_folder_created_cb,
+ signal_closure,
+ (GDestroyNotify) signal_closure_free);
+
+ g_object_unref (session);
+}
+
+/**
+ * camel_store_folder_deleted:
+ * @store: a #CamelStore
+ * @folder_info: information about the deleted folder
+ *
+ * Emits the #CamelStore::folder-deleted signal from an idle source on
+ * the main loop. The idle source's priority is #G_PRIORITY_HIGH_IDLE.
+ *
+ * This function is only intended for Camel providers.
+ *
+ * Since: 2.32
+ **/
+void
+camel_store_folder_deleted (CamelStore *store,
+ CamelFolderInfo *folder_info)
+{
+ CamelSession *session;
+ SignalClosure *signal_closure;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+ g_return_if_fail (folder_info != NULL);
+
+ session = camel_service_ref_session (CAMEL_SERVICE (store));
+ if (!session)
+ return;
+
+ signal_closure = g_slice_new0 (SignalClosure);
+ g_weak_ref_init (&signal_closure->store, store);
+ signal_closure->folder_info = camel_folder_info_clone (folder_info);
+
+ /* Prioritize ahead of GTK+ redraws. */
+ camel_session_idle_add (
+ session, G_PRIORITY_HIGH_IDLE,
+ store_emit_folder_deleted_cb,
+ signal_closure,
+ (GDestroyNotify) signal_closure_free);
+
+ g_object_unref (session);
+}
+
+/**
+ * camel_store_folder_opened:
+ * @store: a #CamelStore
+ * @folder: the #CamelFolder that was opened
+ *
+ * Emits the #CamelStore::folder-opened signal from an idle source on
+ * the main loop. The idle source's priority is #G_PRIORITY_HIGH_IDLE.
+ *
+ * This function is only intended for Camel providers.
+ *
+ * Since: 3.0
+ **/
+void
+camel_store_folder_opened (CamelStore *store,
+ CamelFolder *folder)
+{
+ CamelSession *session;
+ SignalClosure *signal_closure;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+
+ session = camel_service_ref_session (CAMEL_SERVICE (store));
+ if (!session)
+ return;
+
+ signal_closure = g_slice_new0 (SignalClosure);
+ g_weak_ref_init (&signal_closure->store, store);
+ signal_closure->folder = g_object_ref (folder);
+
+ /* Prioritize ahead of GTK+ redraws. */
+ camel_session_idle_add (
+ session, G_PRIORITY_HIGH_IDLE,
+ store_emit_folder_opened_cb,
+ signal_closure,
+ (GDestroyNotify) signal_closure_free);
+
+ g_object_unref (session);
+}
+
+/**
+ * camel_store_folder_renamed:
+ * @store: a #CamelStore
+ * @old_name: the old name of the folder
+ * @folder_info: information about the renamed folder
+ *
+ * Emits the #CamelStore::folder-renamed signal from an idle source on
+ * the main loop. The idle source's priority is #G_PRIORITY_HIGH_IDLE.
+ *
+ * This function is only intended for Camel providers.
+ *
+ * Since: 2.32
+ **/
+void
+camel_store_folder_renamed (CamelStore *store,
+ const gchar *old_name,
+ CamelFolderInfo *folder_info)
+{
+ CamelSession *session;
+ SignalClosure *signal_closure;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+ g_return_if_fail (old_name != NULL);
+ g_return_if_fail (folder_info != NULL);
+
+ session = camel_service_ref_session (CAMEL_SERVICE (store));
+ if (!session)
+ return;
+
+ signal_closure = g_slice_new0 (SignalClosure);
+ g_weak_ref_init (&signal_closure->store, store);
+ signal_closure->folder_info = camel_folder_info_clone (folder_info);
+ signal_closure->folder_name = g_strdup (old_name);
+
+ /* Prioritize ahead of GTK+ redraws. */
+ camel_session_idle_add (
+ session, G_PRIORITY_HIGH_IDLE,
+ store_emit_folder_renamed_cb,
+ signal_closure,
+ (GDestroyNotify) signal_closure_free);
+
+ g_object_unref (session);
+}
+
+/**
+ * camel_store_folder_info_stale:
+ * @store: a #CamelStore
+ *
+ * Emits the #CamelStore::folder-info-stale signal from an idle source
+ * on the main loop. The idle source's priority is #G_PRIORITY_LOW.
+ *
+ * See the #CamelStore::folder-info-stale documentation for details on
+ * when to use this signal.
+ *
+ * This function is only intended for Camel providers.
+ *
+ * Since: 3.10
+ **/
+void
+camel_store_folder_info_stale (CamelStore *store)
+{
+ CamelSession *session;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ session = camel_service_ref_session (CAMEL_SERVICE (store));
+ if (!session)
+ return;
+
+ g_mutex_lock (&store->priv->signal_emission_lock);
+
+ /* Handling this signal is probably going to be expensive for
+ * applications so try and accumulate multiple calls into one
+ * signal emission if we can. Hence the G_PRIORITY_LOW. */
+ if (!store->priv->folder_info_stale_scheduled) {
+ SignalClosure *signal_closure;
+
+ signal_closure = g_slice_new0 (SignalClosure);
+ g_weak_ref_init (&signal_closure->store, store);
+
+ camel_session_idle_add (
+ session, G_PRIORITY_LOW,
+ store_emit_folder_info_stale_cb,
+ signal_closure,
+ (GDestroyNotify) signal_closure_free);
+
+ store->priv->folder_info_stale_scheduled = TRUE;
+ }
+
+ g_mutex_unlock (&store->priv->signal_emission_lock);
+
+ g_object_unref (session);
+}
+
+static void
+add_special_info (CamelStore *store,
+ CamelFolderInfo *info,
+ const gchar *name,
+ const gchar *translated,
+ gboolean unread_count,
+ CamelFolderInfoFlags flags)
+{
+ CamelFolderInfo *fi, *vinfo, *parent;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+ g_return_if_fail (info != NULL);
+
+ parent = NULL;
+ for (fi = info; fi; fi = fi->next) {
+ if (!strcmp (fi->full_name, name))
+ break;
+ parent = fi;
+ }
+
+ 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->display_name);
+ } else {
+ g_return_if_fail (parent != NULL);
+
+ /* There wasn't a Trash/Junk folder so create a new
+ * folder entry. */
+ vinfo = camel_folder_info_new ();
+
+ 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 |= flags;
+ vinfo->full_name = g_strdup (name);
+ vinfo->display_name = g_strdup (translated);
+
+ if (!unread_count)
+ vinfo->unread = -1;
+}
+
+static void
+dump_fi (CamelFolderInfo *fi,
+ gint depth)
+{
+ gchar *s;
+
+ s = g_alloca (depth + 1);
+ memset (s, ' ', depth);
+ s[depth] = 0;
+
+ while (fi) {
+ 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_folder_info_free:
+ * @fi: a #CamelFolderInfo
+ *
+ * Frees @fi.
+ **/
+void
+camel_folder_info_free (CamelFolderInfo *fi)
+{
+ if (fi != NULL) {
+ camel_folder_info_free (fi->next);
+ camel_folder_info_free (fi->child);
+ g_free (fi->full_name);
+ g_free (fi->display_name);
+ g_slice_free (CamelFolderInfo, fi);
+ }
+}
+
+/**
+ * camel_folder_info_new:
+ *
+ * Allocates a new #CamelFolderInfo instance. Free it with
+ * camel_folder_info_free().
+ *
+ * Returns: a new #CamelFolderInfo instance
+ *
+ * Since: 2.22
+ **/
+CamelFolderInfo *
+camel_folder_info_new (void)
+{
+ return g_slice_new0 (CamelFolderInfo);
+}
+
+static gint
+folder_info_cmp (gconstpointer ap,
+ gconstpointer bp)
+{
+ const CamelFolderInfo *a = ((CamelFolderInfo **) ap)[0];
+ const CamelFolderInfo *b = ((CamelFolderInfo **) bp)[0];
+
+ return strcmp (a->full_name, b->full_name);
+}
+
+/**
+ * camel_folder_info_build:
+ * @folders: (element-type CamelFolderInfo): 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.
+ *
+ * Deprecated:
+ * Returns: the top level of the tree of linked folder info.
+ **/
+CamelFolderInfo *
+camel_folder_info_build (GPtrArray *folders,
+ const gchar *namespace_,
+ gchar separator,
+ gboolean short_names)
+{
+ CamelFolderInfo *fi, *pfi, *top = NULL, *tail = NULL;
+ GHashTable *hash;
+ gchar *p, *pname;
+ gint i, nlen;
+
+ if (namespace_ == NULL)
+ namespace_ = "";
+ nlen = strlen (namespace_);
+
+ qsort (folders->pdata, folders->len, sizeof (folders->pdata[0]), folder_info_cmp);
+
+ /* Hash the folders. */
+ hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ for (i = 0; i < folders->len; i++) {
+ fi = folders->pdata[i];
+ g_hash_table_insert (hash, g_strdup (fi->full_name), fi);
+ }
+
+ /* Now find parents. */
+ for (i = 0; i < folders->len; i++) {
+ fi = folders->pdata[i];
+ if (!strncmp (namespace_, fi->full_name, nlen)
+ && (p = strrchr (fi->full_name + nlen, separator))) {
+ pname = g_strndup (fi->full_name, p - fi->full_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 */
+
+ pfi = camel_folder_info_new ();
+
+ if (short_names) {
+ pfi->display_name = strrchr (pname, separator);
+ if (pfi->display_name != NULL)
+ pfi->display_name = g_strdup (pfi->display_name + 1);
+ else
+ pfi->display_name = g_strdup (pname);
+ } else
+ pfi->display_name = g_strdup (pname);
+
+ pfi->full_name = g_strdup (pname);
+
+ /* Since this is a "fake" folder
+ * node, it is not selectable. */
+ pfi->flags |= CAMEL_FOLDER_NOSELECT;
+
+ 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 || !g_ascii_strcasecmp (fi->full_name, "Inbox"))
+ top = fi;
+ }
+ 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->child)
+ fi->flags &= ~CAMEL_FOLDER_NOCHILDREN;
+
+ 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 = camel_folder_info_new ();
+ info->parent = parent;
+ info->full_name = g_strdup (fi->full_name);
+ info->display_name = g_strdup (fi->display_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;
+}
+
+/**
+ * camel_folder_info_clone:
+ * @fi: a #CamelFolderInfo
+ *
+ * Clones @fi recursively.
+ *
+ * Returns: the cloned #CamelFolderInfo tree.
+ **/
+CamelFolderInfo *
+camel_folder_info_clone (CamelFolderInfo *fi)
+{
+ if (fi == NULL)
+ return NULL;
+
+ return folder_info_clone_rec (fi, NULL);
+}
+
+/**
+ * camel_store_can_refresh_folder
+ * @store: a #CamelStore
+ * @info: a #CamelFolderInfo
+ * @error: return location for a #GError, or %NULL
+ *
+ * Returns if this folder (param info) should be checked for new mail or not.
+ * It should not look into sub infos (info->child) or next infos, it should
+ * return value only for the actual folder info.
+ * Default behavior is that all Inbox folders are intended to be refreshed.
+ *
+ * Returns: whether folder should be checked for new mails
+ *
+ * Since: 2.22
+ **/
+gboolean
+camel_store_can_refresh_folder (CamelStore *store,
+ CamelFolderInfo *info,
+ GError **error)
+{
+ CamelStoreClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
+ g_return_val_if_fail (info != NULL, FALSE);
+
+ class = CAMEL_STORE_GET_CLASS (store);
+ g_return_val_if_fail (class->can_refresh_folder != NULL, FALSE);
+
+ return class->can_refresh_folder (store, info, error);
+}
+
+/**
+ * camel_store_get_folder_sync:
+ * @store: a #CamelStore
+ * @folder_name: name of the folder to get
+ * @flags: folder flags (create, save body index, etc)
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets a specific folder object from @store by name.
+ *
+ * Returns: (transfer full): the requested #CamelFolder object, or %NULL on error
+ *
+ * Since: 3.0
+ **/
+CamelFolder *
+camel_store_get_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStoreClass *class;
+ CamelFolder *folder = NULL;
+ CamelVeeFolder *vjunk = NULL;
+ CamelVeeFolder *vtrash = NULL;
+ gboolean create_folder = FALSE;
+ gboolean folder_name_is_vjunk;
+ gboolean folder_name_is_vtrash;
+ gboolean store_uses_vjunk;
+ gboolean store_uses_vtrash;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+ g_return_val_if_fail (folder_name != NULL, NULL);
+
+ class = CAMEL_STORE_GET_CLASS (store);
+
+try_again:
+ /* Try cache first. */
+ folder = camel_object_bag_reserve (store->folders, folder_name);
+ if (folder != NULL) {
+ if ((flags & CAMEL_STORE_FOLDER_INFO_REFRESH) != 0)
+ camel_folder_prepare_content_refresh (folder);
+
+ return folder;
+ }
+
+ store_uses_vjunk =
+ ((store->flags & CAMEL_STORE_VJUNK) != 0);
+ store_uses_vtrash =
+ ((store->flags & CAMEL_STORE_VTRASH) != 0);
+ folder_name_is_vjunk =
+ store_uses_vjunk &&
+ (strcmp (folder_name, CAMEL_VJUNK_NAME) == 0);
+ folder_name_is_vtrash =
+ store_uses_vtrash &&
+ (strcmp (folder_name, CAMEL_VTRASH_NAME) == 0);
+
+ if (flags & CAMEL_STORE_IS_MIGRATING) {
+ if (folder_name_is_vtrash) {
+ if (store->folders != NULL)
+ camel_object_bag_abort (
+ store->folders, folder_name);
+ return NULL;
+ }
+
+ if (folder_name_is_vjunk) {
+ if (store->folders != NULL)
+ camel_object_bag_abort (
+ store->folders, folder_name);
+ return NULL;
+ }
+ }
+
+ camel_operation_push_message (
+ cancellable, _("Opening folder '%s'"), folder_name);
+
+ if (folder_name_is_vtrash) {
+ folder = class->get_trash_folder_sync (
+ store, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ store, get_trash_folder_sync,
+ folder != NULL, error);
+ } else if (folder_name_is_vjunk) {
+ folder = class->get_junk_folder_sync (
+ store, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ store, get_junk_folder_sync,
+ folder != NULL, error);
+ } else {
+ GError *local_error = NULL;
+
+ /* If CAMEL_STORE_FOLDER_CREATE flag is set, note it and
+ * strip it so subclasses never receive it. We'll handle
+ * it ourselves below. */
+ create_folder = ((flags & CAMEL_STORE_FOLDER_CREATE) != 0);
+ flags &= ~CAMEL_STORE_FOLDER_CREATE;
+
+ folder = class->get_folder_sync (
+ store, folder_name, flags,
+ cancellable, &local_error);
+ CAMEL_CHECK_LOCAL_GERROR (
+ store, get_folder_sync,
+ folder != NULL, local_error);
+
+ /* XXX This depends on subclasses setting this error code
+ * consistently. Do they? I guess we'll find out... */
+ create_folder &= g_error_matches (
+ local_error,
+ CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER);
+
+ if (create_folder)
+ g_clear_error (&local_error);
+
+ if (local_error != NULL)
+ g_propagate_error (error, local_error);
+
+ if (folder != NULL && store_uses_vjunk)
+ vjunk = camel_object_bag_get (
+ store->folders, CAMEL_VJUNK_NAME);
+
+ if (folder != NULL && store_uses_vtrash)
+ vtrash = camel_object_bag_get (
+ store->folders, CAMEL_VTRASH_NAME);
+ }
+
+ /* Release the folder name reservation before adding the
+ * folder to the virtual Junk and Trash folders, just to
+ * reduce the chance of deadlock. */
+ if (folder != NULL)
+ camel_object_bag_add (
+ store->folders, folder_name, folder);
+ else
+ camel_object_bag_abort (
+ store->folders, folder_name);
+
+ /* If this is a normal folder and the store uses a
+ * virtual Junk folder, let the virtual Junk folder
+ * track this folder. */
+ if (vjunk != NULL) {
+ camel_vee_folder_add_folder (vjunk, folder, NULL);
+ g_object_unref (vjunk);
+ }
+
+ /* If this is a normal folder and the store uses a
+ * virtual Trash folder, let the virtual Trash folder
+ * track this folder. */
+ if (vtrash != NULL) {
+ camel_vee_folder_add_folder (vtrash, folder, NULL);
+ g_object_unref (vtrash);
+ }
+
+ camel_operation_pop_message (cancellable);
+
+ if (folder != NULL)
+ camel_store_folder_opened (store, folder);
+
+ /* Handle CAMEL_STORE_FOLDER_CREATE flag. */
+ if (create_folder) {
+ CamelFolderInfo *folder_info;
+ gchar *reversed_name;
+ gchar **child_and_parent;
+
+ g_warn_if_fail (folder == NULL);
+
+ /* XXX GLib lacks a rightmost string splitting function,
+ * so we'll reverse the string and use g_strsplit(). */
+ reversed_name = g_strreverse (g_strdup (folder_name));
+ child_and_parent = g_strsplit (reversed_name, "/", 2);
+ g_return_val_if_fail (child_and_parent[0] != NULL, NULL);
+
+ /* Element 0 is the new folder name.
+ * Element 1 is the parent path, or NULL. */
+
+ /* XXX Reverse the child and parent names back. */
+ g_strreverse (child_and_parent[0]);
+ if (child_and_parent[1] != NULL)
+ g_strreverse (child_and_parent[1]);
+
+ /* Call the method directly to avoid the queuing
+ * behavior of camel_store_create_folder_sync(). */
+ folder_info = class->create_folder_sync (
+ store,
+ child_and_parent[1],
+ child_and_parent[0],
+ cancellable, error);
+ CAMEL_CHECK_GERROR (
+ store, create_folder_sync,
+ folder_info != NULL, error);
+
+ g_strfreev (child_and_parent);
+ g_free (reversed_name);
+
+ /* If we successfully created the folder, retry the
+ * method without the CAMEL_STORE_FOLDER_CREATE flag. */
+ if (folder_info != NULL) {
+ camel_folder_info_free (folder_info);
+ goto try_again;
+ }
+ }
+
+ if (folder && (flags & CAMEL_STORE_FOLDER_INFO_REFRESH) != 0)
+ camel_folder_prepare_content_refresh (folder);
+
+ return folder;
+}
+
+/* Helper for camel_store_get_folder() */
+static void
+store_get_folder_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelFolder *folder;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ folder = camel_store_get_folder_sync (
+ CAMEL_STORE (source_object),
+ async_context->folder_name_1,
+ async_context->flags,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_warn_if_fail (folder == NULL);
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_pointer (
+ task, folder,
+ (GDestroyNotify) g_object_unref);
+ }
+}
+
+/**
+ * camel_store_get_folder:
+ * @store: a #CamelStore
+ * @folder_name: name of the folder to get
+ * @flags: folder flags (create, save body index, etc)
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously gets a specific folder object from @store by name.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_store_get_folder_finish() to get the result of the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_store_get_folder (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+ g_return_if_fail (folder_name != NULL);
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->folder_name_1 = g_strdup (folder_name);
+ async_context->flags = flags;
+
+ task = g_task_new (store, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_store_get_folder);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, store_get_folder_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_store_get_folder_finish:
+ * @store: a #CamelStore
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_store_get_folder().
+ *
+ * Returns: (transfer full): the requested #CamelFolder object, or %NULL on error
+ *
+ * Since: 3.0
+ **/
+CamelFolder *
+camel_store_get_folder_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, store), NULL);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_store_get_folder), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/**
+ * camel_store_get_folder_info_sync:
+ * @store: a #CamelStore
+ * @top: the name of the folder to start from
+ * @flags: various CAMEL_STORE_FOLDER_INFO_* flags to control behavior
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * 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. If the store doesn't support
+ * subscriptions, then it will list all folders. 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).
+ *
+ * The returned #CamelFolderInfo tree should be freed with
+ * camel_folder_info_free().
+ *
+ * The CAMEL_STORE_FOLDER_INFO_FAST flag should be considered
+ * deprecated; most backends will behave the same whether it is
+ * supplied or not. The only guaranteed way to get updated folder
+ * counts is to both open the folder and invoke refresh_info() it.
+ *
+ * Returns: a #CamelFolderInfo tree, or %NULL on error
+ *
+ * Since: 3.0
+ **/
+CamelFolderInfo *
+camel_store_get_folder_info_sync (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStoreClass *class;
+ CamelFolderInfo *info;
+ gboolean allow_virtual;
+ gboolean start_at_root;
+ gboolean store_has_vtrash;
+ gboolean store_has_vjunk;
+ gchar *name;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+
+ class = CAMEL_STORE_GET_CLASS (store);
+ g_return_val_if_fail (class->get_folder_info_sync != NULL, NULL);
+
+ name = camel_service_get_name (CAMEL_SERVICE (store), TRUE);
+ camel_operation_push_message (
+ cancellable, _("Scanning folders in '%s'"), name);
+ g_free (name);
+
+ /* Recover from a dropped connection, unless we're offline. */
+ if (!store_maybe_connect_sync (store, cancellable, error)) {
+ camel_operation_pop_message (cancellable);
+ return NULL;
+ }
+
+ info = class->get_folder_info_sync (
+ store, top, flags, cancellable, error);
+ if ((flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) == 0)
+ CAMEL_CHECK_GERROR (
+ store, get_folder_info_sync, info != NULL, error);
+
+ /* For readability. */
+ allow_virtual = ((flags & CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL) == 0);
+ start_at_root = (top == NULL || *top == '\0');
+ store_has_vtrash = ((store->flags & CAMEL_STORE_VTRASH) != 0);
+ store_has_vjunk = ((store->flags & CAMEL_STORE_VJUNK) != 0);
+
+ if (info != NULL && start_at_root && allow_virtual) {
+ if (store_has_vtrash) {
+ /* Add the virtual Trash folder. */
+ add_special_info (
+ store,
+ info,
+ CAMEL_VTRASH_NAME,
+ _("Trash"),
+ FALSE,
+ CAMEL_FOLDER_VIRTUAL |
+ CAMEL_FOLDER_SYSTEM |
+ CAMEL_FOLDER_VTRASH |
+ CAMEL_FOLDER_TYPE_TRASH);
+ }
+
+ if (store_has_vjunk) {
+ /* Add the virtual Junk folder. */
+ add_special_info (
+ store,
+ info,
+ CAMEL_VJUNK_NAME,
+ _("Junk"),
+ TRUE,
+ CAMEL_FOLDER_VIRTUAL |
+ CAMEL_FOLDER_SYSTEM |
+ CAMEL_FOLDER_VTRASH |
+ CAMEL_FOLDER_TYPE_JUNK);
+ }
+
+ } else if (info == NULL && !start_at_root && allow_virtual) {
+ CamelFolderInfo *root_info = NULL;
+ gboolean start_at_vtrash;
+ gboolean start_at_vjunk;
+
+ start_at_vtrash =
+ store_has_vtrash &&
+ g_str_equal (top, CAMEL_VTRASH_NAME);
+
+ start_at_vjunk =
+ store_has_vjunk &&
+ g_str_equal (top, CAMEL_VJUNK_NAME);
+
+ if (start_at_vtrash) {
+ root_info = class->get_folder_info_sync (
+ store, NULL,
+ flags & (~CAMEL_STORE_FOLDER_INFO_RECURSIVE),
+ cancellable, error);
+ if (root_info != NULL)
+ add_special_info (
+ store,
+ root_info,
+ CAMEL_VTRASH_NAME,
+ _("Trash"),
+ FALSE,
+ CAMEL_FOLDER_VIRTUAL |
+ CAMEL_FOLDER_SYSTEM |
+ CAMEL_FOLDER_VTRASH |
+ CAMEL_FOLDER_TYPE_TRASH);
+
+ } else if (start_at_vjunk) {
+ root_info = class->get_folder_info_sync (
+ store, NULL,
+ flags & (~CAMEL_STORE_FOLDER_INFO_RECURSIVE),
+ cancellable, error);
+ if (root_info != NULL)
+ add_special_info (
+ store,
+ root_info,
+ CAMEL_VJUNK_NAME,
+ _("Junk"),
+ TRUE,
+ CAMEL_FOLDER_VIRTUAL |
+ CAMEL_FOLDER_SYSTEM |
+ CAMEL_FOLDER_VTRASH |
+ CAMEL_FOLDER_TYPE_JUNK);
+ }
+
+ if (root_info != NULL) {
+ info = root_info->next;
+ root_info->next = NULL;
+ info->next = NULL;
+ info->parent = NULL;
+
+ camel_folder_info_free (root_info);
+ }
+ }
+
+ camel_operation_pop_message (cancellable);
+
+ if (camel_debug_start ("store:folder_info")) {
+ const gchar *uid;
+
+ uid = camel_service_get_uid (CAMEL_SERVICE (store));
+ printf (
+ "Get folder info(%p:%s, '%s') =\n",
+ (gpointer) store, uid, top ? top : "<null>");
+ dump_fi (info, 2);
+ camel_debug_end ();
+ }
+
+ return info;
+}
+
+/* Helper for camel_store_get_folder_info() */
+static void
+store_get_folder_info_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelFolderInfo *folder_info;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ folder_info = camel_store_get_folder_info_sync (
+ CAMEL_STORE (source_object),
+ async_context->folder_name_1,
+ async_context->flags,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_warn_if_fail (folder_info == NULL);
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_pointer (
+ task, folder_info,
+ (GDestroyNotify) camel_folder_info_free);
+ }
+}
+
+/**
+ * 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
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously fetches information about the folder structure of @store,
+ * starting with @top. For details of the behavior, see
+ * camel_store_get_folder_info_sync().
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_store_get_folder_info_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_store_get_folder_info (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->folder_name_1 = g_strdup (top);
+ async_context->flags = flags;
+
+ task = g_task_new (store, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_store_get_folder_info);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, store_get_folder_info_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_store_get_folder_info_finish:
+ * @store: a #CamelStore
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_store_get_folder_info().
+ * The returned #CamelFolderInfo tree should be freed with
+ * camel_folder_info_free().
+ *
+ * Returns: a #CamelFolderInfo tree, or %NULL on error
+ *
+ * Since: 3.0
+ **/
+CamelFolderInfo *
+camel_store_get_folder_info_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, store), NULL);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_store_get_folder_info), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/**
+ * camel_store_get_inbox_folder_sync:
+ * @store: a #CamelStore
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets the folder in @store into which new mail is delivered.
+ *
+ * Returns: (transfer full): the inbox folder for @store, or %NULL on error or if no such
+ * folder exists
+ *
+ * Since: 3.0
+ **/
+CamelFolder *
+camel_store_get_inbox_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStoreClass *class;
+ CamelFolder *folder;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+
+ class = CAMEL_STORE_GET_CLASS (store);
+ g_return_val_if_fail (class->get_inbox_folder_sync != NULL, NULL);
+
+ folder = class->get_inbox_folder_sync (store, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ store, get_inbox_folder_sync, folder != NULL, error);
+
+ return folder;
+}
+
+/* Helper for camel_store_get_inbox_folder() */
+static void
+store_get_inbox_folder_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelFolder *folder;
+ GError *local_error = NULL;
+
+ folder = camel_store_get_inbox_folder_sync (
+ CAMEL_STORE (source_object),
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_warn_if_fail (folder == NULL);
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_pointer (
+ task, folder,
+ (GDestroyNotify) g_object_unref);
+ }
+}
+
+/**
+ * camel_store_get_inbox_folder:
+ * @store: a #CamelStore
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously gets the folder in @store into which new mail is delivered.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_store_get_inbox_folder_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_store_get_inbox_folder (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ task = g_task_new (store, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_store_get_inbox_folder);
+ g_task_set_priority (task, io_priority);
+
+ g_task_run_in_thread (task, store_get_inbox_folder_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_store_get_inbox_folder_finish:
+ * @store: a #CamelStore
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_store_get_inbox_folder().
+ *
+ * Returns: (transfer full): the inbox folder for @store, or %NULL on error or if no such
+ * folder exists
+ *
+ * Since: 3.0
+ **/
+CamelFolder *
+camel_store_get_inbox_folder_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, store), NULL);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_store_get_inbox_folder), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/**
+ * camel_store_get_junk_folder_sync:
+ * @store: a #CamelStore
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets the folder in @store into which junk is delivered.
+ *
+ * Returns: (transfer full): the junk folder for @store, or %NULL on error or if no such
+ * folder exists
+ *
+ * Since: 3.0
+ **/
+CamelFolder *
+camel_store_get_junk_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+
+ if ((store->flags & CAMEL_STORE_VJUNK) == 0) {
+ CamelStoreClass *class;
+ CamelFolder *folder;
+
+ class = CAMEL_STORE_GET_CLASS (store);
+ g_return_val_if_fail (class->get_junk_folder_sync != NULL, NULL);
+
+ folder = class->get_junk_folder_sync (store, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ store, get_junk_folder_sync, folder != NULL, error);
+
+ return folder;
+ }
+
+ return camel_store_get_folder_sync (
+ store, CAMEL_VJUNK_NAME, 0, cancellable, error);
+}
+
+/* Helper for camel_store_get_junk_folder() */
+static void
+store_get_junk_folder_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelFolder *folder;
+ GError *local_error = NULL;
+
+ folder = camel_store_get_junk_folder_sync (
+ CAMEL_STORE (source_object),
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_warn_if_fail (folder == NULL);
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_pointer (
+ task, folder,
+ (GDestroyNotify) g_object_unref);
+ }
+}
+
+/**
+ * camel_store_get_junk_folder:
+ * @store: a #CamelStore
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously gets the folder in @store into which junk is delivered.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_store_get_junk_folder_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_store_get_junk_folder (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ task = g_task_new (store, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_store_get_junk_folder);
+ g_task_set_priority (task, io_priority);
+
+ g_task_run_in_thread (task, store_get_junk_folder_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_store_get_junk_folder_finish:
+ * @store: a #CamelStore
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_store_get_junk_folder().
+ *
+ * Returns: (transfer full): the junk folder for @store, or %NULL on error or if no such
+ * folder exists
+ *
+ * Since: 3.0
+ **/
+CamelFolder *
+camel_store_get_junk_folder_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, store), NULL);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_store_get_junk_folder), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/**
+ * camel_store_get_trash_folder_sync:
+ * @store: a #CamelStore
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets the folder in @store into which trash is delivered.
+ *
+ * Returns:(transfer full): the trash folder for @store, or %NULL on error or if no such
+ * folder exists
+ *
+ * Since: 3.0
+ **/
+CamelFolder *
+camel_store_get_trash_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+
+ if ((store->flags & CAMEL_STORE_VTRASH) == 0) {
+ CamelStoreClass *class;
+ CamelFolder *folder;
+
+ class = CAMEL_STORE_GET_CLASS (store);
+ g_return_val_if_fail (class->get_trash_folder_sync != NULL, NULL);
+
+ folder = class->get_trash_folder_sync (
+ store, cancellable, error);
+ CAMEL_CHECK_GERROR (
+ store, get_trash_folder_sync, folder != NULL, error);
+
+ return folder;
+ }
+
+ return camel_store_get_folder_sync (
+ store, CAMEL_VTRASH_NAME, 0, cancellable, error);
+}
+
+/* Helper for camel_store_get_trash_folder() */
+static void
+store_get_trash_folder_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelFolder *folder;
+ GError *local_error = NULL;
+
+ folder = camel_store_get_trash_folder_sync (
+ CAMEL_STORE (source_object),
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_warn_if_fail (folder == NULL);
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_pointer (
+ task, folder,
+ (GDestroyNotify) g_object_unref);
+ }
+}
+
+/**
+ * camel_store_get_trash_folder:
+ * @store: a #CamelStore
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously gets the folder in @store into which trash is delivered.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call camel_store_get_trash_folder_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_store_get_trash_folder (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ task = g_task_new (store, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_store_get_trash_folder);
+ g_task_set_priority (task, io_priority);
+
+ g_task_run_in_thread (task, store_get_trash_folder_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_store_get_trash_folder_finish:
+ * @store: a #CamelStore
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_store_get_trash_folder().
+ *
+ * Returns: (transfer full): the trash folder for @store, or %NULL on error or if no such
+ * folder exists
+ *
+ * Since: 3.0
+ **/
+CamelFolder *
+camel_store_get_trash_folder_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, store), NULL);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_store_get_trash_folder), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/**
+ * camel_store_create_folder_sync:
+ * @store: a #CamelStore
+ * @parent_name: name of the new folder's parent, or %NULL
+ * @folder_name: name of the folder to create
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a new folder as a child of an existing folder.
+ * @parent_name can be %NULL to create a new top-level folder.
+ * The returned #CamelFolderInfo struct should be freed with
+ * camel_folder_info_free().
+ *
+ * Returns: info about the created folder, or %NULL on error
+ *
+ * Since: 3.0
+ **/
+CamelFolderInfo *
+camel_store_create_folder_sync (CamelStore *store,
+ const gchar *parent_name,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelAsyncClosure *closure;
+ GAsyncResult *result;
+ CamelFolderInfo *folder_info;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+ g_return_val_if_fail (folder_name != NULL, NULL);
+
+ closure = camel_async_closure_new ();
+
+ camel_store_create_folder (
+ store, parent_name, folder_name,
+ G_PRIORITY_DEFAULT, cancellable,
+ camel_async_closure_callback, closure);
+
+ result = camel_async_closure_wait (closure);
+
+ folder_info = camel_store_create_folder_finish (store, result, error);
+
+ camel_async_closure_free (closure);
+
+ return folder_info;
+}
+
+/* Helper for camel_store_create_folder() */
+static void
+store_create_folder_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelStore *store;
+ CamelStoreClass *class;
+ AsyncContext *async_context;
+ CamelFolderInfo *folder_info;
+ const gchar *parent_name;
+ const gchar *folder_name;
+ GError *local_error = NULL;
+
+ store = CAMEL_STORE (source_object);
+ async_context = (AsyncContext *) task_data;
+
+ parent_name = async_context->folder_name_1;
+ folder_name = async_context->folder_name_2;
+
+ class = CAMEL_STORE_GET_CLASS (store);
+ g_return_if_fail (class->create_folder_sync != NULL);
+
+ if (parent_name == NULL || *parent_name == '\0') {
+ gboolean reserved_vfolder_name;
+
+ reserved_vfolder_name =
+ ((store->flags & CAMEL_STORE_VJUNK) &&
+ g_str_equal (folder_name, CAMEL_VJUNK_NAME)) ||
+ ((store->flags & CAMEL_STORE_VTRASH) &&
+ g_str_equal (folder_name, CAMEL_VTRASH_NAME));
+
+ if (reserved_vfolder_name) {
+ g_task_return_new_error (
+ task, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_INVALID,
+ _("Cannot create folder: %s: folder exists"),
+ folder_name);
+ return;
+ }
+ }
+
+ camel_operation_push_message (
+ cancellable, _("Creating folder '%s'"), folder_name);
+
+ folder_info = class->create_folder_sync (
+ store, parent_name, folder_name, cancellable, &local_error);
+ CAMEL_CHECK_LOCAL_GERROR (
+ store, create_folder_sync, folder_info != NULL, local_error);
+
+ camel_operation_pop_message (cancellable);
+
+ if (local_error != NULL) {
+ g_warn_if_fail (folder_info == NULL);
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_pointer (
+ task, folder_info,
+ (GDestroyNotify) camel_folder_info_free);
+ }
+}
+
+/**
+ * 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
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously creates a new folder as a child of an existing folder.
+ * @parent_name can be %NULL to create a new top-level folder.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_store_create_folder_finish() to get the result of the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_store_create_folder (CamelStore *store,
+ const gchar *parent_name,
+ const gchar *folder_name,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CamelService *service;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+ g_return_if_fail (folder_name != NULL);
+
+ service = CAMEL_SERVICE (store);
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->folder_name_1 = g_strdup (parent_name);
+ async_context->folder_name_2 = g_strdup (folder_name);
+
+ task = g_task_new (store, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_store_create_folder);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ camel_service_queue_task (
+ service, task, store_create_folder_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_store_create_folder_finish:
+ * @store: a #CamelStore
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_store_create_folder().
+ * The returned #CamelFolderInfo struct should be freed with
+ * camel_folder_info_free().
+ *
+ * Returns: info about the created folder, or %NULL on error
+ *
+ * Since: 3.0
+ **/
+CamelFolderInfo *
+camel_store_create_folder_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, store), NULL);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_store_create_folder), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/**
+ * camel_store_delete_folder_sync:
+ * @store: a #CamelStore
+ * @folder_name: name of the folder to delete
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Deletes the folder described by @folder_name. The folder must be empty.
+ *
+ * Returns: %TRUE on success, %FALSE on failure
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_store_delete_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelAsyncClosure *closure;
+ GAsyncResult *result;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
+ g_return_val_if_fail (folder_name != NULL, FALSE);
+
+ closure = camel_async_closure_new ();
+
+ camel_store_delete_folder (
+ store, folder_name,
+ G_PRIORITY_DEFAULT, cancellable,
+ camel_async_closure_callback, closure);
+
+ result = camel_async_closure_wait (closure);
+
+ success = camel_store_delete_folder_finish (store, result, error);
+
+ camel_async_closure_free (closure);
+
+ return success;
+}
+
+/* Helper for camel_store_delete_folder() */
+static void
+store_delete_folder_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelStore *store;
+ CamelStoreClass *class;
+ AsyncContext *async_context;
+ const gchar *folder_name;
+ gboolean reserved_vfolder_name;
+ gboolean success;
+ GError *local_error = NULL;
+
+ store = CAMEL_STORE (source_object);
+ async_context = (AsyncContext *) task_data;
+
+ folder_name = async_context->folder_name_1;
+
+ class = CAMEL_STORE_GET_CLASS (store);
+ g_return_if_fail (class->delete_folder_sync != NULL);
+
+ reserved_vfolder_name =
+ ((store->flags & CAMEL_STORE_VJUNK) &&
+ g_str_equal (folder_name, CAMEL_VJUNK_NAME)) ||
+ ((store->flags & CAMEL_STORE_VTRASH) &&
+ g_str_equal (folder_name, CAMEL_VTRASH_NAME));
+
+ if (reserved_vfolder_name) {
+ g_task_return_new_error (
+ task, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Cannot delete folder: %s: Invalid operation"),
+ folder_name);
+ return;
+ }
+
+ success = class->delete_folder_sync (
+ store, folder_name, cancellable, &local_error);
+ CAMEL_CHECK_LOCAL_GERROR (
+ store, delete_folder_sync, success, local_error);
+
+ /* ignore 'no such table' errors */
+ if (local_error != NULL &&
+ g_ascii_strncasecmp (local_error->message, "no such table", 13) == 0)
+ g_clear_error (&local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ cs_delete_cached_folder (store, folder_name);
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_store_delete_folder:
+ * @store: a #CamelStore
+ * @folder_name: name of the folder to delete
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously deletes the folder described by @folder_name. The
+ * folder must be empty.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_store_delete_folder_finish() to get the result of the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_store_delete_folder (CamelStore *store,
+ const gchar *folder_name,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CamelService *service;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+ g_return_if_fail (folder_name != NULL);
+
+ service = CAMEL_SERVICE (store);
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->folder_name_1 = g_strdup (folder_name);
+
+ task = g_task_new (store, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_store_delete_folder);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ camel_service_queue_task (
+ service, task, store_delete_folder_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_store_delete_folder_finish:
+ * @store: a #CamelStore
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_store_delete_folder().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_store_delete_folder_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, store), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_store_delete_folder), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_store_rename_folder_sync:
+ * @store: a #CamelStore
+ * @old_name: the current name of the folder
+ * @new_name: the new name of the folder
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Renames the folder described by @old_name to @new_name.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_store_rename_folder_sync (CamelStore *store,
+ const gchar *old_name,
+ const gchar *new_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelAsyncClosure *closure;
+ GAsyncResult *result;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
+ g_return_val_if_fail (old_name != NULL, FALSE);
+ g_return_val_if_fail (new_name != NULL, FALSE);
+
+ closure = camel_async_closure_new ();
+
+ camel_store_rename_folder (
+ store, old_name, new_name,
+ G_PRIORITY_DEFAULT, cancellable,
+ camel_async_closure_callback, closure);
+
+ result = camel_async_closure_wait (closure);
+
+ success = camel_store_rename_folder_finish (store, result, error);
+
+ camel_async_closure_free (closure);
+
+ return success;
+}
+
+/* Helper for camel_store_rename_folder() */
+static void
+store_rename_folder_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelStore *store;
+ CamelStoreClass *class;
+ CamelFolder *folder;
+ GPtrArray *folders;
+ const gchar *old_name;
+ const gchar *new_name;
+ gboolean reserved_vfolder_name;
+ gboolean success;
+ gsize old_name_len;
+ guint ii;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ store = CAMEL_STORE (source_object);
+ async_context = (AsyncContext *) task_data;
+
+ old_name = async_context->folder_name_1;
+ new_name = async_context->folder_name_2;
+
+ class = CAMEL_STORE_GET_CLASS (store);
+ g_return_if_fail (class->rename_folder_sync != NULL);
+
+ if (g_str_equal (old_name, new_name)) {
+ g_task_return_boolean (task, TRUE);
+ return;
+ }
+
+ reserved_vfolder_name =
+ ((store->flags & CAMEL_STORE_VJUNK) &&
+ g_str_equal (old_name, CAMEL_VJUNK_NAME)) ||
+ ((store->flags & CAMEL_STORE_VTRASH) &&
+ g_str_equal (old_name, CAMEL_VTRASH_NAME));
+
+ if (reserved_vfolder_name) {
+ g_task_return_new_error (
+ task, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Cannot rename folder: %s: Invalid operation"),
+ old_name);
+ return;
+ }
+
+ old_name_len = strlen (old_name);
+
+ /* If the folder is open (or any subfolders of the open folder)
+ * We need to rename them atomically with renaming the actual
+ * folder path. */
+ folders = camel_object_bag_list (store->folders);
+ for (ii = 0; ii < folders->len; ii++) {
+ const gchar *full_name;
+ gsize full_name_len;
+
+ folder = folders->pdata[ii];
+ full_name = camel_folder_get_full_name (folder);
+ full_name_len = strlen (full_name);
+
+ if ((full_name_len == old_name_len &&
+ strcmp (full_name, old_name) == 0)
+ || ((full_name_len > old_name_len)
+ && strncmp (full_name, old_name, old_name_len) == 0
+ && full_name[old_name_len] == '/')) {
+ camel_folder_lock (folder);
+ } else {
+ g_ptr_array_remove_index_fast (folders, ii);
+ ii--;
+ g_object_unref (folder);
+ }
+ }
+
+ /* Now try the real rename (will emit renamed signal) */
+ success = class->rename_folder_sync (
+ store, old_name, new_name, cancellable, &local_error);
+ CAMEL_CHECK_LOCAL_GERROR (
+ store, rename_folder_sync, success, local_error);
+
+ /* If it worked, update all open folders/unlock them */
+ if (success) {
+ CamelStoreGetFolderInfoFlags flags;
+ CamelFolderInfo *folder_info;
+
+ flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE;
+
+ for (ii = 0; ii < folders->len; ii++) {
+ const gchar *full_name;
+ gchar *new;
+
+ folder = folders->pdata[ii];
+ full_name = camel_folder_get_full_name (folder);
+
+ new = g_strdup_printf ("%s%s", new_name, 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);
+ g_object_unref (folder);
+ }
+
+ /* Emit renamed signal */
+ if (CAMEL_IS_SUBSCRIBABLE (store))
+ flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED;
+
+ folder_info = class->get_folder_info_sync (
+ store, new_name, flags, cancellable, &local_error);
+ CAMEL_CHECK_LOCAL_GERROR (
+ store, get_folder_info,
+ folder_info != NULL, local_error);
+
+ if (folder_info != NULL) {
+ camel_store_folder_renamed (store, old_name, folder_info);
+ camel_folder_info_free (folder_info);
+ }
+ } else {
+ /* Failed, just unlock our folders for re-use */
+ for (ii = 0; ii < folders->len; ii++) {
+ folder = folders->pdata[ii];
+ camel_folder_unlock (folder);
+ g_object_unref (folder);
+ }
+ }
+
+ g_ptr_array_free (folders, TRUE);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_store_rename_folder:
+ * @store: a #CamelStore
+ * @old_name: the current name of the folder
+ * @new_name: the new name of the folder
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously renames the folder described by @old_name to @new_name.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_store_rename_folder_finish() to get the result of the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_store_rename_folder (CamelStore *store,
+ const gchar *old_name,
+ const gchar *new_name,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CamelService *service;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+ g_return_if_fail (old_name != NULL);
+ g_return_if_fail (new_name != NULL);
+
+ service = CAMEL_SERVICE (store);
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->folder_name_1 = g_strdup (old_name);
+ async_context->folder_name_2 = g_strdup (new_name);
+
+ task = g_task_new (store, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_store_rename_folder);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ camel_service_queue_task (
+ service, task, store_rename_folder_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_store_rename_folder_finish:
+ * @store: a #CamelStore
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_store_rename_folder().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_store_rename_folder_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, store), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_store_rename_folder), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_store_synchronize_sync:
+ * @store: a #CamelStore
+ * @expunge: whether to expunge after synchronizing
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronizes any changes that have been made to @store and its folders
+ * with the real store.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_store_synchronize_sync (CamelStore *store,
+ gboolean expunge,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStoreClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
+
+ class = CAMEL_STORE_GET_CLASS (store);
+ g_return_val_if_fail (class->synchronize_sync != NULL, FALSE);
+
+ success = class->synchronize_sync (store, expunge, cancellable, error);
+ CAMEL_CHECK_GERROR (store, synchronize_sync, success, error);
+
+ return success;
+}
+
+/* Helper for camel_store_synchronize() */
+static void
+store_synchronize_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_store_synchronize_sync (
+ CAMEL_STORE (source_object),
+ async_context->expunge,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_store_synchronize:
+ * @store: a #CamelStore
+ * @expunge: whether to expunge after synchronizing
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Synchronizes any changes that have been made to @store and its folders
+ * with the real store asynchronously.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_store_synchronize_finish() to get the result of the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_store_synchronize (CamelStore *store,
+ gboolean expunge,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->expunge = expunge;
+
+ task = g_task_new (store, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_store_synchronize);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, store_synchronize_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_store_synchronize_finish:
+ * @store: a #CamelStore
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_store_synchronize().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_store_synchronize_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, store), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_store_synchronize), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_store_initial_setup_sync:
+ * @store: a #CamelStore
+ * @out_save_setup: (out) (transfer container) (element-type utf8 utf8): setup values to save
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Runs initial setup for the @store. It's meant to preset some
+ * values the first time the account connects to the server after
+ * it had been created. The function should return %TRUE even if
+ * it didn't populate anything. The default implementation does
+ * just that.
+ *
+ * The save_setup result, if not %NULL, should be freed using
+ * g_hash_table_destroy(). It's not an error to have it %NULL,
+ * it only means the @store doesn't have anything to save.
+ * Both the key and the value in the hash are newly allocated
+ * UTF-8 strings, owned by the hash table.
+ *
+ * The @store advertises support of this function by including
+ * CAMEL_STORE_SUPPORTS_INITIAL_SETUP in CamelStore::flags.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.20
+ **/
+gboolean
+camel_store_initial_setup_sync (CamelStore *store,
+ GHashTable **out_save_setup,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GHashTable *save_setup;
+ CamelStoreClass *class;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
+ g_return_val_if_fail (out_save_setup != NULL, FALSE);
+
+ *out_save_setup = NULL;
+
+ class = CAMEL_STORE_GET_CLASS (store);
+ g_return_val_if_fail (class->initial_setup_sync != NULL, FALSE);
+
+ save_setup = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ success = class->initial_setup_sync (store, save_setup, cancellable, error);
+
+ if (!success || !g_hash_table_size (save_setup)) {
+ g_hash_table_destroy (save_setup);
+ save_setup = NULL;
+ }
+
+ CAMEL_CHECK_GERROR (store, initial_setup_sync, success, error);
+
+ *out_save_setup = save_setup;
+
+ return success;
+}
+
+static void
+store_initial_setup_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) task_data;
+
+ success = camel_store_initial_setup_sync (
+ CAMEL_STORE (source_object),
+ &async_context->save_setup,
+ cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_store_initial_setup:
+ * @store: a #CamelStore
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Runs initial setup for the @store asynchronously.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_store_initial_setup_finish() to get the result of the operation.
+ *
+ * The @store advertises support of this function by including
+ * CAMEL_STORE_SUPPORTS_INITIAL_SETUP in CamelStore::flags.
+ *
+ * Since: 3.20
+ **/
+void
+camel_store_initial_setup (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ async_context = g_slice_new0 (AsyncContext);
+
+ task = g_task_new (store, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_store_initial_setup);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ g_task_run_in_thread (task, store_initial_setup_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_store_initial_setup_finish:
+ * @store: a #CamelStore
+ * @result: a #GAsyncResult
+ * @out_save_setup: (out) (transfer container) (element-type utf8 utf8): setup values to save
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_store_initial_setup().
+ *
+ * The save_setup result, if not %NULL, should be freed using
+ * g_hash_table_destroy(). It's not an error to have it %NULL,
+ * it only means the @store doesn't have anything to save.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.20
+ **/
+gboolean
+camel_store_initial_setup_finish (CamelStore *store,
+ GAsyncResult *result,
+ GHashTable **out_save_setup,
+ GError **error)
+{
+ AsyncContext *async_context;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
+ g_return_val_if_fail (out_save_setup != NULL, FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, store), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_store_initial_setup), FALSE);
+
+ async_context = g_task_get_task_data (G_TASK (result));
+ *out_save_setup = async_context->save_setup;
+ async_context->save_setup = NULL;
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_store_maybe_run_db_maintenance:
+ * @store: a #CamelStore instance
+ * @error: (allow-none): return location for a #GError, or %NULL
+ *
+ * Checks the state of the current CamelDB used for the @store and eventually
+ * runs maintenance routines on it.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.16
+ **/
+gboolean
+camel_store_maybe_run_db_maintenance (CamelStore *store,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
+
+ if (g_atomic_int_get (&store->priv->maintenance_lock) > 0)
+ return TRUE;
+
+ if (!store->cdb_w)
+ return TRUE;
+
+ return camel_db_maybe_run_maintenance (store->cdb_w, error);
+}
diff --git a/src/camel/camel-store.h b/src/camel/camel-store.h
new file mode 100644
index 000000000..19d930e6b
--- /dev/null
+++ b/src/camel/camel-store.h
@@ -0,0 +1,410 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-store.h : Abstract class for an email store
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <NotZed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_STORE_H
+#define CAMEL_STORE_H
+
+/* for mode_t */
+#include <sys/types.h>
+
+#include <camel/camel-enums.h>
+#include <camel/camel-folder.h>
+#include <camel/camel-service.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_STORE \
+ (camel_store_get_type ())
+#define CAMEL_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_STORE, CamelStore))
+#define CAMEL_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_STORE, CamelStoreClass))
+#define CAMEL_IS_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_STORE))
+#define CAMEL_IS_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_STORE))
+#define CAMEL_STORE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_STORE, CamelStoreClass))
+
+/**
+ * CAMEL_STORE_ERROR:
+ *
+ * Since: 2.32
+ **/
+#define CAMEL_STORE_ERROR \
+ (camel_store_error_quark ())
+
+/**
+ * CAMEL_STORE_SETUP
+ * @CAMEL_STORE_SETUP_ARCHIVE_FOLDER: Name of an Archive folder key
+ * @CAMEL_STORE_SETUP_DRAFTS_FOLDER: Name of a Drafts folder key
+ * @CAMEL_STORE_SETUP_SENT_FOLDER: Name of a Sent folder key
+ * @CAMEL_STORE_SETUP_TEMPLATES_FOLDER: Name of a Templates folder key
+ *
+ * Key names to a hash table with values to preset for the account used
+ * as in the camel_store_initial_setup_sync() function.
+ *
+ * The key name consists of up to four parts: Source:Extension:Property[:Type]
+ * Source can be 'Collection', 'Account', 'Submission', 'Transport', 'Backend'.
+ * Extension is any extension name; it's up to the key creator to make sure
+ * the extension belongs to that particular Source.
+ * Property is a property name in the Extension.
+ * Type is an optional letter describing the type of the value; if not set, then
+ * string is used. Available values are: 'b' for boolean, 'i' for integer,
+ * 's' for string, 'f' for folder full path.
+ * All the part values are case sensitive.
+ *
+ * Since: 3.20
+ **/
+#define CAMEL_STORE_SETUP_ARCHIVE_FOLDER "Account:Mail Account:archive-folder:f"
+#define CAMEL_STORE_SETUP_DRAFTS_FOLDER "Submission:Mail Composition:drafts-folder:f"
+#define CAMEL_STORE_SETUP_SENT_FOLDER "Submission:Mail Submission:sent-folder:f"
+#define CAMEL_STORE_SETUP_TEMPLATES_FOLDER "Submission:Mail Composition:templates-folder:f"
+
+G_BEGIN_DECLS
+
+/**
+ * CamelStoreError:
+ *
+ * Since: 2.32
+ **/
+typedef enum {
+ CAMEL_STORE_ERROR_INVALID,
+ CAMEL_STORE_ERROR_NO_FOLDER
+} CamelStoreError;
+
+typedef struct _CamelFolderInfo {
+ struct _CamelFolderInfo *next;
+ struct _CamelFolderInfo *parent;
+ struct _CamelFolderInfo *child;
+
+ gchar *full_name;
+ gchar *display_name;
+
+ CamelFolderInfoFlags flags;
+ gint32 unread;
+ gint32 total;
+} CamelFolderInfo;
+
+struct _CamelDB;
+
+typedef struct _CamelStore CamelStore;
+typedef struct _CamelStoreClass CamelStoreClass;
+typedef struct _CamelStorePrivate CamelStorePrivate;
+
+/* open mode for folder */
+typedef enum {
+ CAMEL_STORE_FOLDER_CREATE = 1 << 0,
+ CAMEL_STORE_FOLDER_EXCL = 1 << 1, /* deprecated, not honored */
+ CAMEL_STORE_FOLDER_BODY_INDEX = 1 << 2,
+ CAMEL_STORE_FOLDER_PRIVATE = 1 << 3 /* a private folder that
+ should not show up in
+ unmatched, folder
+ info's, etc. */
+} CamelStoreGetFolderFlags;
+
+struct _CamelStore {
+ CamelService parent;
+ CamelStorePrivate *priv;
+
+ CamelObjectBag *folders;
+ struct _CamelDB *cdb_r;
+ struct _CamelDB *cdb_w;
+
+ CamelStoreFlags flags;
+
+ /* XXX The default "mode" (read/write) is changed only by
+ * evolution-groupwise for non-writable proxy accounts.
+ * The mode is only checked by the account combo box in
+ * Evolution's composer window. */
+ CamelStorePermissionFlags mode;
+
+ /* Future ABI expansion */
+ gpointer later[4];
+};
+
+struct _CamelStoreClass {
+ CamelServiceClass parent_class;
+
+ GHashFunc hash_folder_name;
+ GEqualFunc equal_folder_name;
+
+ /* Non-Blocking Methods */
+ gboolean (*can_refresh_folder) (CamelStore *store,
+ CamelFolderInfo *info,
+ GError **error);
+
+ /* Synchronous I/O Methods */
+ CamelFolder * (*get_folder_sync) (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+ CamelFolderInfo *
+ (*get_folder_info_sync) (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+ CamelFolder * (*get_inbox_folder_sync)
+ (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error);
+ CamelFolder * (*get_junk_folder_sync) (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error);
+ CamelFolder * (*get_trash_folder_sync)
+ (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error);
+ CamelFolderInfo *
+ (*create_folder_sync) (CamelStore *store,
+ const gchar *parent_name,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*delete_folder_sync) (CamelStore *store,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*rename_folder_sync) (CamelStore *store,
+ const gchar *old_name,
+ const gchar *new_name,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*synchronize_sync) (CamelStore *store,
+ gboolean expunge,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*initial_setup_sync) (CamelStore *store,
+ GHashTable *out_save_setup,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* Reserved slots for methods. */
+ gpointer reserved_for_methods[20];
+
+ /* Signals */
+ void (*folder_created) (CamelStore *store,
+ CamelFolderInfo *folder_info);
+ void (*folder_deleted) (CamelStore *store,
+ CamelFolderInfo *folder_info);
+ void (*folder_opened) (CamelStore *store,
+ CamelFolder *folder);
+ void (*folder_renamed) (CamelStore *store,
+ const gchar *old_name,
+ CamelFolderInfo *folder_info);
+ void (*folder_info_stale) (CamelStore *store);
+};
+
+GType camel_store_get_type (void);
+GQuark camel_store_error_quark (void) G_GNUC_CONST;
+void camel_store_folder_created (CamelStore *store,
+ CamelFolderInfo *folder_info);
+void camel_store_folder_deleted (CamelStore *store,
+ CamelFolderInfo *folder_info);
+void camel_store_folder_opened (CamelStore *store,
+ CamelFolder *folder);
+void camel_store_folder_renamed (CamelStore *store,
+ const gchar *old_name,
+ CamelFolderInfo *folder_info);
+void camel_store_folder_info_stale (CamelStore *store);
+GType camel_folder_info_get_type (void);
+CamelFolderInfo *
+ camel_folder_info_new (void);
+void camel_folder_info_free (CamelFolderInfo *fi);
+#ifndef CAMEL_DISABLE_DEPRECATED
+CamelFolderInfo *
+ camel_folder_info_build (GPtrArray *folders,
+ const gchar *namespace_,
+ gchar separator,
+ gboolean short_names);
+#endif /* CAMEL_DISABLE_DEPRECATED */
+CamelFolderInfo *
+ camel_folder_info_clone (CamelFolderInfo *fi);
+gboolean camel_store_can_refresh_folder (CamelStore *store,
+ CamelFolderInfo *info,
+ GError **error);
+
+CamelFolder * camel_store_get_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+void camel_store_get_folder (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelFolder * camel_store_get_folder_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error);
+CamelFolderInfo *
+ camel_store_get_folder_info_sync
+ (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+void camel_store_get_folder_info (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelFolderInfo *
+ camel_store_get_folder_info_finish
+ (CamelStore *store,
+ GAsyncResult *result,
+ GError **error);
+CamelFolder * camel_store_get_inbox_folder_sync
+ (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error);
+void camel_store_get_inbox_folder (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelFolder * camel_store_get_inbox_folder_finish
+ (CamelStore *store,
+ GAsyncResult *result,
+ GError **error);
+CamelFolder * camel_store_get_junk_folder_sync
+ (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error);
+void camel_store_get_junk_folder (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelFolder * camel_store_get_junk_folder_finish
+ (CamelStore *store,
+ GAsyncResult *result,
+ GError **error);
+CamelFolder * camel_store_get_trash_folder_sync
+ (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error);
+void camel_store_get_trash_folder (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelFolder * camel_store_get_trash_folder_finish
+ (CamelStore *store,
+ GAsyncResult *result,
+ GError **error);
+CamelFolderInfo *
+ camel_store_create_folder_sync (CamelStore *store,
+ const gchar *parent_name,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error);
+void camel_store_create_folder (CamelStore *store,
+ const gchar *parent_name,
+ const gchar *folder_name,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+CamelFolderInfo *
+ camel_store_create_folder_finish
+ (CamelStore *store,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_store_delete_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error);
+void camel_store_delete_folder (CamelStore *store,
+ const gchar *folder_name,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_store_delete_folder_finish
+ (CamelStore *store,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_store_rename_folder_sync (CamelStore *store,
+ const gchar *old_name,
+ const gchar *new_name,
+ GCancellable *cancellable,
+ GError **error);
+void camel_store_rename_folder (CamelStore *store,
+ const gchar *old_name,
+ const gchar *new_name,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_store_rename_folder_finish
+ (CamelStore *store,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_store_synchronize_sync (CamelStore *store,
+ gboolean expunge,
+ GCancellable *cancellable,
+ GError **error);
+void camel_store_synchronize (CamelStore *store,
+ gboolean expunge,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_store_synchronize_finish (CamelStore *store,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_store_initial_setup_sync (CamelStore *store,
+ GHashTable **out_save_setup,
+ GCancellable *cancellable,
+ GError **error);
+void camel_store_initial_setup (CamelStore *store,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_store_initial_setup_finish
+ (CamelStore *store,
+ GAsyncResult *result,
+ GHashTable **out_save_setup,
+ GError **error);
+gboolean camel_store_maybe_run_db_maintenance
+ (CamelStore *store,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_STORE_H */
diff --git a/src/camel/camel-stream-buffer.c b/src/camel/camel-stream-buffer.c
new file mode 100644
index 000000000..7ad8c624c
--- /dev/null
+++ b/src/camel/camel-stream-buffer.c
@@ -0,0 +1,526 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/* camel-stream-buffer.c : Buffer any other other stream
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "camel-stream-buffer.h"
+
+#define CAMEL_STREAM_BUFFER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_STREAM_BUFFER, CamelStreamBufferPrivate))
+
+struct _CamelStreamBufferPrivate {
+
+ CamelStream *stream;
+
+ guchar *buf, *ptr, *end;
+ gint size;
+
+ guchar *linebuf; /* for reading lines at a time */
+ gint linesize;
+
+ CamelStreamBufferMode mode;
+};
+
+G_DEFINE_TYPE (CamelStreamBuffer, camel_stream_buffer, CAMEL_TYPE_STREAM)
+
+#define BUF_SIZE 1024
+
+/* only returns the number passed in, or -1 on an error */
+static gssize
+stream_write_all (CamelStream *stream,
+ const gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gsize left = n, w;
+
+ while (left > 0) {
+ w = camel_stream_write (
+ stream, buffer, left, cancellable, error);
+ if (w == -1)
+ return -1;
+ left -= w;
+ buffer += w;
+ }
+
+ return n;
+}
+
+static void
+set_vbuf (CamelStreamBuffer *stream,
+ CamelStreamBufferMode mode)
+{
+ CamelStreamBufferPrivate *priv;
+
+ priv = CAMEL_STREAM_BUFFER_GET_PRIVATE (stream);
+
+ g_free (priv->buf);
+
+ priv->buf = g_malloc (BUF_SIZE);
+ priv->ptr = priv->buf;
+ priv->end = priv->buf;
+ priv->size = BUF_SIZE;
+ priv->mode = mode;
+}
+
+static void
+stream_buffer_dispose (GObject *object)
+{
+ CamelStreamBufferPrivate *priv;
+
+ priv = CAMEL_STREAM_BUFFER_GET_PRIVATE (object);
+
+ if (priv->stream != NULL) {
+ g_object_unref (priv->stream);
+ priv->stream = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_stream_buffer_parent_class)->dispose (object);
+}
+
+static void
+stream_buffer_finalize (GObject *object)
+{
+ CamelStreamBufferPrivate *priv;
+
+ priv = CAMEL_STREAM_BUFFER_GET_PRIVATE (object);
+
+ g_free (priv->buf);
+ g_free (priv->linebuf);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_stream_buffer_parent_class)->finalize (object);
+}
+
+static gssize
+stream_buffer_read (CamelStream *stream,
+ gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamBufferPrivate *priv;
+ gssize bytes_read = 1;
+ gssize bytes_left;
+ gchar *bptr = buffer;
+ GError *local_error = NULL;
+
+ priv = CAMEL_STREAM_BUFFER_GET_PRIVATE (stream);
+
+ g_return_val_if_fail (
+ (priv->mode & CAMEL_STREAM_BUFFER_MODE) ==
+ CAMEL_STREAM_BUFFER_READ, 0);
+
+ while (n && bytes_read > 0) {
+ bytes_left = priv->end - priv->ptr;
+ if (bytes_left < n) {
+ if (bytes_left > 0) {
+ memcpy (bptr, priv->ptr, bytes_left);
+ n -= bytes_left;
+ bptr += bytes_left;
+ priv->ptr += bytes_left;
+ }
+ /* if we are reading a lot, then read directly to the destination buffer */
+ if (n >= priv->size / 3) {
+ bytes_read = camel_stream_read (
+ priv->stream, bptr, n,
+ cancellable, &local_error);
+ if (bytes_read > 0) {
+ n -= bytes_read;
+ bptr += bytes_read;
+ }
+ } else {
+ bytes_read = camel_stream_read (
+ priv->stream, (gchar *) priv->buf,
+ priv->size, cancellable, &local_error);
+ if (bytes_read > 0) {
+ gsize bytes_used = bytes_read > n ? n : bytes_read;
+ priv->ptr = priv->buf;
+ priv->end = priv->buf + bytes_read;
+ memcpy (bptr, priv->ptr, bytes_used);
+ priv->ptr += bytes_used;
+ bptr += bytes_used;
+ n -= bytes_used;
+ }
+ }
+ } else {
+ memcpy (bptr, priv->ptr, n);
+ priv->ptr += n;
+ bptr += n;
+ n = 0;
+ }
+ }
+
+ /* If camel_stream_read() failed but we managed to read some data
+ * before the failure, discard the error and return the number of
+ * bytes read. If we didn't read any data, propagate the error. */
+ if (local_error != NULL) {
+ if (bptr > buffer)
+ g_clear_error (&local_error);
+ else {
+ g_propagate_error (error, local_error);
+ return -1;
+ }
+ }
+
+ return (gssize)(bptr - buffer);
+}
+
+static gssize
+stream_buffer_write (CamelStream *stream,
+ const gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamBufferPrivate *priv;
+ gssize total = n;
+ gssize left, todo;
+
+ priv = CAMEL_STREAM_BUFFER_GET_PRIVATE (stream);
+
+ g_return_val_if_fail (
+ (priv->mode & CAMEL_STREAM_BUFFER_MODE) ==
+ CAMEL_STREAM_BUFFER_WRITE, 0);
+
+ /* first, copy as much as we can */
+ left = priv->size - (priv->ptr - priv->buf);
+ todo = MIN (left, n);
+
+ memcpy (priv->ptr, buffer, todo);
+ n -= todo;
+ buffer += todo;
+ priv->ptr += todo;
+
+ /* if we've filled the buffer, write it out, reset buffer */
+ if (left == todo) {
+ if (stream_write_all (
+ priv->stream, (gchar *) priv->buf,
+ priv->size, cancellable, error) == -1)
+ return -1;
+
+ priv->ptr = priv->buf;
+ }
+
+ /* if we still have more, write directly, or copy to buffer */
+ if (n > 0) {
+ if (n >= priv->size / 3) {
+ if (stream_write_all (
+ priv->stream, buffer, n,
+ cancellable, error) == -1)
+ return -1;
+ } else {
+ memcpy (priv->ptr, buffer, n);
+ priv->ptr += n;
+ }
+ }
+
+ return total;
+}
+
+static gint
+stream_buffer_flush (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamBufferPrivate *priv;
+
+ priv = CAMEL_STREAM_BUFFER_GET_PRIVATE (stream);
+
+ if ((priv->mode & CAMEL_STREAM_BUFFER_MODE) == CAMEL_STREAM_BUFFER_WRITE) {
+ gsize len = priv->ptr - priv->buf;
+
+ if (camel_stream_write (
+ priv->stream, (gchar *) priv->buf,
+ len, cancellable, error) == -1)
+ return -1;
+
+ priv->ptr = priv->buf;
+ } else {
+ /* nothing to do for read mode 'flush' */
+ }
+
+ return camel_stream_flush (priv->stream, cancellable, error);
+}
+
+static gint
+stream_buffer_close (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamBufferPrivate *priv;
+
+ priv = CAMEL_STREAM_BUFFER_GET_PRIVATE (stream);
+
+ if (stream_buffer_flush (stream, cancellable, error) == -1)
+ return -1;
+
+ return camel_stream_close (priv->stream, cancellable, error);
+}
+
+static gboolean
+stream_buffer_eos (CamelStream *stream)
+{
+ CamelStreamBufferPrivate *priv;
+
+ priv = CAMEL_STREAM_BUFFER_GET_PRIVATE (stream);
+
+ return camel_stream_eos (priv->stream) && priv->ptr == priv->end;
+}
+
+static void
+stream_buffer_init_method (CamelStreamBuffer *stream,
+ CamelStream *other_stream,
+ CamelStreamBufferMode mode)
+{
+ CamelStreamBufferPrivate *priv;
+
+ priv = CAMEL_STREAM_BUFFER_GET_PRIVATE (stream);
+
+ set_vbuf (stream, mode);
+
+ if (priv->stream != NULL)
+ g_object_unref (priv->stream);
+
+ priv->stream = g_object_ref (other_stream);
+}
+
+static void
+camel_stream_buffer_class_init (CamelStreamBufferClass *class)
+{
+ GObjectClass *object_class;
+ CamelStreamClass *stream_class;
+
+ g_type_class_add_private (class, sizeof (CamelStreamBufferPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = stream_buffer_dispose;
+ object_class->finalize = stream_buffer_finalize;
+
+ stream_class = CAMEL_STREAM_CLASS (class);
+ stream_class->read = stream_buffer_read;
+ stream_class->write = stream_buffer_write;
+ stream_class->flush = stream_buffer_flush;
+ stream_class->close = stream_buffer_close;
+ stream_class->eos = stream_buffer_eos;
+
+ class->init = stream_buffer_init_method;
+}
+
+static void
+camel_stream_buffer_init (CamelStreamBuffer *stream)
+{
+ stream->priv = CAMEL_STREAM_BUFFER_GET_PRIVATE (stream);
+ stream->priv->size = BUF_SIZE;
+ stream->priv->buf = g_malloc (BUF_SIZE);
+ stream->priv->ptr = stream->priv->buf;
+ stream->priv->end = stream->priv->buf;
+ stream->priv->mode =
+ CAMEL_STREAM_BUFFER_READ |
+ CAMEL_STREAM_BUFFER_BUFFER;
+ stream->priv->stream = NULL;
+ stream->priv->linesize = 80;
+ stream->priv->linebuf = g_malloc (stream->priv->linesize);
+}
+
+/**
+ * camel_stream_buffer_new:
+ * @stream: a #CamelStream object to buffer
+ * @mode: Operational mode of buffered stream.
+ *
+ * Create a new buffered stream of another stream. A default
+ * buffer size (1024 bytes), automatically managed will be used
+ * for buffering.
+ *
+ * The following values are available for @mode:
+ *
+ * #CAMEL_STREAM_BUFFER_BUFFER, Buffer the input/output in blocks.
+ * #CAMEL_STREAM_BUFFER_NEWLINE, Buffer on newlines (for output).
+ * #CAMEL_STREAM_BUFFER_NONE, Perform no buffering.
+ *
+ * Note that currently this is ignored and #CAMEL_STREAM_BUFFER_BUFFER
+ * is always used.
+ *
+ * In addition, one of the following mode options should be or'd
+ * together with the buffering mode:
+ *
+ * #CAMEL_STREAM_BUFFER_WRITE, Buffer in write mode.
+ * #CAMEL_STREAM_BUFFER_READ, Buffer in read mode.
+ *
+ * Buffering can only be done in one direction for any
+ * buffer instance.
+ *
+ * Returns: a newly created buffered stream.
+ **/
+CamelStream *
+camel_stream_buffer_new (CamelStream *stream,
+ CamelStreamBufferMode mode)
+{
+ CamelStreamBuffer *sbf;
+ CamelStreamBufferClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), NULL);
+
+ sbf = g_object_new (CAMEL_TYPE_STREAM_BUFFER, NULL);
+
+ class = CAMEL_STREAM_BUFFER_GET_CLASS (sbf);
+ g_return_val_if_fail (class->init != NULL, NULL);
+
+ class->init (sbf, stream, mode);
+
+ return CAMEL_STREAM (sbf);
+}
+
+/**
+ * camel_stream_buffer_gets:
+ * @sbf: a #CamelStreamBuffer object
+ * @buf: (out) (array): Memory to write the string to.
+ * @max: Maxmimum number of characters to store.
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Read a line of characters up to the next newline character or
+ * @max-1 characters.
+ *
+ * If the newline character is encountered, then it will be
+ * included in the buffer @buf. The buffer will be %NULL terminated.
+ *
+ * Returns: the number of characters read, or %0 for end of file,
+ * and %-1 on error.
+ **/
+gint
+camel_stream_buffer_gets (CamelStreamBuffer *sbf,
+ gchar *buf,
+ guint max,
+ GCancellable *cancellable,
+ GError **error)
+{
+ register gchar *outptr, *inptr, *inend, c, *outend;
+ gint bytes_read;
+ GError *local_error = NULL;
+
+ outptr = buf;
+ inptr = (gchar *) sbf->priv->ptr;
+ inend = (gchar *) sbf->priv->end;
+ outend = buf + max-1; /* room for NUL */
+
+ do {
+ while (inptr < inend && outptr < outend) {
+ c = *inptr++;
+ *outptr++ = c;
+ if (c == '\n') {
+ *outptr = 0;
+ sbf->priv->ptr = (guchar *) inptr;
+ return outptr - buf;
+ }
+ }
+ if (outptr == outend)
+ break;
+
+ bytes_read = camel_stream_read (
+ sbf->priv->stream, (gchar *) sbf->priv->buf,
+ sbf->priv->size, cancellable, &local_error);
+ if (bytes_read == -1) {
+ if (buf == outptr) {
+ if (local_error)
+ g_propagate_error (error, local_error);
+ return -1;
+ } else
+ bytes_read = 0;
+ }
+ sbf->priv->ptr = sbf->priv->buf;
+ sbf->priv->end = sbf->priv->buf + bytes_read;
+ inptr = (gchar *) sbf->priv->ptr;
+ inend = (gchar *) sbf->priv->end;
+ } while (bytes_read > 0);
+
+ sbf->priv->ptr = (guchar *) inptr;
+ *outptr = 0;
+
+ g_clear_error (&local_error);
+
+ return (gint)(outptr - buf);
+}
+
+/**
+ * camel_stream_buffer_read_line:
+ * @sbf: a #CamelStreamBuffer object
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a @GError, or %NULL
+ *
+ * This function reads a complete newline-terminated line from the stream
+ * and returns it in allocated memory. The trailing newline (and carriage
+ * return if any) are not included in the returned string.
+ *
+ * Returns: (nullable): the line read, which the caller must free when done with,
+ * or %NULL on eof. If an error occurs, @error will be set.
+ **/
+gchar *
+camel_stream_buffer_read_line (CamelStreamBuffer *sbf,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guchar *p;
+ gint nread;
+ GError *local_error = NULL;
+
+ p = sbf->priv->linebuf;
+
+ while (1) {
+ nread = camel_stream_buffer_gets (
+ sbf, (gchar *) p, sbf->priv->linesize -
+ (p - sbf->priv->linebuf), cancellable, &local_error);
+ if (nread <= 0) {
+ if (p > sbf->priv->linebuf)
+ break;
+ if (local_error)
+ g_propagate_error (error, local_error);
+ return NULL;
+ }
+
+ p += nread;
+ if (p[-1] == '\n')
+ break;
+
+ nread = p - sbf->priv->linebuf;
+ sbf->priv->linesize *= 2;
+ sbf->priv->linebuf = g_realloc (
+ sbf->priv->linebuf, sbf->priv->linesize);
+ p = sbf->priv->linebuf + nread;
+ }
+
+ p--;
+ if (p > sbf->priv->linebuf && p[-1] == '\r')
+ p--;
+ p[0] = 0;
+
+ g_clear_error (&local_error);
+
+ return g_strdup ((gchar *) sbf->priv->linebuf);
+}
diff --git a/src/camel/camel-stream-buffer.h b/src/camel/camel-stream-buffer.h
new file mode 100644
index 000000000..d7a014bdc
--- /dev/null
+++ b/src/camel/camel-stream-buffer.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-stream-buffer.h :stream which buffers another stream
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_STREAM_BUFFER_H
+#define CAMEL_STREAM_BUFFER_H
+
+#include <stdio.h>
+#include <camel/camel-stream.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_STREAM_BUFFER \
+ (camel_stream_buffer_get_type ())
+#define CAMEL_STREAM_BUFFER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_STREAM_BUFFER, CamelStreamBuffer))
+#define CAMEL_STREAM_BUFFER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_STREAM_BUFFER, CamelStreamBufferClass))
+#define CAMEL_IS_STREAM_BUFFER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_STREAM_BUFFER))
+#define CAMEL_IS_STREAM_BUFFER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_STREAM_BUFFER))
+#define CAMEL_STREAM_BUFFER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_STREAM_BUFFER, CamelStreamBufferClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelStreamBuffer CamelStreamBuffer;
+typedef struct _CamelStreamBufferClass CamelStreamBufferClass;
+typedef struct _CamelStreamBufferPrivate CamelStreamBufferPrivate;
+
+typedef enum {
+ CAMEL_STREAM_BUFFER_BUFFER = 0,
+ CAMEL_STREAM_BUFFER_NONE,
+ CAMEL_STREAM_BUFFER_READ = 0x00,
+ CAMEL_STREAM_BUFFER_WRITE = 0x80,
+ CAMEL_STREAM_BUFFER_MODE = 0x80
+} CamelStreamBufferMode;
+
+struct _CamelStreamBuffer {
+ CamelStream parent;
+ CamelStreamBufferPrivate *priv;
+};
+
+struct _CamelStreamBufferClass {
+ CamelStreamClass parent_class;
+
+ void (*init) (CamelStreamBuffer *stream_buffer,
+ CamelStream *stream,
+ CamelStreamBufferMode mode);
+ void (*init_vbuf) (CamelStreamBuffer *stream_buffer,
+ CamelStream *stream,
+ CamelStreamBufferMode mode,
+ gchar *buf,
+ guint32 size);
+};
+
+GType camel_stream_buffer_get_type (void);
+CamelStream * camel_stream_buffer_new (CamelStream *stream,
+ CamelStreamBufferMode mode);
+gint camel_stream_buffer_gets (CamelStreamBuffer *sbf,
+ gchar *buf,
+ guint max,
+ GCancellable *cancellable,
+ GError **error);
+gchar * camel_stream_buffer_read_line (CamelStreamBuffer *sbf,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_STREAM_BUFFER_H */
diff --git a/src/camel/camel-stream-filter.c b/src/camel/camel-stream-filter.c
new file mode 100644
index 000000000..9ef6df58a
--- /dev/null
+++ b/src/camel/camel-stream-filter.c
@@ -0,0 +1,526 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-stream-filter.h"
+
+#define d(x)
+
+/* use my malloc debugger? */
+/*extern void g_check(gpointer mp);*/
+#define g_check(x)
+
+#define CAMEL_STREAM_FILTER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_STREAM_FILTER, CamelStreamFilterPrivate))
+
+struct _filter {
+ struct _filter *next;
+ gint id;
+ CamelMimeFilter *filter;
+};
+
+struct _CamelStreamFilterPrivate {
+
+ CamelStream *source;
+
+ struct _filter *filters;
+ gint filterid; /* next filter id */
+
+ gchar *realbuffer; /* buffer - READ_PAD */
+ gchar *buffer; /* READ_SIZE bytes */
+
+ gchar *filtered; /* the filtered data */
+ gsize filteredlen;
+
+ guint last_was_read:1; /* was the last op read or write? */
+ guint flushed:1; /* were the filters flushed? */
+};
+
+#define READ_PAD (128) /* bytes padded before buffer */
+#define READ_SIZE (4096)
+
+static void camel_stream_filter_seekable_init (GSeekableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (CamelStreamFilter, camel_stream_filter, CAMEL_TYPE_STREAM,
+ G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, camel_stream_filter_seekable_init))
+
+static void
+stream_filter_finalize (GObject *object)
+{
+ CamelStreamFilter *stream = CAMEL_STREAM_FILTER (object);
+ struct _filter *fn, *f;
+
+ f = stream->priv->filters;
+ while (f) {
+ fn = f->next;
+ g_object_unref (f->filter);
+ g_free (f);
+ f = fn;
+ }
+
+ g_free (stream->priv->realbuffer);
+ g_object_unref (stream->priv->source);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_stream_filter_parent_class)->finalize (object);
+}
+
+static gssize
+stream_filter_read (CamelStream *stream,
+ gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamFilterPrivate *priv;
+ gssize size;
+ struct _filter *f;
+
+ priv = CAMEL_STREAM_FILTER_GET_PRIVATE (stream);
+
+ priv->last_was_read = TRUE;
+
+ g_check (priv->realbuffer);
+
+ if (priv->filteredlen <= 0) {
+ gsize presize = READ_PAD;
+
+ size = camel_stream_read (
+ priv->source, priv->buffer,
+ READ_SIZE, cancellable, error);
+ if (size <= 0) {
+ /* this is somewhat untested */
+ if (camel_stream_eos (priv->source)) {
+ f = priv->filters;
+ priv->filtered = priv->buffer;
+ priv->filteredlen = 0;
+ while (f) {
+ camel_mime_filter_complete (
+ f->filter, priv->filtered, priv->filteredlen,
+ presize, &priv->filtered, &priv->filteredlen, &presize);
+ g_check (priv->realbuffer);
+ f = f->next;
+ }
+ size = priv->filteredlen;
+ priv->flushed = TRUE;
+ }
+ if (size <= 0)
+ return size;
+ } else {
+ f = priv->filters;
+ priv->filtered = priv->buffer;
+ priv->filteredlen = size;
+
+ d (printf (
+ "\n\nOriginal content (%s): '",
+ G_OBJECT_TYPE_NAME (priv->source)));
+ d (fwrite (priv->filtered, sizeof (gchar), priv->filteredlen, stdout));
+ d (printf ("'\n"));
+
+ while (f) {
+ camel_mime_filter_filter (
+ f->filter, priv->filtered, priv->filteredlen, presize,
+ &priv->filtered, &priv->filteredlen, &presize);
+ g_check (priv->realbuffer);
+
+ d (printf (
+ "Filtered content (%s): '",
+ G_OBJECT_TYPE_NAME (f->filter)));
+ d (fwrite (priv->filtered, sizeof (gchar), priv->filteredlen, stdout));
+ d (printf ("'\n"));
+
+ f = f->next;
+ }
+ }
+ }
+
+ size = MIN (n, priv->filteredlen);
+ memcpy (buffer, priv->filtered, size);
+ priv->filteredlen -= size;
+ priv->filtered += size;
+
+ g_check (priv->realbuffer);
+
+ return size;
+}
+
+/* Note: Since the caller expects to write out as much as they asked us to
+ * write (for 'success'), we return what they asked us to write (for 'success')
+ * rather than the true number of written bytes */
+static gssize
+stream_filter_write (CamelStream *stream,
+ const gchar *buf,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamFilterPrivate *priv;
+ struct _filter *f;
+ gsize presize, len, left = n;
+ gchar *buffer, realbuffer[READ_SIZE + READ_PAD];
+
+ priv = CAMEL_STREAM_FILTER_GET_PRIVATE (stream);
+
+ priv->last_was_read = FALSE;
+
+ d (printf (
+ "\n\nWriting: Original content (%s): '",
+ G_OBJECT_TYPE_NAME (priv->source)));
+ d (fwrite (buf, sizeof (gchar), n, stdout));
+ d (printf ("'\n"));
+
+ g_check (priv->realbuffer);
+
+ while (left) {
+ /* Sigh, since filters expect non const args, copy the input first, we do this in handy sized chunks */
+ len = MIN (READ_SIZE, left);
+ buffer = realbuffer + READ_PAD;
+ memcpy (buffer, buf, len);
+ buf += len;
+ left -= len;
+
+ f = priv->filters;
+ presize = READ_PAD;
+ while (f) {
+ camel_mime_filter_filter (f->filter, buffer, len, presize, &buffer, &len, &presize);
+
+ g_check (priv->realbuffer);
+
+ d (printf (
+ "Filtered content (%s): '",
+ G_OBJECT_TYPE_NAME (f->filter)));
+ d (fwrite (buffer, sizeof (gchar), len, stdout));
+ d (printf ("'\n"));
+
+ f = f->next;
+ }
+
+ if (camel_stream_write (priv->source, buffer, len, cancellable, error) != len)
+ return -1;
+ }
+
+ g_check (priv->realbuffer);
+
+ return n;
+}
+
+static gint
+stream_filter_flush (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamFilterPrivate *priv;
+ struct _filter *f;
+ gchar *buffer;
+ gsize presize;
+ gsize len;
+
+ priv = CAMEL_STREAM_FILTER_GET_PRIVATE (stream);
+
+ if (priv->last_was_read)
+ return 0;
+
+ buffer = (gchar *) "";
+ len = 0;
+ presize = 0;
+ f = priv->filters;
+
+ d (printf (
+ "\n\nFlushing: Original content (%s): '",
+ G_OBJECT_TYPE_NAME (priv->source)));
+ d (fwrite (buffer, sizeof (gchar), len, stdout));
+ d (printf ("'\n"));
+
+ while (f) {
+ camel_mime_filter_complete (f->filter, buffer, len, presize, &buffer, &len, &presize);
+
+ d (printf (
+ "Filtered content (%s): '",
+ G_OBJECT_TYPE_NAME (f->filter)));
+ d (fwrite (buffer, sizeof (gchar), len, stdout));
+ d (printf ("'\n"));
+
+ f = f->next;
+ }
+
+ if (len > 0 && camel_stream_write (priv->source, buffer, len, cancellable, error) == -1)
+ return -1;
+
+ return camel_stream_flush (priv->source, cancellable, error);
+}
+
+static gint
+stream_filter_close (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamFilterPrivate *priv;
+
+ priv = CAMEL_STREAM_FILTER_GET_PRIVATE (stream);
+
+ /* Ignore errors while flushing. */
+ if (!priv->last_was_read)
+ stream_filter_flush (stream, cancellable, NULL);
+
+ return camel_stream_close (priv->source, cancellable, error);
+}
+
+static gboolean
+stream_filter_eos (CamelStream *stream)
+{
+ CamelStreamFilterPrivate *priv;
+
+ priv = CAMEL_STREAM_FILTER_GET_PRIVATE (stream);
+
+ if (priv->filteredlen > 0)
+ return FALSE;
+
+ if (!priv->flushed)
+ return FALSE;
+
+ return camel_stream_eos (priv->source);
+}
+
+static goffset
+stream_filter_tell (GSeekable *seekable)
+{
+ CamelStreamFilterPrivate *priv;
+
+ priv = CAMEL_STREAM_FILTER_GET_PRIVATE (seekable);
+
+ if (!G_IS_SEEKABLE (priv->source))
+ return 0;
+
+ return g_seekable_tell (G_SEEKABLE (priv->source));
+}
+
+static gboolean
+stream_filter_can_seek (GSeekable *seekable)
+{
+ return TRUE;
+}
+
+static gboolean
+stream_filter_seek (GSeekable *seekable,
+ goffset offset,
+ GSeekType type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamFilterPrivate *priv;
+ struct _filter *f;
+
+ priv = CAMEL_STREAM_FILTER_GET_PRIVATE (seekable);
+
+ if (type != G_SEEK_SET || offset != 0) {
+ g_set_error_literal (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Only reset to beginning is supported with CamelStreamFilter"));
+ return FALSE;
+ }
+
+ priv->filteredlen = 0;
+ priv->flushed = FALSE;
+
+ f = priv->filters;
+ while (f) {
+ if (G_IS_SEEKABLE (f->filter) && !g_seekable_seek (G_SEEKABLE (f->filter), offset, type, cancellable, error))
+ return FALSE;
+
+ f = f->next;
+ }
+
+ return priv->source && G_IS_SEEKABLE (priv->source) ? g_seekable_seek (G_SEEKABLE (priv->source), offset, type, cancellable, error) : TRUE;
+}
+
+static gboolean
+stream_filter_can_truncate (GSeekable *seekable)
+{
+ return FALSE;
+}
+
+static gboolean
+stream_filter_truncate_fn (GSeekable *seekable,
+ goffset offset,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* XXX Don't bother translating this. Camel never calls it. */
+ g_set_error_literal (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Truncation is not supported");
+
+ return FALSE;
+}
+
+static void
+camel_stream_filter_class_init (CamelStreamFilterClass *class)
+{
+ GObjectClass *object_class;
+ CamelStreamClass *stream_class;
+
+ g_type_class_add_private (class, sizeof (CamelStreamFilterPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = stream_filter_finalize;
+
+ stream_class = CAMEL_STREAM_CLASS (class);
+ stream_class->read = stream_filter_read;
+ stream_class->write = stream_filter_write;
+ stream_class->flush = stream_filter_flush;
+ stream_class->close = stream_filter_close;
+ stream_class->eos = stream_filter_eos;
+}
+
+static void
+camel_stream_filter_seekable_init (GSeekableIface *iface)
+{
+ iface->tell = stream_filter_tell;
+ iface->can_seek = stream_filter_can_seek;
+ iface->seek = stream_filter_seek;
+ iface->can_truncate = stream_filter_can_truncate;
+ iface->truncate_fn = stream_filter_truncate_fn;
+}
+
+static void
+camel_stream_filter_init (CamelStreamFilter *stream)
+{
+ stream->priv = CAMEL_STREAM_FILTER_GET_PRIVATE (stream);
+ stream->priv->realbuffer = g_malloc (READ_SIZE + READ_PAD);
+ stream->priv->buffer = stream->priv->realbuffer + READ_PAD;
+ stream->priv->last_was_read = TRUE;
+ stream->priv->flushed = FALSE;
+}
+
+/**
+ * camel_stream_filter_new:
+ *
+ * Create a new #CamelStreamFilter object.
+ *
+ * Returns: a new #CamelStreamFilter object.
+ *
+ * Since: 2.32
+ **/
+CamelStream *
+camel_stream_filter_new (CamelStream *source)
+{
+ CamelStream *stream;
+ CamelStreamFilterPrivate *priv;
+
+ g_return_val_if_fail (CAMEL_IS_STREAM (source), NULL);
+
+ stream = g_object_new (CAMEL_TYPE_STREAM_FILTER, NULL);
+ priv = CAMEL_STREAM_FILTER_GET_PRIVATE (stream);
+
+ priv->source = g_object_ref (source);
+
+ return stream;
+}
+
+/**
+ * camel_stream_filter_get_source:
+ * @stream: a #CamelStreamFilter
+ *
+ * Returns: (transfer none):
+ *
+ * Since: 2.32
+ **/
+CamelStream *
+camel_stream_filter_get_source (CamelStreamFilter *stream)
+{
+ g_return_val_if_fail (CAMEL_IS_STREAM_FILTER (stream), NULL);
+
+ return stream->priv->source;
+}
+
+/**
+ * camel_stream_filter_add:
+ * @stream: a #CamelStreamFilter object
+ * @filter: a #CamelMimeFilter object
+ *
+ * Add a new #CamelMimeFilter to execute during the processing of this
+ * stream. Each filter added is processed after the previous one.
+ *
+ * Note that a filter should only be added to a single stream
+ * at a time, otherwise unpredictable results may occur.
+ *
+ * Returns: a filter id for the added @filter.
+ **/
+gint
+camel_stream_filter_add (CamelStreamFilter *stream,
+ CamelMimeFilter *filter)
+{
+ CamelStreamFilterPrivate *priv;
+ struct _filter *fn, *f;
+
+ g_return_val_if_fail (CAMEL_IS_STREAM_FILTER (stream), -1);
+ g_return_val_if_fail (CAMEL_IS_MIME_FILTER (filter), -1);
+
+ priv = CAMEL_STREAM_FILTER_GET_PRIVATE (stream);
+
+ fn = g_malloc (sizeof (*fn));
+ fn->id = priv->filterid++;
+ fn->filter = g_object_ref (filter);
+
+ /* sure, we could use a GList, but we wouldn't save much */
+ f = (struct _filter *) &priv->filters;
+ while (f->next)
+ f = f->next;
+ f->next = fn;
+ fn->next = NULL;
+ return fn->id;
+}
+
+/**
+ * camel_stream_filter_remove:
+ * @stream: a #CamelStreamFilter object
+ * @id: Filter id, as returned from camel_stream_filter_add()
+ *
+ * Remove a processing filter from the stream by id.
+ **/
+void
+camel_stream_filter_remove (CamelStreamFilter *stream,
+ gint id)
+{
+ CamelStreamFilterPrivate *priv;
+ struct _filter *fn, *f;
+
+ g_return_if_fail (CAMEL_IS_STREAM_FILTER (stream));
+
+ priv = CAMEL_STREAM_FILTER_GET_PRIVATE (stream);
+
+ f = (struct _filter *) &priv->filters;
+ while (f && f->next) {
+ fn = f->next;
+ if (fn->id == id) {
+ f->next = fn->next;
+ g_object_unref (fn->filter);
+ g_free (fn);
+ }
+ f = f->next;
+ }
+}
diff --git a/src/camel/camel-stream-filter.h b/src/camel/camel-stream-filter.h
new file mode 100644
index 000000000..853ae1795
--- /dev/null
+++ b/src/camel/camel-stream-filter.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_STREAM_FILTER_H
+#define CAMEL_STREAM_FILTER_H
+
+#include <camel/camel-stream.h>
+#include <camel/camel-mime-filter.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_STREAM_FILTER \
+ (camel_stream_filter_get_type ())
+#define CAMEL_STREAM_FILTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_STREAM_FILTER, CamelStreamFilter))
+#define CAMEL_STREAM_FILTER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_STREAM_FILTER, CamelStreamFilterClass))
+#define CAMEL_IS_STREAM_FILTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_STREAM_FILTER))
+#define CAMEL_IS_STREAM_FILTER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_STREAM_FILTER))
+#define CAMEL_STREAM_FILTER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_STREAM_FILTER, CamelStreamFilterClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelStreamFilter CamelStreamFilter;
+typedef struct _CamelStreamFilterClass CamelStreamFilterClass;
+typedef struct _CamelStreamFilterPrivate CamelStreamFilterPrivate;
+
+struct _CamelStreamFilter {
+ CamelStream parent;
+ CamelStreamFilterPrivate *priv;
+};
+
+struct _CamelStreamFilterClass {
+ CamelStreamClass parent_class;
+};
+
+GType camel_stream_filter_get_type (void);
+CamelStream * camel_stream_filter_new (CamelStream *source);
+CamelStream * camel_stream_filter_get_source (CamelStreamFilter *stream);
+gint camel_stream_filter_add (CamelStreamFilter *stream,
+ CamelMimeFilter *filter);
+void camel_stream_filter_remove (CamelStreamFilter *stream,
+ gint id);
+
+G_END_DECLS
+
+#endif /* CAMEL_STREAM_FILTER_H */
diff --git a/src/camel/camel-stream-fs.c b/src/camel/camel-stream-fs.c
new file mode 100644
index 000000000..78045df01
--- /dev/null
+++ b/src/camel/camel-stream-fs.c
@@ -0,0 +1,335 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-stream-fs.c : file system based stream
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gstdio.h>
+
+#include "camel-file-utils.h"
+#include "camel-operation.h"
+#include "camel-stream-fs.h"
+#include "camel-win32.h"
+
+#define CAMEL_STREAM_FS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_STREAM_FS, CamelStreamFsPrivate))
+
+struct _CamelStreamFsPrivate {
+ gint fd; /* file descriptor on the underlying file */
+};
+
+/* Forward Declarations */
+static void camel_stream_fs_seekable_init (GSeekableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ CamelStreamFs, camel_stream_fs, CAMEL_TYPE_STREAM,
+ G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, camel_stream_fs_seekable_init))
+
+static void
+stream_fs_finalize (GObject *object)
+{
+ CamelStreamFsPrivate *priv;
+
+ priv = CAMEL_STREAM_FS_GET_PRIVATE (object);
+
+ if (priv->fd != -1)
+ close (priv->fd);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_stream_fs_parent_class)->finalize (object);
+}
+
+static gssize
+stream_fs_read (CamelStream *stream,
+ gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamFsPrivate *priv;
+ gssize nread;
+
+ priv = CAMEL_STREAM_FS_GET_PRIVATE (stream);
+
+ nread = camel_read (priv->fd, buffer, n, cancellable, error);
+
+ if (nread == 0)
+ stream->eos = TRUE;
+
+ return nread;
+}
+
+static gssize
+stream_fs_write (CamelStream *stream,
+ const gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamFsPrivate *priv;
+
+ priv = CAMEL_STREAM_FS_GET_PRIVATE (stream);
+
+ return camel_write (priv->fd, buffer, n, cancellable, error);
+}
+
+static gint
+stream_fs_flush (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamFsPrivate *priv;
+
+ priv = CAMEL_STREAM_FS_GET_PRIVATE (stream);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return -1;
+
+ if (fsync (priv->fd) == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static gint
+stream_fs_close (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamFsPrivate *priv;
+
+ priv = CAMEL_STREAM_FS_GET_PRIVATE (stream);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return -1;
+
+ if (close (priv->fd) == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ return -1;
+ }
+
+ priv->fd = -1;
+
+ return 0;
+}
+
+static goffset
+stream_fs_tell (GSeekable *seekable)
+{
+ CamelStreamFsPrivate *priv;
+
+ priv = CAMEL_STREAM_FS_GET_PRIVATE (seekable);
+
+ return (goffset) lseek (priv->fd, 0, SEEK_CUR);
+}
+
+static gboolean
+stream_fs_can_seek (GSeekable *seekable)
+{
+ return TRUE;
+}
+
+static gboolean
+stream_fs_seek (GSeekable *seekable,
+ goffset offset,
+ GSeekType type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamFsPrivate *priv;
+ goffset real = 0;
+
+ priv = CAMEL_STREAM_FS_GET_PRIVATE (seekable);
+
+ switch (type) {
+ case G_SEEK_SET:
+ real = offset;
+ break;
+ case G_SEEK_CUR:
+ real = g_seekable_tell (seekable) + offset;
+ break;
+ case G_SEEK_END:
+ real = lseek (priv->fd, offset, SEEK_END);
+ if (real == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ real = lseek (priv->fd, real, SEEK_SET);
+ if (real == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ return FALSE;
+ }
+
+ CAMEL_STREAM (seekable)->eos = FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+stream_fs_can_truncate (GSeekable *seekable)
+{
+ return FALSE;
+}
+
+static gboolean
+stream_fs_truncate_fn (GSeekable *seekable,
+ goffset offset,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* XXX Don't bother translating this. Camel never calls it. */
+ g_set_error (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Truncation is not supported");
+
+ return FALSE;
+}
+
+static void
+camel_stream_fs_class_init (CamelStreamFsClass *class)
+{
+ GObjectClass *object_class;
+ CamelStreamClass *stream_class;
+
+ g_type_class_add_private (class, sizeof (CamelStreamFsPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = stream_fs_finalize;
+
+ stream_class = CAMEL_STREAM_CLASS (class);
+ stream_class->read = stream_fs_read;
+ stream_class->write = stream_fs_write;
+ stream_class->flush = stream_fs_flush;
+ stream_class->close = stream_fs_close;
+}
+
+static void
+camel_stream_fs_seekable_init (GSeekableIface *iface)
+{
+ iface->tell = stream_fs_tell;
+ iface->can_seek = stream_fs_can_seek;
+ iface->seek = stream_fs_seek;
+ iface->can_truncate = stream_fs_can_truncate;
+ iface->truncate_fn = stream_fs_truncate_fn;
+}
+
+static void
+camel_stream_fs_init (CamelStreamFs *stream)
+{
+ stream->priv = CAMEL_STREAM_FS_GET_PRIVATE (stream);
+ stream->priv->fd = -1;
+}
+
+/**
+ * camel_stream_fs_new_with_fd:
+ * @fd: a file descriptor
+ *
+ * Creates a new fs stream using the given file descriptor @fd as the
+ * backing store. When the stream is destroyed, the file descriptor
+ * will be closed.
+ *
+ * Returns: a new #CamelStreamFs
+ **/
+CamelStream *
+camel_stream_fs_new_with_fd (gint fd)
+{
+ CamelStreamFsPrivate *priv;
+ CamelStream *stream;
+
+ if (fd == -1)
+ return NULL;
+
+ stream = g_object_new (CAMEL_TYPE_STREAM_FS, NULL);
+ priv = CAMEL_STREAM_FS_GET_PRIVATE (stream);
+
+ priv->fd = fd;
+
+ return stream;
+}
+
+/**
+ * camel_stream_fs_new_with_name:
+ * @name: a local filename
+ * @flags: flags as in open(2)
+ * @mode: a file mode
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a new #CamelStreamFs corresponding to the named file, flags,
+ * and mode.
+ *
+ * Returns: the new stream, or %NULL on error.
+ **/
+CamelStream *
+camel_stream_fs_new_with_name (const gchar *name,
+ gint flags,
+ mode_t mode,
+ GError **error)
+{
+ gint fd;
+
+ fd = g_open (name, flags | O_BINARY, mode);
+ if (fd == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ return NULL;
+ }
+
+ return camel_stream_fs_new_with_fd (fd);
+}
+
+/**
+ * camel_stream_fs_get_fd:
+ * @stream: a #CamelStream
+ *
+ * Since: 2.32
+ **/
+gint
+camel_stream_fs_get_fd (CamelStreamFs *stream)
+{
+ g_return_val_if_fail (CAMEL_IS_STREAM_FS (stream), -1);
+
+ return stream->priv->fd;
+}
diff --git a/src/camel/camel-stream-fs.h b/src/camel/camel-stream-fs.h
new file mode 100644
index 000000000..31cfe8c54
--- /dev/null
+++ b/src/camel/camel-stream-fs.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-stream-fs.h :stream based on unix filesystem
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_STREAM_FS_H
+#define CAMEL_STREAM_FS_H
+
+/* for open flags */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <camel/camel-stream.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_STREAM_FS \
+ (camel_stream_fs_get_type ())
+#define CAMEL_STREAM_FS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_STREAM_FS, CamelStreamFs))
+#define CAMEL_STREAM_FS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_STREAM_FS, CamelStreamFsClass))
+#define CAMEL_IS_STREAM_FS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_STREAM_FS))
+#define CAMEL_IS_STREAM_FS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_STREAM_FS))
+#define CAMEL_STREAM_FS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_STREAM_FS, CamelStreamFsClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelStreamFs CamelStreamFs;
+typedef struct _CamelStreamFsClass CamelStreamFsClass;
+typedef struct _CamelStreamFsPrivate CamelStreamFsPrivate;
+
+struct _CamelStreamFs {
+ CamelStream parent;
+ CamelStreamFsPrivate *priv;
+};
+
+struct _CamelStreamFsClass {
+ CamelStreamClass parent_class;
+};
+
+GType camel_stream_fs_get_type (void);
+CamelStream * camel_stream_fs_new_with_name (const gchar *name,
+ gint flags,
+ mode_t mode,
+ GError **error);
+CamelStream * camel_stream_fs_new_with_fd (gint fd);
+gint camel_stream_fs_get_fd (CamelStreamFs *stream);
+
+G_END_DECLS
+
+#endif /* CAMEL_STREAM_FS_H */
diff --git a/src/camel/camel-stream-mem.c b/src/camel/camel-stream-mem.c
new file mode 100644
index 000000000..e7c2e1d28
--- /dev/null
+++ b/src/camel/camel-stream-mem.c
@@ -0,0 +1,410 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-stream-mem.c: memory buffer based stream
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "camel-stream-mem.h"
+
+#define CAMEL_STREAM_MEM_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_STREAM_MEM, CamelStreamMemPrivate))
+
+struct _CamelStreamMemPrivate {
+ guint owner : 1; /* do we own the buffer? */
+ guint secure : 1; /* do we clear the buffer on finalize?
+ (only if we own it) */
+
+ GByteArray *buffer;
+ goffset position;
+};
+
+/* Forward Declarations */
+static void camel_stream_mem_seekable_init (GSeekableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ CamelStreamMem, camel_stream_mem, CAMEL_TYPE_STREAM,
+ G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, camel_stream_mem_seekable_init))
+
+/* could probably be a util method */
+static void
+clear_mem (gpointer p,
+ gsize len)
+{
+ gchar *s = p;
+
+ /* This also helps debug bad access memory errors */
+ while (len > 4) {
+ *s++ = 0xAB;
+ *s++ = 0xAD;
+ *s++ = 0xF0;
+ *s++ = 0x0D;
+ len -= 4;
+ }
+
+ memset (s, 0xbf, len);
+}
+
+static void
+stream_mem_finalize (GObject *object)
+{
+ CamelStreamMemPrivate *priv;
+
+ priv = CAMEL_STREAM_MEM_GET_PRIVATE (object);
+
+ if (priv->buffer && priv->owner) {
+ /* TODO: we need our own bytearray type since we don't know
+ * the real size of the underlying buffer :-/ */
+ if (priv->secure && priv->buffer->len)
+ clear_mem (priv->buffer->data, priv->buffer->len);
+ g_byte_array_free (priv->buffer, TRUE);
+ }
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_stream_mem_parent_class)->finalize (object);
+}
+
+static gssize
+stream_mem_read (CamelStream *stream,
+ gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamMemPrivate *priv;
+ gssize nread;
+
+ priv = CAMEL_STREAM_MEM_GET_PRIVATE (stream);
+
+ nread = MIN (n, priv->buffer->len - priv->position);
+ if (nread > 0) {
+ memcpy (buffer, priv->buffer->data + priv->position, nread);
+ priv->position += nread;
+ } else
+ nread = 0;
+
+ return nread;
+}
+
+static gssize
+stream_mem_write (CamelStream *stream,
+ const gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamMemPrivate *priv;
+ gssize nwrite = n;
+
+ priv = CAMEL_STREAM_MEM_GET_PRIVATE (stream);
+
+ /* FIXME: we shouldn't use g_byte_arrays or g_malloc perhaps? */
+ if (priv->position == priv->buffer->len) {
+ g_byte_array_append (priv->buffer, (const guint8 *) buffer, nwrite);
+ } else {
+ g_byte_array_set_size (priv->buffer, nwrite + priv->buffer->len);
+ memcpy (priv->buffer->data + priv->position, buffer, nwrite);
+ }
+ priv->position += nwrite;
+
+ return nwrite;
+}
+
+static gboolean
+stream_mem_eos (CamelStream *stream)
+{
+ CamelStreamMemPrivate *priv;
+
+ priv = CAMEL_STREAM_MEM_GET_PRIVATE (stream);
+
+ return priv->buffer->len <= priv->position;
+}
+
+static goffset
+stream_mem_tell (GSeekable *seekable)
+{
+ CamelStreamMemPrivate *priv;
+
+ priv = CAMEL_STREAM_MEM_GET_PRIVATE (seekable);
+
+ return priv->position;
+}
+
+static gboolean
+stream_mem_can_seek (GSeekable *seekable)
+{
+ return TRUE;
+}
+
+static gboolean
+stream_mem_seek (GSeekable *seekable,
+ goffset offset,
+ GSeekType type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamMemPrivate *priv;
+ goffset position;
+
+ priv = CAMEL_STREAM_MEM_GET_PRIVATE (seekable);
+
+ switch (type) {
+ case G_SEEK_SET:
+ position = offset;
+ break;
+ case G_SEEK_CUR:
+ position = priv->position + offset;
+ break;
+ case G_SEEK_END:
+ position = (priv->buffer)->len + offset;
+ break;
+ default:
+ position = offset;
+ break;
+ }
+
+ position = MAX (position, 0);
+
+ if (position > priv->buffer->len) {
+ gint oldlen = priv->buffer->len;
+ g_byte_array_set_size (priv->buffer, position);
+ memset (priv->buffer->data + oldlen, 0, position - oldlen);
+ }
+
+ priv->position = position;
+
+ return TRUE;
+}
+
+static gboolean
+stream_mem_can_truncate (GSeekable *seekable)
+{
+ return FALSE;
+}
+
+static gboolean
+stream_mem_truncate_fn (GSeekable *seekable,
+ goffset offset,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* XXX Don't bother translating this. Camel never calls it. */
+ g_set_error (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Truncation is not supported");
+
+ return FALSE;
+}
+
+static void
+camel_stream_mem_class_init (CamelStreamMemClass *class)
+{
+ GObjectClass *object_class;
+ CamelStreamClass *stream_class;
+
+ g_type_class_add_private (class, sizeof (CamelStreamMemPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = stream_mem_finalize;
+
+ stream_class = CAMEL_STREAM_CLASS (class);
+ stream_class->read = stream_mem_read;
+ stream_class->write = stream_mem_write;
+ stream_class->eos = stream_mem_eos;
+}
+
+static void
+camel_stream_mem_seekable_init (GSeekableIface *iface)
+{
+ iface->tell = stream_mem_tell;
+ iface->can_seek = stream_mem_can_seek;
+ iface->seek = stream_mem_seek;
+ iface->can_truncate = stream_mem_can_truncate;
+ iface->truncate_fn = stream_mem_truncate_fn;
+}
+
+static void
+camel_stream_mem_init (CamelStreamMem *stream)
+{
+ stream->priv = CAMEL_STREAM_MEM_GET_PRIVATE (stream);
+}
+
+/**
+ * camel_stream_mem_new:
+ *
+ * Create a new #CamelStreamMem object.
+ *
+ * Returns: a new #CamelStreamMem
+ **/
+CamelStream *
+camel_stream_mem_new (void)
+{
+ return camel_stream_mem_new_with_byte_array (g_byte_array_new ());
+}
+
+/**
+ * camel_stream_mem_new_with_buffer:
+ * @buffer: (array length=len): a memory buffer to use as the stream data
+ * @len: length of @buffer
+ *
+ * Create a new memory stream using @buffer as the stream data.
+ *
+ * Note: @buffer will be copied into an internal #GByteArray structure
+ * for use as the stream backing. This may have resource implications
+ * you may wish to consider.
+ *
+ * Returns: a new #CamelStreamMem
+ **/
+CamelStream *
+camel_stream_mem_new_with_buffer (const gchar *buffer,
+ gsize len)
+{
+ GByteArray *ba;
+
+ g_return_val_if_fail (buffer != NULL, NULL);
+
+ ba = g_byte_array_new ();
+ g_byte_array_append (ba, (const guint8 *) buffer, len);
+
+ return camel_stream_mem_new_with_byte_array (ba);
+}
+
+/**
+ * camel_stream_mem_new_with_byte_array:
+ * @buffer: a #GByteArray to use as the stream data
+ *
+ * Create a new #CamelStreamMem using @buffer as the stream data.
+ *
+ * Note: The newly created #CamelStreamMem will destroy @buffer
+ * when destroyed.
+ *
+ * Returns: a new #CamelStreamMem
+ **/
+CamelStream *
+camel_stream_mem_new_with_byte_array (GByteArray *buffer)
+{
+ CamelStream *stream;
+ CamelStreamMemPrivate *priv;
+
+ g_return_val_if_fail (buffer != NULL, NULL);
+
+ stream = g_object_new (CAMEL_TYPE_STREAM_MEM, NULL);
+ priv = CAMEL_STREAM_MEM_GET_PRIVATE (stream);
+
+ priv->buffer = buffer;
+ priv->owner = TRUE;
+
+ return stream;
+}
+
+/**
+ * camel_stream_mem_set_secure:
+ * @mem: a #CamelStreamMem object
+ *
+ * Mark the memory stream as secure. At the very least this means the
+ * data in the buffer will be cleared when the buffer is finalized.
+ * This only applies to buffers owned by the stream.
+ **/
+void
+camel_stream_mem_set_secure (CamelStreamMem *mem)
+{
+ g_return_if_fail (CAMEL_IS_STREAM_MEM (mem));
+
+ mem->priv->secure = 1;
+}
+
+/* note: with these functions the caller is the 'owner' of the buffer */
+
+/**
+ * camel_stream_mem_get_byte_array:
+ * @mem: a #CamelStreamMem
+ *
+ * Returns: (transfer none):
+ *
+ * Since: 2.32
+ **/
+GByteArray *
+camel_stream_mem_get_byte_array (CamelStreamMem *mem)
+{
+ g_return_val_if_fail (CAMEL_IS_STREAM_MEM (mem), NULL);
+
+ return mem->priv->buffer;
+}
+
+/**
+ * camel_stream_mem_set_byte_array:
+ * @mem: a #CamelStreamMem object
+ * @buffer: a #GByteArray
+ *
+ * Set @buffer to be the backing data to the existing #CamelStreamMem, @mem.
+ *
+ * Note: @mem will not take ownership of @buffer and so will need to
+ * be freed separately from @mem.
+ **/
+void
+camel_stream_mem_set_byte_array (CamelStreamMem *mem,
+ GByteArray *buffer)
+{
+ g_return_if_fail (CAMEL_IS_STREAM_MEM (mem));
+ g_return_if_fail (buffer != NULL);
+
+ if (mem->priv->buffer && mem->priv->owner) {
+ if (mem->priv->secure && mem->priv->buffer->len)
+ clear_mem (
+ mem->priv->buffer->data,
+ mem->priv->buffer->len);
+ g_byte_array_free (mem->priv->buffer, TRUE);
+ }
+ mem->priv->owner = FALSE;
+ mem->priv->buffer = buffer;
+}
+
+/**
+ * camel_stream_mem_set_buffer:
+ * @mem: a #CamelStreamMem object
+ * @buffer: (array length=len): a memory buffer
+ * @len: length of @buffer
+ *
+ * Set @buffer to be the backing data to the existing #CamelStreamMem, @mem.
+ *
+ * Note: @buffer will be copied into an internal #GByteArray structure
+ * and so may have resource implications to consider.
+ **/
+void
+camel_stream_mem_set_buffer (CamelStreamMem *mem,
+ const gchar *buffer,
+ gsize len)
+{
+ GByteArray *ba;
+
+ g_return_if_fail (CAMEL_IS_STREAM_MEM (mem));
+ g_return_if_fail (buffer != NULL);
+
+ ba = g_byte_array_new ();
+ g_byte_array_append (ba, (const guint8 *) buffer, len);
+ camel_stream_mem_set_byte_array (mem, ba);
+ mem->priv->owner = TRUE;
+}
diff --git a/src/camel/camel-stream-mem.h b/src/camel/camel-stream-mem.h
new file mode 100644
index 000000000..385ce864e
--- /dev/null
+++ b/src/camel/camel-stream-mem.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-stream-mem.h: stream based on memory buffer
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_STREAM_MEM_H
+#define CAMEL_STREAM_MEM_H
+
+#include <sys/types.h>
+#include <camel/camel-stream.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_STREAM_MEM \
+ (camel_stream_mem_get_type ())
+#define CAMEL_STREAM_MEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_STREAM_MEM, CamelStreamMem))
+#define CAMEL_STREAM_MEM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_STREAM_MEM, CamelStreamMemClass))
+#define CAMEL_IS_STREAM_MEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_STREAM_MEM))
+#define CAMEL_IS_STREAM_MEM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_STREAM_MEM))
+#define CAMEL_STREAM_MEM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_STREAM_MEM, CamelStreamMemClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelStreamMem CamelStreamMem;
+typedef struct _CamelStreamMemClass CamelStreamMemClass;
+typedef struct _CamelStreamMemPrivate CamelStreamMemPrivate;
+
+struct _CamelStreamMem {
+ CamelStream parent;
+ CamelStreamMemPrivate *priv;
+};
+
+struct _CamelStreamMemClass {
+ CamelStreamClass parent_class;
+};
+
+GType camel_stream_mem_get_type (void);
+CamelStream * camel_stream_mem_new (void);
+CamelStream * camel_stream_mem_new_with_byte_array
+ (GByteArray *buffer);
+CamelStream * camel_stream_mem_new_with_buffer (const gchar *buffer,
+ gsize len);
+void camel_stream_mem_set_secure (CamelStreamMem *mem);
+GByteArray * camel_stream_mem_get_byte_array (CamelStreamMem *mem);
+void camel_stream_mem_set_byte_array (CamelStreamMem *mem,
+ GByteArray *buffer);
+void camel_stream_mem_set_buffer (CamelStreamMem *mem,
+ const gchar *buffer,
+ gsize len);
+
+G_END_DECLS
+
+#endif /* CAMEL_STREAM_MEM_H */
diff --git a/src/camel/camel-stream-null.c b/src/camel/camel-stream-null.c
new file mode 100644
index 000000000..db759897b
--- /dev/null
+++ b/src/camel/camel-stream-null.c
@@ -0,0 +1,138 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/* camel-stream.c : abstract class for a stream
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-stream-null.h"
+
+static void camel_stream_null_seekable_init (GSeekableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (CamelStreamNull, camel_stream_null, CAMEL_TYPE_STREAM,
+ G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, camel_stream_null_seekable_init))
+
+static gssize
+stream_null_write (CamelStream *stream,
+ const gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CAMEL_STREAM_NULL (stream)->written += n;
+
+ return n;
+}
+
+static gboolean
+stream_null_eos (CamelStream *stream)
+{
+ return TRUE;
+}
+
+static goffset
+stream_null_tell (GSeekable *seekable)
+{
+ return 0;
+}
+
+static gboolean
+stream_null_can_seek (GSeekable *seekable)
+{
+ return TRUE;
+}
+
+static gboolean
+stream_null_seek (GSeekable *seekable,
+ goffset offset,
+ GSeekType type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (type != G_SEEK_SET || offset != 0) {
+ g_set_error_literal (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Only reset to beginning is supported with CamelHttpStream"));
+ return FALSE;
+ }
+
+ CAMEL_STREAM_NULL (seekable)->written = 0;
+
+ return TRUE;
+}
+
+static gboolean
+stream_null_can_truncate (GSeekable *seekable)
+{
+ return FALSE;
+}
+
+static gboolean
+stream_null_truncate_fn (GSeekable *seekable,
+ goffset offset,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* XXX Don't bother translating this. Camel never calls it. */
+ g_set_error_literal (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Truncation is not supported");
+
+ return FALSE;
+}
+
+static void
+camel_stream_null_class_init (CamelStreamNullClass *class)
+{
+ CamelStreamClass *stream_class;
+
+ stream_class = CAMEL_STREAM_CLASS (class);
+ stream_class->write = stream_null_write;
+ stream_class->eos = stream_null_eos;
+}
+
+static void
+camel_stream_null_seekable_init (GSeekableIface *iface)
+{
+ iface->tell = stream_null_tell;
+ iface->can_seek = stream_null_can_seek;
+ iface->seek = stream_null_seek;
+ iface->can_truncate = stream_null_can_truncate;
+ iface->truncate_fn = stream_null_truncate_fn;
+}
+
+static void
+camel_stream_null_init (CamelStreamNull *stream_null)
+{
+}
+
+/**
+ * camel_stream_null_new:
+ *
+ * Returns a null stream. A null stream is always at eof, and
+ * always returns success for all reads and writes.
+ *
+ * Returns: a new #CamelStreamNull
+ **/
+CamelStream *
+camel_stream_null_new (void)
+{
+ return g_object_new (CAMEL_TYPE_STREAM_NULL, NULL);
+}
diff --git a/src/camel/camel-stream-null.h b/src/camel/camel-stream-null.h
new file mode 100644
index 000000000..d0d96d532
--- /dev/null
+++ b/src/camel/camel-stream-null.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_STREAM_NULL_H
+#define CAMEL_STREAM_NULL_H
+
+#include <camel/camel-stream.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_STREAM_NULL \
+ (camel_stream_null_get_type ())
+#define CAMEL_STREAM_NULL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_STREAM_NULL, CamelStreamNull))
+#define CAMEL_STREAM_NULL_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_STREAM_NULL, CamelStreamNullClass))
+#define CAMEL_IS_STREAM_NULL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_STREAM_NULL))
+#define CAMEL_IS_STREAM_NULL_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_STREAM_NULL))
+#define CAMEL_STREAM_NULL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_STREAM_NULL, CamelStreamNullClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelStreamNull CamelStreamNull;
+typedef struct _CamelStreamNullClass CamelStreamNullClass;
+
+struct _CamelStreamNull {
+ CamelStream parent;
+
+ gsize written;
+};
+
+struct _CamelStreamNullClass {
+ CamelStreamClass parent_class;
+};
+
+GType camel_stream_null_get_type (void);
+
+CamelStream *camel_stream_null_new (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_STREAM_NULL_H */
diff --git a/src/camel/camel-stream-process.c b/src/camel/camel-stream-process.c
new file mode 100644
index 000000000..c8ab3ac96
--- /dev/null
+++ b/src/camel/camel-stream-process.c
@@ -0,0 +1,285 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/* camel-stream-process.c : stream over piped process
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: David Woodhouse <dwmw2@infradead.org>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-file-utils.h"
+#include "camel-stream-process.h"
+
+#define CHECK_CALL(x) G_STMT_START { \
+ if ((x) == -1) { \
+ g_debug ("%s: Call of '" #x "' failed: %s", G_STRFUNC, g_strerror (errno)); \
+ } \
+ } G_STMT_END
+
+extern gint camel_verbose_debug;
+
+G_DEFINE_TYPE (CamelStreamProcess, camel_stream_process, CAMEL_TYPE_STREAM)
+
+static void
+stream_process_finalize (GObject *object)
+{
+ /* Ensure we clean up after ourselves -- kill
+ * the child process and reap it. */
+ camel_stream_close (CAMEL_STREAM (object), NULL, NULL);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_stream_process_parent_class)->finalize (object);
+}
+
+static gssize
+stream_process_read (CamelStream *stream,
+ gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamProcess *stream_process = CAMEL_STREAM_PROCESS (stream);
+ gint fd = stream_process->sockfd;
+
+ return camel_read (fd, buffer, n, cancellable, error);
+}
+
+static gssize
+stream_process_write (CamelStream *stream,
+ const gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamProcess *stream_process = CAMEL_STREAM_PROCESS (stream);
+ gint fd = stream_process->sockfd;
+
+ return camel_write (fd, buffer, n, cancellable, error);
+}
+
+static gint
+stream_process_close (CamelStream *object,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamProcess *stream = CAMEL_STREAM_PROCESS (object);
+
+ if (camel_verbose_debug)
+ fprintf (
+ stderr,
+ "Process stream close. sockfd %d, childpid %d\n",
+ stream->sockfd, stream->childpid);
+
+ if (stream->sockfd != -1) {
+ close (stream->sockfd);
+ stream->sockfd = -1;
+ }
+
+ if (stream->childpid) {
+ gint ret, i;
+ for (i = 0; i < 4; i++) {
+ ret = waitpid (stream->childpid, NULL, WNOHANG);
+ if (camel_verbose_debug)
+ fprintf (
+ stderr,
+ "waitpid() for pid %d returned %d (errno %d)\n",
+ stream->childpid, ret, ret == -1 ? errno : 0);
+ if (ret == stream->childpid || errno == ECHILD)
+ break;
+ switch (i) {
+ case 0:
+ if (camel_verbose_debug)
+ fprintf (
+ stderr,
+ "Sending SIGTERM to pid %d\n",
+ stream->childpid);
+ kill (stream->childpid, SIGTERM);
+ break;
+ case 2:
+ if (camel_verbose_debug)
+ fprintf (
+ stderr,
+ "Sending SIGKILL to pid %d\n",
+ stream->childpid);
+ kill (stream->childpid, SIGKILL);
+ break;
+ case 1:
+ case 3:
+ sleep (1);
+ break;
+ }
+ }
+
+ stream->childpid = 0;
+ }
+
+ return 0;
+}
+
+static gint
+stream_process_flush (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return 0;
+}
+
+static void
+camel_stream_process_class_init (CamelStreamProcessClass *class)
+{
+ GObjectClass *object_class;
+ CamelStreamClass *stream_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = stream_process_finalize;
+
+ stream_class = CAMEL_STREAM_CLASS (class);
+ stream_class->read = stream_process_read;
+ stream_class->write = stream_process_write;
+ stream_class->close = stream_process_close;
+ stream_class->flush = stream_process_flush;
+}
+
+static void
+camel_stream_process_init (CamelStreamProcess *stream)
+{
+ stream->sockfd = -1;
+ stream->childpid = 0;
+}
+
+/**
+ * camel_stream_process_new:
+ *
+ * Returns a PROCESS stream.
+ *
+ * Returns: the stream
+ **/
+CamelStream *
+camel_stream_process_new (void)
+{
+ return g_object_new (CAMEL_TYPE_STREAM_PROCESS, NULL);
+}
+
+G_GNUC_NORETURN static void
+do_exec_command (gint fd,
+ const gchar *command,
+ gchar **env)
+{
+ gint i, maxopen;
+
+ /* Not a lot we can do if there's an error other than bail. */
+ if (dup2 (fd, 0) == -1)
+ exit (1);
+ if (dup2 (fd, 1) == -1)
+ exit (1);
+
+ /* What to do with stderr? Possibly put it through a separate pipe
+ * and bring up a dialog box with its output if anything does get
+ * spewed to it? It'd help the user understand what was going wrong
+ * with their command, but it's hard to do cleanly. For now we just
+ * leave it as it is. Perhaps we should close it and reopen /dev/null? */
+
+ maxopen = sysconf (_SC_OPEN_MAX);
+ for (i = 3; i < maxopen; i++) {
+ CHECK_CALL (fcntl (i, F_SETFD, FD_CLOEXEC));
+ }
+
+ setsid ();
+#ifdef TIOCNOTTY
+ /* Detach from the controlling tty if we have one. Otherwise,
+ * SSH might do something stupid like trying to use it instead
+ * of running $SSH_ASKPASS. Doh. */
+ if ((fd = open ("/dev/tty", O_RDONLY)) != -1) {
+ ioctl (fd, TIOCNOTTY, NULL);
+ close (fd);
+ }
+#endif /* TIOCNOTTY */
+
+ /* Set up child's environment. We _add_ to it, don't use execle,
+ * because otherwise we'd destroy stuff like SSH_AUTH_SOCK etc. */
+ for (; env && *env; env++)
+ putenv (*env);
+
+ execl ("/bin/sh", "/bin/sh", "-c", command, NULL);
+
+ if (camel_verbose_debug)
+ fprintf (stderr, "exec failed %d\n", errno);
+
+ exit (1);
+}
+
+gint
+camel_stream_process_connect (CamelStreamProcess *stream,
+ const gchar *command,
+ const gchar **env,
+ GError **error)
+{
+ gint sockfds[2];
+
+ g_return_val_if_fail (CAMEL_IS_STREAM_PROCESS (stream), -1);
+ g_return_val_if_fail (command != NULL, -1);
+
+ if (stream->sockfd != -1 || stream->childpid)
+ camel_stream_close (CAMEL_STREAM (stream), NULL, NULL);
+
+ if (socketpair (AF_UNIX, SOCK_STREAM, 0, sockfds))
+ goto fail;
+
+ stream->childpid = fork ();
+ if (!stream->childpid) {
+ do_exec_command (sockfds[1], command, (gchar **) env);
+ } else if (stream->childpid == -1) {
+ close (sockfds[0]);
+ close (sockfds[1]);
+ stream->sockfd = -1;
+ goto fail;
+ }
+
+ close (sockfds[1]);
+ stream->sockfd = sockfds[0];
+
+ return 0;
+
+fail:
+ if (errno == EINTR)
+ g_set_error (
+ error, G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Connection cancelled"));
+ else
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not connect with command \"%s\": %s"),
+ command, g_strerror (errno));
+
+ return -1;
+}
diff --git a/src/camel/camel-stream-process.h b/src/camel/camel-stream-process.h
new file mode 100644
index 000000000..1c48645ca
--- /dev/null
+++ b/src/camel/camel-stream-process.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: David Woodhouse <dwmw2@infradead.org>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_STREAM_PROCESS_H
+#define CAMEL_STREAM_PROCESS_H
+
+#include <camel/camel-stream.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_STREAM_PROCESS \
+ (camel_stream_process_get_type ())
+#define CAMEL_STREAM_PROCESS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_STREAM_PROCESS, CamelStreamProcess))
+#define CAMEL_STREAM_PROCESS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_STREAM_PROCESS, CamelStreamProcessClass))
+#define CAMEL_IS_STREAM_PROCESS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_STREAM_PROCESS))
+#define CAMEL_IS_STREAM_PROCESS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_STREAM_PROCESS))
+#define CAMEL_STREAM_PROCESS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_STREAM_PROCSS, CamelStreamProcessClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelStreamProcess CamelStreamProcess;
+typedef struct _CamelStreamProcessClass CamelStreamProcessClass;
+
+struct _CamelStreamProcess {
+ CamelStream parent;
+
+ gint sockfd;
+ pid_t childpid;
+};
+
+struct _CamelStreamProcessClass {
+ CamelStreamClass parent_class;
+};
+
+GType camel_stream_process_get_type (void);
+CamelStream * camel_stream_process_new (void);
+gint camel_stream_process_connect (CamelStreamProcess *stream,
+ const gchar *command,
+ const gchar **env,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_STREAM_PROCESS_H */
diff --git a/src/camel/camel-stream.c b/src/camel/camel-stream.c
new file mode 100644
index 000000000..3841819b0
--- /dev/null
+++ b/src/camel/camel-stream.c
@@ -0,0 +1,704 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-stream.c : abstract class for a stream
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel-debug.h>
+
+#include "camel-stream.h"
+
+#define CAMEL_STREAM_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_STREAM, CamelStreamPrivate))
+
+struct _CamelStreamPrivate {
+ GIOStream *base_stream;
+ GMutex base_stream_lock;
+};
+
+enum {
+ PROP_0,
+ PROP_BASE_STREAM
+};
+
+/* Forward Declarations */
+static void camel_stream_seekable_init (GSeekableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ CamelStream,
+ camel_stream,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (
+ G_TYPE_SEEKABLE,
+ camel_stream_seekable_init))
+
+static void
+stream_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_BASE_STREAM:
+ camel_stream_set_base_stream (
+ CAMEL_STREAM (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+stream_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_BASE_STREAM:
+ g_value_take_object (
+ value,
+ camel_stream_ref_base_stream (
+ CAMEL_STREAM (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+stream_dispose (GObject *object)
+{
+ CamelStreamPrivate *priv;
+
+ priv = CAMEL_STREAM_GET_PRIVATE (object);
+
+ g_clear_object (&priv->base_stream);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_stream_parent_class)->dispose (object);
+}
+
+static void
+stream_finalize (GObject *object)
+{
+ CamelStreamPrivate *priv;
+
+ priv = CAMEL_STREAM_GET_PRIVATE (object);
+
+ g_mutex_clear (&priv->base_stream_lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_stream_parent_class)->finalize (object);
+}
+
+static gssize
+stream_read (CamelStream *stream,
+ gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GIOStream *base_stream;
+ gssize n_bytes_read = 0;
+
+ base_stream = camel_stream_ref_base_stream (stream);
+
+ if (base_stream != NULL) {
+ GInputStream *input_stream;
+
+ input_stream = g_io_stream_get_input_stream (base_stream);
+
+ n_bytes_read = g_input_stream_read (
+ input_stream, buffer, n, cancellable, error);
+
+ g_object_unref (base_stream);
+ }
+
+ stream->eos = n_bytes_read <= 0;
+
+ return n_bytes_read;
+}
+
+static gssize
+stream_write (CamelStream *stream,
+ const gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GIOStream *base_stream;
+ gssize n_bytes_written = -1;
+
+ base_stream = camel_stream_ref_base_stream (stream);
+
+ if (base_stream != NULL) {
+ GOutputStream *output_stream;
+ gsize n_written = 0;
+
+ output_stream = g_io_stream_get_output_stream (base_stream);
+ stream->eos = FALSE;
+
+ if (g_output_stream_write_all (output_stream, buffer, n, &n_written, cancellable, error))
+ n_bytes_written = (gssize) n_written;
+ else
+ n_bytes_written = -1;
+
+ g_object_unref (base_stream);
+ } else {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot write with no base stream"));
+ }
+
+ return n_bytes_written;
+}
+
+static gint
+stream_close (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GIOStream *base_stream;
+ gboolean success = TRUE;
+
+ base_stream = camel_stream_ref_base_stream (stream);
+
+ if (base_stream != NULL) {
+ success = g_io_stream_close (
+ base_stream, cancellable, error);
+
+ g_object_unref (base_stream);
+ }
+
+ return success ? 0 : -1;
+}
+
+static gint
+stream_flush (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GIOStream *base_stream;
+ gboolean success = TRUE;
+
+ base_stream = camel_stream_ref_base_stream (stream);
+
+ if (base_stream != NULL) {
+ GOutputStream *output_stream;
+
+ output_stream = g_io_stream_get_output_stream (base_stream);
+
+ success = g_output_stream_flush (
+ output_stream, cancellable, error);
+
+ g_object_unref (base_stream);
+ }
+
+ return success ? 0 : -1;
+}
+
+static gboolean
+stream_eos (CamelStream *stream)
+{
+ return stream->eos;
+}
+
+static goffset
+stream_tell (GSeekable *seekable)
+{
+ CamelStream *stream;
+ GIOStream *base_stream;
+ goffset position = 0;
+
+ stream = CAMEL_STREAM (seekable);
+ base_stream = camel_stream_ref_base_stream (stream);
+
+ if (G_IS_SEEKABLE (base_stream)) {
+ position = g_seekable_tell (G_SEEKABLE (base_stream));
+ } else if (base_stream != NULL) {
+ g_critical (
+ "Stream type '%s' is not seekable",
+ G_OBJECT_TYPE_NAME (base_stream));
+ }
+
+ g_clear_object (&base_stream);
+
+ return position;
+}
+
+static gboolean
+stream_can_seek (GSeekable *seekable)
+{
+ CamelStream *stream;
+ GIOStream *base_stream;
+ gboolean can_seek = FALSE;
+
+ stream = CAMEL_STREAM (seekable);
+ base_stream = camel_stream_ref_base_stream (stream);
+
+ if (G_IS_SEEKABLE (base_stream))
+ can_seek = g_seekable_can_seek (G_SEEKABLE (base_stream));
+
+ g_clear_object (&base_stream);
+
+ return can_seek;
+}
+
+static gboolean
+stream_seek (GSeekable *seekable,
+ goffset offset,
+ GSeekType type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStream *stream;
+ GIOStream *base_stream;
+ gboolean success = FALSE;
+
+ stream = CAMEL_STREAM (seekable);
+ base_stream = camel_stream_ref_base_stream (stream);
+
+ if (G_IS_SEEKABLE (base_stream)) {
+ stream->eos = FALSE;
+ success = g_seekable_seek (
+ G_SEEKABLE (base_stream),
+ offset, type, cancellable, error);
+ } else if (base_stream != NULL) {
+ g_set_error (
+ error, G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("Stream type '%s' is not seekable"),
+ G_OBJECT_TYPE_NAME (base_stream));
+ } else {
+ g_warn_if_reached ();
+ }
+
+ g_clear_object (&base_stream);
+
+ return success;
+}
+
+static gboolean
+stream_can_truncate (GSeekable *seekable)
+{
+ CamelStream *stream;
+ GIOStream *base_stream;
+ gboolean can_truncate = FALSE;
+
+ stream = CAMEL_STREAM (seekable);
+ base_stream = camel_stream_ref_base_stream (stream);
+
+ if (G_IS_SEEKABLE (base_stream))
+ can_truncate = g_seekable_can_truncate (
+ G_SEEKABLE (base_stream));
+
+ g_clear_object (&base_stream);
+
+ return can_truncate;
+}
+
+static gboolean
+stream_truncate (GSeekable *seekable,
+ goffset offset,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStream *stream;
+ GIOStream *base_stream;
+ gboolean success = FALSE;
+
+ stream = CAMEL_STREAM (seekable);
+ base_stream = camel_stream_ref_base_stream (stream);
+
+ if (G_IS_SEEKABLE (base_stream)) {
+ success = g_seekable_truncate (
+ G_SEEKABLE (base_stream),
+ offset, cancellable, error);
+ } else if (base_stream != NULL) {
+ g_set_error (
+ error, G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("Stream type '%s' is not seekable"),
+ G_OBJECT_TYPE_NAME (base_stream));
+ } else {
+ g_warn_if_reached ();
+ }
+
+ g_clear_object (&base_stream);
+
+ return success;
+}
+
+static void
+camel_stream_class_init (CamelStreamClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelStreamPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = stream_set_property;
+ object_class->get_property = stream_get_property;
+ object_class->dispose = stream_dispose;
+ object_class->finalize = stream_finalize;
+
+ class->read = stream_read;
+ class->write = stream_write;
+ class->close = stream_close;
+ class->flush = stream_flush;
+ class->eos = stream_eos;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_BASE_STREAM,
+ g_param_spec_object (
+ "base-stream",
+ "Base Stream",
+ "The base GIOStream",
+ G_TYPE_IO_STREAM,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_stream_seekable_init (GSeekableIface *iface)
+{
+ iface->tell = stream_tell;
+ iface->can_seek = stream_can_seek;
+ iface->seek = stream_seek;
+ iface->can_truncate = stream_can_truncate;
+ iface->truncate_fn = stream_truncate;
+}
+
+static void
+camel_stream_init (CamelStream *stream)
+{
+ stream->priv = CAMEL_STREAM_GET_PRIVATE (stream);
+
+ g_mutex_init (&stream->priv->base_stream_lock);
+}
+
+/**
+ * camel_stream_new:
+ * @base_stream: a #GIOStream
+ *
+ * Creates a #CamelStream as a thin wrapper for @base_stream.
+ *
+ * Returns: a #CamelStream
+ *
+ * Since: 3.12
+ **/
+CamelStream *
+camel_stream_new (GIOStream *base_stream)
+{
+ g_return_val_if_fail (G_IS_IO_STREAM (base_stream), NULL);
+
+ return g_object_new (
+ CAMEL_TYPE_STREAM, "base-stream", base_stream, NULL);
+}
+
+/**
+ * camel_stream_ref_base_stream:
+ * @stream: a #CamelStream
+ *
+ * Returns the #GIOStream for @stream. This is only valid if @stream was
+ * created with camel_stream_new(). For all other #CamelStream subclasses
+ * this function returns %NULL.
+ *
+ * The returned #GIOStream is referenced for thread-safety and should be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: (transfer full) (nullable): a #GIOStream, or %NULL
+ *
+ * Since: 3.12
+ **/
+GIOStream *
+camel_stream_ref_base_stream (CamelStream *stream)
+{
+ GIOStream *base_stream = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), NULL);
+
+ g_mutex_lock (&stream->priv->base_stream_lock);
+
+ if (stream->priv->base_stream != NULL)
+ base_stream = g_object_ref (stream->priv->base_stream);
+
+ g_mutex_unlock (&stream->priv->base_stream_lock);
+
+ return base_stream;
+}
+
+/**
+ * camel_stream_set_base_stream:
+ * @stream: a #CamelStream
+ * @base_stream: a #GIOStream
+ *
+ * Replaces the #GIOStream passed to camel_stream_new() with @base_stream.
+ * The new @base_stream should wrap the original #GIOStream, such as when
+ * adding Transport Layer Security after issuing a STARTTLS command.
+ *
+ * Since: 3.12
+ **/
+void
+camel_stream_set_base_stream (CamelStream *stream,
+ GIOStream *base_stream)
+{
+ g_return_if_fail (CAMEL_IS_STREAM (stream));
+ g_return_if_fail (G_IS_IO_STREAM (base_stream));
+
+ g_mutex_lock (&stream->priv->base_stream_lock);
+
+ g_clear_object (&stream->priv->base_stream);
+ stream->priv->base_stream = g_object_ref (base_stream);
+
+ g_mutex_unlock (&stream->priv->base_stream_lock);
+
+ g_object_notify (G_OBJECT (stream), "base-stream");
+}
+
+/**
+ * camel_stream_read:
+ * @stream: a #CamelStream object.
+ * @buffer: output buffer
+ * @n: max number of bytes to read.
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Attempts to read up to @len bytes from @stream into @buf.
+ *
+ * Returns: the number of bytes actually read, or %-1 on error and set
+ * errno.
+ **/
+gssize
+camel_stream_read (CamelStream *stream,
+ gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamClass *class;
+ gssize n_bytes;
+
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
+ g_return_val_if_fail (n == 0 || buffer, -1);
+
+ class = CAMEL_STREAM_GET_CLASS (stream);
+ g_return_val_if_fail (class->read != NULL, -1);
+
+ n_bytes = class->read (stream, buffer, n, cancellable, error);
+ CAMEL_CHECK_GERROR (stream, read, n_bytes >= 0, error);
+
+ return n_bytes;
+}
+
+/**
+ * camel_stream_write:
+ * @stream: a #CamelStream object
+ * @buffer: buffer to write.
+ * @n: number of bytes to write
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Attempts to write up to @n bytes of @buffer into @stream.
+ *
+ * Returns: the number of bytes written to the stream, or %-1 on error
+ * along with setting errno.
+ **/
+gssize
+camel_stream_write (CamelStream *stream,
+ const gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamClass *class;
+ gssize n_bytes;
+
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
+ g_return_val_if_fail (n == 0 || buffer, -1);
+
+ class = CAMEL_STREAM_GET_CLASS (stream);
+ g_return_val_if_fail (class->write != NULL, -1);
+
+ n_bytes = class->write (stream, buffer, n, cancellable, error);
+ CAMEL_CHECK_GERROR (stream, write, n_bytes >= 0, error);
+
+ return n_bytes;
+}
+
+/**
+ * camel_stream_flush:
+ * @stream: a #CamelStream object
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Flushes any buffered data to the stream's backing store. Only
+ * meaningful for writable streams.
+ *
+ * Returns: %0 on success or %-1 on fail along with setting @error
+ **/
+gint
+camel_stream_flush (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamClass *class;
+ gint retval;
+
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
+
+ class = CAMEL_STREAM_GET_CLASS (stream);
+ g_return_val_if_fail (class->flush != NULL, -1);
+
+ retval = class->flush (stream, cancellable, error);
+ CAMEL_CHECK_GERROR (stream, flush, retval == 0, error);
+
+ return retval;
+}
+
+/**
+ * camel_stream_close:
+ * @stream: a #CamelStream object
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Closes the stream.
+ *
+ * Returns: %0 on success or %-1 on error.
+ **/
+gint
+camel_stream_close (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStreamClass *class;
+ gint retval;
+
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
+
+ class = CAMEL_STREAM_GET_CLASS (stream);
+ g_return_val_if_fail (class->close != NULL, -1);
+
+ retval = class->close (stream, cancellable, error);
+ CAMEL_CHECK_GERROR (stream, close, retval == 0, error);
+
+ return retval;
+}
+
+/**
+ * camel_stream_eos:
+ * @stream: a #CamelStream object
+ *
+ * Tests if there are bytes left to read on the @stream object.
+ *
+ * Returns: %TRUE on EOS or %FALSE otherwise.
+ **/
+gboolean
+camel_stream_eos (CamelStream *stream)
+{
+ CamelStreamClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), TRUE);
+
+ class = CAMEL_STREAM_GET_CLASS (stream);
+ g_return_val_if_fail (class->eos != NULL, TRUE);
+
+ return class->eos (stream);
+}
+
+/***************** Utility functions ********************/
+
+/**
+ * camel_stream_write_string:
+ * @stream: a #CamelStream object
+ * @string: a string
+ * @error: return location for a #GError, or %NULL
+ *
+ * Writes the string to the stream.
+ *
+ * Returns: the number of characters written or %-1 on error.
+ **/
+gssize
+camel_stream_write_string (CamelStream *stream,
+ const gchar *string,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
+ g_return_val_if_fail (string != NULL, -1);
+
+ return camel_stream_write (
+ stream, string, strlen (string), cancellable, error);
+}
+
+/**
+ * camel_stream_write_to_stream:
+ * @stream: source #CamelStream object
+ * @output_stream: destination #CamelStream object
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Write all of a stream (until eos) into another stream, in a
+ * blocking fashion.
+ *
+ * Returns: %-1 on error, or the number of bytes succesfully
+ * copied across streams.
+ **/
+gssize
+camel_stream_write_to_stream (CamelStream *stream,
+ CamelStream *output_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar tmp_buf[4096];
+ gssize total = 0;
+ gssize nb_read;
+ gssize nb_written;
+
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
+ g_return_val_if_fail (CAMEL_IS_STREAM (output_stream), -1);
+
+ while (!camel_stream_eos (stream)) {
+ nb_read = camel_stream_read (
+ stream, tmp_buf, sizeof (tmp_buf),
+ cancellable, error);
+ if (nb_read < 0)
+ return -1;
+ else if (nb_read > 0) {
+ nb_written = 0;
+
+ while (nb_written < nb_read) {
+ gssize len = camel_stream_write (
+ output_stream,
+ tmp_buf + nb_written,
+ nb_read - nb_written,
+ cancellable, error);
+ if (len < 0)
+ return -1;
+ nb_written += len;
+ }
+ total += nb_written;
+ }
+ }
+ return total;
+}
diff --git a/src/camel/camel-stream.h b/src/camel/camel-stream.h
new file mode 100644
index 000000000..4e7571ee2
--- /dev/null
+++ b/src/camel/camel-stream.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/* camel-stream.h : class for an abstract stream
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_STREAM_H
+#define CAMEL_STREAM_H
+
+#include <stdarg.h>
+#include <unistd.h>
+
+#include <gio/gio.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_STREAM \
+ (camel_stream_get_type ())
+#define CAMEL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_STREAM, CamelStream))
+#define CAMEL_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_STREAM, CamelStreamClass))
+#define CAMEL_IS_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_STREAM))
+#define CAMEL_IS_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_STREAM))
+#define CAMEL_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_STREAM, CamelStreamClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelStream CamelStream;
+typedef struct _CamelStreamClass CamelStreamClass;
+typedef struct _CamelStreamPrivate CamelStreamPrivate;
+
+struct _CamelStream {
+ GObject parent;
+ CamelStreamPrivate *priv;
+
+ gboolean eos;
+};
+
+struct _CamelStreamClass {
+ GObjectClass parent_class;
+
+ gssize (*read) (CamelStream *stream,
+ gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error);
+ gssize (*write) (CamelStream *stream,
+ const gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error);
+ gint (*close) (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+ gint (*flush) (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*eos) (CamelStream *stream);
+};
+
+GType camel_stream_get_type (void);
+CamelStream * camel_stream_new (GIOStream *base_stream);
+GIOStream * camel_stream_ref_base_stream (CamelStream *stream);
+void camel_stream_set_base_stream (CamelStream *stream,
+ GIOStream *base_stream);
+gssize camel_stream_read (CamelStream *stream,
+ gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error);
+gssize camel_stream_write (CamelStream *stream,
+ const gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error);
+gint camel_stream_flush (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+gint camel_stream_close (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_stream_eos (CamelStream *stream);
+
+/* utility macros and funcs */
+gssize camel_stream_write_string (CamelStream *stream,
+ const gchar *string,
+ GCancellable *cancellable,
+ GError **error);
+
+/* Write a whole stream to another stream, until eof or error on
+ * either stream. */
+gssize camel_stream_write_to_stream (CamelStream *stream,
+ CamelStream *output_stream,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_STREAM_H */
diff --git a/src/camel/camel-string-utils.c b/src/camel/camel-string-utils.c
new file mode 100644
index 000000000..f362c853d
--- /dev/null
+++ b/src/camel/camel-string-utils.c
@@ -0,0 +1,376 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include "camel-string-utils.h"
+
+gint
+camel_strcase_equal (gconstpointer a,
+ gconstpointer b)
+{
+ return (g_ascii_strcasecmp ((const gchar *) a, (const gchar *) b) == 0);
+}
+
+guint
+camel_strcase_hash (gconstpointer v)
+{
+ const gchar *p = (gchar *) v;
+ guint h = 0, g;
+
+ for (; *p != '\0'; p++) {
+ h = (h << 4) + g_ascii_toupper (*p);
+ if ((g = h & 0xf0000000)) {
+ h = h ^ (g >> 24);
+ h = h ^ g;
+ }
+ }
+
+ return h;
+}
+
+gchar *
+camel_strstrcase (const gchar *haystack,
+ const gchar *needle)
+{
+ /* find the needle in the haystack neglecting case */
+ const gchar *ptr;
+ guint len;
+
+ g_return_val_if_fail (haystack != NULL, NULL);
+ g_return_val_if_fail (needle != NULL, NULL);
+
+ len = strlen (needle);
+ if (len > strlen (haystack))
+ return NULL;
+
+ if (len == 0)
+ return (gchar *) haystack;
+
+ for (ptr = haystack; *(ptr + len - 1) != '\0'; ptr++)
+ if (!g_ascii_strncasecmp (ptr, needle, len))
+ return (gchar *) ptr;
+
+ return NULL;
+}
+
+const gchar *
+camel_strdown (gchar *str)
+{
+ register gchar *s = str;
+
+ while (*s) {
+ if (*s >= 'A' && *s <= 'Z')
+ *s += 0x20;
+ s++;
+ }
+
+ return str;
+}
+
+/* working stuff for pstrings */
+static GMutex string_pool_lock;
+static GHashTable *string_pool = NULL;
+
+typedef struct _StringPoolNode StringPoolNode;
+
+struct _StringPoolNode {
+ gchar *string;
+ gulong ref_count;
+};
+
+static StringPoolNode *
+string_pool_node_new (gchar *string)
+{
+ StringPoolNode *node;
+
+ node = g_slice_new (StringPoolNode);
+ node->string = string; /* takes ownership */
+ node->ref_count = 1;
+
+ return node;
+}
+
+static void
+string_pool_node_free (StringPoolNode *node)
+{
+ g_free (node->string);
+
+ g_slice_free (StringPoolNode, node);
+}
+
+static guint
+string_pool_node_hash (const StringPoolNode *node)
+{
+ return g_str_hash (node->string);
+}
+
+static gboolean
+string_pool_node_equal (const StringPoolNode *node_a,
+ const StringPoolNode *node_b)
+{
+ return g_str_equal (node_a->string, node_b->string);
+}
+
+static void
+string_pool_init (void)
+{
+ if (G_UNLIKELY (string_pool == NULL))
+ string_pool = g_hash_table_new_full (
+ (GHashFunc) string_pool_node_hash,
+ (GEqualFunc) string_pool_node_equal,
+ (GDestroyNotify) string_pool_node_free,
+ (GDestroyNotify) NULL);
+}
+
+/**
+ * camel_pstring_add:
+ * @string: string to add to the string pool
+ * @own: whether the string pool will own the memory pointed to by
+ * @string, if @string is not yet in the pool
+ *
+ * Add @string to the pool.
+ *
+ * The %NULL and empty strings are special cased to constant values.
+ *
+ * Unreference the returned string with camel_pstring_free().
+ *
+ * Returns: a canonicalized copy of @string
+ **/
+const gchar *
+camel_pstring_add (gchar *string,
+ gboolean own)
+{
+ StringPoolNode static_node = { string, };
+ StringPoolNode *node;
+ const gchar *interned;
+
+ if (string == NULL)
+ return NULL;
+
+ if (*string == '\0') {
+ if (own)
+ g_free (string);
+ return "";
+ }
+
+ g_mutex_lock (&string_pool_lock);
+
+ string_pool_init ();
+
+ node = g_hash_table_lookup (string_pool, &static_node);
+
+ if (node != NULL) {
+ node->ref_count++;
+ if (own)
+ g_free (string);
+ } else {
+ if (!own)
+ string = g_strdup (string);
+ node = string_pool_node_new (string);
+ g_hash_table_add (string_pool, node);
+ }
+
+ interned = node->string;
+
+ g_mutex_unlock (&string_pool_lock);
+
+ return interned;
+}
+
+/**
+ * camel_pstring_peek:
+ * @string: string to fetch from the string pool
+ *
+ * Returns the canonicalized copy of @string without increasing its
+ * reference count in the string pool. If necessary, @string is first
+ * added to the string pool.
+ *
+ * The %NULL and empty strings are special cased to constant values.
+ *
+ * Returns: a canonicalized copy of @string
+ *
+ * Since: 2.24
+ **/
+const gchar *
+camel_pstring_peek (const gchar *string)
+{
+ StringPoolNode static_node = { (gchar *) string, };
+ StringPoolNode *node;
+ const gchar *interned;
+
+ if (string == NULL)
+ return NULL;
+
+ if (*string == '\0')
+ return "";
+
+ g_mutex_lock (&string_pool_lock);
+
+ string_pool_init ();
+
+ node = g_hash_table_lookup (string_pool, &static_node);
+
+ if (node == NULL) {
+ node = string_pool_node_new (g_strdup (string));
+ g_hash_table_add (string_pool, node);
+ }
+
+ interned = node->string;
+
+ g_mutex_unlock (&string_pool_lock);
+
+ return interned;
+}
+
+/**
+ * camel_pstring_contains:
+ * @string: string to look up in the string pool
+ *
+ * Returns whether the @string exists in the string pool.
+ *
+ * The %NULL and empty strings are special cased to constant values.
+ *
+ * Returns: Whether the @string exists in the string pool
+ *
+ * Since: 3.22
+ **/
+gboolean
+camel_pstring_contains (const gchar *string)
+{
+ StringPoolNode static_node = { (gchar *) string, };
+ gboolean contains;
+
+ if (string == NULL)
+ return FALSE;
+
+ if (*string == '\0')
+ return FALSE;
+
+ g_mutex_lock (&string_pool_lock);
+
+ string_pool_init ();
+
+ contains = g_hash_table_contains (string_pool, &static_node);
+
+ g_mutex_unlock (&string_pool_lock);
+
+ return contains;
+}
+
+/**
+ * camel_pstring_strdup:
+ * @string: string to copy
+ *
+ * Create a new pooled string entry for @strings. A pooled string
+ * is a table where common strings are canonicalized. They are also
+ * reference counted and freed when no longer referenced.
+ *
+ * The %NULL and empty strings are special cased to constant values.
+ *
+ * Unreference the returned string with camel_pstring_free().
+ *
+ * Returns: a canonicalized copy of @string
+ **/
+const gchar *
+camel_pstring_strdup (const gchar *string)
+{
+ return camel_pstring_add ((gchar *) string, FALSE);
+}
+
+/**
+ * camel_pstring_free:
+ * @string: string to free
+ *
+ * Unreferences a pooled string. If the string's reference count drops to
+ * zero it will be deallocated. %NULL and the empty string are special cased.
+ **/
+void
+camel_pstring_free (const gchar *string)
+{
+ StringPoolNode static_node = { (gchar *) string, };
+ StringPoolNode *node;
+
+ if (string_pool == NULL)
+ return;
+
+ if (string == NULL || *string == '\0')
+ return;
+
+ g_mutex_lock (&string_pool_lock);
+
+ node = g_hash_table_lookup (string_pool, &static_node);
+
+ if (node == NULL) {
+ g_warning ("%s: String not in pool: %s", G_STRFUNC, string);
+ } else if (node->string != string) {
+ g_warning ("%s: String is not ours: %s", G_STRFUNC, string);
+ } else if (node->ref_count == 0) {
+ g_warning ("%s: Orphaned pool node: %s", G_STRFUNC, string);
+ } else {
+ node->ref_count--;
+ if (node->ref_count == 0)
+ g_hash_table_remove (string_pool, node);
+ }
+
+ g_mutex_unlock (&string_pool_lock);
+}
+
+/**
+ * camel_pstring_dump_stat:
+ *
+ * Dumps to stdout memory statistic about the string pool.
+ *
+ * Since: 3.6
+ **/
+void
+camel_pstring_dump_stat (void)
+{
+ g_mutex_lock (&string_pool_lock);
+
+ g_print (" String Pool Statistics: ");
+
+ if (string_pool == NULL) {
+ g_print ("Not used yet\n");
+ } else {
+ GHashTableIter iter;
+ gchar *format_size;
+ guint64 bytes = 0;
+ gpointer key;
+
+ g_hash_table_iter_init (&iter, string_pool);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ bytes += strlen (((StringPoolNode *) key)->string);
+
+ format_size = g_format_size_full (
+ bytes, G_FORMAT_SIZE_LONG_FORMAT);
+
+ g_print (
+ "Holds %d strings totaling %s\n",
+ g_hash_table_size (string_pool),
+ format_size);
+
+ g_free (format_size);
+ }
+
+ g_mutex_unlock (&string_pool_lock);
+}
diff --git a/src/camel/camel-string-utils.h b/src/camel/camel-string-utils.h
new file mode 100644
index 000000000..a66fcdf36
--- /dev/null
+++ b/src/camel/camel-string-utils.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_STRING_UTILS_H
+#define CAMEL_STRING_UTILS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gint camel_strcase_equal (gconstpointer a, gconstpointer b);
+guint camel_strcase_hash (gconstpointer v);
+
+gchar *camel_strstrcase (const gchar *haystack, const gchar *needle);
+
+const gchar *camel_strdown (gchar *str);
+
+const gchar *camel_pstring_add (gchar *string, gboolean own);
+const gchar *camel_pstring_strdup (const gchar *string);
+void camel_pstring_free (const gchar *string);
+const gchar * camel_pstring_peek (const gchar *string);
+gboolean camel_pstring_contains (const gchar *string);
+void camel_pstring_dump_stat (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_STRING_UTILS_H */
diff --git a/src/camel/camel-subscribable.c b/src/camel/camel-subscribable.c
new file mode 100644
index 000000000..3ec4f5863
--- /dev/null
+++ b/src/camel/camel-subscribable.c
@@ -0,0 +1,627 @@
+/*
+ * camel-subscribable.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-async-closure.h"
+#include "camel-debug.h"
+#include "camel-session.h"
+#include "camel-vtrash-folder.h"
+
+#include "camel-subscribable.h"
+
+typedef struct _AsyncContext AsyncContext;
+typedef struct _SignalClosure SignalClosure;
+
+struct _AsyncContext {
+ gchar *folder_name;
+};
+
+struct _SignalClosure {
+ GWeakRef subscribable;
+ CamelFolderInfo *folder_info;
+};
+
+enum {
+ FOLDER_SUBSCRIBED,
+ FOLDER_UNSUBSCRIBED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_INTERFACE (CamelSubscribable, camel_subscribable, CAMEL_TYPE_STORE)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+ g_free (async_context->folder_name);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static void
+signal_closure_free (SignalClosure *signal_closure)
+{
+ g_weak_ref_clear (&signal_closure->subscribable);
+
+ if (signal_closure->folder_info != NULL)
+ camel_folder_info_free (signal_closure->folder_info);
+
+ g_slice_free (SignalClosure, signal_closure);
+}
+
+static gboolean
+subscribable_emit_folder_subscribed_cb (gpointer user_data)
+{
+ SignalClosure *signal_closure = user_data;
+ CamelSubscribable *subscribable;
+
+ subscribable = g_weak_ref_get (&signal_closure->subscribable);
+
+ if (subscribable != NULL) {
+ g_signal_emit (
+ subscribable,
+ signals[FOLDER_SUBSCRIBED], 0,
+ signal_closure->folder_info);
+ g_object_unref (subscribable);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+subscribable_emit_folder_unsubscribed_cb (gpointer user_data)
+{
+ SignalClosure *signal_closure = user_data;
+ CamelSubscribable *subscribable;
+
+ subscribable = g_weak_ref_get (&signal_closure->subscribable);
+
+ if (subscribable != NULL) {
+ g_signal_emit (
+ subscribable,
+ signals[FOLDER_UNSUBSCRIBED], 0,
+ signal_closure->folder_info);
+ g_object_unref (subscribable);
+ }
+
+ return FALSE;
+}
+
+static void
+subscribable_delete_cached_folder (CamelStore *store,
+ const gchar *folder_name)
+{
+ CamelFolder *folder;
+ CamelVeeFolder *vfolder;
+
+ /* XXX Copied from camel-store.c. Should this be public? */
+
+ if (store->folders == NULL)
+ return;
+
+ folder = camel_object_bag_get (store->folders, folder_name);
+ if (folder == NULL)
+ return;
+
+ if (store->flags & CAMEL_STORE_VTRASH) {
+ folder_name = CAMEL_VTRASH_NAME;
+ vfolder = camel_object_bag_get (store->folders, folder_name);
+ if (vfolder != NULL) {
+ camel_vee_folder_remove_folder (vfolder, folder, NULL);
+ g_object_unref (vfolder);
+ }
+ }
+
+ if (store->flags & CAMEL_STORE_VJUNK) {
+ folder_name = CAMEL_VJUNK_NAME;
+ vfolder = camel_object_bag_get (store->folders, folder_name);
+ if (vfolder != NULL) {
+ camel_vee_folder_remove_folder (vfolder, folder, NULL);
+ g_object_unref (vfolder);
+ }
+ }
+
+ camel_folder_delete (folder);
+
+ camel_object_bag_remove (store->folders, folder);
+ g_object_unref (folder);
+}
+
+static void
+camel_subscribable_default_init (CamelSubscribableInterface *iface)
+{
+ signals[FOLDER_SUBSCRIBED] = g_signal_new (
+ "folder-subscribed",
+ G_OBJECT_CLASS_TYPE (iface),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (
+ CamelSubscribableInterface,
+ folder_subscribed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ signals[FOLDER_UNSUBSCRIBED] = g_signal_new (
+ "folder-unsubscribed",
+ G_OBJECT_CLASS_TYPE (iface),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (
+ CamelSubscribableInterface,
+ folder_unsubscribed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+}
+
+/**
+ * camel_subscribable_folder_is_subscribed:
+ * @subscribable: a #CamelSubscribable
+ * @folder_name: full path of the folder
+ *
+ * Find out if a folder has been subscribed to.
+ *
+ * Returns: %TRUE if the folder has been subscribed to or %FALSE otherwise
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_subscribable_folder_is_subscribed (CamelSubscribable *subscribable,
+ const gchar *folder_name)
+{
+ CamelSubscribableInterface *iface;
+
+ g_return_val_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable), FALSE);
+ g_return_val_if_fail (folder_name != NULL, FALSE);
+
+ iface = CAMEL_SUBSCRIBABLE_GET_INTERFACE (subscribable);
+ g_return_val_if_fail (iface->folder_is_subscribed != NULL, FALSE);
+
+ return iface->folder_is_subscribed (subscribable, folder_name);
+}
+
+/**
+ * camel_subscribable_subscribe_folder_sync:
+ * @subscribable: a #CamelSubscribable
+ * @folder_name: full path of the folder
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Subscribes to the folder described by @folder_name.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_subscribable_subscribe_folder_sync (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelAsyncClosure *closure;
+ GAsyncResult *result;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable), FALSE);
+ g_return_val_if_fail (folder_name != NULL, FALSE);
+
+ closure = camel_async_closure_new ();
+
+ camel_subscribable_subscribe_folder (
+ subscribable, folder_name,
+ G_PRIORITY_DEFAULT, cancellable,
+ camel_async_closure_callback, closure);
+
+ result = camel_async_closure_wait (closure);
+
+ success = camel_subscribable_subscribe_folder_finish (
+ subscribable, result, error);
+
+ camel_async_closure_free (closure);
+
+ return success;
+}
+
+/* Helper for camel_subscribable_subscribe_folder() */
+static void
+subscribable_subscribe_folder_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelSubscribable *subscribable;
+ CamelSubscribableInterface *iface;
+ const gchar *folder_name;
+ const gchar *message;
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ subscribable = CAMEL_SUBSCRIBABLE (source_object);
+ async_context = (AsyncContext *) task_data;
+
+ folder_name = async_context->folder_name;
+
+ iface = CAMEL_SUBSCRIBABLE_GET_INTERFACE (subscribable);
+ g_return_if_fail (iface->subscribe_folder_sync != NULL);
+
+ /* Need to establish a connection before subscribing. */
+ camel_service_connect_sync (
+ CAMEL_SERVICE (subscribable), cancellable, &local_error);
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ return;
+ }
+
+ message = _("Subscribing to folder '%s'");
+ camel_operation_push_message (cancellable, message, folder_name);
+
+ success = iface->subscribe_folder_sync (
+ subscribable, folder_name, cancellable, &local_error);
+ CAMEL_CHECK_LOCAL_GERROR (
+ subscribable, subscribe_folder_sync, success, local_error);
+
+ camel_operation_pop_message (cancellable);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_subscribable_subscribe_folder:
+ * @subscribable: a #CamelSubscribable
+ * @folder_name: full path of the folder
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously subscribes to the folder described by @folder_name.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_subscribable_subscribe_folder_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.2
+ **/
+void
+camel_subscribable_subscribe_folder (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CamelService *service;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable));
+ g_return_if_fail (folder_name != NULL);
+
+ service = CAMEL_SERVICE (subscribable);
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->folder_name = g_strdup (folder_name);
+
+ task = g_task_new (subscribable, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_subscribable_subscribe_folder);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ camel_service_queue_task (
+ service, task, subscribable_subscribe_folder_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_subscribable_subscribe_folder_finish:
+ * @subscribable: a #CamelSubscribable
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_subscribable_subscribe_folder().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_subscribable_subscribe_folder_finish (CamelSubscribable *subscribable,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, subscribable), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_subscribable_subscribe_folder), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_subscribable_unsubscribe_folder_sync:
+ * @subscribable: a #CamelSubscribable
+ * @folder_name: full path of the folder
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Unsubscribes from the folder described by @folder_name.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_subscribable_unsubscribe_folder_sync (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelAsyncClosure *closure;
+ GAsyncResult *result;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable), FALSE);
+ g_return_val_if_fail (folder_name != NULL, FALSE);
+
+ closure = camel_async_closure_new ();
+
+ camel_subscribable_unsubscribe_folder (
+ subscribable, folder_name,
+ G_PRIORITY_DEFAULT, cancellable,
+ camel_async_closure_callback, closure);
+
+ result = camel_async_closure_wait (closure);
+
+ success = camel_subscribable_unsubscribe_folder_finish (
+ subscribable, result, error);
+
+ camel_async_closure_free (closure);
+
+ return success;
+}
+
+/* Helper for camel_subscribable_unsubscribe_folder() */
+static void
+subscribable_unsubscribe_folder_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelSubscribable *subscribable;
+ CamelSubscribableInterface *iface;
+ const gchar *folder_name;
+ const gchar *message;
+ gboolean success;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ subscribable = CAMEL_SUBSCRIBABLE (source_object);
+ async_context = (AsyncContext *) task_data;
+
+ folder_name = async_context->folder_name;
+
+ iface = CAMEL_SUBSCRIBABLE_GET_INTERFACE (subscribable);
+ g_return_if_fail (iface->unsubscribe_folder_sync != NULL);
+
+ /* Need to establish a connection before unsubscribing. */
+ camel_service_connect_sync (
+ CAMEL_SERVICE (subscribable), cancellable, &local_error);
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ return;
+ }
+
+ message = _("Unsubscribing from folder '%s'");
+ camel_operation_push_message (cancellable, message, folder_name);
+
+ success = iface->unsubscribe_folder_sync (
+ subscribable, folder_name, cancellable, &local_error);
+ CAMEL_CHECK_LOCAL_GERROR (
+ subscribable, unsubscribe_folder_sync, success, local_error);
+
+ if (success)
+ subscribable_delete_cached_folder (
+ CAMEL_STORE (subscribable), folder_name);
+
+ camel_operation_pop_message (cancellable);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_subscribable_unsubscribe_folder:
+ * @subscribable: a #CamelSubscribable
+ * @folder_name: full path of the folder
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously unsubscribes from the folder described by @folder_name.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_subscribable_unsubscribe_folder_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.2
+ **/
+void
+camel_subscribable_unsubscribe_folder (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CamelService *service;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable));
+ g_return_if_fail (folder_name != NULL);
+
+ service = CAMEL_SERVICE (subscribable);
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->folder_name = g_strdup (folder_name);
+
+ task = g_task_new (subscribable, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_subscribable_unsubscribe_folder);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ camel_service_queue_task (
+ service, task, subscribable_unsubscribe_folder_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_subscribable_unsubscribe_folder_finish:
+ * @subscribable: a #CamelSubscribable
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_subscribable_unsubscribe_folder().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_subscribable_unsubscribe_folder_finish (CamelSubscribable *subscribable,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, subscribable), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_subscribable_unsubscribe_folder), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * camel_subscribable_folder_subscribed:
+ * @subscribable: a #CamelSubscribable
+ * @folder_info: information about the subscribed folder
+ *
+ * Emits the #CamelSubscribable::folder-subscribed signal from an idle source
+ * on the main loop. The idle source's priority is #G_PRIORITY_HIGH_IDLE.
+ *
+ * This function is only intended for Camel providers.
+ *
+ * Since: 3.2
+ **/
+void
+camel_subscribable_folder_subscribed (CamelSubscribable *subscribable,
+ CamelFolderInfo *folder_info)
+{
+ CamelService *service;
+ CamelSession *session;
+ SignalClosure *signal_closure;
+
+ g_return_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable));
+ g_return_if_fail (folder_info != NULL);
+
+ service = CAMEL_SERVICE (subscribable);
+ session = camel_service_ref_session (service);
+
+ if (!session)
+ return;
+
+ signal_closure = g_slice_new0 (SignalClosure);
+ g_weak_ref_init (&signal_closure->subscribable, subscribable);
+ signal_closure->folder_info = camel_folder_info_clone (folder_info);
+
+ /* Prioritize ahead of GTK+ redraws. */
+ camel_session_idle_add (
+ session, G_PRIORITY_HIGH_IDLE,
+ subscribable_emit_folder_subscribed_cb,
+ signal_closure,
+ (GDestroyNotify) signal_closure_free);
+
+ g_object_unref (session);
+}
+
+/**
+ * camel_subscribable_folder_unsubscribed:
+ * @subscribable: a #CamelSubscribable
+ * @folder_info: information about the unsubscribed folder
+ *
+ * Emits the #CamelSubscribable::folder-unsubscribed signal from an idle source
+ * on the main loop. The idle source's priority is #G_PRIORITY_HIGH_IDLE.
+ *
+ * This function is only intended for Camel providers.
+ *
+ * Since: 3.2
+ **/
+void
+camel_subscribable_folder_unsubscribed (CamelSubscribable *subscribable,
+ CamelFolderInfo *folder_info)
+{
+ CamelService *service;
+ CamelSession *session;
+ SignalClosure *signal_closure;
+
+ g_return_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable));
+ g_return_if_fail (folder_info != NULL);
+
+ service = CAMEL_SERVICE (subscribable);
+ session = camel_service_ref_session (service);
+
+ if (!session)
+ return;
+
+ signal_closure = g_slice_new0 (SignalClosure);
+ g_weak_ref_init (&signal_closure->subscribable, subscribable);
+ signal_closure->folder_info = camel_folder_info_clone (folder_info);
+
+ /* Prioritize ahead of GTK+ redraws. */
+ camel_session_idle_add (
+ session, G_PRIORITY_HIGH_IDLE,
+ subscribable_emit_folder_unsubscribed_cb,
+ signal_closure,
+ (GDestroyNotify) signal_closure_free);
+
+ g_object_unref (session);
+}
+
diff --git a/src/camel/camel-subscribable.h b/src/camel/camel-subscribable.h
new file mode 100644
index 000000000..6d5349222
--- /dev/null
+++ b/src/camel/camel-subscribable.h
@@ -0,0 +1,134 @@
+/*
+ * camel-subscribable.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_SUBSCRIBABLE_H
+#define CAMEL_SUBSCRIBABLE_H
+
+#include <camel/camel-store.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SUBSCRIBABLE \
+ (camel_subscribable_get_type ())
+#define CAMEL_SUBSCRIBABLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SUBSCRIBABLE, CamelSubscribable))
+#define CAMEL_SUBSCRIBABLE_INTERFACE(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SUBSCRIBABLE, CamelSubscribableInterface))
+#define CAMEL_IS_SUBSCRIBABLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SUBSCRIBABLE))
+#define CAMEL_IS_SUBSCRIBABLE_INTERFACE(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SUBSCRIBABLE))
+#define CAMEL_SUBSCRIBABLE_GET_INTERFACE(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE \
+ ((obj), CAMEL_TYPE_SUBSCRIBABLE, CamelSubscribableInterface))
+
+G_BEGIN_DECLS
+
+/**
+ * CamelSubscribable:
+ *
+ * Since: 3.2
+ **/
+typedef struct _CamelSubscribable CamelSubscribable;
+typedef struct _CamelSubscribableInterface CamelSubscribableInterface;
+
+struct _CamelSubscribableInterface {
+ GTypeInterface parent_interface;
+
+ /* Non-Blocking Methods */
+ gboolean (*folder_is_subscribed)
+ (CamelSubscribable *subscribable,
+ const gchar *folder_name);
+
+ /* Synchronous I/O Methods */
+ gboolean (*subscribe_folder_sync)
+ (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (*unsubscribe_folder_sync)
+ (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* Reserved slots for methods. */
+ gpointer reserved_for_methods[4];
+
+ /* Signals */
+ void (*folder_subscribed)
+ (CamelSubscribable *subscribable,
+ CamelFolderInfo *folder_info);
+ void (*folder_unsubscribed)
+ (CamelSubscribable *subscribable,
+ CamelFolderInfo *folder_info);
+};
+
+GType camel_subscribable_get_type
+ (void) G_GNUC_CONST;
+gboolean camel_subscribable_folder_is_subscribed
+ (CamelSubscribable *subscribable,
+ const gchar *folder_name);
+gboolean camel_subscribable_subscribe_folder_sync
+ (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error);
+void camel_subscribable_subscribe_folder
+ (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_subscribable_subscribe_folder_finish
+ (CamelSubscribable *subscribable,
+ GAsyncResult *result,
+ GError **error);
+gboolean camel_subscribable_unsubscribe_folder_sync
+ (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error);
+void camel_subscribable_unsubscribe_folder
+ (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_subscribable_unsubscribe_folder_finish
+ (CamelSubscribable *subscribable,
+ GAsyncResult *result,
+ GError **error);
+void camel_subscribable_folder_subscribed
+ (CamelSubscribable *subscribable,
+ CamelFolderInfo *folder_info);
+void camel_subscribable_folder_unsubscribed
+ (CamelSubscribable *subscribable,
+ CamelFolderInfo *folder_info);
+
+G_END_DECLS
+
+#endif /* CAMEL_SUBSCRIBABLE_H */
diff --git a/src/camel/camel-text-index.c b/src/camel/camel-text-index.c
new file mode 100644
index 000000000..780775383
--- /dev/null
+++ b/src/camel/camel-text-index.c
@@ -0,0 +1,2004 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gstdio.h>
+
+#include "camel-block-file.h"
+#include "camel-mempool.h"
+#include "camel-object.h"
+#include "camel-partition-table.h"
+#include "camel-text-index.h"
+
+#define w(x)
+#define io(x)
+#define d(x) /*(printf ("%s (%d):%s: ", __FILE__, __LINE__, __PRETTY_FUNCTION__),(x))*/
+
+/* cursor debug */
+#define c(x)
+
+#define CAMEL_TEXT_INDEX_MAX_WORDLEN (36)
+
+#define CAMEL_TEXT_INDEX_LOCK(kf, lock) \
+ (g_rec_mutex_lock (&((CamelTextIndex *) kf)->priv->lock))
+#define CAMEL_TEXT_INDEX_UNLOCK(kf, lock) \
+ (g_rec_mutex_unlock (&((CamelTextIndex *) kf)->priv->lock))
+
+static gint text_index_compress_nosync (CamelIndex *idx);
+
+/* ********************************************************************** */
+
+#define CAMEL_TEXT_INDEX_NAME_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_TEXT_INDEX_NAME, CamelTextIndexNamePrivate))
+
+struct _CamelTextIndexNamePrivate {
+ GString *buffer;
+ camel_key_t nameid;
+ CamelMemPool *pool;
+};
+
+CamelTextIndexName *camel_text_index_name_new (CamelTextIndex *idx, const gchar *name, camel_key_t nameid);
+
+/* ****************************** */
+
+#define CAMEL_TEXT_INDEX_CURSOR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_TEXT_INDEX_CURSOR, CamelTextIndexCursorPrivate))
+
+struct _CamelTextIndexCursorPrivate {
+ camel_block_t first;
+ camel_block_t next;
+
+ gint record_index;
+
+ gsize record_count;
+ camel_key_t *records;
+
+ gchar *current;
+};
+
+CamelTextIndexCursor *camel_text_index_cursor_new (CamelTextIndex *idx, camel_block_t data);
+
+/* ****************************** */
+
+#define CAMEL_TEXT_INDEX_KEY_CURSOR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_TEXT_INDEX_KEY_CURSOR, CamelTextIndexKeyCursorPrivate))
+
+struct _CamelTextIndexKeyCursorPrivate {
+ CamelKeyTable *table;
+
+ camel_key_t keyid;
+ guint flags;
+ camel_block_t data;
+ gchar *current;
+};
+
+CamelTextIndexKeyCursor *camel_text_index_key_cursor_new (CamelTextIndex *idx, CamelKeyTable *table);
+
+/* ********************************************************************** */
+
+#define CAMEL_TEXT_INDEX_VERSION "TEXT.000"
+#define CAMEL_TEXT_INDEX_KEY_VERSION "KEYS.000"
+
+#define CAMEL_TEXT_INDEX_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_TEXT_INDEX, CamelTextIndexPrivate))
+
+struct _CamelTextIndexPrivate {
+ CamelBlockFile *blocks;
+ CamelKeyFile *links;
+
+ CamelKeyTable *word_index;
+ CamelPartitionTable *word_hash;
+
+ CamelKeyTable *name_index;
+ CamelPartitionTable *name_hash;
+
+ /* Cache of words to write */
+ guint word_cache_limit;
+ GQueue word_cache;
+ GHashTable *words;
+ GRecMutex lock;
+};
+
+/* Root block of text index */
+struct _CamelTextIndexRoot {
+ struct _CamelBlockRoot root;
+
+ /* FIXME: the index root could contain a pointer to the hash root */
+ camel_block_t word_index_root; /* a keyindex containing the keyid -> word mapping */
+ camel_block_t word_hash_root; /* a partitionindex containing word -> keyid mapping */
+
+ camel_block_t name_index_root; /* same, for names */
+ camel_block_t name_hash_root;
+
+ guint32 words; /* total words */
+ guint32 names; /* total names */
+ guint32 deleted; /* deleted names */
+ guint32 keys; /* total key 'chunks' written, used with deleted to determine fragmentation */
+};
+
+struct _CamelTextIndexWord {
+ camel_block_t data; /* where the data starts */
+ camel_key_t wordid;
+ gchar *word;
+ guint used;
+ camel_key_t names[32];
+};
+
+/* ********************************************************************** */
+/* CamelTextIndex */
+/* ********************************************************************** */
+
+G_DEFINE_TYPE (CamelTextIndex, camel_text_index, CAMEL_TYPE_INDEX)
+
+static void
+text_index_dispose (GObject *object)
+{
+ CamelTextIndexPrivate *priv;
+
+ priv = CAMEL_TEXT_INDEX_GET_PRIVATE (object);
+
+ /* Only run this the first time. */
+ if (priv->word_index != NULL)
+ camel_index_sync (CAMEL_INDEX (object));
+
+ if (priv->word_index != NULL) {
+ g_object_unref (priv->word_index);
+ priv->word_index = NULL;
+ }
+
+ if (priv->word_hash != NULL) {
+ g_object_unref (priv->word_hash);
+ priv->word_hash = NULL;
+ }
+
+ if (priv->name_index != NULL) {
+ g_object_unref (priv->name_index);
+ priv->name_index = NULL;
+ }
+
+ if (priv->name_hash != NULL) {
+ g_object_unref (priv->name_hash);
+ priv->name_hash = NULL;
+ }
+
+ if (priv->blocks != NULL) {
+ g_object_unref (priv->blocks);
+ priv->blocks = NULL;
+ }
+
+ if (priv->links != NULL) {
+ g_object_unref (priv->links);
+ priv->links = NULL;
+ }
+
+ /* Chain up to parent's dispose () method. */
+ G_OBJECT_CLASS (camel_text_index_parent_class)->dispose (object);
+}
+
+static void
+text_index_finalize (GObject *object)
+{
+ CamelTextIndexPrivate *priv;
+
+ priv = CAMEL_TEXT_INDEX_GET_PRIVATE (object);
+
+ g_warn_if_fail (g_queue_is_empty (&priv->word_cache));
+ g_warn_if_fail (g_hash_table_size (priv->words) == 0);
+
+ g_hash_table_destroy (priv->words);
+
+ g_rec_mutex_clear (&priv->lock);
+
+ /* Chain up to parent's finalize () method. */
+ G_OBJECT_CLASS (camel_text_index_parent_class)->finalize (object);
+}
+
+/* call locked */
+static void
+text_index_add_name_to_word (CamelIndex *idx,
+ const gchar *word,
+ camel_key_t nameid)
+{
+ struct _CamelTextIndexWord *w;
+ CamelTextIndexPrivate *p = CAMEL_TEXT_INDEX (idx)->priv;
+ camel_key_t wordid;
+ camel_block_t data;
+ struct _CamelTextIndexRoot *rb = (struct _CamelTextIndexRoot *) p->blocks->root;
+
+ w = g_hash_table_lookup (p->words, word);
+ if (w == NULL) {
+ GQueue trash = G_QUEUE_INIT;
+ GList *link;
+ guint length;
+
+ wordid = camel_partition_table_lookup (p->word_hash, word);
+ if (wordid == 0) {
+ data = 0;
+ wordid = camel_key_table_add (p->word_index, word, 0, 0);
+ if (wordid == 0) {
+ g_warning (
+ "Could not create key entry for word '%s': %s\n",
+ word, g_strerror (errno));
+ return;
+ }
+ if (camel_partition_table_add (p->word_hash, word, wordid) == -1) {
+ g_warning (
+ "Could not create hash entry for word '%s': %s\n",
+ word, g_strerror (errno));
+ return;
+ }
+ rb->words++;
+ camel_block_file_touch_block (p->blocks, p->blocks->root_block);
+ } else {
+ data = camel_key_table_lookup (p->word_index, wordid, NULL, NULL);
+ if (data == 0) {
+ g_warning (
+ "Could not find key entry for word '%s': %s\n",
+ word, g_strerror (errno));
+ return;
+ }
+ }
+
+ w = g_malloc0 (sizeof (*w));
+ w->word = g_strdup (word);
+ w->wordid = wordid;
+ w->used = 1;
+ w->data = data;
+
+ w->names[0] = nameid;
+ g_hash_table_insert (p->words, w->word, w);
+ g_queue_push_head (&p->word_cache, w);
+
+ length = p->word_cache.length;
+ link = g_queue_peek_tail_link (&p->word_cache);
+
+ while (link != NULL && length > p->word_cache_limit) {
+ struct _CamelTextIndexWord *ww = link->data;
+
+ io (printf ("writing key file entry '%s' [%x]\n", ww->word, ww->data));
+ if (camel_key_file_write (p->links, &ww->data, ww->used, ww->names) != -1) {
+ io (printf (" new data [%x]\n", ww->data));
+ rb->keys++;
+ camel_block_file_touch_block (p->blocks, p->blocks->root_block);
+ /* if this call fails - we still point to the old data - not fatal */
+ camel_key_table_set_data (
+ p->word_index, ww->wordid, ww->data);
+ g_hash_table_remove (p->words, ww->word);
+ g_queue_push_tail (&trash, link);
+ link->data = NULL;
+ g_free (ww->word);
+ g_free (ww);
+ length--;
+ }
+
+ link = g_list_previous (link);
+ }
+
+ /* Remove deleted words from the cache. */
+ while ((link = g_queue_pop_head (&trash)) != NULL)
+ g_queue_delete_link (&p->word_cache, link);
+
+ } else {
+ g_queue_remove (&p->word_cache, w);
+ g_queue_push_head (&p->word_cache, w);
+ w->names[w->used] = nameid;
+ w->used++;
+ if (w->used == G_N_ELEMENTS (w->names)) {
+ io (printf ("writing key file entry '%s' [%x]\n", w->word, w->data));
+ if (camel_key_file_write (p->links, &w->data, w->used, w->names) != -1) {
+ rb->keys++;
+ camel_block_file_touch_block (p->blocks, p->blocks->root_block);
+ /* if this call fails - we still point to the old data - not fatal */
+ camel_key_table_set_data (
+ p->word_index, w->wordid, w->data);
+ }
+ /* FIXME: what to on error? lost data? */
+ w->used = 0;
+ }
+ }
+}
+
+static gint
+text_index_sync (CamelIndex *idx)
+{
+ CamelTextIndexPrivate *p = CAMEL_TEXT_INDEX_GET_PRIVATE (idx);
+ struct _CamelTextIndexWord *ww;
+ struct _CamelTextIndexRoot *rb;
+ gint ret = 0, wfrag, nfrag;
+
+ d (printf ("sync: blocks = %p\n", p->blocks));
+
+ if (p->blocks == NULL || p->links == NULL
+ || p->word_index == NULL || p->word_hash == NULL
+ || p->name_index == NULL || p->name_hash == NULL)
+ return 0;
+
+ rb = (struct _CamelTextIndexRoot *) p->blocks->root;
+
+ /* sync/flush word cache */
+
+ CAMEL_TEXT_INDEX_LOCK (idx, lock);
+
+ /* we sync, bump down the cache limits since we dont need them for reading */
+ p->blocks->block_cache_limit = 128;
+ /* this doesn't really need to be dropped, its only used in updates anyway */
+ p->word_cache_limit = 1024;
+
+ while ((ww = g_queue_pop_head (&p->word_cache))) {
+ if (ww->used > 0) {
+ io (printf ("writing key file entry '%s' [%x]\n", ww->word, ww->data));
+ if (camel_key_file_write (p->links, &ww->data, ww->used, ww->names) != -1) {
+ io (printf (" new data [%x]\n", ww->data));
+ rb->keys++;
+ camel_block_file_touch_block (p->blocks, p->blocks->root_block);
+ camel_key_table_set_data (
+ p->word_index, ww->wordid, ww->data);
+ } else {
+ ret = -1;
+ }
+ ww->used = 0;
+ }
+ g_hash_table_remove (p->words, ww->word);
+ g_free (ww->word);
+ g_free (ww);
+ }
+
+ if (camel_key_table_sync (p->word_index) == -1
+ || camel_key_table_sync (p->name_index) == -1
+ || camel_partition_table_sync (p->word_hash) == -1
+ || camel_partition_table_sync (p->name_hash) == -1)
+ ret = -1;
+
+ /* only do the frag/compress check if we did some new writes on this index */
+ wfrag = rb->words ? (((rb->keys - rb->words) * 100)/ rb->words) : 0;
+ nfrag = rb->names ? ((rb->deleted * 100) / rb->names) : 0;
+ d (printf (" words = %d, keys = %d\n", rb->words, rb->keys));
+
+ if (ret == 0) {
+ if (wfrag > 30 || nfrag > 20)
+ ret = text_index_compress_nosync (idx);
+ }
+
+ ret = ret == -1 ? ret : camel_block_file_sync (p->blocks);
+
+ CAMEL_TEXT_INDEX_UNLOCK (idx, lock);
+
+ return ret;
+}
+
+static void
+tmp_name (const gchar *in,
+ gchar *o,
+ gsize o_len)
+{
+ gchar *s;
+
+ s = strrchr (in, '/');
+ if (s) {
+ memcpy (o, in, s - in + 1);
+ memcpy (o + (s - in + 1), ".#", 2);
+ strcpy (o + (s - in + 3), s + 1);
+ } else {
+ g_snprintf (o, o_len, ".#%s", in);
+ }
+}
+
+static gint
+text_index_compress (CamelIndex *idx)
+{
+ gint ret;
+
+ CAMEL_TEXT_INDEX_LOCK (idx, lock);
+
+ ret = camel_index_sync (idx);
+ if (ret != -1)
+ ret = text_index_compress_nosync (idx);
+
+ CAMEL_TEXT_INDEX_UNLOCK (idx, lock);
+
+ return ret;
+}
+
+/* Attempt to recover index space by compressing the indices */
+static gint
+text_index_compress_nosync (CamelIndex *idx)
+{
+ CamelTextIndex *newidx;
+ CamelTextIndexPrivate *newp, *oldp;
+ camel_key_t oldkeyid, newkeyid;
+ GHashTable *remap;
+ guint deleted;
+ camel_block_t data, newdata;
+ gint i, ret = -1;
+ gchar *name = NULL;
+ guint flags;
+ gchar *newpath, *savepath, *oldpath;
+ gsize count, newcount;
+ camel_key_t *records, newrecords[256];
+ struct _CamelTextIndexRoot *rb;
+
+ i = strlen (idx->path) + 16;
+ oldpath = alloca (i);
+ newpath = alloca (i);
+ savepath = alloca (i);
+
+ g_strlcpy (oldpath, idx->path, i);
+ oldpath[strlen (oldpath) - strlen (".index")] = 0;
+
+ tmp_name (oldpath, newpath, i);
+ g_snprintf (savepath, i, "%s~", oldpath);
+
+ d (printf ("Old index: %s\n", idx->path));
+ d (printf ("Old path: %s\n", oldpath));
+ d (printf ("New: %s\n", newpath));
+ d (printf ("Save: %s\n", savepath));
+
+ newidx = camel_text_index_new (newpath, O_RDWR | O_CREAT);
+ if (newidx == NULL)
+ return -1;
+
+ newp = CAMEL_TEXT_INDEX_GET_PRIVATE (newidx);
+ oldp = CAMEL_TEXT_INDEX_GET_PRIVATE (idx);
+
+ CAMEL_TEXT_INDEX_LOCK (idx, lock);
+
+ rb = (struct _CamelTextIndexRoot *) newp->blocks->root;
+
+ rb->words = 0;
+ rb->names = 0;
+ rb->deleted = 0;
+ rb->keys = 0;
+
+ /* Process:
+ * For each name we still have:
+ * Add it to the new index & setup remap table
+ *
+ * For each word:
+ * Copy word's data to a new file
+ * Add new word to index (*) (can we just copy blocks?) */
+
+ /* Copy undeleted names to new index file, creating new indices */
+ io (printf ("Copying undeleted names to new file\n"));
+ remap = g_hash_table_new (NULL, NULL);
+ oldkeyid = 0;
+ deleted = 0;
+ while ((oldkeyid = camel_key_table_next (oldp->name_index, oldkeyid, &name, &flags, &data))) {
+ if ((flags&1) == 0) {
+ io (printf ("copying name '%s'\n", name));
+ newkeyid = camel_key_table_add (
+ newp->name_index, name, data, flags);
+ if (newkeyid == 0)
+ goto fail;
+ rb->names++;
+ camel_partition_table_add (
+ newp->name_hash, name, newkeyid);
+ g_hash_table_insert (remap, GINT_TO_POINTER (oldkeyid), GINT_TO_POINTER (newkeyid));
+ } else {
+ io (printf ("deleted name '%s'\n", name));
+ }
+ g_free (name);
+ name = NULL;
+ deleted |= flags;
+ }
+
+ /* Copy word data across, remapping/deleting and create new index for it */
+ /* We re-block the data into 256 entry lots while we're at it, since we only
+ * have to do 1 at a time and its cheap */
+ oldkeyid = 0;
+ while ((oldkeyid = camel_key_table_next (oldp->word_index, oldkeyid, &name, &flags, &data))) {
+ io (printf ("copying word '%s'\n", name));
+ newdata = 0;
+ newcount = 0;
+ if (data) {
+ rb->words++;
+ rb->keys++;
+ }
+ while (data) {
+ if (camel_key_file_read (oldp->links, &data, &count, &records) == -1) {
+ io (printf ("could not read from old keys at %d for word '%s'\n", (gint) data, name));
+ goto fail;
+ }
+ for (i = 0; i < count; i++) {
+ newkeyid = (camel_key_t) GPOINTER_TO_INT (g_hash_table_lookup (remap, GINT_TO_POINTER (records[i])));
+ if (newkeyid) {
+ newrecords[newcount++] = newkeyid;
+ if (newcount == G_N_ELEMENTS (newrecords)) {
+ if (camel_key_file_write (newp->links, &newdata, newcount, newrecords) == -1) {
+ g_free (records);
+ goto fail;
+ }
+ newcount = 0;
+ }
+ }
+ }
+ g_free (records);
+ }
+
+ if (newcount > 0) {
+ if (camel_key_file_write (newp->links, &newdata, newcount, newrecords) == -1)
+ goto fail;
+ }
+
+ if (newdata != 0) {
+ newkeyid = camel_key_table_add (
+ newp->word_index, name, newdata, flags);
+ if (newkeyid == 0)
+ goto fail;
+ camel_partition_table_add (
+ newp->word_hash, name, newkeyid);
+ }
+ g_free (name);
+ name = NULL;
+ }
+
+ camel_block_file_touch_block (newp->blocks, newp->blocks->root_block);
+
+ if (camel_index_sync (CAMEL_INDEX (newidx)) == -1)
+ goto fail;
+
+ /* Rename underlying files to match */
+ ret = camel_index_rename (idx, savepath);
+ if (ret == -1)
+ goto fail;
+
+ /* If this fails, we'll pick up something during restart? */
+ ret = camel_index_rename ((CamelIndex *) newidx, oldpath);
+
+#define myswap(a, b) { gpointer tmp = a; a = b; b = tmp; }
+ /* Poke the private data across to the new object */
+ /* And change the fd's over, etc? */
+ /* Yes: This is a hack */
+ myswap (newp->blocks, oldp->blocks);
+ myswap (newp->links, oldp->links);
+ myswap (newp->word_index, oldp->word_index);
+ myswap (newp->word_hash, oldp->word_hash);
+ myswap (newp->name_index, oldp->name_index);
+ myswap (newp->name_hash, oldp->name_hash);
+ myswap (((CamelIndex *) newidx)->path, ((CamelIndex *) idx)->path);
+#undef myswap
+
+ ret = 0;
+fail:
+ CAMEL_TEXT_INDEX_UNLOCK (idx, lock);
+
+ camel_index_delete ((CamelIndex *) newidx);
+
+ g_object_unref (newidx);
+ g_free (name);
+ g_hash_table_destroy (remap);
+
+ /* clean up temp files always */
+ g_snprintf (savepath, i, "%s~.index", oldpath);
+ g_unlink (savepath);
+ g_snprintf (newpath, i, "%s.data", savepath);
+ g_unlink (newpath);
+
+ return ret;
+}
+
+static gint
+text_index_delete (CamelIndex *idx)
+{
+ CamelTextIndexPrivate *p = CAMEL_TEXT_INDEX_GET_PRIVATE (idx);
+ gint ret = 0;
+
+ if (camel_block_file_delete (p->blocks) == -1)
+ ret = -1;
+ if (camel_key_file_delete (p->links) == -1)
+ ret = -1;
+
+ return ret;
+}
+
+static gint
+text_index_rename (CamelIndex *idx,
+ const gchar *path)
+{
+ CamelTextIndexPrivate *p = CAMEL_TEXT_INDEX_GET_PRIVATE (idx);
+ gchar *newlink, *newblock;
+ gsize newlink_len, newblock_len;
+ gint err, ret;
+
+ CAMEL_TEXT_INDEX_LOCK (idx, lock);
+
+ newblock_len = strlen (path) + 8;
+ newblock = alloca (newblock_len);
+ g_snprintf (newblock, newblock_len, "%s.index", path);
+ ret = camel_block_file_rename (p->blocks, newblock);
+ if (ret == -1) {
+ CAMEL_TEXT_INDEX_UNLOCK (idx, lock);
+ return -1;
+ }
+
+ newlink_len = strlen (path) + 16;
+ newlink = alloca (newlink_len);
+ g_snprintf (newlink, newlink_len, "%s.index.data", path);
+ ret = camel_key_file_rename (p->links, newlink);
+ if (ret == -1) {
+ err = errno;
+ camel_block_file_rename (p->blocks, idx->path);
+ CAMEL_TEXT_INDEX_UNLOCK (idx, lock);
+ errno = err;
+ return -1;
+ }
+
+ g_free (idx->path);
+ idx->path = g_strdup (newblock);
+
+ CAMEL_TEXT_INDEX_UNLOCK (idx, lock);
+
+ return 0;
+}
+
+static gint
+text_index_has_name (CamelIndex *idx,
+ const gchar *name)
+{
+ CamelTextIndexPrivate *p = CAMEL_TEXT_INDEX_GET_PRIVATE (idx);
+
+ return camel_partition_table_lookup (p->name_hash, name) != 0;
+}
+
+static CamelIndexName *
+text_index_add_name (CamelIndex *idx,
+ const gchar *name)
+{
+ CamelTextIndexPrivate *p = CAMEL_TEXT_INDEX_GET_PRIVATE (idx);
+ camel_key_t keyid;
+ CamelIndexName *idn;
+ struct _CamelTextIndexRoot *rb = (struct _CamelTextIndexRoot *) p->blocks->root;
+
+ CAMEL_TEXT_INDEX_LOCK (idx, lock);
+
+ /* if we're adding words, up the cache limits a lot */
+ if (p->word_cache_limit < 8192) {
+ p->blocks->block_cache_limit = 1024;
+ p->word_cache_limit = 8192;
+ }
+
+ /* If we have it already replace it */
+ keyid = camel_partition_table_lookup (p->name_hash, name);
+ if (keyid != 0) {
+ /* TODO: We could just update the partition table's
+ * key pointer rather than having to delete it */
+ rb->deleted++;
+ camel_key_table_set_flags (p->name_index, keyid, 1, 1);
+ camel_partition_table_remove (p->name_hash, name);
+ }
+
+ keyid = camel_key_table_add (p->name_index, name, 0, 0);
+ if (keyid != 0) {
+ camel_partition_table_add (p->name_hash, name, keyid);
+ rb->names++;
+ }
+
+ camel_block_file_touch_block (p->blocks, p->blocks->root_block);
+
+ /* TODO: if keyid == 0, we had a failure, we should somehow flag that, but for
+ * now just return a valid object but discard its results, see text_index_write_name */
+
+ CAMEL_TEXT_INDEX_UNLOCK (idx, lock);
+
+ idn = (CamelIndexName *) camel_text_index_name_new ((CamelTextIndex *) idx, name, keyid);
+
+ return idn;
+}
+
+/* call locked */
+static void
+hash_write_word (gchar *word,
+ gpointer data,
+ CamelIndexName *idn)
+{
+ CamelTextIndexName *tin = (CamelTextIndexName *) idn;
+
+ text_index_add_name_to_word (idn->index, word, tin->priv->nameid);
+}
+
+static gint
+text_index_write_name (CamelIndex *idx,
+ CamelIndexName *idn)
+{
+ /* force 'flush' of any outstanding data */
+ camel_index_name_add_buffer (idn, NULL, 0);
+
+ /* see text_index_add_name for when this can be 0 */
+ if (((CamelTextIndexName *) idn)->priv->nameid != 0) {
+ CAMEL_TEXT_INDEX_LOCK (idx, lock);
+
+ g_hash_table_foreach (idn->words, (GHFunc) hash_write_word, idn);
+
+ CAMEL_TEXT_INDEX_UNLOCK (idx, lock);
+ }
+
+ return 0;
+}
+
+static CamelIndexCursor *
+text_index_find_name (CamelIndex *idx,
+ const gchar *name)
+{
+ /* what was this for, umm */
+ return NULL;
+}
+
+static void
+text_index_delete_name (CamelIndex *idx,
+ const gchar *name)
+{
+ CamelTextIndexPrivate *p = CAMEL_TEXT_INDEX_GET_PRIVATE (idx);
+ camel_key_t keyid;
+ struct _CamelTextIndexRoot *rb = (struct _CamelTextIndexRoot *) p->blocks->root;
+
+ d (printf ("Delete name: %s\n", name));
+
+ /* probably doesn't really need locking, but oh well */
+ CAMEL_TEXT_INDEX_LOCK (idx, lock);
+
+ /* We just mark the key deleted, and remove it from the hash table */
+ keyid = camel_partition_table_lookup (p->name_hash, name);
+ if (keyid != 0) {
+ rb->deleted++;
+ camel_block_file_touch_block (p->blocks, p->blocks->root_block);
+ camel_key_table_set_flags (p->name_index, keyid, 1, 1);
+ camel_partition_table_remove (p->name_hash, name);
+ }
+
+ CAMEL_TEXT_INDEX_UNLOCK (idx, lock);
+}
+
+static CamelIndexCursor *
+text_index_find (CamelIndex *idx,
+ const gchar *word)
+{
+ CamelTextIndexPrivate *p = CAMEL_TEXT_INDEX_GET_PRIVATE (idx);
+ camel_key_t keyid;
+ camel_block_t data = 0;
+ guint flags;
+ CamelIndexCursor *idc;
+
+ CAMEL_TEXT_INDEX_LOCK (idx, lock);
+
+ keyid = camel_partition_table_lookup (p->word_hash, word);
+ if (keyid != 0) {
+ data = camel_key_table_lookup (
+ p->word_index, keyid, NULL, &flags);
+ if (flags & 1)
+ data = 0;
+ }
+
+ CAMEL_TEXT_INDEX_UNLOCK (idx, lock);
+
+ idc = (CamelIndexCursor *) camel_text_index_cursor_new ((CamelTextIndex *) idx, data);
+
+ return idc;
+}
+
+static CamelIndexCursor *
+text_index_words (CamelIndex *idx)
+{
+ CamelTextIndexPrivate *p = CAMEL_TEXT_INDEX_GET_PRIVATE (idx);
+
+ return (CamelIndexCursor *) camel_text_index_key_cursor_new ((CamelTextIndex *) idx, p->word_index);
+}
+
+static void
+camel_text_index_class_init (CamelTextIndexClass *class)
+{
+ GObjectClass *object_class;
+ CamelIndexClass *index_class;
+
+ g_type_class_add_private (class, sizeof (CamelTextIndexPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = text_index_dispose;
+ object_class->finalize = text_index_finalize;
+
+ index_class = CAMEL_INDEX_CLASS (class);
+ index_class->sync = text_index_sync;
+ index_class->compress = text_index_compress;
+ index_class->delete_ = text_index_delete;
+ index_class->rename = text_index_rename;
+ index_class->has_name = text_index_has_name;
+ index_class->add_name = text_index_add_name;
+ index_class->write_name = text_index_write_name;
+ index_class->find_name = text_index_find_name;
+ index_class->delete_name = text_index_delete_name;
+ index_class->find = text_index_find;
+ index_class->words = text_index_words;
+}
+
+static void
+camel_text_index_init (CamelTextIndex *text_index)
+{
+ text_index->priv = CAMEL_TEXT_INDEX_GET_PRIVATE (text_index);
+
+ g_queue_init (&text_index->priv->word_cache);
+ text_index->priv->words = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* This cache size and the block cache size have been tuned for
+ * about the best with moderate memory usage. Doubling the memory
+ * usage barely affects performance. */
+ text_index->priv->word_cache_limit = 4096; /* 1024 = 128K */
+
+ g_rec_mutex_init (&text_index->priv->lock);
+}
+
+static gchar *
+text_index_normalize (CamelIndex *idx,
+ const gchar *in,
+ gpointer data)
+{
+ gchar *word;
+
+ /* Sigh, this is really expensive */
+ /*g_utf8_normalize (in, strlen (in), G_NORMALIZE_ALL);*/
+ word = g_utf8_strdown (in, -1);
+
+ return word;
+}
+
+CamelTextIndex *
+camel_text_index_new (const gchar *path,
+ gint flags)
+{
+ CamelTextIndex *idx = g_object_new (CAMEL_TYPE_TEXT_INDEX, NULL);
+ CamelTextIndexPrivate *p = CAMEL_TEXT_INDEX_GET_PRIVATE (idx);
+ struct _CamelTextIndexRoot *rb;
+ gchar *link;
+ gsize link_len;
+ CamelBlock *bl;
+
+ camel_index_construct ((CamelIndex *) idx, path, flags);
+ camel_index_set_normalize ((CamelIndex *) idx, text_index_normalize, NULL);
+
+ p->blocks = camel_block_file_new (
+ idx->parent.path, flags, CAMEL_TEXT_INDEX_VERSION, CAMEL_BLOCK_SIZE);
+ if (p->blocks == NULL)
+ goto fail;
+
+ link_len = strlen (idx->parent.path) + 7;
+ link = alloca (link_len);
+ g_snprintf (link, link_len, "%s.data", idx->parent.path);
+ p->links = camel_key_file_new (link, flags, CAMEL_TEXT_INDEX_KEY_VERSION);
+
+ if (p->links == NULL)
+ goto fail;
+
+ rb = (struct _CamelTextIndexRoot *) p->blocks->root;
+
+ if (rb->word_index_root == 0) {
+ bl = camel_block_file_new_block (p->blocks);
+
+ if (bl == NULL)
+ goto fail;
+
+ rb->word_index_root = bl->id;
+ camel_block_file_unref_block (p->blocks, bl);
+ camel_block_file_touch_block (p->blocks, p->blocks->root_block);
+ }
+
+ if (rb->word_hash_root == 0) {
+ bl = camel_block_file_new_block (p->blocks);
+
+ if (bl == NULL)
+ goto fail;
+
+ rb->word_hash_root = bl->id;
+ camel_block_file_unref_block (p->blocks, bl);
+ camel_block_file_touch_block (p->blocks, p->blocks->root_block);
+ }
+
+ if (rb->name_index_root == 0) {
+ bl = camel_block_file_new_block (p->blocks);
+
+ if (bl == NULL)
+ goto fail;
+
+ rb->name_index_root = bl->id;
+ camel_block_file_unref_block (p->blocks, bl);
+ camel_block_file_touch_block (p->blocks, p->blocks->root_block);
+ }
+
+ if (rb->name_hash_root == 0) {
+ bl = camel_block_file_new_block (p->blocks);
+
+ if (bl == NULL)
+ goto fail;
+
+ rb->name_hash_root = bl->id;
+ camel_block_file_unref_block (p->blocks, bl);
+ camel_block_file_touch_block (p->blocks, p->blocks->root_block);
+ }
+
+ p->word_index = camel_key_table_new (p->blocks, rb->word_index_root);
+ p->word_hash = camel_partition_table_new (p->blocks, rb->word_hash_root);
+ p->name_index = camel_key_table_new (p->blocks, rb->name_index_root);
+ p->name_hash = camel_partition_table_new (p->blocks, rb->name_hash_root);
+
+ if (p->word_index == NULL || p->word_hash == NULL
+ || p->name_index == NULL || p->name_hash == NULL) {
+ g_object_unref (idx);
+ idx = NULL;
+ }
+
+ return idx;
+
+fail:
+ g_object_unref (idx);
+ return NULL;
+}
+
+/* returns 0 if the index exists, is valid, and synced, -1 otherwise */
+gint
+camel_text_index_check (const gchar *path)
+{
+ gchar *block, *key;
+ gsize block_len, key_len;
+ CamelBlockFile *blocks;
+ CamelKeyFile *keys;
+
+ block_len = strlen (path) + 7;
+ block = alloca (block_len);
+ g_snprintf (block, block_len, "%s.index", path);
+ blocks = camel_block_file_new (block, O_RDONLY, CAMEL_TEXT_INDEX_VERSION, CAMEL_BLOCK_SIZE);
+ if (blocks == NULL) {
+ io (printf ("Check failed: No block file: %s\n", g_strerror (errno)));
+ return -1;
+ }
+ key_len = strlen (path) + 12;
+ key = alloca (key_len);
+ g_snprintf (key, key_len, "%s.index.data", path);
+ keys = camel_key_file_new (key, O_RDONLY, CAMEL_TEXT_INDEX_KEY_VERSION);
+ if (keys == NULL) {
+ io (printf ("Check failed: No key file: %s\n", g_strerror (errno)));
+ g_object_unref (blocks);
+ return -1;
+ }
+
+ g_object_unref (keys);
+ g_object_unref (blocks);
+
+ return 0;
+}
+
+gint
+camel_text_index_rename (const gchar *old,
+ const gchar *new)
+{
+ gchar *oldname, *newname;
+ gsize oldname_len, newname_len;
+ gint err;
+
+ /* TODO: camel_text_index_rename should find out if we have an active index and use that instead */
+
+ oldname_len = strlen (old) + 12;
+ newname_len = strlen (new) + 12;
+ oldname = alloca (oldname_len);
+ newname = alloca (newname_len);
+ g_snprintf (oldname, oldname_len, "%s.index", old);
+ g_snprintf (newname, newname_len, "%s.index", new);
+
+ if (g_rename (oldname, newname) == -1 && errno != ENOENT)
+ return -1;
+
+ g_snprintf (oldname, oldname_len, "%s.index.data", old);
+ g_snprintf (newname, newname_len, "%s.index.data", new);
+
+ if (g_rename (oldname, newname) == -1 && errno != ENOENT) {
+ err = errno;
+ g_snprintf (oldname, oldname_len, "%s.index", old);
+ g_snprintf (newname, newname_len, "%s.index", new);
+ if (g_rename (newname, oldname) == -1) {
+ g_warning (
+ "%s: Failed to rename '%s' to '%s': %s",
+ G_STRFUNC, newname, oldname, g_strerror (errno));
+ }
+ errno = err;
+ return -1;
+ }
+
+ return 0;
+}
+
+gint
+camel_text_index_remove (const gchar *old)
+{
+ gchar *block, *key;
+ gsize block_len, key_len;
+ gint ret = 0;
+
+ /* TODO: needs to poke any active indices to remain unlinked */
+
+ block_len = strlen (old) + 12;
+ block = alloca (block_len);
+ key_len = strlen (old) + 12;
+ key = alloca (key_len);
+ g_snprintf (block, block_len, "%s.index", old);
+ g_snprintf (key, key_len, "%s.index.data", old);
+
+ if (g_unlink (block) == -1 && errno != ENOENT && errno != ENOTDIR)
+ ret = -1;
+ if (g_unlink (key) == -1 && errno != ENOENT && errno != ENOTDIR)
+ ret = -1;
+
+ if (ret == 0)
+ errno = 0;
+
+ return ret;
+}
+
+/* Debug */
+void
+camel_text_index_info (CamelTextIndex *idx)
+{
+ CamelTextIndexPrivate *p = idx->priv;
+ struct _CamelTextIndexRoot *rb = (struct _CamelTextIndexRoot *) p->blocks->root;
+ gint frag;
+
+ printf ("Path: '%s'\n", idx->parent.path);
+ printf ("Version: %u\n", idx->parent.version);
+ printf ("Flags: %08x\n", idx->parent.flags);
+ printf ("Total words: %u\n", rb->words);
+ printf ("Total names: %u\n", rb->names);
+ printf ("Total deleted: %u\n", rb->deleted);
+ printf ("Total key blocks: %u\n", rb->keys);
+
+ if (rb->words > 0) {
+ frag = ((rb->keys - rb->words) * 100)/ rb->words;
+ printf ("Word fragmentation: %d%%\n", frag);
+ }
+
+ if (rb->names > 0) {
+ frag = (rb->deleted * 100)/ rb->names;
+ printf ("Name fragmentation: %d%%\n", frag);
+ }
+}
+
+/* #define DUMP_RAW */
+
+#ifdef DUMP_RAW
+enum { KEY_ROOT = 1, KEY_DATA = 2, PARTITION_MAP = 4, PARTITION_DATA = 8 };
+
+static void
+add_type (GHashTable *map,
+ camel_block_t id,
+ gint type)
+{
+ camel_block_t old;
+
+ old = g_hash_table_lookup (map, id);
+ if (old == type)
+ return;
+
+ if (old != 0 && old != type)
+ g_warning ("block %x redefined as type %d, already type %d\n", id, type, old);
+ g_hash_table_insert (map, id, GINT_TO_POINTER (type | old));
+}
+
+static void
+add_partition (GHashTable *map,
+ CamelBlockFile *blocks,
+ camel_block_t id)
+{
+ CamelBlock *bl;
+ CamelPartitionMapBlock *pm;
+ gint i;
+
+ while (id) {
+ add_type (map, id, PARTITION_MAP);
+ bl = camel_block_file_get_block (blocks, id);
+ if (bl == NULL) {
+ g_warning ("couldn't get parition: %x\n", id);
+ return;
+ }
+
+ pm = (CamelPartitionMapBlock *) &bl->data;
+ if (pm->used > G_N_ELEMENTS (pm->partition)) {
+ g_warning ("Partition block %x invalid\n", id);
+ camel_block_file_unref_block (blocks, bl);
+ return;
+ }
+
+ for (i = 0; i < pm->used; i++)
+ add_type (map, pm->partition[i].blockid, PARTITION_DATA);
+
+ id = pm->next;
+ camel_block_file_unref_block (blocks, bl);
+ }
+}
+
+static void
+add_keys (GHashTable *map,
+ CamelBlockFile *blocks,
+ camel_block_t id)
+{
+ CamelBlock *rbl, *bl;
+ CamelKeyRootBlock *root;
+ CamelKeyBlock *kb;
+
+ add_type (map, id, KEY_ROOT);
+ rbl = camel_block_file_get_block (blocks, id);
+ if (rbl == NULL) {
+ g_warning ("couldn't get key root: %x\n", id);
+ return;
+ }
+ root = (CamelKeyRootBlock *) &rbl->data;
+ id = root->first;
+
+ while (id) {
+ add_type (map, id, KEY_DATA);
+ bl = camel_block_file_get_block (blocks, id);
+ if (bl == NULL) {
+ g_warning ("couldn't get key: %x\n", id);
+ break;
+ }
+
+ kb = (CamelKeyBlock *) &bl->data;
+ id = kb->next;
+ camel_block_file_unref_block (blocks, bl);
+ }
+
+ camel_block_file_unref_block (blocks, rbl);
+}
+
+static void
+dump_raw (GHashTable *map,
+ gchar *path)
+{
+ gchar buf[1024];
+ gchar line[256];
+ gchar *p, c, *e, *a, *o;
+ gint v, n, len, i, type;
+ gchar hex[16] = "0123456789ABCDEF";
+ gint fd;
+ camel_block_t id, total;
+
+ fd = g_open (path, O_RDONLY | O_BINARY, 0);
+ if (fd == -1)
+ return;
+
+ total = 0;
+ while ((len = read (fd, buf, 1024)) == 1024) {
+ id = total;
+
+ type = g_hash_table_lookup (map, id);
+ switch (type) {
+ case 0:
+ printf (" - unknown -\n");
+ break;
+ default:
+ printf (" - invalid -\n");
+ break;
+ case KEY_ROOT: {
+ CamelKeyRootBlock *r = (CamelKeyRootBlock *) buf;
+ printf ("Key root:\n");
+ printf ("First: %08x Last: %08x Free: %08x\n", r->first, r->last, r->free);
+ } break;
+ case KEY_DATA: {
+ CamelKeyBlock *k = (CamelKeyBlock *) buf;
+ printf ("Key data:\n");
+ printf ("Next: %08x Used: %u\n", k->next, k->used);
+ for (i = 0; i < k->used; i++) {
+ if (i == 0)
+ len = sizeof (k->u.keydata);
+ else
+ len = k->u.keys[i - 1].offset;
+ len -= k->u.keys[i].offset;
+ printf (
+ "[%03d]: %08x %5d %06x %3d '%.*s'\n", i,
+ k->u.keys[i].data, k->u.keys[i].offset, k->u.keys[i].flags,
+ len, len, k->u.keydata + k->u.keys[i].offset);
+ }
+ } break;
+ case PARTITION_MAP: {
+ CamelPartitionMapBlock *m = (CamelPartitionMapBlock *) buf;
+ printf ("Partition map\n");
+ printf ("Next: %08x Used: %u\n", m->next, m->used);
+ for (i = 0; i < m->used; i++) {
+ printf ("[%03d]: %08x -> %08x\n", i, m->partition[i].hashid, m->partition[i].blockid);
+ }
+ } break;
+ case PARTITION_DATA: {
+ CamelPartitionKeyBlock *k = (CamelPartitionKeyBlock *) buf;
+ printf ("Partition data\n");
+ printf ("Used: %u\n", k->used);
+ } break;
+ }
+
+ printf ("--raw--\n");
+
+ len = 1024;
+ p = buf;
+ do {
+ g_snprintf (line, sizeof (line), "%08x: ", total);
+ total += 16;
+ o = line + 10;
+ a = o + 16 * 2 + 2;
+ i = 0;
+ while (len && i < 16) {
+ c = *p++;
+ *a++ = isprint (c)?c:'.';
+ *o++ = hex[(c>>4)&0x0f];
+ *o++ = hex[c&0x0f];
+ i++;
+ if (i == 8)
+ *o++ = ' ';
+ len--;
+ }
+ *a = 0;
+ printf ("%s\n", line);
+ } while (len);
+ printf ("\n");
+ }
+ close (fd);
+}
+#endif
+
+/* Debug */
+void
+camel_text_index_dump (CamelTextIndex *idx)
+{
+ CamelTextIndexPrivate *p = CAMEL_TEXT_INDEX_GET_PRIVATE (idx);
+#ifndef DUMP_RAW
+ camel_key_t keyid;
+ gchar *word;
+ const gchar *name;
+ guint flags;
+ camel_block_t data;
+
+ /* Iterate over all names in the file first */
+
+ printf ("UID's in index\n");
+
+ keyid = 0;
+ while ((keyid = camel_key_table_next (p->name_index, keyid, &word, &flags, &data))) {
+ if ((flags & 1) == 0)
+ printf (" %s\n", word);
+ else
+ printf (" %s (deleted)\n", word);
+ g_free (word);
+ }
+
+ printf ("Word's in index\n");
+
+ keyid = 0;
+ while ((keyid = camel_key_table_next (p->word_index, keyid, &word, &flags, &data))) {
+ CamelIndexCursor *idc;
+
+ printf ("Word: '%s':\n", word);
+
+ idc = camel_index_find ((CamelIndex *) idx, word);
+ while ((name = camel_index_cursor_next (idc))) {
+ printf (" %s", name);
+ }
+ printf ("\n");
+ g_object_unref (idc);
+ g_free (word);
+ }
+#else
+ /* a more low-level dump routine */
+ GHashTable *block_type = g_hash_table_new (NULL, NULL);
+ camel_block_t id;
+ struct stat st;
+ gint type;
+
+ add_keys (block_type, p->blocks, p->word_index->rootid);
+ add_keys (block_type, p->blocks, p->name_index->rootid);
+
+ add_partition (block_type, p->blocks, p->word_hash->rootid);
+ add_partition (block_type, p->blocks, p->name_hash->rootid);
+
+ dump_raw (block_type, p->blocks->path);
+ g_hash_table_destroy (block_type);
+#endif
+}
+
+/* more debug stuff */
+void
+camel_text_index_validate (CamelTextIndex *idx)
+{
+ CamelTextIndexPrivate *p = CAMEL_TEXT_INDEX_GET_PRIVATE (idx);
+ camel_key_t keyid;
+ gchar *word;
+ const gchar *name;
+ guint flags;
+ camel_block_t data;
+ gchar *oldword;
+ camel_key_t *records;
+ gsize count;
+
+ GHashTable *names, *deleted, *words, *keys, *name_word, *word_word;
+
+ names = g_hash_table_new (NULL, NULL);
+ deleted = g_hash_table_new (NULL, NULL);
+
+ name_word = g_hash_table_new (g_str_hash, g_str_equal);
+
+ words = g_hash_table_new (NULL, NULL);
+ keys = g_hash_table_new (NULL, NULL);
+
+ word_word = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* Iterate over all names in the file first */
+
+ printf ("Checking UID consistency\n");
+
+ keyid = 0;
+ while ((keyid = camel_key_table_next (p->name_index, keyid, &word, &flags, &data))) {
+ if ((oldword = g_hash_table_lookup (names, GINT_TO_POINTER (keyid))) != NULL
+ || (oldword = g_hash_table_lookup (deleted, GINT_TO_POINTER (keyid))) != NULL) {
+ printf ("Warning, name '%s' duplicates key (%x) with name '%s'\n", word, keyid, oldword);
+ g_free (word);
+ } else {
+ g_hash_table_insert (name_word, word, GINT_TO_POINTER (1));
+ if ((flags & 1) == 0) {
+ g_hash_table_insert (names, GINT_TO_POINTER (keyid), word);
+ } else {
+ g_hash_table_insert (deleted, GINT_TO_POINTER (keyid), word);
+ }
+ }
+ }
+
+ printf ("Checking WORD member consistency\n");
+
+ keyid = 0;
+ while ((keyid = camel_key_table_next (p->word_index, keyid, &word, &flags, &data))) {
+ CamelIndexCursor *idc;
+ GHashTable *used;
+
+ /* first, check for duplicates of keyid, and data */
+ if ((oldword = g_hash_table_lookup (words, GINT_TO_POINTER (keyid))) != NULL) {
+ printf ("Warning, word '%s' duplicates key (%x) with name '%s'\n", word, keyid, oldword);
+ g_free (word);
+ word = oldword;
+ } else {
+ g_hash_table_insert (words, GINT_TO_POINTER (keyid), word);
+ }
+
+ if (data == 0) {
+ /* This may not be an issue if things have been removed over time,
+ * though it is a problem if its a fresh index */
+ printf ("Word '%s' has no data associated with it\n", word);
+ } else {
+ if ((oldword = g_hash_table_lookup (keys, GUINT_TO_POINTER (data))) != NULL) {
+ printf ("Warning, word '%s' duplicates data (%x) with name '%s'\n", word, data, oldword);
+ } else {
+ g_hash_table_insert (keys, GUINT_TO_POINTER (data), word);
+ }
+ }
+
+ if (g_hash_table_lookup (word_word, word) != NULL) {
+ printf ("Warning, word '%s' occurs more than once\n", word);
+ } else {
+ g_hash_table_insert (word_word, word, word);
+ }
+
+ used = g_hash_table_new (g_str_hash, g_str_equal);
+
+ idc = camel_index_find ((CamelIndex *) idx, word);
+ while ((name = camel_index_cursor_next (idc))) {
+ if (g_hash_table_lookup (name_word, name) == NULL) {
+ printf ("word '%s' references non-existant name '%s'\n", word, name);
+ }
+ if (g_hash_table_lookup (used, name) != NULL) {
+ printf ("word '%s' uses word '%s' more than once\n", word, name);
+ } else {
+ g_hash_table_insert (used, g_strdup (name), (gpointer) 1);
+ }
+ }
+ g_object_unref (idc);
+
+ g_hash_table_foreach (used, (GHFunc) g_free, NULL);
+ g_hash_table_destroy (used);
+
+ printf ("word '%s'\n", word);
+
+ while (data) {
+ printf (" data %x ", data);
+ if (camel_key_file_read (p->links, &data, &count, &records) == -1) {
+ printf ("Warning, read failed for word '%s', at data '%u'\n", word, data);
+ data = 0;
+ } else {
+ printf ("(%d)\n", (gint) count);
+ g_free (records);
+ }
+ }
+ }
+
+ g_hash_table_destroy (names);
+ g_hash_table_destroy (deleted);
+ g_hash_table_destroy (words);
+ g_hash_table_destroy (keys);
+
+ g_hash_table_foreach (name_word, (GHFunc) g_free, NULL);
+ g_hash_table_destroy (name_word);
+
+ g_hash_table_foreach (word_word, (GHFunc) g_free, NULL);
+ g_hash_table_destroy (word_word);
+}
+
+/* ********************************************************************** */
+/* CamelTextIndexName */
+/* ********************************************************************** */
+
+G_DEFINE_TYPE (CamelTextIndexName, camel_text_index_name, CAMEL_TYPE_INDEX_NAME)
+
+static void
+text_index_name_finalize (GObject *object)
+{
+ CamelTextIndexNamePrivate *priv;
+
+ priv = CAMEL_TEXT_INDEX_NAME_GET_PRIVATE (object);
+
+ g_hash_table_destroy (CAMEL_TEXT_INDEX_NAME (object)->parent.words);
+
+ g_string_free (priv->buffer, TRUE);
+ camel_mempool_destroy (priv->pool);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_text_index_name_parent_class)->finalize (object);
+}
+
+static void
+text_index_name_add_word (CamelIndexName *idn,
+ const gchar *word)
+{
+ CamelTextIndexNamePrivate *p = ((CamelTextIndexName *) idn)->priv;
+
+ if (g_hash_table_lookup (idn->words, word) == NULL) {
+ gchar *w = camel_mempool_strdup (p->pool, word);
+
+ g_hash_table_insert (idn->words, w, w);
+ }
+}
+
+/* Why?
+ * Because it doesn't hang/loop forever on bad data
+ * Used to clean up utf8 before it gets further */
+
+static inline guint32
+camel_utf8_next (const guchar **ptr,
+ const guchar *ptrend)
+{
+ register guchar *p = (guchar *) * ptr;
+ register guint c;
+ register guint32 v;
+ gint l;
+
+ if (p == ptrend)
+ return 0;
+
+ while ((c = *p++)) {
+ if (c < 0x80) {
+ *ptr = p;
+ return c;
+ } else if ((c&0xe0) == 0xc0) {
+ v = c & 0x1f;
+ l = 1;
+ } else if ((c&0xf0) == 0xe0) {
+ v = c & 0x0f;
+ l = 2;
+ } else if ((c&0xf8) == 0xf0) {
+ v = c & 0x07;
+ l = 3;
+ } else if ((c&0xfc) == 0xf8) {
+ v = c & 0x03;
+ l = 4;
+ } else if ((c&0xfe) == 0xfc) {
+ v = c & 0x01;
+ l = 5;
+ } else
+ /* Invalid, ignore and look for next start gchar if room */
+ if (p == ptrend) {
+ return 0;
+ } else {
+ continue;
+ }
+
+ /* bad data or truncated buffer */
+ if (p + l > ptrend)
+ return 0;
+
+ while (l && ((c = *p) & 0xc0) == 0x80) {
+ p++;
+ l--;
+ v = (v << 6) | (c & 0x3f);
+ }
+
+ /* valid gchar */
+ if (l == 0) {
+ *ptr = p;
+ return v;
+ }
+
+ /* else look for a start gchar again */
+ }
+
+ return 0;
+}
+
+static gsize
+text_index_name_add_buffer (CamelIndexName *idn,
+ const gchar *buffer,
+ gsize len)
+{
+ CamelTextIndexNamePrivate *p = CAMEL_TEXT_INDEX_NAME_GET_PRIVATE (idn);
+ const guchar *ptr, *ptrend;
+ guint32 c;
+ gchar utf8[8];
+ gint utf8len;
+
+ if (buffer == NULL) {
+ if (p->buffer->len) {
+ camel_index_name_add_word (idn, p->buffer->str);
+ g_string_truncate (p->buffer, 0);
+ }
+ return 0;
+ }
+
+ ptr = (const guchar *) buffer;
+ ptrend = (const guchar *) buffer + len;
+ while ((c = camel_utf8_next (&ptr, ptrend))) {
+ if (g_unichar_isalnum (c)) {
+ c = g_unichar_tolower (c);
+ utf8len = g_unichar_to_utf8 (c, utf8);
+ utf8[utf8len] = 0;
+ g_string_append (p->buffer, utf8);
+ } else {
+ if (p->buffer->len > 0 && p->buffer->len <= CAMEL_TEXT_INDEX_MAX_WORDLEN) {
+ text_index_name_add_word (idn, p->buffer->str);
+ /*camel_index_name_add_word (idn, p->buffer->str);*/
+ }
+
+ g_string_truncate (p->buffer, 0);
+ }
+ }
+
+ return 0;
+}
+
+static void
+camel_text_index_name_class_init (CamelTextIndexNameClass *class)
+{
+ GObjectClass *object_class;
+ CamelIndexNameClass *index_name_class;
+
+ g_type_class_add_private (class, sizeof (CamelTextIndexNamePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = text_index_name_finalize;
+
+ index_name_class = CAMEL_INDEX_NAME_CLASS (class);
+ index_name_class->add_word = text_index_name_add_word;
+ index_name_class->add_buffer = text_index_name_add_buffer;
+}
+
+static void
+camel_text_index_name_init (CamelTextIndexName *text_index_name)
+{
+ text_index_name->priv =
+ CAMEL_TEXT_INDEX_NAME_GET_PRIVATE (text_index_name);
+
+ text_index_name->parent.words = g_hash_table_new (
+ g_str_hash, g_str_equal);
+
+ text_index_name->priv->buffer = g_string_new ("");
+ text_index_name->priv->pool =
+ camel_mempool_new (256, 128, CAMEL_MEMPOOL_ALIGN_BYTE);
+}
+
+CamelTextIndexName *
+camel_text_index_name_new (CamelTextIndex *idx,
+ const gchar *name,
+ camel_key_t nameid)
+{
+ CamelTextIndexName *idn = g_object_new (CAMEL_TYPE_TEXT_INDEX_NAME, NULL);
+ CamelIndexName *cin = &idn->parent;
+ CamelTextIndexNamePrivate *p = CAMEL_TEXT_INDEX_NAME_GET_PRIVATE (idn);
+
+ cin->index = g_object_ref (idx);
+ cin->name = camel_mempool_strdup (p->pool, name);
+ p->nameid = nameid;
+
+ return idn;
+}
+
+/* ********************************************************************** */
+/* CamelTextIndexCursor */
+/* ********************************************************************** */
+
+G_DEFINE_TYPE (CamelTextIndexCursor, camel_text_index_cursor, CAMEL_TYPE_INDEX_CURSOR)
+
+static void
+text_index_cursor_finalize (GObject *object)
+{
+ CamelTextIndexCursorPrivate *priv;
+
+ priv = CAMEL_TEXT_INDEX_CURSOR_GET_PRIVATE (object);
+
+ g_free (priv->records);
+ g_free (priv->current);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_text_index_cursor_parent_class)->finalize (object);
+}
+
+static const gchar *
+text_index_cursor_next (CamelIndexCursor *idc)
+{
+ CamelTextIndexCursorPrivate *p = CAMEL_TEXT_INDEX_CURSOR_GET_PRIVATE (idc);
+ CamelTextIndexPrivate *tip = CAMEL_TEXT_INDEX_GET_PRIVATE (idc->index);
+ guint flags;
+
+ c (printf ("Going to next cursor for word with data '%08x' next %08x\n", p->first, p->next));
+
+ do {
+ while (p->record_index >= p->record_count) {
+ g_free (p->records);
+ p->records = NULL;
+ p->record_index = 0;
+ p->record_count = 0;
+ if (p->next == 0)
+ return NULL;
+ if (camel_key_file_read (tip->links, &p->next, &p->record_count, &p->records) == -1)
+ return NULL;
+ }
+
+ g_free (p->current);
+ p->current = NULL;
+ flags = 0;
+
+ camel_key_table_lookup (
+ tip->name_index, p->records[p->record_index],
+ &p->current, &flags);
+ if (flags & 1) {
+ g_free (p->current);
+ p->current = NULL;
+ }
+ p->record_index++;
+ } while (p->current == NULL);
+
+ return p->current;
+}
+
+static void
+camel_text_index_cursor_class_init (CamelTextIndexCursorClass *class)
+{
+ GObjectClass *object_class;
+ CamelIndexCursorClass *index_cursor_class;
+
+ g_type_class_add_private (class, sizeof (CamelTextIndexCursorPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = text_index_cursor_finalize;
+
+ index_cursor_class = CAMEL_INDEX_CURSOR_CLASS (class);
+ index_cursor_class->next = text_index_cursor_next;
+}
+
+static void
+camel_text_index_cursor_init (CamelTextIndexCursor *text_index_cursor)
+{
+ text_index_cursor->priv =
+ CAMEL_TEXT_INDEX_CURSOR_GET_PRIVATE (text_index_cursor);
+}
+
+CamelTextIndexCursor *
+camel_text_index_cursor_new (CamelTextIndex *idx,
+ camel_block_t data)
+{
+ CamelTextIndexCursor *idc = g_object_new (CAMEL_TYPE_TEXT_INDEX_CURSOR, NULL);
+ CamelIndexCursor *cic = &idc->parent;
+ CamelTextIndexCursorPrivate *p = CAMEL_TEXT_INDEX_CURSOR_GET_PRIVATE (idc);
+
+ cic->index = g_object_ref (idx);
+ p->first = data;
+ p->next = data;
+ p->record_count = 0;
+ p->record_index = 0;
+
+ return idc;
+}
+
+/* ********************************************************************** */
+/* CamelTextIndexKeyCursor */
+/* ********************************************************************** */
+
+G_DEFINE_TYPE (CamelTextIndexKeyCursor, camel_text_index_key_cursor, CAMEL_TYPE_INDEX_CURSOR)
+
+static void
+text_index_key_cursor_dispose (GObject *object)
+{
+ CamelTextIndexKeyCursorPrivate *priv;
+
+ priv = CAMEL_TEXT_INDEX_KEY_CURSOR_GET_PRIVATE (object);
+
+ if (priv->table != NULL) {
+ g_object_unref (priv->table);
+ priv->table = NULL;
+ }
+
+ /* Chain up parent's dispose() method. */
+ G_OBJECT_CLASS (camel_text_index_key_cursor_parent_class)->dispose (object);
+}
+
+static void
+text_index_key_cursor_finalize (GObject *object)
+{
+ CamelTextIndexKeyCursorPrivate *priv;
+
+ priv = CAMEL_TEXT_INDEX_KEY_CURSOR_GET_PRIVATE (object);
+
+ g_free (priv->current);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_text_index_key_cursor_parent_class)->finalize (object);
+}
+
+static const gchar *
+text_index_key_cursor_next (CamelIndexCursor *idc)
+{
+ CamelTextIndexKeyCursorPrivate *p = CAMEL_TEXT_INDEX_KEY_CURSOR_GET_PRIVATE (idc);
+
+ c (printf ("Going to next cursor for keyid %08x\n", p->keyid));
+
+ g_free (p->current);
+ p->current = NULL;
+
+ while ((p->keyid = camel_key_table_next (p->table, p->keyid, &p->current, &p->flags, &p->data))) {
+ if ((p->flags & 1) == 0) {
+ return p->current;
+ } else {
+ g_free (p->current);
+ p->current = NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+camel_text_index_key_cursor_class_init (CamelTextIndexKeyCursorClass *class)
+{
+ GObjectClass *object_class;
+ CamelIndexCursorClass *index_cursor_class;
+
+ g_type_class_add_private (class, sizeof (CamelTextIndexKeyCursorPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = text_index_key_cursor_dispose;
+ object_class->finalize = text_index_key_cursor_finalize;
+
+ index_cursor_class = CAMEL_INDEX_CURSOR_CLASS (class);
+ index_cursor_class->next = text_index_key_cursor_next;
+}
+
+static void
+camel_text_index_key_cursor_init (CamelTextIndexKeyCursor *text_index_key_cursor)
+{
+ text_index_key_cursor->priv =
+ CAMEL_TEXT_INDEX_KEY_CURSOR_GET_PRIVATE (text_index_key_cursor);
+
+ text_index_key_cursor->priv->keyid = 0;
+ text_index_key_cursor->priv->flags = 0;
+ text_index_key_cursor->priv->data = 0;
+ text_index_key_cursor->priv->current = NULL;
+}
+
+CamelTextIndexKeyCursor *
+camel_text_index_key_cursor_new (CamelTextIndex *idx,
+ CamelKeyTable *table)
+{
+ CamelTextIndexKeyCursor *idc = g_object_new (CAMEL_TYPE_TEXT_INDEX_KEY_CURSOR, NULL);
+ CamelIndexCursor *cic = &idc->parent;
+ CamelTextIndexKeyCursorPrivate *p = CAMEL_TEXT_INDEX_KEY_CURSOR_GET_PRIVATE (idc);
+
+ cic->index = g_object_ref (idx);
+ p->table = g_object_ref (table);
+
+ return idc;
+}
+
+/* ********************************************************************** */
+
+#define m(x)
+
+#if 0
+
+struct _CamelIndexRoot {
+ struct _CamelBlockRoot root;
+
+ camel_block_t word_root; /* a keyindex containing the keyid -> word mapping */
+ camel_block_t word_hash_root; /* a partitionindex containing word -> keyid mapping */
+
+ camel_block_t name_root; /* same, for names */
+ camel_block_t name_hash_root;
+};
+
+gchar wordbuffer[] = "This is a buffer of multiple words. Some of the words are duplicates"
+" while other words are the same, some are in difFerenT Different different case cAsE casE,"
+" with,with:with;with-with'with\"'\"various punctuation as well. So much for those Words. and 10"
+" numbers in a row too 1,2,3,4,5,6,7,8,9,10! Yay!.";
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+#if 0
+ CamelBlockFile *bs;
+ CamelKeyTable *ki;
+ CamelPartitionTable *cpi;
+ CamelBlock *keyroot, *partroot;
+ struct _CamelIndexRoot *root;
+ FILE *fp;
+ gchar line[256], *key;
+ camel_key_t keyid;
+ gint index = 0, flags, data;
+#endif
+ CamelIndex *idx;
+ CamelIndexName *idn;
+ CamelIndexCursor *idc;
+ const gchar *word;
+ gint i;
+
+ printf ("Camel text index tester!\n");
+
+ camel_init (NULL, 0);
+
+ idx = (CamelIndex *) camel_text_index_new ("textindex", O_CREAT | O_RDWR | O_TRUNC);
+
+#if 1
+ camel_index_compress (idx);
+
+ return 0;
+#endif
+
+ for (i = 0; i < 100; i++) {
+ gchar name[16];
+
+ g_snprintf (name, sizeof (name), "%d", i);
+ printf ("Adding words to name '%s'\n", name);
+ idn = camel_index_add_name (idx, name);
+ camel_index_name_add_buffer (idn, wordbuffer, sizeof (wordbuffer) - 1);
+ camel_index_write_name (idx, idn);
+ g_object_unref (idn);
+ }
+
+ printf ("Looking up which names contain word 'word'\n");
+ idc = camel_index_find (idx, "words");
+ while ((word = camel_index_cursor_next (idc)) != NULL) {
+ printf (" name is '%s'\n", word);
+ }
+ g_object_unref (idc);
+ printf ("done.\n");
+
+ printf ("Looking up which names contain word 'truncate'\n");
+ idc = camel_index_find (idx, "truncate");
+ while ((word = camel_index_cursor_next (idc)) != NULL) {
+ printf (" name is '%s'\n", word);
+ }
+ g_object_unref (idc);
+ printf ("done.\n");
+
+ camel_index_sync (idx);
+ g_object_unref (idx);
+
+#if 0
+ bs = camel_block_file_new ("blocks", "TESTINDX", CAMEL_BLOCK_SIZE);
+
+ root = (struct _CamelIndexRoot *) bs->root;
+ if (root->word_root == 0) {
+ keyroot = camel_block_file_new_block (bs);
+ root->word_root = keyroot->id;
+ camel_block_file_touch_block (bs, bs->root_block);
+ }
+ if (root->word_hash_root == 0) {
+ partroot = camel_block_file_new_block (bs);
+ root->word_hash_root = partroot->id;
+ camel_block_file_touch_block (bs, bs->root_block);
+ }
+
+ ki = camel_key_table_new (bs, root->word_root);
+ cpi = camel_partition_table_new (bs, root->word_hash_root);
+
+ fp = fopen ("/usr/dict/words", "r");
+ if (fp == NULL) {
+ perror ("fopen");
+ return 1;
+ }
+
+ while (fgets (line, sizeof (line), fp) != NULL) {
+ line[strlen (line) - 1] = 0;
+
+ /* see if its already there */
+ keyid = camel_partition_table_lookup (cpi, line);
+ if (keyid == 0) {
+ m (printf ("Adding word '%s' %d\n", line, index));
+
+ keyid = camel_key_table_add (ki, line, index, 0);
+ m (printf (" key = %08x\n", keyid));
+
+ camel_partition_table_add (cpi, line, keyid);
+
+ m (printf ("Lookup word '%s'\n", line));
+ keyid = camel_partition_table_lookup (cpi, line);
+ m (printf (" key = %08x\n", keyid));
+ }
+
+ m (printf ("Lookup key %08x\n", keyid));
+
+ camel_key_table_set_flags (ki, keyid, index, 1);
+
+ data = camel_key_table_lookup (ki, keyid, &key, &flags);
+ m (printf (" word = '%s' %d %04x\n", key, data, flags));
+
+ g_return_val_if_fail (data == index && strcmp (key, line) == 0, -1);
+
+ g_free (key);
+
+ index++;
+ }
+
+ printf ("Scanning again\n");
+ fseek (fp, SEEK_SET, 0);
+ index = 0;
+ while (fgets (line, sizeof (line), fp) != NULL) {
+ line[strlen (line) - 1] = 0;
+ m (printf ("Lookup word '%s' %d\n", line, index));
+ keyid = camel_partition_table_lookup (cpi, line);
+ m (printf (" key = %08d\n", keyid));
+
+ m (printf ("Lookup key %08x\n", keyid));
+ data = camel_key_table_lookup (ki, keyid, &key, &flags);
+ m (printf (" word = '%s' %d\n", key, data));
+
+ g_return_val_if_fail (data == index && strcmp (key, line) == 0, -1);
+
+ g_free (key);
+
+ index++;
+ }
+ fclose (fp);
+
+ printf ("Freeing partition index\n");
+ camel_partition_table_free (cpi);
+
+ printf ("Syncing block file\n");
+ camel_block_file_sync (bs);
+#endif
+ return 0;
+}
+
+#endif
diff --git a/src/camel/camel-text-index.h b/src/camel/camel-text-index.h
new file mode 100644
index 000000000..c4e275091
--- /dev/null
+++ b/src/camel/camel-text-index.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_TEXT_INDEX_H
+#define CAMEL_TEXT_INDEX_H
+
+#include <camel/camel-index.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_TEXT_INDEX \
+ (camel_text_index_get_type ())
+#define CAMEL_TEXT_INDEX(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_TEXT_INDEX, CamelTextIndex))
+#define CAMEL_TEXT_INDEX_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_TEXT_INDEX, CamelTextIndexClass))
+#define CAMEL_IS_TEXT_INDEX(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_TEXT_INDEX))
+#define CAMEL_IS_TEXT_INDEX_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_TEXT_INDEX))
+#define CAMEL_TEXT_INDEX_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_TEXT_INDEX, CamelTextIndexClass))
+
+#define CAMEL_TYPE_TEXT_INDEX_NAME \
+ (camel_text_index_name_get_type ())
+#define CAMEL_TEXT_INDEX_NAME(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_TEXT_INDEX_NAME, CamelTextIndexName))
+#define CAMEL_TEXT_INDEX_NAME_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_TEXT_INDEX_NAME, CamelTextIndexNameClass))
+#define CAMEL_IS_TEXT_INDEX_NAME(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_TEXT_INDEX_NAME))
+#define CAMEL_IS_TEXT_INDEX_NAME_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_TEXT_INDEX_NAME))
+#define CAMEL_TEXT_INDEX_NAME_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_TEXT_INDEX_NAME, CamelTextIndexNameClass))
+
+#define CAMEL_TYPE_TEXT_INDEX_CURSOR \
+ (camel_text_index_cursor_get_type ())
+#define CAMEL_TEXT_INDEX_CURSOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_TEXT_INDEX_CURSOR, CamelTextIndexCursor))
+#define CAMEL_TEXT_INDEX_CURSOR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_TEXT_INDEX_CURSOR, CamelTextIndexCursorClass))
+#define CAMEL_IS_TEXT_INDEX_CURSOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_TEXT_INDEX_CURSOR))
+#define CAMEL_IS_TEXT_INDEX_CURSOR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_TEXT_INDEX_CURSOR))
+#define CAMEL_TEXT_INDEX_CURSOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_TEXT_INDEX_CURSOR, CamelTextIndexCursorClass))
+
+#define CAMEL_TYPE_TEXT_INDEX_KEY_CURSOR \
+ (camel_text_index_key_cursor_get_type ())
+#define CAMEL_TEXT_INDEX_KEY_CURSOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_TEXT_INDEX_KEY_CURSOR, CamelTextIndexKeyCursor))
+#define CAMEL_TEXT_INDEX_KEY_CURSOR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_TEXT_INDEX_KEY_CURSOR, CamelTextIndexKeyCursorClass))
+#define CAMEL_IS_TEXT_INDEX_KEY_CURSOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_TEXT_INDEX_KEY_CURSOR))
+#define CAMEL_IS_TEXT_INDEX_KEY_CURSOR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_TEXT_INDEX_KEY_CURSOR))
+#define CAMEL_TEXT_INDEX_KEY_CURSOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_TEXT_INDEX_KEY_CURSOR, CamelTextIndexKeyCursorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelTextIndex CamelTextIndex;
+typedef struct _CamelTextIndexClass CamelTextIndexClass;
+typedef struct _CamelTextIndexPrivate CamelTextIndexPrivate;
+
+typedef struct _CamelTextIndexName CamelTextIndexName;
+typedef struct _CamelTextIndexNameClass CamelTextIndexNameClass;
+typedef struct _CamelTextIndexNamePrivate CamelTextIndexNamePrivate;
+
+typedef struct _CamelTextIndexCursor CamelTextIndexCursor;
+typedef struct _CamelTextIndexCursorClass CamelTextIndexCursorClass;
+typedef struct _CamelTextIndexCursorPrivate CamelTextIndexCursorPrivate;
+
+typedef struct _CamelTextIndexKeyCursor CamelTextIndexKeyCursor;
+typedef struct _CamelTextIndexKeyCursorClass CamelTextIndexKeyCursorClass;
+typedef struct _CamelTextIndexKeyCursorPrivate CamelTextIndexKeyCursorPrivate;
+
+typedef void (*CamelTextIndexFunc)(CamelTextIndex *idx, const gchar *word, gchar *buffer);
+
+/* ********************************************************************** */
+
+struct _CamelTextIndexCursor {
+ CamelIndexCursor parent;
+ CamelTextIndexCursorPrivate *priv;
+};
+
+struct _CamelTextIndexCursorClass {
+ CamelIndexCursorClass parent_class;
+};
+
+GType camel_text_index_cursor_get_type (void);
+
+/* ********************************************************************** */
+
+struct _CamelTextIndexKeyCursor {
+ CamelIndexCursor parent;
+ CamelTextIndexKeyCursorPrivate *priv;
+};
+
+struct _CamelTextIndexKeyCursorClass {
+ CamelIndexCursorClass parent_class;
+};
+
+GType camel_text_index_key_cursor_get_type (void);
+
+/* ********************************************************************** */
+
+struct _CamelTextIndexName {
+ CamelIndexName parent;
+ CamelTextIndexNamePrivate *priv;
+};
+
+struct _CamelTextIndexNameClass {
+ CamelIndexNameClass parent_class;
+};
+
+GType camel_text_index_name_get_type (void);
+
+/* ********************************************************************** */
+
+struct _CamelTextIndex {
+ CamelIndex parent;
+ CamelTextIndexPrivate *priv;
+};
+
+struct _CamelTextIndexClass {
+ CamelIndexClass parent_class;
+};
+
+GType camel_text_index_get_type (void);
+CamelTextIndex *camel_text_index_new (const gchar *path,
+ gint flags);
+
+/* static utility functions */
+gint camel_text_index_check (const gchar *path);
+gint camel_text_index_rename (const gchar *old,
+ const gchar *new_);
+gint camel_text_index_remove (const gchar *old);
+
+void camel_text_index_dump (CamelTextIndex *idx);
+void camel_text_index_info (CamelTextIndex *idx);
+void camel_text_index_validate (CamelTextIndex *idx);
+
+G_END_DECLS
+
+#endif /* CAMEL_TEXT_INDEX_H */
diff --git a/src/camel/camel-transport.c b/src/camel/camel-transport.c
new file mode 100644
index 000000000..d83eef0fa
--- /dev/null
+++ b/src/camel/camel-transport.c
@@ -0,0 +1,270 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-transport.c : Abstract class for an email transport
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include "camel-address.h"
+#include "camel-async-closure.h"
+#include "camel-debug.h"
+#include "camel-mime-message.h"
+#include "camel-transport.h"
+
+#define CAMEL_TRANSPORT_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_TRANSPORT, CamelTransportPrivate))
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _CamelTransportPrivate {
+ gint placeholder;
+};
+
+struct _AsyncContext {
+ CamelAddress *from;
+ CamelAddress *recipients;
+ CamelMimeMessage *message;
+ gboolean sent_message_saved;
+};
+
+G_DEFINE_ABSTRACT_TYPE (CamelTransport, camel_transport, CAMEL_TYPE_SERVICE)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+ if (async_context->from != NULL)
+ g_object_unref (async_context->from);
+
+ if (async_context->recipients != NULL)
+ g_object_unref (async_context->recipients);
+
+ if (async_context->message != NULL)
+ g_object_unref (async_context->message);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static void
+camel_transport_class_init (CamelTransportClass *class)
+{
+ g_type_class_add_private (class, sizeof (CamelTransportPrivate));
+}
+
+static void
+camel_transport_init (CamelTransport *transport)
+{
+ transport->priv = CAMEL_TRANSPORT_GET_PRIVATE (transport);
+}
+
+/**
+ * camel_transport_send_to_sync:
+ * @transport: a #CamelTransport
+ * @message: a #CamelMimeMessage to send
+ * @from: a #CamelAddress to send from
+ * @recipients: a #CamelAddress containing all recipients
+ * @out_sent_message_saved: (out): set to %TRUE, if the sent message was also saved
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Sends the message to the given recipients, regardless of the contents
+ * of @message. If the message contains a "Bcc" header, the transport
+ * is responsible for stripping it.
+ *
+ * Returns: %TRUE on success or %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_transport_send_to_sync (CamelTransport *transport,
+ CamelMimeMessage *message,
+ CamelAddress *from,
+ CamelAddress *recipients,
+ gboolean *out_sent_message_saved,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelAsyncClosure *closure;
+ GAsyncResult *result;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_TRANSPORT (transport), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
+ g_return_val_if_fail (CAMEL_IS_ADDRESS (from), FALSE);
+ g_return_val_if_fail (CAMEL_IS_ADDRESS (recipients), FALSE);
+ g_return_val_if_fail (out_sent_message_saved != NULL, FALSE);
+
+ closure = camel_async_closure_new ();
+
+ camel_transport_send_to (
+ transport, message, from, recipients,
+ G_PRIORITY_DEFAULT, cancellable,
+ camel_async_closure_callback, closure);
+
+ result = camel_async_closure_wait (closure);
+
+ success = camel_transport_send_to_finish (transport, result, out_sent_message_saved, error);
+
+ camel_async_closure_free (closure);
+
+ return success;
+}
+
+/* Helper for camel_transport_send_to() */
+static void
+transport_send_to_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CamelTransport *transport;
+ CamelTransportClass *class;
+ AsyncContext *async_context;
+ gboolean success;
+ GError *local_error = NULL;
+
+ transport = CAMEL_TRANSPORT (source_object);
+ async_context = (AsyncContext *) task_data;
+
+ class = CAMEL_TRANSPORT_GET_CLASS (transport);
+ g_return_if_fail (class->send_to_sync != NULL);
+
+ success = class->send_to_sync (
+ CAMEL_TRANSPORT (source_object),
+ async_context->message,
+ async_context->from,
+ async_context->recipients,
+ &async_context->sent_message_saved,
+ cancellable, &local_error);
+ CAMEL_CHECK_LOCAL_GERROR (
+ transport, send_to_sync, success, local_error);
+
+ if (local_error != NULL) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, success);
+ }
+}
+
+/**
+ * camel_transport_send_to:
+ * @transport: a #CamelTransport
+ * @message: a #CamelMimeMessage to send
+ * @from: a #CamelAddress to send from
+ * @recipients: a #CamelAddress containing all recipients
+ * @io_priority: the I/O priority of the request
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Sends the message asynchronously to the given recipients, regardless of
+ * the contents of @message. If the message contains a "Bcc" header, the
+ * transport is responsible for stripping it.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call camel_transport_send_to_finish() to get the result of the operation.
+ *
+ * Since: 3.0
+ **/
+void
+camel_transport_send_to (CamelTransport *transport,
+ CamelMimeMessage *message,
+ CamelAddress *from,
+ CamelAddress *recipients,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CamelService *service;
+ AsyncContext *async_context;
+
+ g_return_if_fail (CAMEL_IS_TRANSPORT (transport));
+ g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
+ g_return_if_fail (CAMEL_IS_ADDRESS (from));
+ g_return_if_fail (CAMEL_IS_ADDRESS (recipients));
+
+ service = CAMEL_SERVICE (transport);
+
+ async_context = g_slice_new0 (AsyncContext);
+ if (CAMEL_IS_INTERNET_ADDRESS (from)) {
+ async_context->from = camel_address_new_clone (from);
+ camel_internet_address_ensure_ascii_domains (CAMEL_INTERNET_ADDRESS (async_context->from));
+ } else {
+ async_context->from = g_object_ref (from);
+ }
+ if (CAMEL_IS_INTERNET_ADDRESS (recipients)) {
+ async_context->recipients = camel_address_new_clone (recipients);
+ camel_internet_address_ensure_ascii_domains (CAMEL_INTERNET_ADDRESS (async_context->recipients));
+ } else {
+ async_context->recipients = g_object_ref (recipients);
+ }
+ async_context->message = g_object_ref (message);
+
+ task = g_task_new (transport, cancellable, callback, user_data);
+ g_task_set_source_tag (task, camel_transport_send_to);
+ g_task_set_priority (task, io_priority);
+
+ g_task_set_task_data (
+ task, async_context,
+ (GDestroyNotify) async_context_free);
+
+ camel_service_queue_task (
+ service, task, transport_send_to_thread);
+
+ g_object_unref (task);
+}
+
+/**
+ * camel_transport_send_to_finish:
+ * @transport: a #CamelTransport
+ * @result: a #GAsyncResult
+ * @out_sent_message_saved: (out): set to %TRUE, if the sent message was also saved
+ * @error: return locaton for a #GError, or %NULL
+ *
+ * Finishes the operation started with camel_transport_send_to().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ *
+ * Since: 3.0
+ **/
+gboolean
+camel_transport_send_to_finish (CamelTransport *transport,
+ GAsyncResult *result,
+ gboolean *out_sent_message_saved,
+ GError **error)
+{
+ AsyncContext *async_context;
+
+ g_return_val_if_fail (CAMEL_IS_TRANSPORT (transport), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, transport), FALSE);
+
+ g_return_val_if_fail (
+ g_async_result_is_tagged (
+ result, camel_transport_send_to), FALSE);
+
+ g_return_val_if_fail (out_sent_message_saved != NULL, FALSE);
+
+ async_context = g_task_get_task_data (G_TASK (result));
+ g_return_val_if_fail (async_context != NULL, FALSE);
+
+ *out_sent_message_saved = async_context->sent_message_saved;
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/src/camel/camel-transport.h b/src/camel/camel-transport.h
new file mode 100644
index 000000000..df5109479
--- /dev/null
+++ b/src/camel/camel-transport.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-transport.h : Abstract class for an email transport
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_TRANSPORT_H
+#define CAMEL_TRANSPORT_H
+
+#include <camel/camel-address.h>
+#include <camel/camel-mime-message.h>
+#include <camel/camel-service.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_TRANSPORT \
+ (camel_transport_get_type ())
+#define CAMEL_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_TRANSPORT, CamelTransport))
+#define CAMEL_TRANSPORT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_TRANSPORT, CamelTransportClass))
+#define CAMEL_IS_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_TRANSPORT))
+#define CAMEL_IS_TRANSPORT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_TRANSPORT))
+#define CAMEL_TRANSPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_TRANSPORT, CamelTransportClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelTransport CamelTransport;
+typedef struct _CamelTransportClass CamelTransportClass;
+typedef struct _CamelTransportPrivate CamelTransportPrivate;
+
+struct _CamelTransport {
+ CamelService parent;
+ CamelTransportPrivate *priv;
+};
+
+struct _CamelTransportClass {
+ CamelServiceClass parent_class;
+
+ /* Synchronous I/O Methods */
+ gboolean (*send_to_sync) (CamelTransport *transport,
+ CamelMimeMessage *message,
+ CamelAddress *from,
+ CamelAddress *recipients,
+ gboolean *out_sent_message_saved,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* Reserved slots. */
+ gpointer reserved[2];
+};
+
+GType camel_transport_get_type (void);
+gboolean camel_transport_send_to_sync (CamelTransport *transport,
+ CamelMimeMessage *message,
+ CamelAddress *from,
+ CamelAddress *recipients,
+ gboolean *out_sent_message_saved,
+ GCancellable *cancellable,
+ GError **error);
+void camel_transport_send_to (CamelTransport *transport,
+ CamelMimeMessage *message,
+ CamelAddress *from,
+ CamelAddress *recipients,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean camel_transport_send_to_finish (CamelTransport *transport,
+ GAsyncResult *result,
+ gboolean *out_sent_message_saved,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_TRANSPORT_H */
diff --git a/src/camel/camel-trie.c b/src/camel/camel-trie.c
new file mode 100644
index 000000000..4d9ea9f0f
--- /dev/null
+++ b/src/camel/camel-trie.c
@@ -0,0 +1,393 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-memchunk.h"
+#include "camel-trie.h"
+
+#define d(x)
+
+struct _trie_state {
+ struct _trie_state *next;
+ struct _trie_state *fail;
+ struct _trie_match *match;
+ guint final;
+ gint id;
+};
+
+struct _trie_match {
+ struct _trie_match *next;
+ struct _trie_state *state;
+ gunichar c;
+};
+
+/**
+ * CamelTrie:
+ *
+ * A trie data structure.
+ *
+ * Since: 2.24
+ **/
+struct _CamelTrie {
+ struct _trie_state root;
+ GPtrArray *fail_states;
+ gboolean icase;
+
+ CamelMemChunk *match_chunks;
+ CamelMemChunk *state_chunks;
+};
+
+static inline gunichar
+trie_utf8_getc (const guchar **in,
+ gsize inlen)
+{
+ register const guchar *inptr = *in;
+ const guchar *inend = inptr + inlen;
+ register guchar c, r;
+ register gunichar u, m;
+
+ if (inlen == 0)
+ return 0;
+
+ r = *inptr++;
+ if (r < 0x80) {
+ *in = inptr;
+ u = r;
+ } else if (r < 0xfe) { /* valid start char? */
+ u = r;
+ m = 0x7f80; /* used to mask out the length bits */
+ do {
+ if (inptr >= inend)
+ return 0;
+
+ c = *inptr++;
+ if ((c & 0xc0) != 0x80)
+ goto error;
+
+ u = (u << 6) | (c & 0x3f);
+ r <<= 1;
+ m <<= 5;
+ } while (r & 0x40);
+
+ *in = inptr;
+
+ u &= ~m;
+ } else {
+ error:
+ *in = (*in)+1;
+ u = 0xfffe;
+ }
+
+ return u;
+}
+
+/**
+ * camel_trie_new:
+ * @icase: Case sensitivity for the #CamelTrie.
+ *
+ * Creates a new #CamelTrie. If @icase is %TRUE, then pattern matching
+ * done by the CamelTrie will be case insensitive.
+ *
+ * Returns: The newly-created #CamelTrie.
+ *
+ * Since: 2.24
+ **/
+CamelTrie *
+camel_trie_new (gboolean icase)
+{
+ CamelTrie *trie;
+
+ trie = g_new (CamelTrie, 1);
+ trie->root.next = NULL;
+ trie->root.fail = NULL;
+ trie->root.match = NULL;
+ trie->root.final = 0;
+
+ trie->fail_states = g_ptr_array_sized_new (8);
+ trie->icase = icase;
+
+ trie->match_chunks = camel_memchunk_new (8, sizeof (struct _trie_match));
+ trie->state_chunks = camel_memchunk_new (8, sizeof (struct _trie_state));
+
+ return trie;
+}
+
+/**
+ * camel_trie_free:
+ * @trie: The #CamelTrie to free.
+ *
+ * Frees the memory associated with the #CamelTrie @trie.
+ *
+ * Since: 2.24
+ **/
+void
+camel_trie_free (CamelTrie *trie)
+{
+ g_ptr_array_free (trie->fail_states, TRUE);
+ camel_memchunk_destroy (trie->match_chunks);
+ camel_memchunk_destroy (trie->state_chunks);
+ g_free (trie);
+}
+
+static struct _trie_match *
+g (struct _trie_state *s,
+ gunichar c)
+{
+ struct _trie_match *m = s->match;
+
+ while (m && m->c != c)
+ m = m->next;
+
+ return m;
+}
+
+static struct _trie_state *
+trie_insert (CamelTrie *trie,
+ gint depth,
+ struct _trie_state *q,
+ gunichar c)
+{
+ struct _trie_match *m;
+
+ m = camel_memchunk_alloc (trie->match_chunks);
+ m->next = q->match;
+ m->c = c;
+
+ q->match = m;
+ q = m->state = camel_memchunk_alloc (trie->state_chunks);
+ q->match = NULL;
+ q->fail = &trie->root;
+ q->final = 0;
+ q->id = -1;
+
+ if (trie->fail_states->len < depth + 1) {
+ guint size = trie->fail_states->len;
+
+ size = MAX (size + 64, depth + 1);
+ g_ptr_array_set_size (trie->fail_states, size);
+ }
+
+ q->next = trie->fail_states->pdata[depth];
+ trie->fail_states->pdata[depth] = q;
+
+ return q;
+}
+
+#if d(!)0
+static void
+dump_trie (struct _trie_state *s,
+ gint depth)
+{
+ gchar *p = g_alloca ((depth * 2) + 1);
+ struct _trie_match *m;
+
+ memset (p, ' ', depth * 2);
+ p[depth * 2] = '\0';
+
+ fprintf (
+ stderr, "%s[state] %p: final=%d; pattern-id=%d; fail=%p\n",
+ p, s, s->final, s->id, s->fail);
+ m = s->match;
+ while (m) {
+ fprintf (stderr, " %s'%c' -> %p\n", p, m->c, m->state);
+ if (m->state)
+ dump_trie (m->state, depth + 1);
+
+ m = m->next;
+ }
+}
+#endif
+
+/*
+ * final = empty set
+ * FOR p = 1 TO #pat
+ * q = root
+ * FOR j = 1 TO m[p]
+ * IF g(q, pat[p][j]) == null
+ * insert(q, pat[p][j])
+ * ENDIF
+ * q = g(q, pat[p][j])
+ * ENDFOR
+ * final = union(final, q)
+ * ENDFOR
+*/
+
+/**
+ * camel_trie_add:
+ * @trie: The #CamelTrie to add a pattern to.
+ * @pattern: The pattern to add.
+ * @pattern_id: The id to use for the pattern.
+ *
+ * Add a new pattern to the #CamelTrie @trie.
+ *
+ * Since: 2.24
+ **/
+void
+camel_trie_add (CamelTrie *trie,
+ const gchar *pattern,
+ gint pattern_id)
+{
+ const guchar *inptr = (const guchar *) pattern;
+ struct _trie_state *q, *q1, *r;
+ struct _trie_match *m, *n;
+ gint i, depth = 0;
+ gunichar c;
+
+ /* Step 1: add the pattern to the trie */
+
+ q = &trie->root;
+
+ while ((c = trie_utf8_getc (&inptr, -1))) {
+ if (trie->icase)
+ c = g_unichar_tolower (c);
+
+ m = g (q, c);
+ if (m == NULL) {
+ q = trie_insert (trie, depth, q, c);
+ } else {
+ q = m->state;
+ }
+
+ depth++;
+ }
+
+ q->final = depth;
+ q->id = pattern_id;
+
+ /* Step 2: compute failure graph */
+
+ for (i = 0; i < trie->fail_states->len; i++) {
+ q = trie->fail_states->pdata[i];
+ while (q) {
+ m = q->match;
+ while (m) {
+ c = m->c;
+ q1 = m->state;
+ r = q->fail;
+ while (r && (n = g (r, c)) == NULL)
+ r = r->fail;
+
+ if (r != NULL) {
+ q1->fail = n->state;
+ if (q1->fail->final > q1->final)
+ q1->final = q1->fail->final;
+ } else {
+ if ((n = g (&trie->root, c)))
+ q1->fail = n->state;
+ else
+ q1->fail = &trie->root;
+ }
+
+ m = m->next;
+ }
+
+ q = q->next;
+ }
+ }
+
+ d (fprintf (stderr, "\nafter adding pattern '%s' to trie %p:\n", pattern, trie));
+ d (dump_trie (&trie->root, 0));
+}
+
+/*
+ * Aho-Corasick
+ *
+ * q = root
+ * FOR i = 1 TO n
+ * WHILE q != fail AND g(q, text[i]) == fail
+ * q = h(q)
+ * ENDWHILE
+ * IF q == fail
+ * q = root
+ * ELSE
+ * q = g(q, text[i])
+ * ENDIF
+ * IF isElement(q, final)
+ * RETURN TRUE
+ * ENDIF
+ * ENDFOR
+ * RETURN FALSE
+ */
+
+/**
+ * camel_trie_search:
+ * @trie: The #CamelTrie to search in.
+ * @buffer: The string to match against a pattern in @trie.
+ * @buflen: The length of @buffer.
+ * @matched_id: An integer address to store the matched pattern id in.
+ *
+ * Try to match the string @buffer with a pattern in @trie.
+ *
+ * Returns: The matched pattern, or %NULL if no pattern is matched.
+ *
+ * Since: 2.24
+ **/
+const gchar *
+camel_trie_search (CamelTrie *trie,
+ const gchar *buffer,
+ gsize buflen,
+ gint *matched_id)
+{
+ const guchar *inptr, *inend, *prev, *pat;
+ register gsize inlen = buflen;
+ struct _trie_state *q;
+ struct _trie_match *m = NULL; /* init to please gcc */
+ gunichar c;
+
+ inptr = (const guchar *) buffer;
+ inend = inptr + buflen;
+
+ q = &trie->root;
+ pat = prev = inptr;
+ while ((c = trie_utf8_getc (&inptr, inlen))) {
+ inlen = (inend - inptr);
+
+ if (c != 0xfffe) {
+ if (trie->icase)
+ c = g_unichar_tolower (c);
+
+ while (q != NULL && (m = g (q, c)) == NULL)
+ q = q->fail;
+
+ if (q == &trie->root)
+ pat = prev;
+
+ if (q == NULL) {
+ q = &trie->root;
+ pat = inptr;
+ } else if (m != NULL) {
+ q = m->state;
+
+ if (q->final) {
+ if (matched_id)
+ *matched_id = q->id;
+
+ return (const gchar *) pat;
+ }
+ }
+ }
+
+ prev = inptr;
+ }
+
+ return NULL;
+}
diff --git a/src/camel/camel-trie.h b/src/camel/camel-trie.h
new file mode 100644
index 000000000..ddef44edd
--- /dev/null
+++ b/src/camel/camel-trie.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_TRIE_H
+#define CAMEL_TRIE_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _CamelTrie CamelTrie;
+
+CamelTrie * camel_trie_new (gboolean icase);
+void camel_trie_free (CamelTrie *trie);
+void camel_trie_add (CamelTrie *trie,
+ const gchar *pattern,
+ gint pattern_id);
+const gchar * camel_trie_search (CamelTrie *trie,
+ const gchar *buffer,
+ gsize buflen,
+ gint *matched_id);
+
+G_END_DECLS
+
+#endif /* CAMEL_TRIE_H */
diff --git a/src/camel/camel-uid-cache.c b/src/camel/camel-uid-cache.c
new file mode 100644
index 000000000..e6fbc2844
--- /dev/null
+++ b/src/camel/camel-uid-cache.c
@@ -0,0 +1,335 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-uid-cache.c: UID caching code
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <glib/gstdio.h>
+
+#include "camel-file-utils.h"
+#include "camel-uid-cache.h"
+#include "camel-win32.h"
+
+struct _uid_state {
+ gint 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.
+ *
+ * Returns: a new UID cache, or %NULL
+ **/
+CamelUIDCache *
+camel_uid_cache_new (const gchar *filename)
+{
+ CamelUIDCache *cache;
+ struct stat st;
+ gchar *dirname, *buf, **uids;
+ gint fd, i;
+
+ dirname = g_path_get_dirname (filename);
+ if (g_mkdir_with_parents (dirname, 0700) == -1) {
+ g_free (dirname);
+ return NULL;
+ }
+
+ g_free (dirname);
+
+ if ((fd = g_open (filename, O_RDONLY | O_CREAT | O_BINARY, 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, NULL, NULL) == -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), NULL, NULL) == -1 ||
+ camel_write (cache->fd, "\n", 1, NULL, NULL) == -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.
+ *
+ * Returns: success or failure
+ **/
+gboolean
+camel_uid_cache_save (CamelUIDCache *cache)
+{
+ gchar *filename;
+ gint errnosav;
+ gint fd;
+
+ filename = g_strdup_printf ("%s~", cache->filename);
+ if ((fd = g_open (filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 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 || fsync (fd) == -1)
+ goto exception;
+
+ close (fd);
+ fd = -1;
+ cache->fd = -1;
+
+ if (g_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 (g_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) {
+ close (fd);
+ g_rename (filename, cache->filename);
+ g_free (filename);
+ cache->expired = 0;
+ cache->size = 0;
+ cache->fd = -1;
+
+ return TRUE;
+ }
+ }
+ }
+#endif
+
+ if (fd != -1) {
+ close (fd);
+ cache->fd = -1;
+ }
+
+ g_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: (element-type utf8) (transfer none): 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.
+ *
+ * Returns: (element-type utf8) (transfer full): 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;
+ gchar *uid;
+ gint 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, (gpointer *) &old_uid, (gpointer *) &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 gchar *uid)
+{
+ struct _uid_state *state;
+ gpointer old_uid;
+
+ g_return_if_fail (uid != NULL);
+
+ if (g_hash_table_lookup_extended (cache->uids, uid, (gpointer *) &old_uid, (gpointer *) &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: (element-type utf8) (transfer full): an array returned from camel_uid_cache_get_new_uids()
+ *
+ * Frees the array of UIDs.
+ **/
+void
+camel_uid_cache_free_uids (GPtrArray *uids)
+{
+ gint i;
+
+ for (i = 0; i < uids->len; i++)
+ g_free (uids->pdata[i]);
+ g_ptr_array_free (uids, TRUE);
+}
diff --git a/src/camel/camel-uid-cache.h b/src/camel/camel-uid-cache.h
new file mode 100644
index 000000000..1635d19aa
--- /dev/null
+++ b/src/camel/camel-uid-cache.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-uid-cache.h: UID caching code
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_UID_CACHE_H
+#define CAMEL_UID_CACHE_H
+
+#include <glib.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+ gchar *filename;
+ GHashTable *uids;
+ guint level;
+ gsize expired;
+ gsize size;
+ gint fd;
+} CamelUIDCache;
+
+CamelUIDCache *camel_uid_cache_new (const gchar *filename);
+gboolean camel_uid_cache_save (CamelUIDCache *cache);
+void camel_uid_cache_destroy (CamelUIDCache *cache);
+
+GPtrArray *camel_uid_cache_get_new_uids (CamelUIDCache *cache, GPtrArray *uids);
+
+void camel_uid_cache_save_uid (CamelUIDCache *cache, const gchar *uid);
+void camel_uid_cache_free_uids (GPtrArray *uids);
+
+G_END_DECLS
+
+#endif /* CAMEL_UID_CACHE_H */
diff --git a/src/camel/camel-url-scanner.c b/src/camel/camel-url-scanner.c
new file mode 100644
index 000000000..c977e1407
--- /dev/null
+++ b/src/camel/camel-url-scanner.c
@@ -0,0 +1,552 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-trie.h"
+#include "camel-url-scanner.h"
+#include "camel-utf8.h"
+
+struct _CamelUrlScanner {
+ GPtrArray *patterns;
+ CamelTrie *trie;
+};
+
+CamelUrlScanner *
+camel_url_scanner_new (void)
+{
+ CamelUrlScanner *scanner;
+
+ scanner = g_new (CamelUrlScanner, 1);
+ scanner->patterns = g_ptr_array_new ();
+ scanner->trie = camel_trie_new (TRUE);
+
+ return scanner;
+}
+
+void
+camel_url_scanner_free (CamelUrlScanner *scanner)
+{
+ g_return_if_fail (scanner != NULL);
+
+ g_ptr_array_free (scanner->patterns, TRUE);
+ camel_trie_free (scanner->trie);
+ g_free (scanner);
+}
+
+void
+camel_url_scanner_add (CamelUrlScanner *scanner,
+ CamelUrlPattern *pattern)
+{
+ g_return_if_fail (scanner != NULL);
+
+ camel_trie_add (scanner->trie, pattern->pattern, scanner->patterns->len);
+ g_ptr_array_add (scanner->patterns, pattern);
+}
+
+gboolean
+camel_url_scanner_scan (CamelUrlScanner *scanner,
+ const gchar *in,
+ gsize inlen,
+ CamelUrlMatch *match)
+{
+ const gchar *pos;
+ const guchar *inptr, *inend;
+ CamelUrlPattern *pat;
+ gint pattern;
+
+ g_return_val_if_fail (scanner != NULL, FALSE);
+ g_return_val_if_fail (in != NULL, FALSE);
+
+ inptr = (const guchar *) in;
+ inend = inptr + inlen;
+
+ /* check validity of a string first */
+ if (!g_utf8_validate (in, inlen, NULL))
+ return FALSE;
+
+ do {
+ if (!(pos = camel_trie_search (scanner->trie, (const gchar *) inptr, inlen, &pattern)))
+ return FALSE;
+
+ pat = g_ptr_array_index (scanner->patterns, pattern);
+
+ match->pattern = pat->pattern;
+ match->prefix = pat->prefix;
+
+ if (pat->start (in, pos, (const gchar *) inend, match) && pat->end (in, pos, (const gchar *) inend, match))
+ return TRUE;
+
+ inptr = (const guchar *) pos;
+ if (camel_utf8_getc_limit (&inptr, inend) == 0xffff)
+ break;
+
+ inlen = inend - inptr;
+ } while (inptr < inend);
+
+ return FALSE;
+}
+
+static guchar url_scanner_table[256] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 1, 1, 9, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 24,128,160,128,128,128,128,128,160,160,128,128,160,192,160,160,
+ 68, 68, 68, 68, 68, 68, 68, 68, 68, 68,160,160, 32,128, 32,128,
+ 160, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,160,160,160,128,128,
+ 128, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,128,128,128,128, 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, 1
+};
+
+enum {
+ IS_CTRL = (1 << 0),
+ IS_ALPHA = (1 << 1),
+ IS_DIGIT = (1 << 2),
+ IS_LWSP = (1 << 3),
+ IS_SPACE = (1 << 4),
+ IS_SPECIAL = (1 << 5),
+ IS_DOMAIN = (1 << 6),
+ IS_URLSAFE = (1 << 7)
+};
+
+#define is_ctrl(x) ((url_scanner_table[(guchar)(x)] & IS_CTRL) != 0)
+#define is_lwsp(x) ((url_scanner_table[(guchar)(x)] & IS_LWSP) != 0)
+#define is_atom(x) ((url_scanner_table[(guchar)(x)] & (IS_SPECIAL|IS_SPACE|IS_CTRL)) == 0)
+#define is_alpha(x) ((url_scanner_table[(guchar)(x)] & IS_ALPHA) != 0)
+#define is_digit(x) ((url_scanner_table[(guchar)(x)] & IS_DIGIT) != 0)
+#define is_domain(x) ((url_scanner_table[(guchar)(x)] & IS_DOMAIN) != 0)
+#define is_urlsafe(x) ((url_scanner_table[(guchar)(x)] & (IS_ALPHA|IS_DIGIT|IS_URLSAFE)) != 0)
+
+static const struct {
+ const gchar open;
+ const gchar close;
+} url_braces[] = {
+ { '(', ')' },
+ { '{', '}' },
+ { '[', ']' },
+ { '<', '>' },
+ { '|', '|' },
+ { '\'', '\'' },
+};
+
+static gboolean
+is_open_brace (gchar c)
+{
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (url_braces); i++) {
+ if (c == url_braces[i].open)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static char
+url_stop_at_brace (const gchar *in,
+ gsize so,
+ gchar *open_brace)
+{
+ gint i;
+
+ if (open_brace != NULL)
+ *open_brace = '\0';
+
+ if (so > 0) {
+ for (i = 0; i < G_N_ELEMENTS (url_braces); i++) {
+ if (in[so - 1] == url_braces[i].open) {
+ if (open_brace != NULL)
+ *open_brace = url_braces[i].open;
+ return url_braces[i].close;
+ }
+ }
+ }
+
+ return '\0';
+}
+
+gboolean
+camel_url_addrspec_start (const gchar *in,
+ const gchar *pos,
+ const gchar *inend,
+ CamelUrlMatch *match)
+{
+ register const gchar *inptr = pos;
+
+ g_return_val_if_fail (*inptr == '@', FALSE);
+
+ if (inptr > in)
+ inptr--;
+
+ while (inptr > in) {
+ if (is_atom (*inptr))
+ inptr--;
+ else
+ break;
+
+ while (inptr > in && is_atom (*inptr))
+ inptr--;
+
+ if (inptr > in && *inptr == '.')
+ inptr--;
+ }
+
+ while (!is_atom (*inptr) || is_open_brace (*inptr))
+ inptr++;
+
+ if (inptr >= pos)
+ return FALSE;
+
+ match->um_so = (inptr - in);
+
+ return TRUE;
+}
+
+gboolean
+camel_url_addrspec_end (const gchar *in,
+ const gchar *pos,
+ const gchar *inend,
+ CamelUrlMatch *match)
+{
+ const gchar *inptr = pos;
+ gint parts = 0, digits;
+ gboolean got_dot = FALSE;
+
+ g_return_val_if_fail (*inptr == '@', FALSE);
+
+ inptr++;
+
+ if (*inptr == '[') {
+ /* domain literal */
+ do {
+ inptr++;
+
+ digits = 0;
+ while (inptr < inend && is_digit (*inptr) && digits < 3) {
+ inptr++;
+ digits++;
+ }
+
+ parts++;
+
+ if (*inptr != '.' && parts != 4)
+ return FALSE;
+ } while (parts < 4);
+
+ if (*inptr == ']')
+ inptr++;
+ else
+ return FALSE;
+
+ got_dot = TRUE;
+ } else {
+ while (inptr < inend) {
+ if (is_domain (*inptr))
+ inptr++;
+ else
+ break;
+
+ while (inptr < inend && is_domain (*inptr))
+ inptr++;
+
+ if (inptr < inend && *inptr == '.' && is_domain (inptr[1])) {
+ if (*inptr == '.')
+ got_dot = TRUE;
+ inptr++;
+ }
+ }
+ }
+
+ /* don't allow toplevel domains */
+ if (inptr == pos + 1 || !got_dot)
+ return FALSE;
+
+ match->um_eo = (inptr - in);
+
+ return TRUE;
+}
+
+gboolean
+camel_url_file_start (const gchar *in,
+ const gchar *pos,
+ const gchar *inend,
+ CamelUrlMatch *match)
+{
+ match->um_so = (pos - in);
+
+ return TRUE;
+}
+
+gboolean
+camel_url_file_end (const gchar *in,
+ const gchar *pos,
+ const gchar *inend,
+ CamelUrlMatch *match)
+{
+ register const gchar *inptr = pos;
+ gchar close_brace;
+
+ inptr += strlen (match->pattern);
+
+ if (*inptr == '/')
+ inptr++;
+
+ close_brace = url_stop_at_brace (in, match->um_so, NULL);
+
+ while (inptr < inend && is_urlsafe (*inptr) && *inptr != close_brace)
+ inptr++;
+
+ if (inptr == pos)
+ return FALSE;
+
+ match->um_eo = (inptr - in);
+
+ return TRUE;
+}
+
+gboolean
+camel_url_web_start (const gchar *in,
+ const gchar *pos,
+ const gchar *inend,
+ CamelUrlMatch *match)
+{
+ if (pos > in && !strncmp (pos, "www", 3)) {
+ /* make sure we aren't actually part of another word */
+ if (!is_open_brace (pos[-1]) && !isspace (pos[-1]))
+ return FALSE;
+ }
+
+ match->um_so = (pos - in);
+
+ return TRUE;
+}
+
+gboolean
+camel_url_web_end (const gchar *in,
+ const gchar *pos,
+ const gchar *inend,
+ CamelUrlMatch *match)
+{
+ register const gchar *inptr = pos;
+ gboolean passwd = FALSE;
+ const gchar *save;
+ gchar close_brace, open_brace;
+ gint brace_stack = 0;
+ gint port;
+
+ inptr += strlen (match->pattern);
+
+ close_brace = url_stop_at_brace (in, match->um_so, &open_brace);
+
+ /* find the end of the domain */
+ if (is_atom (*inptr)) {
+ /* might be a domain or user@domain */
+ save = inptr;
+ while (inptr < inend) {
+ if (!is_atom (*inptr))
+ break;
+
+ inptr++;
+
+ while (inptr < inend && is_atom (*inptr))
+ inptr++;
+
+ if ((inptr + 1) < inend && *inptr == '.' && (is_atom (inptr[1]) || inptr[1] == '/'))
+ inptr++;
+ }
+
+ if (*inptr != '@')
+ inptr = save;
+ else
+ inptr++;
+
+ goto domain;
+ } else if (is_domain (*inptr)) {
+ domain:
+ while (inptr < inend) {
+ if (!is_domain (*inptr))
+ break;
+
+ inptr++;
+
+ while (inptr < inend && is_domain (*inptr))
+ inptr++;
+
+ if ((inptr + 1) < inend && *inptr == '.' && (is_domain (inptr[1]) || inptr[1] == '/'))
+ inptr++;
+ }
+ } else {
+ return FALSE;
+ }
+
+ if (inptr < inend) {
+ switch (*inptr) {
+ case ':': /* we either have a port or a password */
+ inptr++;
+
+ if (is_digit (*inptr) || passwd) {
+ port = (*inptr++ - '0');
+
+ while (inptr < inend && is_digit (*inptr) && port < 65536)
+ port = (port * 10) + (*inptr++ - '0');
+
+ if (!passwd && (port >= 65536 || *inptr == '@')) {
+ if (inptr < inend) {
+ /* this must be a password? */
+ goto passwd;
+ }
+
+ inptr--;
+ }
+ } else {
+ passwd:
+ passwd = TRUE;
+ save = inptr;
+
+ while (inptr < inend && is_atom (*inptr))
+ inptr++;
+
+ if ((inptr + 2) < inend) {
+ if (*inptr == '@') {
+ inptr++;
+ if (is_domain (*inptr))
+ goto domain;
+ }
+
+ return FALSE;
+ }
+ }
+
+ if (inptr >= inend || *inptr != '/')
+ break;
+
+ /* we have a '/' so there could be a path - fall through */
+ case '/': /* we've detected a path component to our url */
+ inptr++;
+ /* coverity[fallthrough] */
+ case '?':
+ while (inptr < inend && is_urlsafe (*inptr)) {
+ if (*inptr == open_brace) {
+ brace_stack++;
+ } else if (*inptr == close_brace) {
+ brace_stack--;
+ if (brace_stack == -1)
+ break;
+ }
+ inptr++;
+ }
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* urls are extremely unlikely to end with any
+ * punctuation, so strip any trailing
+ * punctuation off. Also strip off any closing
+ * double-quotes. */
+ while (inptr > pos && strchr (",.:;?!-|}])\"", inptr[-1]))
+ inptr--;
+
+ match->um_eo = (inptr - in);
+
+ return TRUE;
+}
+
+#ifdef BUILD_TABLE
+
+/* got these from rfc1738 */
+#define CHARS_LWSP " \t\n\r" /* linear whitespace chars */
+#define CHARS_SPECIAL "()<>@,;:\\\".[]"
+
+/* got these from rfc1738 */
+#define CHARS_URLSAFE "$-_.+!*'(),{}|\\^~[]`#%\";/?:@&="
+
+static void
+table_init_bits (guint mask,
+ const guchar *vals)
+{
+ gint i;
+
+ for (i = 0; vals[i] != '\0'; i++)
+ url_scanner_table[vals[i]] |= mask;
+}
+
+static void
+url_scanner_table_init (void)
+{
+ gint i;
+
+ for (i = 0; i < 256; i++) {
+ url_scanner_table[i] = 0;
+ if (i < 32)
+ url_scanner_table[i] |= IS_CTRL;
+ if ((i >= '0' && i <= '9'))
+ url_scanner_table[i] |= IS_DIGIT | IS_DOMAIN;
+ if ((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z'))
+ url_scanner_table[i] |= IS_ALPHA | IS_DOMAIN;
+ if (i >= 127)
+ url_scanner_table[i] |= IS_CTRL;
+ }
+
+ url_scanner_table[' '] |= IS_SPACE;
+ url_scanner_table['-'] |= IS_DOMAIN;
+
+ /* not defined to be special in rfc0822, but when scanning
+ * backwards to find the beginning of the email address we do
+ * not want to include this gchar if we come accross it - so
+ * this is kind of a hack */
+ url_scanner_table['/'] |= IS_SPECIAL;
+
+ table_init_bits (IS_LWSP, CHARS_LWSP);
+ table_init_bits (IS_SPECIAL, CHARS_SPECIAL);
+ table_init_bits (IS_URLSAFE, CHARS_URLSAFE);
+}
+
+gint main (gint argc, gchar **argv)
+{
+ gint i;
+
+ url_scanner_table_init ();
+
+ printf ("static guchar url_scanner_table[256] = {");
+ for (i = 0; i < 256; i++) {
+ printf (
+ "%s%3d%s", (i % 16) ? "" : "\n\t",
+ url_scanner_table[i], i != 255 ? "," : "\n");
+ }
+ printf ("};\n\n");
+
+ return 0;
+}
+
+#endif /* BUILD_TABLE */
diff --git a/src/camel/camel-url-scanner.h b/src/camel/camel-url-scanner.h
new file mode 100644
index 000000000..75fd82d3b
--- /dev/null
+++ b/src/camel/camel-url-scanner.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_URL_SCANNER_H
+#define CAMEL_URL_SCANNER_H
+
+#include <glib.h>
+#include <sys/types.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+ const gchar *pattern;
+ const gchar *prefix;
+ goffset um_so;
+ goffset um_eo;
+} CamelUrlMatch;
+
+typedef gboolean (*CamelUrlScanFunc) (const gchar *in, const gchar *pos, const gchar *inend, CamelUrlMatch *match);
+
+/* some default CamelUrlScanFunc's */
+gboolean camel_url_file_start (const gchar *in, const gchar *pos, const gchar *inend, CamelUrlMatch *match);
+gboolean camel_url_file_end (const gchar *in, const gchar *pos, const gchar *inend, CamelUrlMatch *match);
+gboolean camel_url_web_start (const gchar *in, const gchar *pos, const gchar *inend, CamelUrlMatch *match);
+gboolean camel_url_web_end (const gchar *in, const gchar *pos, const gchar *inend, CamelUrlMatch *match);
+gboolean camel_url_addrspec_start (const gchar *in, const gchar *pos, const gchar *inend, CamelUrlMatch *match);
+gboolean camel_url_addrspec_end (const gchar *in, const gchar *pos, const gchar *inend, CamelUrlMatch *match);
+
+typedef struct {
+ const gchar *pattern;
+ const gchar *prefix;
+ CamelUrlScanFunc start;
+ CamelUrlScanFunc end;
+} CamelUrlPattern;
+
+typedef struct _CamelUrlScanner CamelUrlScanner;
+
+CamelUrlScanner *camel_url_scanner_new (void);
+void camel_url_scanner_free (CamelUrlScanner *scanner);
+
+void camel_url_scanner_add (CamelUrlScanner *scanner, CamelUrlPattern *pattern);
+
+gboolean camel_url_scanner_scan (CamelUrlScanner *scanner, const gchar *in, gsize inlen, CamelUrlMatch *match);
+
+G_END_DECLS
+
+#endif /* CAMEL_URL_SCANNER_H */
diff --git a/src/camel/camel-url.c b/src/camel/camel-url.c
new file mode 100644
index 000000000..b51ed09ee
--- /dev/null
+++ b/src/camel/camel-url.c
@@ -0,0 +1,814 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-url.c : utility functions to parse URLs
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-mime-utils.h"
+#include "camel-object.h"
+#include "camel-service.h"
+#include "camel-string-utils.h"
+#include "camel-url.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 gchar *in, const gchar *extra_enc_chars);
+
+GType
+camel_url_get_type (void)
+{
+ static GType type = G_TYPE_INVALID;
+
+ if (G_UNLIKELY (type == G_TYPE_INVALID))
+ type = g_boxed_type_register_static (
+ "CamelURL",
+ (GBoxedCopyFunc) camel_url_copy,
+ (GBoxedFreeFunc) camel_url_free);
+
+ return type;
+}
+
+/**
+ * camel_url_new_with_base:
+ * @base: a base URL
+ * @url_string: the URL
+ *
+ * Parses @url_string relative to @base.
+ *
+ * Returns: a parsed #CamelURL
+ **/
+CamelURL *
+camel_url_new_with_base (CamelURL *base,
+ const gchar *url_string)
+{
+ CamelURL *url;
+ const gchar *end, *hash, *colon, *semi, *at, *slash, *question;
+ const gchar *p;
+
+#ifdef G_OS_WIN32
+ const gchar *start = url_string;
+#endif
+
+ g_return_val_if_fail (url_string != NULL, NULL);
+
+ 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 ((guchar) * 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;
+
+#ifdef G_OS_WIN32
+ if (url->protocol && !strcmp (url->protocol, "file")) {
+ url->path = g_filename_from_uri (start, &url->host, NULL);
+ return url;
+ }
+#endif
+
+ /* 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) {
+ /* XXX We used to extract and store the
+ * password here, now we just eat it. */
+ } else {
+ colon = at;
+ }
+
+ semi = strchr (url_string, ';');
+ if (semi && semi < colon &&
+ !g_ascii_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->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 gchar *cur, *p, *eq;
+ gchar *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->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->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 != '/') {
+ gchar *newpath, *last, *p, *q;
+
+ /* the base->path is NULL if given Content-Base url was without last slash,
+ * i.e. like "http://example.com" (this expected only "http://example.com/") */
+ last = base->path ? strrchr (base->path, '/') : NULL;
+ if (last) {
+ newpath = g_strdup_printf (
+ "%.*s/%s",
+ (gint)(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 string
+ * @error: return location for a #GError, or %NULL
+ *
+ * Parses an absolute URL.
+ *
+ * Returns: a #CamelURL if it can be parsed, or %NULL otherwise
+ **/
+CamelURL *
+camel_url_new (const gchar *url_string,
+ GError **error)
+{
+ CamelURL *url;
+
+ if (!url_string || !*url_string)
+ return NULL;
+
+ url = camel_url_new_with_base (NULL, url_string);
+
+ if (!url->protocol) {
+ camel_url_free (url);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Could not parse URL '%s'"), url_string);
+ return NULL;
+ }
+ return url;
+}
+
+/**
+ * camel_url_to_string:
+ * @url: a #CamelURL
+ * @flags: additional translation options
+ *
+ * Flatten a #CamelURL into a string.
+ *
+ * Returns: a string representing @url, which the caller must free
+ **/
+gchar *
+camel_url_to_string (CamelURL *url,
+ CamelURLFlags flags)
+{
+ GString *str;
+ gchar *return_result;
+
+ g_return_val_if_fail (url != NULL, NULL);
+
+ /* IF YOU CHANGE ANYTHING IN THIS FUNCTION, RUN
+ * tests/misc/url AFTERWARD.
+ */
+
+#ifdef G_OS_WIN32
+ if (url->protocol && !strcmp (url->protocol, "file"))
+ return g_filename_to_uri (url->path, url->host, NULL);
+#endif /* G_OS_WIN32 */
+
+ 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 && !(flags & CAMEL_URL_HIDE_AUTH)) {
+ g_string_append (str, ";auth=");
+ append_url_encoded (str, url->authmech, ":@/");
+ }
+ 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 (*(gchar *) 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->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->host);
+ g_free (url->path);
+ g_datalist_clear (&url->params);
+ g_free (url->query);
+ g_free (url->fragment);
+
+ g_free (url);
+ }
+}
+
+/**
+ * camel_url_set_protocol:
+ * @url: a #CamelURL
+ * @protocol: protocol schema
+ *
+ * Set the protocol of a #CamelURL.
+ **/
+void
+camel_url_set_protocol (CamelURL *url,
+ const gchar *protocol)
+{
+ g_return_if_fail (url != NULL);
+
+ g_free (url->protocol);
+ url->protocol = g_strdup (protocol);
+}
+
+/**
+ * camel_url_set_user:
+ * @url: a #CamelURL
+ * @user: username
+ *
+ * Set the user of a #CamelURL.
+ **/
+void
+camel_url_set_user (CamelURL *url,
+ const gchar *user)
+{
+ g_return_if_fail (url != NULL);
+
+ g_free (url->user);
+ url->user = g_strdup (user);
+}
+
+/**
+ * camel_url_set_authmech:
+ * @url: a #CamelURL
+ * @authmech: authentication mechanism
+ *
+ * Set the authmech of a #CamelURL.
+ **/
+void
+camel_url_set_authmech (CamelURL *url,
+ const gchar *authmech)
+{
+ g_return_if_fail (url != NULL);
+
+ g_free (url->authmech);
+ url->authmech = g_strdup (authmech);
+}
+
+/**
+ * camel_url_set_host:
+ * @url: a #CamelURL
+ * @host: hostname
+ *
+ * Set the hostname of a #CamelURL.
+ **/
+void
+camel_url_set_host (CamelURL *url,
+ const gchar *host)
+{
+ g_return_if_fail (url != NULL);
+
+ g_free (url->host);
+ url->host = g_strdup (host);
+}
+
+/**
+ * camel_url_set_path:
+ * @url: a #CamelURL
+ * @path: path
+ *
+ * Set the path component of a #CamelURL.
+ **/
+void
+camel_url_set_path (CamelURL *url,
+ const gchar *path)
+{
+ g_return_if_fail (url != NULL);
+
+ g_free (url->path);
+ url->path = g_strdup (path);
+}
+
+/**
+ * camel_url_set_query:
+ * @url: a #CamelURL
+ * @query: url query
+ *
+ * Set the query of a #CamelURL.
+ **/
+void
+camel_url_set_query (CamelURL *url,
+ const gchar *query)
+{
+ g_return_if_fail (url != NULL);
+
+ g_free (url->query);
+ url->query = g_strdup (query);
+}
+
+/**
+ * camel_url_set_fragment:
+ * @url: a #CamelURL
+ * @fragment: url fragment
+ *
+ * Set the fragment of a #CamelURL.
+ **/
+void
+camel_url_set_fragment (CamelURL *url,
+ const gchar *fragment)
+{
+ g_return_if_fail (url != NULL);
+
+ g_free (url->fragment);
+ url->fragment = g_strdup (fragment);
+}
+
+/**
+ * camel_url_set_port:
+ * @url: a #CamelURL
+ * @port: port
+ *
+ * Set the port on a #CamelURL.
+ **/
+void
+camel_url_set_port (CamelURL *url,
+ gint port)
+{
+ g_return_if_fail (url != NULL);
+
+ url->port = port;
+}
+
+/**
+ * camel_url_set_param:
+ * @url: a #CamelURL
+ * @name: name of the param to set
+ * @value: value of the param to set
+ *
+ * Set a param on the #CamelURL.
+ **/
+void
+camel_url_set_param (CamelURL *url,
+ const gchar *name,
+ const gchar *value)
+{
+ g_return_if_fail (url != NULL);
+
+ if (value)
+ g_datalist_set_data_full (&url->params, name, g_strdup (value), g_free);
+ else
+ g_datalist_remove_data (&url->params, name);
+}
+
+/**
+ * camel_url_get_param:
+ * @url: a #CamelURL
+ * @name: name of the param
+ *
+ * Get the value of the specified param on the URL.
+ *
+ * Returns: the value of a param if found or %NULL otherwise
+ **/
+const gchar *
+camel_url_get_param (CamelURL *url,
+ const gchar *name)
+{
+ g_return_val_if_fail (url != NULL, NULL);
+
+ return g_datalist_get_data (&url->params, name);
+}
+
+/* From RFC 2396 2.4.3, the characters that should always be encoded */
+static const gchar 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 gchar *in,
+ const gchar *extra_enc_chars)
+{
+ const guchar *s = (const guchar *) in;
+
+ while (*s) {
+ if (url_encoded_char[*s] ||
+ (extra_enc_chars && strchr (extra_enc_chars, *s)))
+ g_string_append_printf (str, "%%%02x", (gint) * 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.
+ *
+ * Returns: the encoded string
+ **/
+gchar *
+camel_url_encode (const gchar *part,
+ const gchar *escape_extra)
+{
+ GString *str;
+ gchar *encoded;
+
+ g_return_val_if_fail (part != NULL, NULL);
+
+ 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 (gchar *part)
+{
+ guchar *s, *d;
+
+ g_return_if_fail (part != NULL);
+
+#define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
+
+ s = d = (guchar *) 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 (gconstpointer 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 gint
+check_equal (gchar *s1,
+ gchar *s2)
+{
+ if (s1 == NULL) {
+ if (s2 == NULL)
+ return TRUE;
+ else
+ return FALSE;
+ }
+
+ if (s2 == NULL)
+ return FALSE;
+
+ return strcmp (s1, s2) == 0;
+}
+
+gint
+camel_url_equal (gconstpointer v,
+ gconstpointer 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;
+}
+
+/**
+ * camel_url_copy:
+ * @in: a #CamelURL to copy
+ *
+ * Copy a #CamelURL.
+ *
+ * Returns: a duplicate copy of @in
+ **/
+CamelURL *
+camel_url_copy (CamelURL *in)
+{
+ CamelURL *out;
+
+ g_return_val_if_fail (in != NULL, NULL);
+
+ out = g_malloc0 (sizeof (*out));
+ out->protocol = g_strdup (in->protocol);
+ out->user = g_strdup (in->user);
+ out->authmech = g_strdup (in->authmech);
+ 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;
+}
+
+gchar *
+camel_url_decode_path (const gchar *path)
+{
+ gchar **comps;
+ GString *str;
+ guint length, ii;
+
+ if (path == NULL || *path == '\0')
+ return g_strdup (""); /* ??? or NULL? */
+
+ str = g_string_new (NULL);
+
+ comps = g_strsplit (path, "/", -1);
+ length = g_strv_length (comps);
+
+ for (ii = 0; ii < length; ii++) {
+ if (ii > 0)
+ g_string_append_c (str, '/');
+ camel_url_decode (comps[ii]);
+ g_string_append (str, comps[ii]);
+ }
+
+ g_strfreev (comps);
+
+ return g_string_free (str, FALSE);
+}
+
diff --git a/src/camel/camel-url.h b/src/camel/camel-url.h
new file mode 100644
index 000000000..db3a26e3e
--- /dev/null
+++ b/src/camel/camel-url.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-url.h : utility functions to parse URLs
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Dan Winship <danw@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_URL_H
+#define CAMEL_URL_H
+
+#include <glib.h>
+
+#define CAMEL_TYPE_URL (camel_url_get_type ())
+
+G_BEGIN_DECLS
+
+typedef struct _CamelURL CamelURL;
+
+/* if this changes, remember to change camel_url_copy */
+struct _CamelURL {
+ gchar *protocol;
+ gchar *user;
+ gchar *authmech;
+ gchar *host;
+ gint port;
+ gchar *path;
+ GData *params;
+ gchar *query;
+ gchar *fragment;
+};
+
+typedef enum {
+ CAMEL_URL_HIDE_PARAMS = 1 << 0,
+ CAMEL_URL_HIDE_AUTH = 1 << 1
+} CamelURLFlags;
+
+#define CAMEL_URL_HIDE_ALL \
+ (CAMEL_URL_HIDE_PARAMS | CAMEL_URL_HIDE_AUTH)
+
+GType camel_url_get_type (void) G_GNUC_CONST;
+CamelURL * camel_url_new_with_base (CamelURL *base,
+ const gchar *url_string);
+CamelURL * camel_url_new (const gchar *url_string,
+ GError **error);
+gchar * camel_url_to_string (CamelURL *url,
+ CamelURLFlags flags);
+guint camel_url_hash (gconstpointer v);
+gint camel_url_equal (gconstpointer v,
+ gconstpointer v2);
+CamelURL * camel_url_copy (CamelURL *in);
+void camel_url_free (CamelURL *url);
+
+gchar * camel_url_encode (const gchar *part,
+ const gchar *escape_extra);
+void camel_url_decode (gchar *part);
+gchar * camel_url_decode_path (const gchar *path);
+
+/* for editing url's */
+void camel_url_set_protocol (CamelURL *url,
+ const gchar *protocol);
+void camel_url_set_user (CamelURL *url,
+ const gchar *user);
+void camel_url_set_authmech (CamelURL *url,
+ const gchar *authmech);
+void camel_url_set_host (CamelURL *url,
+ const gchar *host);
+void camel_url_set_port (CamelURL *url,
+ gint port);
+void camel_url_set_path (CamelURL *url,
+ const gchar *path);
+void camel_url_set_param (CamelURL *url,
+ const gchar *name,
+ const gchar *value);
+void camel_url_set_query (CamelURL *url,
+ const gchar *query);
+void camel_url_set_fragment (CamelURL *url,
+ const gchar *fragment);
+
+const gchar * camel_url_get_param (CamelURL *url,
+ const gchar *name);
+
+G_END_DECLS
+
+#endif /* URL_UTIL_H */
diff --git a/src/camel/camel-utf8.c b/src/camel/camel-utf8.c
new file mode 100644
index 000000000..b2c742046
--- /dev/null
+++ b/src/camel/camel-utf8.c
@@ -0,0 +1,422 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+#include <sys/types.h>
+
+#include "camel-utf8.h"
+
+/**
+ * camel_utf8_putc:
+ * @ptr:
+ * @c:
+ *
+ * Output a 32 bit unicode character as utf8 octets. At most 4 octets will
+ * be written to @ptr. @ptr will be advanced to the next character position.
+ **/
+void
+camel_utf8_putc (guchar **ptr,
+ guint32 c)
+{
+ register guchar *p = *ptr;
+
+ if (c <= 0x7f)
+ *p++ = c;
+ else if (c <= 0x7ff) {
+ *p++ = 0xc0 | c >> 6;
+ *p++ = 0x80 | (c & 0x3f);
+ } else if (c <= 0xffff) {
+ *p++ = 0xe0 | c >> 12;
+ *p++ = 0x80 | ((c >> 6) & 0x3f);
+ *p++ = 0x80 | (c & 0x3f);
+ } else {
+ /* see unicode standard 3.0, S 3.8, max 4 octets */
+ *p++ = 0xf0 | c >> 18;
+ *p++ = 0x80 | ((c >> 12) & 0x3f);
+ *p++ = 0x80 | ((c >> 6) & 0x3f);
+ *p++ = 0x80 | (c & 0x3f);
+ }
+
+ *ptr = p;
+}
+
+/**
+ * camel_utf8_getc:
+ * @ptr:
+ *
+ * Get a Unicode character from a utf8 stream. @ptr will be advanced
+ * to the next character position. Invalid utf8 characters will be
+ * silently skipped. @ptr should point to a NUL terminated array.
+ *
+ * Returns: The next Unicode character. @ptr will be advanced to
+ * the next character always.
+ **/
+guint32
+camel_utf8_getc (const guchar **ptr)
+{
+ register guchar *p = (guchar *) * ptr;
+ register guchar c, r;
+ register guint32 v, m;
+
+again:
+ r = *p++;
+loop:
+ if (r < 0x80) {
+ *ptr = p;
+ v = r;
+ } else if (r < 0xf8) { /* valid start char? (max 4 octets) */
+ v = r;
+ m = 0x7f80; /* used to mask out the length bits */
+ do {
+ c = *p++;
+ if ((c & 0xc0) != 0x80) {
+ r = c;
+ goto loop;
+ }
+ v = (v << 6) | (c & 0x3f);
+ r <<= 1;
+ m <<= 5;
+ } while (r & 0x40);
+
+ *ptr = p;
+
+ v &= ~m;
+ } else {
+ goto again;
+ }
+
+ return v;
+}
+
+/**
+ * camel_utf8_getc_limit:
+ * @ptr:
+ * @end: must not be NULL.
+ *
+ * Get the next utf8 gchar at @ptr, and return it, advancing @ptr to
+ * the next character. If @end is reached before a full utf8
+ * character can be read, then the invalid Unicode gchar 0xffff is
+ * returned as a sentinel (Unicode 3.1, section 2.7), and @ptr is not
+ * advanced.
+ *
+ * Returns: The next utf8 char, or 0xffff.
+ **/
+guint32
+camel_utf8_getc_limit (const guchar **ptr,
+ const guchar *end)
+{
+ register guchar *p = (guchar *) * ptr;
+ register guchar c, r;
+ register guint32 v = 0xffff, m;
+
+again:
+ while (p < end) {
+ r = *p++;
+loop:
+ if (r < 0x80) {
+ *ptr = p;
+ return r;
+ } else if (r < 0xf8) { /* valid start char? (max 4 octets) */
+ v = r;
+ m = 0x7f80; /* used to mask out the length bits */
+ do {
+ if (p >= end)
+ return 0xffff;
+
+ c = *p++;
+ if ((c & 0xc0) != 0x80) {
+ r = c;
+ goto loop;
+ }
+ v = (v << 6) | (c & 0x3f);
+ r <<= 1;
+ m <<= 5;
+ } while (r & 0x40);
+
+ *ptr = p;
+
+ v &= ~m;
+ return v;
+ } else {
+ goto again;
+ }
+ }
+
+ return 0xffff;
+}
+
+void
+g_string_append_u (GString *out,
+ guint32 c)
+{
+ guchar buffer[8];
+ guchar *p = buffer;
+
+ camel_utf8_putc (&p, c);
+ *p = 0;
+ g_string_append (out, (const gchar *) buffer);
+}
+
+static const gchar utf7_alphabet[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
+
+static const guchar utf7_rank[256] = {
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3e,0x3f,0xff,0xff,0xff,
+ 0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,
+ 0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
+ 0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+};
+
+/**
+ * camel_utf7_utf8:
+ * @ptr:
+ *
+ * Convert a modified utf7 string to utf8. If the utf7 string
+ * contains 8 bit characters, they are treated as iso-8859-1.
+ *
+ * The IMAP rules [rfc2060] are used in the utf7 encoding.
+ *
+ * Returns: The converted string.
+ **/
+gchar *
+camel_utf7_utf8 (const gchar *ptr)
+{
+ const guchar *p = (guchar *) ptr;
+ guint c;
+ guint32 v = 0, x;
+ GString *out;
+ gint i = 0;
+ gint state = 0;
+ gchar *ret;
+
+ out = g_string_new ("");
+ do {
+ c = *p++;
+ switch (state) {
+ case 0:
+ if (c == '&')
+ state = 1;
+ else
+ g_string_append_c (out, c);
+ break;
+ case 1:
+ if (c == '-') {
+ g_string_append_c (out, '&');
+ state = 0;
+ } else if (utf7_rank[c] != 0xff) {
+ v = utf7_rank[c];
+ i = 6;
+ state = 2;
+ } else {
+ /* invalid */
+ g_string_append (out, "&-");
+ state = 0;
+ }
+ break;
+ case 2:
+ if (c == '-') {
+ state = 0;
+ } else if (utf7_rank[c] != 0xff) {
+ v = (v << 6) | utf7_rank[c];
+ i+=6;
+ if (i >= 16) {
+ x = (v >> (i - 16)) & 0xffff;
+ g_string_append_u (out, x);
+ i-=16;
+ }
+ } else {
+ g_string_append_u (out, c);
+ state = 0;
+ }
+ break;
+ }
+ } while (c);
+
+ ret = g_strdup (out->str);
+ g_string_free (out, TRUE);
+
+ return ret;
+}
+
+static void utf7_closeb64 (GString *out, guint32 v, guint32 i)
+{
+ guint32 x;
+
+ if (i > 0) {
+ x = (v << (6 - i)) & 0x3f;
+ g_string_append_c (out, utf7_alphabet[x]);
+ }
+ g_string_append_c (out, '-');
+}
+
+/**
+ * camel_utf8_utf7:
+ * @ptr:
+ *
+ * Convert a utf8 string to a modified utf7 format.
+ *
+ * The IMAP rules [rfc2060] are used in the utf7 encoding.
+ *
+ * Returns:
+ **/
+gchar *
+camel_utf8_utf7 (const gchar *ptr)
+{
+ const guchar *p = (guchar *) ptr;
+ guint c;
+ guint32 x, v = 0;
+ gint state = 0;
+ GString *out;
+ gint i = 0;
+ gchar *ret;
+
+ out = g_string_new ("");
+
+ while ((c = camel_utf8_getc (&p))) {
+ if (c >= 0x20 && c <= 0x7e) {
+ if (state == 1) {
+ utf7_closeb64 (out, v, i);
+ state = 0;
+ i = 0;
+ }
+ if (c == '&')
+ g_string_append (out, "&-");
+ else
+ g_string_append_c (out, c);
+ } else {
+ if (state == 0) {
+ g_string_append_c (out, '&');
+ state = 1;
+ }
+ v = (v << 16) | c;
+ i += 16;
+ while (i >= 6) {
+ x = (v >> (i - 6)) & 0x3f;
+ g_string_append_c (out, utf7_alphabet[x]);
+ i -= 6;
+ }
+ }
+ }
+
+ if (state == 1)
+ utf7_closeb64 (out, v, i);
+
+ ret = g_strdup (out->str);
+ g_string_free (out, TRUE);
+
+ return ret;
+}
+
+/**
+ * camel_utf8_ucs2:
+ * @ptr:
+ *
+ * Convert a utf8 string into a ucs2 one. The ucs string will be in
+ * network byte order, and terminated with a 16 bit NULL.
+ *
+ * Returns:
+ **/
+gchar *
+camel_utf8_ucs2 (const gchar *pptr)
+{
+ GByteArray *work = g_byte_array_new ();
+ guint32 c;
+ gchar *out;
+ const guchar *ptr = (const guchar *) pptr;
+
+ /* what if c is > 0xffff ? */
+
+ while ((c = camel_utf8_getc (&ptr))) {
+ guint16 s = g_htons (c);
+
+ g_byte_array_append (work, (guchar *) &s, 2);
+ }
+
+ g_byte_array_append (work, (guchar *) "\000\000", 2);
+ out = g_malloc (work->len);
+ memcpy (out, work->data, work->len);
+ g_byte_array_free (work, TRUE);
+
+ return out;
+}
+
+/**
+ * camel_ucs2_utf8:
+ * @ptr:
+ *
+ * Convert a ucs2 string into a utf8 one. The ucs2 string is treated
+ * as network byte ordered, and terminated with a 16 bit NUL.
+ *
+ * Returns:
+ **/
+gchar *camel_ucs2_utf8 (const gchar *ptr)
+{
+ guint16 *ucs = (guint16 *) ptr;
+ guint32 c;
+ GString *work = g_string_new ("");
+ gchar *out;
+
+ while ((c = *ucs++))
+ g_string_append_u (work, g_ntohs (c));
+
+ out = g_strdup (work->str);
+ g_string_free (work, TRUE);
+
+ return out;
+}
+
+/**
+ * camel_utf8_make_valid:
+ * @text:
+ *
+ * Ensures the returned text will be valid UTF-8 string, with incorrect letters
+ * changed to question marks. Returned pointer should be freed with g_free.
+ *
+ * Since: 2.26
+ **/
+gchar *
+camel_utf8_make_valid (const gchar *text)
+{
+ gchar *res = g_strdup (text), *p;
+
+ if (!res)
+ return res;
+
+ p = res;
+ while (!g_utf8_validate (p, -1, (const gchar **) &p)) {
+ /* make all invalid characters appear as question marks */
+ *p = '?';
+ }
+
+ return res;
+}
diff --git a/src/camel/camel-utf8.h b/src/camel/camel-utf8.h
new file mode 100644
index 000000000..d509f06ba
--- /dev/null
+++ b/src/camel/camel-utf8.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_UTF8_H
+#define CAMEL_UTF8_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void camel_utf8_putc (guchar **ptr, guint32 c);
+guint32 camel_utf8_getc (const guchar **ptr);
+guint32 camel_utf8_getc_limit (const guchar **ptr, const guchar *end);
+
+/* utility func for utf8 gstrings */
+void g_string_append_u (GString *out, guint32 c);
+
+/* convert utf7 to/from utf8, actually this is modified IMAP utf7 */
+gchar *camel_utf7_utf8 (const gchar *ptr);
+gchar *camel_utf8_utf7 (const gchar *ptr);
+
+/* convert ucs2 to/from utf8 */
+gchar *camel_utf8_ucs2 (const gchar *ptr);
+gchar *camel_ucs2_utf8 (const gchar *ptr);
+
+/* make valid utf8 string */
+gchar *camel_utf8_make_valid (const gchar *text);
+
+G_END_DECLS
+
+#endif /* CAMEL_UTF8_H */
diff --git a/src/camel/camel-vee-data-cache.c b/src/camel/camel-vee-data-cache.c
new file mode 100644
index 000000000..b3567e8b5
--- /dev/null
+++ b/src/camel/camel-vee-data-cache.c
@@ -0,0 +1,830 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Milan Crha <mcrha@redhat.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include "camel-string-utils.h"
+#include "camel-store.h"
+
+#include "camel-vee-data-cache.h"
+
+#define CAMEL_VEE_SUBFOLDER_DATA_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_VEE_SUBFOLDER_DATA, CamelVeeSubfolderDataPrivate))
+
+struct _CamelVeeSubfolderDataPrivate {
+ CamelFolder *folder;
+ const gchar *folder_id; /* stored in string pool */
+};
+
+G_DEFINE_TYPE (
+ CamelVeeSubfolderData,
+ camel_vee_subfolder_data,
+ G_TYPE_OBJECT)
+
+static void
+vee_subfolder_data_dispose (GObject *object)
+{
+ CamelVeeSubfolderDataPrivate *priv;
+
+ priv = CAMEL_VEE_SUBFOLDER_DATA_GET_PRIVATE (object);
+
+ g_clear_object (&priv->folder);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_vee_subfolder_data_parent_class)->
+ dispose (object);
+}
+
+static void
+vee_subfolder_data_finalize (GObject *object)
+{
+ CamelVeeSubfolderDataPrivate *priv;
+
+ priv = CAMEL_VEE_SUBFOLDER_DATA_GET_PRIVATE (object);
+
+ if (priv->folder_id != NULL)
+ camel_pstring_free (priv->folder_id);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_vee_subfolder_data_parent_class)->
+ finalize (object);
+}
+
+static void
+camel_vee_subfolder_data_class_init (CamelVeeSubfolderDataClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelVeeSubfolderDataPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = vee_subfolder_data_dispose;
+ object_class->finalize = vee_subfolder_data_finalize;
+}
+
+static void
+camel_vee_subfolder_data_init (CamelVeeSubfolderData *data)
+{
+ data->priv = CAMEL_VEE_SUBFOLDER_DATA_GET_PRIVATE (data);
+}
+
+static void
+vee_subfolder_data_hash_folder (CamelFolder *folder,
+ gchar buffer[8])
+{
+ CamelStore *parent_store;
+ GChecksum *checksum;
+ guint8 *digest;
+ gsize length;
+ gint state = 0, save = 0;
+ gchar *ptr_string;
+ const gchar *uid;
+ gint i;
+
+ length = g_checksum_type_get_length (G_CHECKSUM_MD5);
+ digest = g_alloca (length);
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ parent_store = camel_folder_get_parent_store (folder);
+ uid = camel_service_get_uid (CAMEL_SERVICE (parent_store));
+ g_checksum_update (checksum, (guchar *) uid, -1);
+
+ ptr_string = g_strdup_printf ("%p", folder);
+ g_checksum_update (checksum, (guchar *) ptr_string, -1);
+ g_free (ptr_string);
+
+ g_checksum_get_digest (checksum, digest, &length);
+ g_checksum_free (checksum);
+
+ g_base64_encode_step (digest, 6, FALSE, buffer, &state, &save);
+ g_base64_encode_close (FALSE, buffer, &state, &save);
+
+ for (i = 0; i < 8; i++) {
+ if (buffer[i] == '+')
+ buffer[i] = '.';
+ if (buffer[i] == '/')
+ buffer[i] = '_';
+ }
+}
+
+/**
+ * camel_vee_subfolder_data_new:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+CamelVeeSubfolderData *
+camel_vee_subfolder_data_new (CamelFolder *folder)
+{
+ CamelVeeSubfolderData *data;
+ gchar buffer[8], *folder_id;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ data = g_object_new (CAMEL_TYPE_VEE_SUBFOLDER_DATA, NULL);
+ data->priv->folder = g_object_ref (folder);
+
+ vee_subfolder_data_hash_folder (folder, buffer);
+ folder_id = g_strndup (buffer, 8);
+
+ data->priv->folder_id = camel_pstring_add (folder_id, TRUE);
+
+ return data;
+}
+
+/**
+ * camel_vee_subfolder_data_get_folder:
+ *
+ * FIXME Document me!
+ *
+ * Returns: (transfer none):
+ *
+ * Since: 3.6
+ **/
+CamelFolder *
+camel_vee_subfolder_data_get_folder (CamelVeeSubfolderData *data)
+{
+ g_return_val_if_fail (CAMEL_IS_VEE_SUBFOLDER_DATA (data), NULL);
+
+ return data->priv->folder;
+}
+
+/**
+ * camel_vee_subfolder_data_get_folder_id:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+const gchar *
+camel_vee_subfolder_data_get_folder_id (CamelVeeSubfolderData *data)
+{
+ g_return_val_if_fail (CAMEL_IS_VEE_SUBFOLDER_DATA (data), NULL);
+
+ return data->priv->folder_id;
+}
+
+/* ----------------------------------------------------------------------- */
+
+#define CAMEL_VEE_MESSAGE_INFO_DATA_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_VEE_MESSAGE_INFO_DATA, CamelVeeMessageInfoDataPrivate))
+
+struct _CamelVeeMessageInfoDataPrivate {
+ CamelVeeSubfolderData *subfolder_data;
+ const gchar *orig_message_uid; /* stored in string pool */
+ const gchar *vee_message_uid; /* stored in string pool */
+};
+
+G_DEFINE_TYPE (
+ CamelVeeMessageInfoData,
+ camel_vee_message_info_data,
+ G_TYPE_OBJECT)
+
+static void
+vee_message_info_data_dispose (GObject *object)
+{
+ CamelVeeMessageInfoDataPrivate *priv;
+
+ priv = CAMEL_VEE_MESSAGE_INFO_DATA_GET_PRIVATE (object);
+
+ g_clear_object (&priv->subfolder_data);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_vee_message_info_data_parent_class)->
+ dispose (object);
+}
+
+static void
+vee_message_info_data_finalize (GObject *object)
+{
+ CamelVeeMessageInfoDataPrivate *priv;
+
+ priv = CAMEL_VEE_MESSAGE_INFO_DATA_GET_PRIVATE (object);
+
+ if (priv->orig_message_uid != NULL)
+ camel_pstring_free (priv->orig_message_uid);
+
+ if (priv->vee_message_uid != NULL)
+ camel_pstring_free (priv->vee_message_uid);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_vee_message_info_data_parent_class)->
+ finalize (object);
+}
+
+static void
+camel_vee_message_info_data_class_init (CamelVeeMessageInfoDataClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (
+ class, sizeof (CamelVeeMessageInfoDataPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = vee_message_info_data_dispose;
+ object_class->finalize = vee_message_info_data_finalize;
+}
+
+static void
+camel_vee_message_info_data_init (CamelVeeMessageInfoData *data)
+{
+ data->priv = CAMEL_VEE_MESSAGE_INFO_DATA_GET_PRIVATE (data);
+}
+
+/**
+ * camel_vee_message_info_data_new:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+CamelVeeMessageInfoData *
+camel_vee_message_info_data_new (CamelVeeSubfolderData *subfolder_data,
+ const gchar *orig_message_uid)
+{
+ CamelVeeMessageInfoData *data;
+ gchar *vee_message_uid;
+
+ g_return_val_if_fail (CAMEL_IS_VEE_SUBFOLDER_DATA (subfolder_data), NULL);
+ g_return_val_if_fail (orig_message_uid != NULL, NULL);
+
+ data = g_object_new (CAMEL_TYPE_VEE_MESSAGE_INFO_DATA, NULL);
+ data->priv->subfolder_data = g_object_ref (subfolder_data);
+
+ vee_message_uid = g_strconcat (camel_vee_subfolder_data_get_folder_id (subfolder_data), orig_message_uid, NULL);
+
+ data->priv->orig_message_uid = camel_pstring_strdup (orig_message_uid);
+ data->priv->vee_message_uid = camel_pstring_add (vee_message_uid, TRUE);
+
+ return data;
+}
+
+/**
+ * camel_vee_message_info_data_get_subfolder_data:
+ *
+ * FIXME Document me!
+ *
+ * Returns: (transfer none):
+ *
+ * Since: 3.6
+ **/
+CamelVeeSubfolderData *
+camel_vee_message_info_data_get_subfolder_data (CamelVeeMessageInfoData *data)
+{
+ g_return_val_if_fail (CAMEL_IS_VEE_MESSAGE_INFO_DATA (data), NULL);
+
+ return data->priv->subfolder_data;
+}
+
+/**
+ * camel_vee_message_info_data_get_orig_message_uid:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+const gchar *
+camel_vee_message_info_data_get_orig_message_uid (CamelVeeMessageInfoData *data)
+{
+ g_return_val_if_fail (CAMEL_IS_VEE_MESSAGE_INFO_DATA (data), NULL);
+
+ return data->priv->orig_message_uid;
+}
+
+/**
+ * camel_vee_message_info_data_get_vee_message_uid:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+const gchar *
+camel_vee_message_info_data_get_vee_message_uid (CamelVeeMessageInfoData *data)
+{
+ g_return_val_if_fail (CAMEL_IS_VEE_MESSAGE_INFO_DATA (data), NULL);
+
+ return data->priv->vee_message_uid;
+}
+
+/* ----------------------------------------------------------------------- */
+
+#define CAMEL_VEE_DATA_CACHE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_VEE_DATA_CACHE, CamelVeeDataCachePrivate))
+
+struct _CamelVeeDataCachePrivate {
+ GMutex sf_mutex; /* guards subfolder_hash */
+ GHashTable *subfolder_hash; /* CamelFolder * => CamelVeeSubfolderData * */
+
+ GMutex mi_mutex; /* guards message_info_hash */
+ GHashTable *orig_message_uid_hash; /* VeeData * => CamelVeeMessageInfoData * */
+ GHashTable *vee_message_uid_hash; /* const gchar *vee_uid => CamelVeeMessageInfoData * */
+};
+
+G_DEFINE_TYPE (CamelVeeDataCache, camel_vee_data_cache, G_TYPE_OBJECT)
+
+typedef struct _VeeData {
+ CamelFolder *folder;
+ const gchar *orig_message_uid;
+} VeeData;
+
+static guint
+vee_data_hash (gconstpointer ptr)
+{
+ const VeeData *vee_data = ptr;
+
+ if (!vee_data)
+ return 0;
+
+ return g_direct_hash (vee_data->folder)
+ + g_str_hash (vee_data->orig_message_uid);
+}
+
+static gboolean
+vee_data_equal (gconstpointer v1,
+ gconstpointer v2)
+{
+ const VeeData *vee_data1 = v1, *vee_data2 = v2;
+
+ if (!v1 || !v2)
+ return v1 == v2;
+
+ /* can contain ponters directly, strings are always from the string pool */
+ return v1 == v2 ||
+ (vee_data1->folder == vee_data2->folder &&
+ vee_data1->orig_message_uid == vee_data2->orig_message_uid);
+}
+
+static void
+vee_data_cache_dispose (GObject *object)
+{
+ CamelVeeDataCachePrivate *priv;
+
+ priv = CAMEL_VEE_DATA_CACHE_GET_PRIVATE (object);
+
+ if (priv->subfolder_hash != NULL) {
+ g_hash_table_destroy (priv->subfolder_hash);
+ priv->subfolder_hash = NULL;
+ }
+
+ if (priv->orig_message_uid_hash != NULL) {
+ g_hash_table_destroy (priv->orig_message_uid_hash);
+ priv->orig_message_uid_hash = NULL;
+ }
+
+ if (priv->vee_message_uid_hash != NULL) {
+ g_hash_table_destroy (priv->vee_message_uid_hash);
+ priv->vee_message_uid_hash = NULL;
+ }
+
+ /* Chain up to parent's dispose () method. */
+ G_OBJECT_CLASS (camel_vee_data_cache_parent_class)->dispose (object);
+}
+
+static void
+vee_data_cache_finalize (GObject *object)
+{
+ CamelVeeDataCachePrivate *priv;
+
+ priv = CAMEL_VEE_DATA_CACHE_GET_PRIVATE (object);
+
+ g_mutex_clear (&priv->sf_mutex);
+ g_mutex_clear (&priv->mi_mutex);
+
+ /* Chain up to parent's finalize () method. */
+ G_OBJECT_CLASS (camel_vee_data_cache_parent_class)->finalize (object);
+}
+
+static void
+camel_vee_data_cache_class_init (CamelVeeDataCacheClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelVeeDataCachePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = vee_data_cache_dispose;
+ object_class->finalize = vee_data_cache_finalize;
+}
+
+static void
+camel_vee_data_cache_init (CamelVeeDataCache *data_cache)
+{
+ data_cache->priv = CAMEL_VEE_DATA_CACHE_GET_PRIVATE (data_cache);
+
+ g_mutex_init (&data_cache->priv->sf_mutex);
+ data_cache->priv->subfolder_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
+
+ g_mutex_init (&data_cache->priv->mi_mutex);
+ data_cache->priv->orig_message_uid_hash = g_hash_table_new_full (vee_data_hash, vee_data_equal, g_free, g_object_unref);
+ data_cache->priv->vee_message_uid_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+}
+
+/**
+ * camel_vee_data_cache_new:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+CamelVeeDataCache *
+camel_vee_data_cache_new (void)
+{
+ return g_object_new (CAMEL_TYPE_VEE_DATA_CACHE, NULL);
+}
+
+/**
+ * camel_vee_data_cache_add_subfolder:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_data_cache_add_subfolder (CamelVeeDataCache *data_cache,
+ CamelFolder *subfolder)
+{
+ CamelVeeSubfolderData *sf_data;
+
+ g_return_if_fail (CAMEL_IS_VEE_DATA_CACHE (data_cache));
+ g_return_if_fail (CAMEL_IS_FOLDER (subfolder));
+
+ g_mutex_lock (&data_cache->priv->mi_mutex);
+ g_mutex_lock (&data_cache->priv->sf_mutex);
+
+ sf_data = g_hash_table_lookup (data_cache->priv->subfolder_hash, subfolder);
+ if (!sf_data) {
+ GPtrArray *uids;
+ gint ii;
+
+ sf_data = camel_vee_subfolder_data_new (subfolder);
+ g_hash_table_insert (data_cache->priv->subfolder_hash, subfolder, sf_data);
+
+ /* camel_vee_data_cache_get_message_info_data() caches uids on demand,
+ * while here are cached all known uids in once - it is better when
+ * the folder is used in Unmatched folder, where the uid/vuid will
+ * be used in the vfolder or Unmatched folder anyway */
+ uids = camel_folder_get_uids (subfolder);
+ if (uids) {
+ for (ii = 0; ii < uids->len; ii++) {
+ VeeData vdata;
+ CamelVeeMessageInfoData *mi_data;
+
+ /* make sure the orig_message_uid comes from the string pool */
+ vdata.folder = subfolder;
+ vdata.orig_message_uid = camel_pstring_strdup (uids->pdata[ii]);
+
+ mi_data = g_hash_table_lookup (data_cache->priv->orig_message_uid_hash, &vdata);
+ if (!mi_data) {
+ VeeData *hash_data;
+
+ mi_data = camel_vee_message_info_data_new (sf_data, vdata.orig_message_uid);
+
+ hash_data = g_new0 (VeeData, 1);
+ hash_data->folder = subfolder;
+ hash_data->orig_message_uid = camel_vee_message_info_data_get_orig_message_uid (mi_data);
+
+ g_hash_table_insert (data_cache->priv->orig_message_uid_hash, hash_data, mi_data);
+ g_hash_table_insert (
+ data_cache->priv->vee_message_uid_hash,
+ (gpointer) camel_vee_message_info_data_get_vee_message_uid (mi_data),
+ mi_data);
+ }
+
+ camel_pstring_free (vdata.orig_message_uid);
+ }
+
+ camel_folder_free_uids (subfolder, uids);
+ }
+ }
+
+ g_mutex_unlock (&data_cache->priv->sf_mutex);
+ g_mutex_unlock (&data_cache->priv->mi_mutex);
+}
+
+static gboolean
+remove_vee_by_folder_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ CamelVeeMessageInfoData *mi_data = value;
+ CamelVeeSubfolderData *sf_data;
+ CamelFolder *folder = user_data;
+
+ if (!mi_data)
+ return FALSE;
+
+ sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
+ return sf_data && camel_vee_subfolder_data_get_folder (sf_data) == folder;
+}
+
+static gboolean
+remove_orig_by_folder_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ VeeData *vee_data = key;
+ CamelFolder *folder = user_data;
+
+ return vee_data && vee_data->folder == folder;
+}
+
+/**
+ * camel_vee_data_cache_remove_subfolder:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_data_cache_remove_subfolder (CamelVeeDataCache *data_cache,
+ CamelFolder *subfolder)
+{
+ g_return_if_fail (CAMEL_IS_VEE_DATA_CACHE (data_cache));
+ g_return_if_fail (CAMEL_IS_FOLDER (subfolder));
+
+ g_mutex_lock (&data_cache->priv->mi_mutex);
+ g_mutex_lock (&data_cache->priv->sf_mutex);
+
+ g_hash_table_foreach_remove (data_cache->priv->vee_message_uid_hash, remove_vee_by_folder_cb, subfolder);
+ g_hash_table_foreach_remove (data_cache->priv->orig_message_uid_hash, remove_orig_by_folder_cb, subfolder);
+ g_hash_table_remove (data_cache->priv->subfolder_hash, subfolder);
+
+ g_mutex_unlock (&data_cache->priv->sf_mutex);
+ g_mutex_unlock (&data_cache->priv->mi_mutex);
+}
+
+/**
+ * camel_vee_data_cache_get_subfolder_data:
+ *
+ * FIXME Document me!
+ *
+ * Returns: (transfer full):
+ *
+ * Since: 3.6
+ **/
+CamelVeeSubfolderData *
+camel_vee_data_cache_get_subfolder_data (CamelVeeDataCache *data_cache,
+ CamelFolder *folder)
+{
+ CamelVeeSubfolderData *res;
+
+ g_return_val_if_fail (CAMEL_IS_VEE_DATA_CACHE (data_cache), NULL);
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ g_mutex_lock (&data_cache->priv->sf_mutex);
+
+ res = g_hash_table_lookup (data_cache->priv->subfolder_hash, folder);
+ if (!res) {
+ res = camel_vee_subfolder_data_new (folder);
+ g_hash_table_insert (data_cache->priv->subfolder_hash, folder, res);
+ }
+
+ g_object_ref (res);
+
+ g_mutex_unlock (&data_cache->priv->sf_mutex);
+
+ return res;
+}
+
+/**
+ * camel_vee_data_cache_contains_message_info_data:
+ *
+ * Returns whether data_cache contains certain UID for certain folder;
+ * instead of camel_vee_data_cache_get_message_info_data() only
+ * returns FALSE if not, while camel_vee_data_cache_get_message_info_data()
+ * auto-adds it to data_cache.
+ *
+ * Since: 3.6
+ */
+gboolean
+camel_vee_data_cache_contains_message_info_data (CamelVeeDataCache *data_cache,
+ CamelFolder *folder,
+ const gchar *orig_message_uid)
+{
+ gboolean res;
+ VeeData vdata;
+
+ g_return_val_if_fail (CAMEL_IS_VEE_DATA_CACHE (data_cache), FALSE);
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+ g_return_val_if_fail (orig_message_uid != NULL, FALSE);
+
+ g_mutex_lock (&data_cache->priv->mi_mutex);
+
+ /* make sure the orig_message_uid comes from the string pool */
+ vdata.folder = folder;
+ vdata.orig_message_uid = camel_pstring_strdup (orig_message_uid);
+
+ res = g_hash_table_lookup (data_cache->priv->orig_message_uid_hash, &vdata) != NULL;
+
+ camel_pstring_free (vdata.orig_message_uid);
+
+ g_mutex_unlock (&data_cache->priv->mi_mutex);
+
+ return res;
+}
+
+/**
+ * camel_vee_data_cache_get_message_info_data:
+ *
+ * FIXME Document me!
+ *
+ * Returns: (transfer full):
+ *
+ * Since: 3.6
+ **/
+CamelVeeMessageInfoData *
+camel_vee_data_cache_get_message_info_data (CamelVeeDataCache *data_cache,
+ CamelFolder *folder,
+ const gchar *orig_message_uid)
+{
+ CamelVeeMessageInfoData *res;
+ VeeData vdata;
+
+ g_return_val_if_fail (CAMEL_IS_VEE_DATA_CACHE (data_cache), NULL);
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+ g_return_val_if_fail (orig_message_uid != NULL, NULL);
+
+ g_mutex_lock (&data_cache->priv->mi_mutex);
+
+ /* make sure the orig_message_uid comes from the string pool */
+ vdata.folder = folder;
+ vdata.orig_message_uid = camel_pstring_strdup (orig_message_uid);
+
+ res = g_hash_table_lookup (data_cache->priv->orig_message_uid_hash, &vdata);
+ if (!res) {
+ VeeData *hash_data;
+ CamelVeeSubfolderData *sf_data;
+
+ /* this locks also priv->sf_mutex */
+ sf_data = camel_vee_data_cache_get_subfolder_data (data_cache, folder);
+ if (!sf_data) {
+ camel_pstring_free (vdata.orig_message_uid);
+ g_mutex_unlock (&data_cache->priv->mi_mutex);
+ g_return_val_if_fail (sf_data != NULL, NULL);
+ }
+
+ res = camel_vee_message_info_data_new (sf_data, orig_message_uid);
+
+ /* res holds the reference now */
+ g_object_unref (sf_data);
+
+ hash_data = g_new0 (VeeData, 1);
+ hash_data->folder = folder;
+ hash_data->orig_message_uid = camel_vee_message_info_data_get_orig_message_uid (res);
+
+ g_hash_table_insert (data_cache->priv->orig_message_uid_hash, hash_data, res);
+ g_hash_table_insert (
+ data_cache->priv->vee_message_uid_hash,
+ (gpointer) camel_vee_message_info_data_get_vee_message_uid (res),
+ res);
+ }
+
+ camel_pstring_free (vdata.orig_message_uid);
+ g_object_ref (res);
+
+ g_mutex_unlock (&data_cache->priv->mi_mutex);
+
+ return res;
+}
+
+/**
+ * camel_vee_data_cache_get_message_info_data_by_vuid:
+ *
+ * FIXME Document me!
+ *
+ * Returns: (transfer full):
+ *
+ * Since: 3.6
+ **/
+CamelVeeMessageInfoData *
+camel_vee_data_cache_get_message_info_data_by_vuid (CamelVeeDataCache *data_cache,
+ const gchar *vee_message_uid)
+{
+ CamelVeeMessageInfoData *res;
+ const gchar *vuid;
+
+ g_return_val_if_fail (CAMEL_IS_VEE_DATA_CACHE (data_cache), NULL);
+ g_return_val_if_fail (vee_message_uid != NULL, NULL);
+
+ g_mutex_lock (&data_cache->priv->mi_mutex);
+
+ /* make sure vee_message_uid comes from the string pool */
+ vuid = camel_pstring_strdup (vee_message_uid);
+
+ res = g_hash_table_lookup (data_cache->priv->vee_message_uid_hash, vuid);
+ if (res)
+ g_object_ref (res);
+
+ g_mutex_unlock (&data_cache->priv->mi_mutex);
+
+ camel_pstring_free (vuid);
+
+ return res;
+}
+
+struct ForeachMiData {
+ CamelFolder *fromfolder;
+ CamelForeachInfoData func;
+ gpointer user_data;
+};
+
+static void
+cvdc_foreach_mi_data_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ VeeData *vdata = key;
+ CamelVeeMessageInfoData *mi_data = value;
+ struct ForeachMiData *fmd = user_data;
+
+ g_return_if_fail (key != NULL);
+ g_return_if_fail (value != NULL);
+ g_return_if_fail (user_data != NULL);
+
+ if (!fmd->fromfolder || fmd->fromfolder == vdata->folder)
+ fmd->func (mi_data, vdata->folder, fmd->user_data);
+}
+
+/**
+ * camel_vee_data_cache_foreach_message_info_data:
+ * @func: (scope call) (closure user_data):
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_data_cache_foreach_message_info_data (CamelVeeDataCache *data_cache,
+ CamelFolder *fromfolder,
+ CamelForeachInfoData func,
+ gpointer user_data)
+{
+ struct ForeachMiData fmd;
+
+ g_return_if_fail (CAMEL_IS_VEE_DATA_CACHE (data_cache));
+ g_return_if_fail (func != NULL);
+
+ g_mutex_lock (&data_cache->priv->mi_mutex);
+
+ fmd.fromfolder = fromfolder;
+ fmd.func = func;
+ fmd.user_data = user_data;
+
+ g_hash_table_foreach (data_cache->priv->orig_message_uid_hash, cvdc_foreach_mi_data_cb, &fmd);
+
+ g_mutex_unlock (&data_cache->priv->mi_mutex);
+}
+
+/**
+ * camel_vee_data_cache_remove_message_info_data:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_data_cache_remove_message_info_data (CamelVeeDataCache *data_cache,
+ CamelVeeMessageInfoData *mi_data)
+{
+ VeeData vdata;
+ CamelVeeSubfolderData *sf_data;
+ const gchar *vuid;
+
+ g_return_if_fail (CAMEL_IS_VEE_DATA_CACHE (data_cache));
+ g_return_if_fail (CAMEL_IS_VEE_MESSAGE_INFO_DATA (mi_data));
+
+ g_mutex_lock (&data_cache->priv->mi_mutex);
+
+ g_object_ref (mi_data);
+
+ sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
+
+ vdata.folder = camel_vee_subfolder_data_get_folder (sf_data);
+ vdata.orig_message_uid = camel_vee_message_info_data_get_orig_message_uid (mi_data);
+ vuid = camel_vee_message_info_data_get_vee_message_uid (mi_data);
+
+ g_hash_table_remove (data_cache->priv->vee_message_uid_hash, vuid);
+ g_hash_table_remove (data_cache->priv->orig_message_uid_hash, &vdata);
+
+ g_object_unref (mi_data);
+
+ g_mutex_unlock (&data_cache->priv->mi_mutex);
+}
diff --git a/src/camel/camel-vee-data-cache.h b/src/camel/camel-vee-data-cache.h
new file mode 100644
index 000000000..9c42cc451
--- /dev/null
+++ b/src/camel/camel-vee-data-cache.h
@@ -0,0 +1,212 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2012 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Milan Crha <mcrha@redhat.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_VEE_DATA_CACHE_H
+#define CAMEL_VEE_DATA_CACHE_H
+
+#include <camel/camel-folder.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_VEE_SUBFOLDER_DATA \
+ (camel_vee_subfolder_data_get_type ())
+#define CAMEL_VEE_SUBFOLDER_DATA(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_VEE_SUBFOLDER_DATA, CamelVeeSubfolderData))
+#define CAMEL_VEE_SUBFOLDER_DATA_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_VEE_SUBFOLDER_DATA, CamelVeeSubfolderDataClass))
+#define CAMEL_IS_VEE_SUBFOLDER_DATA(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_VEE_SUBFOLDER_DATA))
+#define CAMEL_IS_VEE_SUBFOLDER_DATA_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_VEE_SUBFOLDER_DATA))
+#define CAMEL_VEE_SUBFOLDER_DATA_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_VEE_SUBFOLDER_DATA, CamelVeeSubfolderDataClass))
+
+#define CAMEL_TYPE_VEE_MESSAGE_INFO_DATA \
+ (camel_vee_message_info_data_get_type ())
+#define CAMEL_VEE_MESSAGE_INFO_DATA(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_VEE_MESSAGE_INFO_DATA, CamelVeeMessageInfoData))
+#define CAMEL_VEE_MESSAGE_INFO_DATA_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_VEE_MESSAGE_INFO_DATA, CamelVeeMessageInfoDataClass))
+#define CAMEL_IS_VEE_MESSAGE_INFO_DATA(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_VEE_MESSAGE_INFO_DATA))
+#define CAMEL_IS_VEE_MESSAGE_INFO_DATA_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_VEE_MESSAGE_INFO_DATA))
+#define CAMEL_VEE_MESSAGE_INFO_DATA_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_VEE_MESSAGE_INFO_DATA, CamelVeeMessageInfoDataClass))
+
+#define CAMEL_TYPE_VEE_DATA_CACHE \
+ (camel_vee_data_cache_get_type ())
+#define CAMEL_VEE_DATA_CACHE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_VEE_DATA_CACHE, CamelVeeDataCache))
+#define CAMEL_VEE_DATA_CACHE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_VEE_DATA_CACHE, CamelVeeDataCacheClass))
+#define CAMEL_IS_VEE_DATA_CACHE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_VEE_DATA_CACHE))
+#define CAMEL_IS_VEE_DATA_CACHE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_VEE_DATA_CACHE))
+#define CAMEL_VEE_DATA_CACHE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_VEE_DATA_CACHE, CamelVeeDataCacheClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelVeeSubfolderData CamelVeeSubfolderData;
+typedef struct _CamelVeeSubfolderDataClass CamelVeeSubfolderDataClass;
+typedef struct _CamelVeeSubfolderDataPrivate CamelVeeSubfolderDataPrivate;
+
+typedef struct _CamelVeeMessageInfoData CamelVeeMessageInfoData;
+typedef struct _CamelVeeMessageInfoDataClass CamelVeeMessageInfoDataClass;
+typedef struct _CamelVeeMessageInfoDataPrivate CamelVeeMessageInfoDataPrivate;
+
+typedef struct _CamelVeeDataCache CamelVeeDataCache;
+typedef struct _CamelVeeDataCacheClass CamelVeeDataCacheClass;
+typedef struct _CamelVeeDataCachePrivate CamelVeeDataCachePrivate;
+
+/**
+ * CamelForeachInfoData:
+ *
+ **/
+typedef void (*CamelForeachInfoData) (CamelVeeMessageInfoData *mi_data, CamelFolder *subfolder, gpointer user_data);
+
+/**
+ * CamelVeeSubfolderData:
+ *
+ * Since: 3.6
+ **/
+struct _CamelVeeSubfolderData {
+ GObject parent;
+ CamelVeeSubfolderDataPrivate *priv;
+};
+
+struct _CamelVeeSubfolderDataClass {
+ GObjectClass parent_class;
+};
+
+GType camel_vee_subfolder_data_get_type
+ (void) G_GNUC_CONST;
+CamelVeeSubfolderData *
+ camel_vee_subfolder_data_new (CamelFolder *folder);
+CamelFolder * camel_vee_subfolder_data_get_folder
+ (CamelVeeSubfolderData *data);
+const gchar * camel_vee_subfolder_data_get_folder_id
+ (CamelVeeSubfolderData *data);
+
+/* ----------------------------------------------------------------------- */
+
+/**
+ * CamelVeeMessageInfoData:
+ *
+ * Since: 3.6
+ **/
+struct _CamelVeeMessageInfoData {
+ GObject parent;
+ CamelVeeMessageInfoDataPrivate *priv;
+};
+
+struct _CamelVeeMessageInfoDataClass {
+ GObjectClass parent_class;
+};
+
+GType camel_vee_message_info_data_get_type
+ (void) G_GNUC_CONST;
+CamelVeeMessageInfoData *
+ camel_vee_message_info_data_new
+ (CamelVeeSubfolderData *subfolder_data,
+ const gchar *orig_message_uid);
+CamelVeeSubfolderData *
+ camel_vee_message_info_data_get_subfolder_data
+ (CamelVeeMessageInfoData *data);
+const gchar * camel_vee_message_info_data_get_orig_message_uid
+ (CamelVeeMessageInfoData *data);
+const gchar * camel_vee_message_info_data_get_vee_message_uid
+ (CamelVeeMessageInfoData *data);
+
+/* ----------------------------------------------------------------------- */
+
+/**
+ * CamelVeeDataCache:
+ *
+ * Since: 3.6
+ **/
+struct _CamelVeeDataCache {
+ GObject parent;
+ CamelVeeDataCachePrivate *priv;
+};
+
+struct _CamelVeeDataCacheClass {
+ GObjectClass parent_class;
+};
+
+GType camel_vee_data_cache_get_type (void) G_GNUC_CONST;
+CamelVeeDataCache *
+ camel_vee_data_cache_new (void);
+void camel_vee_data_cache_add_subfolder
+ (CamelVeeDataCache *data_cache,
+ CamelFolder *subfolder);
+void camel_vee_data_cache_remove_subfolder
+ (CamelVeeDataCache *data_cache,
+ CamelFolder *subfolder);
+CamelVeeSubfolderData *
+ camel_vee_data_cache_get_subfolder_data
+ (CamelVeeDataCache *data_cache,
+ CamelFolder *folder);
+gboolean camel_vee_data_cache_contains_message_info_data
+ (CamelVeeDataCache *data_cache,
+ CamelFolder *folder,
+ const gchar *orig_message_uid);
+CamelVeeMessageInfoData *
+ camel_vee_data_cache_get_message_info_data
+ (CamelVeeDataCache *data_cache,
+ CamelFolder *folder,
+ const gchar *orig_message_uid);
+CamelVeeMessageInfoData *
+ camel_vee_data_cache_get_message_info_data_by_vuid
+ (CamelVeeDataCache *data_cache,
+ const gchar *vee_message_uid);
+void camel_vee_data_cache_foreach_message_info_data
+ (CamelVeeDataCache *data_cache,
+ CamelFolder *fromfolder,
+ void (* func) (CamelVeeMessageInfoData *mi_data,
+ CamelFolder *subfolder,
+ gpointer user_data),
+ gpointer user_data);
+void camel_vee_data_cache_remove_message_info_data
+ (CamelVeeDataCache *data_cache,
+ CamelVeeMessageInfoData *mi_data);
+
+G_END_DECLS
+
+#endif /* CAMEL_VEE_DATA_CACHE_H */
diff --git a/src/camel/camel-vee-folder.c b/src/camel/camel-vee-folder.c
new file mode 100644
index 000000000..a6355ad34
--- /dev/null
+++ b/src/camel/camel-vee-folder.c
@@ -0,0 +1,1800 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-db.h"
+#include "camel-debug.h"
+#include "camel-folder-search.h"
+#include "camel-mime-message.h"
+#include "camel-session.h"
+#include "camel-store.h"
+#include "camel-vee-folder.h"
+#include "camel-vee-store.h" /* for open flags */
+#include "camel-vee-summary.h"
+#include "camel-string-utils.h"
+#include "camel-vtrash-folder.h"
+
+#define d(x)
+#define dd(x) (camel_debug ("vfolder")?(x):0)
+
+typedef struct _FolderChangedData FolderChangedData;
+
+#define CAMEL_VEE_FOLDER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_VEE_FOLDER, CamelVeeFolderPrivate))
+
+struct _CamelVeeFolderPrivate {
+ gboolean destroyed;
+ GList *subfolders; /* lock using subfolder_lock before changing/accessing */
+ GHashTable *ignore_changed; /* hash of subfolder pointers to ignore the next folder's 'changed' signal */
+ GHashTable *skipped_changes; /* CamelFolder -> CamelFolderChangeInfo accumulating ignored changes */
+ GHashTable *unmatched_add_changed; /* CamelVeeMessageInfoData -> 1, for unmatched folder, postponed additions from camel_vee_folder_add_vuid () */
+ GHashTable *unmatched_remove_changed; /* CamelVeeMessageInfoData -> 1, for unmatched folder, postponed removal from camel_vee_folder_remove_vuid () */
+ gboolean auto_update;
+
+ /* Processing queue for folder changes. */
+ GAsyncQueue *change_queue;
+ gboolean change_queue_busy;
+
+ GRecMutex subfolder_lock; /* for locking the subfolder list */
+ GRecMutex changed_lock; /* for locking the folders-changed list */
+
+ gchar *expression; /* query expression */
+
+ /* 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 */
+ CamelVeeStore *parent_vee_store;
+
+ CamelVeeDataCache *vee_data_cache;
+};
+
+/* The custom property ID is a CamelArg artifact.
+ * It still identifies the property in state files. */
+enum {
+ PROP_0,
+ PROP_AUTO_UPDATE = 0x2401
+};
+
+G_DEFINE_TYPE (CamelVeeFolder, camel_vee_folder, CAMEL_TYPE_FOLDER)
+
+struct _FolderChangedData {
+ CamelFolderChangeInfo *changes;
+ CamelFolder *subfolder;
+};
+
+static FolderChangedData *
+vee_folder_changed_data_new (CamelFolder *subfolder,
+ CamelFolderChangeInfo *changes)
+{
+ FolderChangedData *data;
+
+ data = g_slice_new0 (FolderChangedData);
+ data->changes = camel_folder_change_info_new ();
+ camel_folder_change_info_cat (data->changes, changes);
+ data->subfolder = g_object_ref (subfolder);
+
+ return data;
+}
+
+static void
+vee_folder_changed_data_free (FolderChangedData *data)
+{
+ camel_folder_change_info_free (data->changes);
+ g_object_unref (data->subfolder);
+
+ g_slice_free (FolderChangedData, data);
+}
+
+static CamelVeeDataCache *
+vee_folder_get_data_cache (CamelVeeFolder *vfolder)
+{
+ g_return_val_if_fail (CAMEL_IS_VEE_FOLDER (vfolder), NULL);
+
+ if (vfolder->priv->parent_vee_store)
+ return camel_vee_store_get_vee_data_cache (vfolder->priv->parent_vee_store);
+
+ return vfolder->priv->vee_data_cache;
+}
+
+static gboolean
+vee_folder_is_unmatched (CamelVeeFolder *vfolder)
+{
+ g_return_val_if_fail (vfolder != NULL, FALSE);
+
+ return vfolder->priv->parent_vee_store &&
+ vfolder == camel_vee_store_get_unmatched_folder (vfolder->priv->parent_vee_store);
+}
+
+static void
+vee_folder_note_added_uid (CamelVeeFolder *vfolder,
+ CamelVeeSummary *vsummary,
+ CamelVeeMessageInfoData *added_mi_data,
+ CamelFolderChangeInfo *changes,
+ gboolean included_as_changed)
+{
+ const gchar *vuid;
+
+ vuid = camel_vee_message_info_data_get_vee_message_uid (added_mi_data);
+ if (!camel_folder_summary_check_uid (&vsummary->summary, vuid)) {
+ /* add it only if it wasn't in yet */
+ CamelVeeMessageInfo *vmi;
+
+ vmi = camel_vee_summary_add (vsummary, added_mi_data);
+ if (vmi) {
+ if (changes)
+ camel_folder_change_info_add_uid (changes, vuid);
+ camel_message_info_unref (vmi);
+
+ if (vfolder->priv->parent_vee_store)
+ camel_vee_store_note_vuid_used (vfolder->priv->parent_vee_store, added_mi_data, vfolder);
+ }
+ } else {
+ camel_vee_summary_replace_flags (vsummary, vuid);
+ if (included_as_changed && changes)
+ camel_folder_change_info_change_uid (changes, vuid);
+ }
+}
+
+static void
+vee_folder_note_unmatch_uid (CamelVeeFolder *vfolder,
+ CamelVeeSummary *vsummary,
+ CamelFolder *subfolder,
+ CamelVeeDataCache *data_cache,
+ CamelVeeMessageInfoData *unmatched_mi_data,
+ CamelFolderChangeInfo *changes)
+{
+ const gchar *vuid;
+
+ vuid = camel_vee_message_info_data_get_vee_message_uid (unmatched_mi_data);
+ if (camel_folder_summary_check_uid (&vsummary->summary, vuid)) {
+ g_object_ref (unmatched_mi_data);
+
+ /* this one doesn't belong to us anymore */
+ if (changes)
+ camel_folder_change_info_remove_uid (changes, vuid);
+ camel_vee_summary_remove (vsummary, vuid, subfolder);
+
+ if (vfolder->priv->parent_vee_store)
+ camel_vee_store_note_vuid_unused (vfolder->priv->parent_vee_store, unmatched_mi_data, vfolder);
+ else
+ camel_vee_data_cache_remove_message_info_data (data_cache, unmatched_mi_data);
+
+ g_object_unref (unmatched_mi_data);
+ }
+}
+
+static void
+vee_folder_remove_unmatched (CamelVeeFolder *vfolder,
+ CamelVeeSummary *vsummary,
+ CamelVeeDataCache *data_cache,
+ CamelFolderChangeInfo *changes,
+ CamelFolder *subfolder,
+ const gchar *orig_message_uid,
+ gboolean is_orig_message_uid) /* if not,
+ then it's 'vee_message_uid' */
+{
+ CamelVeeMessageInfoData *mi_data;
+
+ if (is_orig_message_uid) {
+ /* camel_vee_data_cache_get_message_info_data() auto-adds items if not there,
+ * thus check whether the cache has it already, and if not, then skip the action.
+ * This can happen for virtual Junk/Trash folders.
+ */
+ if (!camel_vee_data_cache_contains_message_info_data (data_cache, subfolder, orig_message_uid))
+ return;
+
+ mi_data = camel_vee_data_cache_get_message_info_data (data_cache, subfolder, orig_message_uid);
+ } else
+ mi_data = camel_vee_data_cache_get_message_info_data_by_vuid (data_cache, orig_message_uid);
+
+ if (!mi_data)
+ return;
+
+ vee_folder_note_unmatch_uid (vfolder, vsummary, subfolder, data_cache, mi_data, changes);
+
+ g_object_unref (mi_data);
+}
+
+struct RemoveUnmatchedData
+{
+ CamelVeeFolder *vfolder;
+ CamelVeeSummary *vsummary;
+ CamelFolder *subfolder;
+ CamelVeeDataCache *data_cache;
+ CamelFolderChangeInfo *changes;
+ gboolean is_orig_message_uid;
+};
+
+static void
+vee_folder_remove_unmatched_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ struct RemoveUnmatchedData *rud = user_data;
+ const gchar *uid = key;
+
+ g_return_if_fail (rud != NULL);
+
+ vee_folder_remove_unmatched (rud->vfolder, rud->vsummary, rud->data_cache, rud->changes, rud->subfolder, uid, rud->is_orig_message_uid);
+}
+
+static void
+vee_folder_merge_matching (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ GHashTable *all_uids,
+ GPtrArray *match,
+ CamelFolderChangeInfo *changes,
+ gboolean included_as_changed)
+{
+ CamelVeeDataCache *data_cache;
+ CamelVeeMessageInfoData *mi_data;
+ CamelFolder *folder;
+ CamelVeeSummary *vsummary;
+ struct RemoveUnmatchedData rud;
+ gint ii;
+
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder));
+ g_return_if_fail (CAMEL_IS_FOLDER (subfolder));
+ g_return_if_fail (all_uids != NULL);
+ g_return_if_fail (match != NULL);
+
+ folder = CAMEL_FOLDER (vfolder);
+ g_return_if_fail (folder != NULL);
+
+ vsummary = CAMEL_VEE_SUMMARY (folder->summary);
+ g_return_if_fail (vsummary != NULL);
+
+ data_cache = vee_folder_get_data_cache (vfolder);
+ /* It can be NULL on dispose of the CamelVeeStore */
+ if (!data_cache)
+ return;
+
+ for (ii = 0; ii < match->len; ii++) {
+ const gchar *uid = match->pdata[ii];
+
+ mi_data = camel_vee_data_cache_get_message_info_data (data_cache, subfolder, uid);
+ if (!mi_data)
+ continue;
+
+ g_hash_table_remove (all_uids, uid);
+
+ vee_folder_note_added_uid (vfolder, vsummary, mi_data, changes, included_as_changed);
+
+ g_object_unref (mi_data);
+ }
+
+ rud.vfolder = vfolder;
+ rud.vsummary = vsummary;
+ rud.subfolder = subfolder;
+ rud.data_cache = data_cache;
+ rud.changes = changes;
+ rud.is_orig_message_uid = TRUE;
+
+ /* in 'all_uids' left only those which are not part of the folder anymore */
+ g_hash_table_foreach (all_uids, vee_folder_remove_unmatched_cb, &rud);
+}
+
+static void
+vee_folder_rebuild_folder_with_changes (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ CamelFolderChangeInfo *changes,
+ GCancellable *cancellable)
+{
+ GPtrArray *match = NULL;
+
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder));
+ g_return_if_fail (CAMEL_IS_FOLDER (subfolder));
+
+ /* Unmatched folder cannot be rebuilt */
+ if (vee_folder_is_unmatched (vfolder))
+ return;
+
+ /* if we have no expression, or its been cleared, then act as if no matches */
+ if (vfolder->priv->expression == NULL) {
+ match = g_ptr_array_new ();
+ } else {
+ match = camel_folder_search_by_expression (subfolder, vfolder->priv->expression, cancellable, NULL);
+ if (!match)
+ return;
+ }
+
+ if (!g_cancellable_is_cancelled (cancellable)) {
+ GHashTable *all_uids;
+
+ all_uids = camel_folder_summary_get_hash (subfolder->summary);
+ vee_folder_merge_matching (vfolder, subfolder, all_uids, match, changes, FALSE);
+ g_hash_table_destroy (all_uids);
+ }
+
+ camel_folder_search_free (subfolder, match);
+}
+
+static void
+vee_folder_rebuild_all (CamelVeeFolder *vfolder,
+ GCancellable *cancellable)
+{
+ CamelFolderChangeInfo *changes;
+ GList *iter;
+
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder));
+
+ /* Unmatched folder cannot be rebuilt */
+ if (vee_folder_is_unmatched (vfolder))
+ return;
+
+ changes = camel_folder_change_info_new ();
+
+ g_rec_mutex_lock (&vfolder->priv->subfolder_lock);
+
+ for (iter = vfolder->priv->subfolders;
+ iter && !g_cancellable_is_cancelled (cancellable);
+ iter = iter->next) {
+ CamelFolder *subfolder = iter->data;
+
+ vee_folder_rebuild_folder_with_changes (vfolder, subfolder, changes, cancellable);
+ }
+
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+
+ if (camel_folder_change_info_changed (changes))
+ camel_folder_changed (CAMEL_FOLDER (vfolder), changes);
+ camel_folder_change_info_free (changes);
+}
+
+static void
+vee_folder_subfolder_changed (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ CamelFolderChangeInfo *subfolder_changes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelVeeDataCache *data_cache;
+ CamelFolderChangeInfo *changes;
+ CamelFolder *v_folder;
+ CamelVeeSummary *vsummary;
+ gint ii;
+
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder));
+ g_return_if_fail (CAMEL_IS_FOLDER (subfolder));
+ g_return_if_fail (subfolder_changes != NULL);
+
+ g_rec_mutex_lock (&vfolder->priv->subfolder_lock);
+ if (!g_list_find (vfolder->priv->subfolders, subfolder)) {
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+ return;
+ }
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+
+ data_cache = vee_folder_get_data_cache (vfolder);
+ /* It can be NULL on dispose of the CamelVeeStore */
+ if (!data_cache)
+ return;
+
+ changes = camel_folder_change_info_new ();
+ v_folder = CAMEL_FOLDER (vfolder);
+ vsummary = CAMEL_VEE_SUMMARY (v_folder->summary);
+
+ camel_folder_freeze (v_folder);
+
+ for (ii = 0; ii < subfolder_changes->uid_removed->len; ii++) {
+ const gchar *orig_message_uid = subfolder_changes->uid_removed->pdata[ii];
+
+ vee_folder_remove_unmatched (vfolder, vsummary, data_cache, changes, subfolder, orig_message_uid, TRUE);
+ }
+
+ if (subfolder_changes->uid_added->len + subfolder_changes->uid_changed->len > 0) {
+ GPtrArray *test_uids, *match;
+ gboolean my_match = FALSE;
+
+ test_uids = g_ptr_array_sized_new (subfolder_changes->uid_added->len + subfolder_changes->uid_changed->len);
+
+ for (ii = 0; ii < subfolder_changes->uid_added->len; ii++) {
+ g_ptr_array_add (test_uids, subfolder_changes->uid_added->pdata[ii]);
+ }
+
+ for (ii = 0; ii < subfolder_changes->uid_changed->len; ii++) {
+ g_ptr_array_add (test_uids, subfolder_changes->uid_changed->pdata[ii]);
+ }
+
+ if (!vfolder->priv->expression) {
+ my_match = TRUE;
+ match = g_ptr_array_new ();
+
+ if (vee_folder_is_unmatched (vfolder)) {
+ CamelVeeMessageInfoData *mi_data;
+ const gchar *vuid;
+
+ /* all common from test_uids and stored uids
+ * in the unmatched folder should be updated */
+ for (ii = 0; ii < test_uids->len; ii++) {
+ mi_data = camel_vee_data_cache_get_message_info_data (data_cache, subfolder, test_uids->pdata[ii]);
+ if (!mi_data)
+ continue;
+
+ vuid = camel_vee_message_info_data_get_vee_message_uid (mi_data);
+ if (camel_folder_summary_check_uid (v_folder->summary, vuid))
+ g_ptr_array_add (match, (gpointer) camel_pstring_strdup (test_uids->pdata[ii]));
+ g_object_unref (mi_data);
+ }
+ }
+ } else {
+ /* sadly, if there are threads involved, then searching by uids doesn't work,
+ * because just changed uids can be brought in by the thread condition */
+ if (strstr (vfolder->priv->expression, "match-threads") != NULL)
+ match = camel_folder_search_by_expression (subfolder, vfolder->priv->expression, cancellable, NULL);
+ else
+ match = camel_folder_search_by_uids (subfolder, vfolder->priv->expression, test_uids, cancellable, NULL);
+ }
+
+ if (match) {
+ GHashTable *with_uids;
+
+ /* uids are taken from the string pool, thus use direct hashes */
+ with_uids = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
+ for (ii = 0; ii < test_uids->len; ii++) {
+ g_hash_table_insert (with_uids, (gpointer) camel_pstring_strdup (test_uids->pdata[ii]), GINT_TO_POINTER (1));
+ }
+
+ vee_folder_merge_matching (vfolder, subfolder, with_uids, match, changes, TRUE);
+
+ g_hash_table_destroy (with_uids);
+ if (my_match) {
+ g_ptr_array_foreach (match, (GFunc) camel_pstring_free, NULL);
+ g_ptr_array_free (match, TRUE);
+ } else {
+ camel_folder_search_free (subfolder, match);
+ }
+ }
+
+ g_ptr_array_free (test_uids, TRUE);
+ }
+
+ camel_folder_thaw (v_folder);
+
+ if (camel_folder_change_info_changed (changes))
+ camel_folder_changed (v_folder, changes);
+ camel_folder_change_info_free (changes);
+}
+
+static void
+vee_folder_process_changes (CamelSession *session,
+ GCancellable *cancellable,
+ CamelVeeFolder *vee_folder,
+ GError **error)
+{
+ CamelFolder *folder;
+ FolderChangedData *data;
+ GAsyncQueue *change_queue;
+ const gchar *display_name;
+ const gchar *message;
+
+ folder = CAMEL_FOLDER (vee_folder);
+
+ change_queue = vee_folder->priv->change_queue;
+
+ message = _("Updating folder '%s'");
+ display_name = camel_folder_get_display_name (folder);
+ camel_operation_push_message (cancellable, message, display_name);
+
+ while ((data = g_async_queue_try_pop (change_queue)) != NULL) {
+ vee_folder_subfolder_changed (vee_folder, data->subfolder, data->changes, cancellable, error);
+ vee_folder_changed_data_free (data);
+
+ if (g_cancellable_is_cancelled (cancellable))
+ break;
+ }
+
+ vee_folder->priv->change_queue_busy = FALSE;
+
+ camel_operation_pop_message (cancellable);
+}
+
+static void
+subfolder_changed (CamelFolder *subfolder,
+ CamelFolderChangeInfo *changes,
+ CamelVeeFolder *vfolder)
+{
+ g_return_if_fail (vfolder != NULL);
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder));
+
+ g_rec_mutex_lock (&vfolder->priv->changed_lock);
+ if (g_hash_table_lookup (vfolder->priv->ignore_changed, subfolder) ||
+ !camel_vee_folder_get_auto_update (vfolder)) {
+ CamelFolderChangeInfo *my_changes;
+
+ g_hash_table_remove (vfolder->priv->ignore_changed, subfolder);
+
+ my_changes = g_hash_table_lookup (vfolder->priv->skipped_changes, subfolder);
+ if (!my_changes)
+ my_changes = camel_folder_change_info_new ();
+ camel_folder_change_info_cat (my_changes, changes);
+ g_hash_table_insert (vfolder->priv->skipped_changes, subfolder, my_changes);
+
+ g_rec_mutex_unlock (&vfolder->priv->changed_lock);
+
+ return;
+ }
+ g_rec_mutex_unlock (&vfolder->priv->changed_lock);
+
+ CAMEL_VEE_FOLDER_GET_CLASS (vfolder)->folder_changed (vfolder, subfolder, changes);
+}
+
+/* track vanishing folders */
+static void
+subfolder_deleted (CamelFolder *subfolder,
+ CamelVeeFolder *vfolder)
+{
+ camel_vee_folder_remove_folder (vfolder, subfolder, NULL);
+}
+
+static void
+vee_folder_dispose (GObject *object)
+{
+ CamelFolder *folder;
+
+ folder = CAMEL_FOLDER (object);
+
+ /* parent's class frees summary on dispose, thus depend on it */
+ if (folder->summary) {
+ CamelVeeFolder *vfolder;
+
+ vfolder = CAMEL_VEE_FOLDER (object);
+ vfolder->priv->destroyed = TRUE;
+
+ camel_folder_freeze ((CamelFolder *) vfolder);
+ while (vfolder->priv->subfolders) {
+ CamelFolder *subfolder = vfolder->priv->subfolders->data;
+ camel_vee_folder_remove_folder (vfolder, subfolder, NULL);
+ }
+ camel_folder_thaw ((CamelFolder *) vfolder);
+ }
+
+ /* Chain up to parent's dispose () method. */
+ G_OBJECT_CLASS (camel_vee_folder_parent_class)->dispose (object);
+}
+
+static void
+free_change_info_cb (gpointer folder,
+ gpointer change_info,
+ gpointer user_data)
+{
+ camel_folder_change_info_free (change_info);
+}
+
+static void
+vee_folder_finalize (GObject *object)
+{
+ CamelVeeFolder *vf;
+
+ vf = CAMEL_VEE_FOLDER (object);
+
+ g_free (vf->priv->expression);
+
+ g_list_free (vf->priv->subfolders);
+
+ g_hash_table_foreach (vf->priv->skipped_changes, free_change_info_cb, NULL);
+
+ g_rec_mutex_clear (&vf->priv->subfolder_lock);
+ g_rec_mutex_clear (&vf->priv->changed_lock);
+ g_hash_table_destroy (vf->priv->ignore_changed);
+ g_hash_table_destroy (vf->priv->skipped_changes);
+ g_hash_table_destroy (vf->priv->unmatched_add_changed);
+ g_hash_table_destroy (vf->priv->unmatched_remove_changed);
+
+ g_async_queue_unref (vf->priv->change_queue);
+
+ if (vf->priv->vee_data_cache)
+ g_object_unref (vf->priv->vee_data_cache);
+ vf->priv->vee_data_cache = NULL;
+
+ /* Chain up to parent's finalize () method. */
+ G_OBJECT_CLASS (camel_vee_folder_parent_class)->finalize (object);
+}
+
+static void
+vee_folder_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTO_UPDATE:
+ g_value_set_boolean (
+ value, camel_vee_folder_get_auto_update (
+ CAMEL_VEE_FOLDER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+vee_folder_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTO_UPDATE:
+ camel_vee_folder_set_auto_update (
+ CAMEL_VEE_FOLDER (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+vee_folder_propagate_skipped_changes (CamelVeeFolder *vf)
+{
+ CamelVeeFolderClass *class;
+ CamelFolderChangeInfo *changes = NULL;
+ GHashTableIter iter;
+ gpointer psub, pchanges;
+
+ g_return_if_fail (vf != NULL);
+
+ class = CAMEL_VEE_FOLDER_GET_CLASS (vf);
+
+ g_rec_mutex_lock (&vf->priv->changed_lock);
+
+ /* this is for Unmatched folder only, other folders have unmatched_remove_changed always empty */
+ if (g_hash_table_size (vf->priv->unmatched_add_changed) +
+ g_hash_table_size (vf->priv->unmatched_remove_changed) > 0) {
+ gpointer pkey, pvalue;
+ CamelVeeSummary *vsummary;
+ CamelFolder *v_folder;
+ CamelVeeDataCache *data_cache;
+
+ data_cache = vee_folder_get_data_cache (vf);
+
+ /* It can be NULL on dispose of the CamelVeeStore */
+ if (!data_cache) {
+ g_rec_mutex_unlock (&vf->priv->changed_lock);
+ return;
+ }
+
+ changes = camel_folder_change_info_new ();
+ v_folder = CAMEL_FOLDER (vf);
+ vsummary = CAMEL_VEE_SUMMARY (v_folder->summary);
+
+ /* first remove ... */
+ g_hash_table_iter_init (&iter, vf->priv->unmatched_remove_changed);
+ while (g_hash_table_iter_next (&iter, &pkey, &pvalue)) {
+ CamelVeeMessageInfoData *mi_data = pkey;
+ CamelVeeSubfolderData *sf_data;
+ CamelFolder *subfolder;
+
+ sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
+ subfolder = camel_vee_subfolder_data_get_folder (sf_data);
+ vee_folder_note_unmatch_uid (vf, vsummary, subfolder, data_cache, mi_data, changes);
+ }
+ g_hash_table_remove_all (vf->priv->unmatched_remove_changed);
+
+ /* ... then add */
+ g_hash_table_iter_init (&iter, vf->priv->unmatched_add_changed);
+ while (g_hash_table_iter_next (&iter, &pkey, &pvalue)) {
+ CamelVeeMessageInfoData *mi_data = pkey;
+
+ vee_folder_note_added_uid (vf, vsummary, mi_data, changes, FALSE);
+ }
+ g_hash_table_remove_all (vf->priv->unmatched_add_changed);
+ }
+
+ g_hash_table_iter_init (&iter, vf->priv->skipped_changes);
+ while (g_hash_table_iter_next (&iter, &psub, &pchanges)) {
+ g_warn_if_fail (pchanges != NULL);
+ if (!pchanges)
+ continue;
+
+ if (g_list_find (vf->priv->subfolders, psub) != NULL)
+ class->folder_changed (vf, psub, pchanges);
+
+ camel_folder_change_info_free (pchanges);
+ }
+
+ g_hash_table_remove_all (vf->priv->skipped_changes);
+
+ g_rec_mutex_unlock (&vf->priv->changed_lock);
+
+ if (changes) {
+ if (camel_folder_change_info_changed (changes))
+ camel_folder_changed (CAMEL_FOLDER (vf), changes);
+ camel_folder_change_info_free (changes);
+ }
+}
+
+static GPtrArray *
+vee_folder_search_by_expression (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderSearch *search;
+ GPtrArray *matches;
+
+ search = camel_folder_search_new ();
+ camel_folder_search_set_folder (search, folder);
+ matches = camel_folder_search_search (search, expression, NULL, cancellable, error);
+ g_object_unref (search);
+
+ return matches;
+}
+
+static GPtrArray *
+vee_folder_search_by_uids (CamelFolder *folder,
+ const gchar *expression,
+ GPtrArray *uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderSearch *search;
+ GPtrArray *matches;
+
+ if (!uids || uids->len == 0)
+ return g_ptr_array_new ();
+
+ search = camel_folder_search_new ();
+ camel_folder_search_set_folder (search, folder);
+ matches = camel_folder_search_search (search, expression, uids, cancellable, error);
+ g_object_unref (search);
+
+ return matches;
+}
+
+static guint32
+vee_folder_count_by_expression (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderSearch *search;
+ guint32 count;
+
+ search = camel_folder_search_new ();
+ camel_folder_search_set_folder (search, folder);
+ count = camel_folder_search_count (search, expression, cancellable, error);
+ g_object_unref (search);
+
+ return count;
+}
+
+static void
+vee_folder_search_free (CamelFolder *folder,
+ GPtrArray *result)
+{
+ camel_folder_search_free_result (NULL, result);
+}
+
+static void
+vee_folder_delete (CamelFolder *folder)
+{
+ CamelVeeFolder *vfolder;
+
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (folder));
+
+ vfolder = CAMEL_VEE_FOLDER (folder);
+
+ g_rec_mutex_lock (&vfolder->priv->subfolder_lock);
+ while (vfolder->priv->subfolders) {
+ CamelFolder *subfolder = vfolder->priv->subfolders->data;
+
+ g_object_ref (subfolder);
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+
+ camel_vee_folder_remove_folder (vfolder, subfolder, NULL);
+ g_object_unref (subfolder);
+
+ g_rec_mutex_lock (&vfolder->priv->subfolder_lock);
+ }
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+
+ ((CamelFolderClass *) camel_vee_folder_parent_class)->delete_ (folder);
+}
+
+static void
+vee_folder_freeze (CamelFolder *folder)
+{
+ CamelVeeFolder *vfolder = CAMEL_VEE_FOLDER (folder);
+ GList *link;
+
+ if (vfolder->priv->parent_vee_store &&
+ !vee_folder_is_unmatched (vfolder)) {
+ CamelVeeFolder *unmatched_folder;
+
+ unmatched_folder = camel_vee_store_get_unmatched_folder (vfolder->priv->parent_vee_store);
+ if (unmatched_folder)
+ camel_folder_freeze (CAMEL_FOLDER (unmatched_folder));
+ }
+
+ g_rec_mutex_lock (&vfolder->priv->subfolder_lock);
+ for (link = vfolder->priv->subfolders; link; link = g_list_next (link)) {
+ CamelFolder *subfolder = link->data;
+
+ camel_folder_freeze (subfolder);
+ }
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+
+ /* call parent implementation */
+ CAMEL_FOLDER_CLASS (camel_vee_folder_parent_class)->freeze (folder);
+}
+
+static void
+vee_folder_thaw (CamelFolder *folder)
+{
+ CamelVeeFolder *vfolder = CAMEL_VEE_FOLDER (folder);
+ GList *link;
+
+ if (vfolder->priv->parent_vee_store &&
+ !vee_folder_is_unmatched (vfolder)) {
+ CamelVeeFolder *unmatched_folder;
+
+ unmatched_folder = camel_vee_store_get_unmatched_folder (vfolder->priv->parent_vee_store);
+ if (unmatched_folder)
+ camel_folder_thaw (CAMEL_FOLDER (unmatched_folder));
+ }
+
+ g_rec_mutex_lock (&vfolder->priv->subfolder_lock);
+ for (link = vfolder->priv->subfolders; link; link = g_list_next (link)) {
+ CamelFolder *subfolder = link->data;
+
+ camel_folder_thaw (subfolder);
+ }
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+
+ /* call parent implementation */
+ CAMEL_FOLDER_CLASS (camel_vee_folder_parent_class)->thaw (folder);
+}
+
+static gboolean
+vee_folder_append_message_sync (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Cannot copy or move messages into a Virtual Folder"));
+
+ return FALSE;
+}
+
+static gboolean
+vee_folder_expunge_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return CAMEL_FOLDER_GET_CLASS (folder)->
+ synchronize_sync (folder, TRUE, cancellable, error);
+}
+
+static CamelMimeMessage *
+vee_folder_get_message_sync (CamelFolder *folder,
+ const gchar *uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelVeeMessageInfo *mi;
+ CamelMimeMessage *msg = NULL;
+
+ mi = (CamelVeeMessageInfo *) camel_folder_summary_get (folder->summary, uid);
+ if (mi) {
+ msg = camel_folder_get_message_sync (
+ camel_folder_summary_get_folder (mi->orig_summary), camel_message_info_get_uid (mi) + 8,
+ cancellable, error);
+ camel_message_info_unref (mi);
+ } else {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID_UID,
+ /* Translators: The first '%s' is replaced with a message UID, the second '%s'
+ is replaced with an account name and the third '%s' is replaced with a full
+ path name. The spaces around ':' are intentional, as the whole '%s : %s' is
+ meant as an absolute identification of the folder. */
+ _("No such message %s in '%s : %s'"), uid,
+ camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
+ camel_folder_get_full_name (folder));
+ }
+
+ return msg;
+}
+
+static gboolean
+vee_folder_refresh_info_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelVeeFolder *vf = (CamelVeeFolder *) folder;
+
+ vee_folder_propagate_skipped_changes (vf);
+ vee_folder_rebuild_all (vf, cancellable);
+
+ return TRUE;
+}
+
+static gboolean
+vee_folder_synchronize_sync (CamelFolder *folder,
+ gboolean expunge,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelVeeFolder *vfolder = (CamelVeeFolder *) folder;
+ gboolean res = TRUE;
+ GList *iter;
+
+ g_return_val_if_fail (CAMEL_IS_VEE_FOLDER (folder), FALSE);
+
+ vee_folder_propagate_skipped_changes (vfolder);
+
+ /* basically no-op here, especially do not call synchronize on subfolders
+ * if not expunging, they are responsible for themselfs */
+ if (!expunge ||
+ vee_folder_is_unmatched (vfolder))
+ return TRUE;
+
+ g_rec_mutex_lock (&vfolder->priv->subfolder_lock);
+
+ for (iter = vfolder->priv->subfolders; iter && !g_cancellable_is_cancelled (cancellable); iter = iter->next) {
+ GError *local_error = NULL;
+ CamelFolder *subfolder = iter->data;
+
+ if (!camel_folder_synchronize_sync (subfolder, expunge, cancellable, &local_error)) {
+ if (local_error && strncmp (local_error->message, "no such table", 13) != 0 && error && !*error) {
+ const gchar *desc;
+
+ desc = camel_folder_get_description (subfolder);
+ g_propagate_prefixed_error (
+ error, local_error,
+ _("Error storing '%s': "), desc);
+
+ res = FALSE;
+ } else
+ g_clear_error (&local_error);
+ }
+ }
+
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+
+ return res;
+}
+
+static gboolean
+vee_folder_transfer_messages_to_sync (CamelFolder *folder,
+ GPtrArray *uids,
+ CamelFolder *dest,
+ gboolean delete_originals,
+ GPtrArray **transferred_uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Cannot copy or move messages into a Virtual Folder"));
+
+ return FALSE;
+}
+
+static void
+vee_folder_set_expression (CamelVeeFolder *vee_folder,
+ const gchar *query)
+{
+ g_rec_mutex_lock (&vee_folder->priv->subfolder_lock);
+
+ /* no change, do nothing */
+ if ((vee_folder->priv->expression && query && strcmp (vee_folder->priv->expression, query) == 0)
+ || (vee_folder->priv->expression == NULL && query == NULL)) {
+ g_rec_mutex_unlock (&vee_folder->priv->subfolder_lock);
+ return;
+ }
+
+ g_free (vee_folder->priv->expression);
+ if (query)
+ vee_folder->priv->expression = g_strdup (query);
+
+ vee_folder_rebuild_all (vee_folder, NULL);
+
+ g_rec_mutex_unlock (&vee_folder->priv->subfolder_lock);
+}
+
+static void
+vee_folder_rebuild_folder (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ GCancellable *cancellable)
+{
+ CamelFolderChangeInfo *changes;
+ CamelFolder *v_folder;
+
+ v_folder = CAMEL_FOLDER (vfolder);
+ changes = camel_folder_change_info_new ();
+
+ camel_folder_freeze (v_folder);
+ vee_folder_rebuild_folder_with_changes (vfolder, subfolder, changes, cancellable);
+ camel_folder_thaw (v_folder);
+
+ if (camel_folder_change_info_changed (changes))
+ camel_folder_changed (CAMEL_FOLDER (vfolder), changes);
+ camel_folder_change_info_free (changes);
+}
+
+static void
+vee_folder_add_folder (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ GCancellable *cancellable)
+{
+ if (vfolder->priv->parent_vee_store)
+ camel_vee_store_note_subfolder_used (vfolder->priv->parent_vee_store, subfolder, vfolder);
+ vee_folder_rebuild_folder (vfolder, subfolder, cancellable);
+}
+
+static gboolean
+vee_folder_remove_from_unmatched_changed_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ CamelVeeMessageInfoData *mi_data = key;
+ CamelFolder *subfolder = user_data;
+ CamelVeeSubfolderData *sf_data;
+
+ g_return_val_if_fail (mi_data != NULL, TRUE);
+
+ sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
+
+ return subfolder == camel_vee_subfolder_data_get_folder (sf_data);
+}
+
+static void
+vee_folder_remove_folder (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ GCancellable *cancellable)
+{
+ CamelFolderChangeInfo *changes;
+ CamelFolder *v_folder;
+ GHashTable *uids;
+
+ v_folder = CAMEL_FOLDER (vfolder);
+ changes = camel_folder_change_info_new ();
+
+ camel_folder_freeze (v_folder);
+
+ uids = camel_vee_summary_get_uids_for_subfolder (CAMEL_VEE_SUMMARY (v_folder->summary), subfolder);
+ if (uids) {
+ struct RemoveUnmatchedData rud;
+
+ rud.vfolder = vfolder;
+ rud.vsummary = CAMEL_VEE_SUMMARY (v_folder->summary);
+ rud.subfolder = subfolder;
+ rud.data_cache = vee_folder_get_data_cache (vfolder);
+ rud.changes = changes;
+ rud.is_orig_message_uid = FALSE;
+
+ /* It can be NULL on dispose of the CamelVeeStore */
+ if (!rud.data_cache) {
+ camel_folder_thaw (v_folder);
+ camel_folder_change_info_free (changes);
+ g_hash_table_destroy (uids);
+ return;
+ }
+
+ g_hash_table_foreach (uids, vee_folder_remove_unmatched_cb, &rud);
+
+ if (vee_folder_is_unmatched (vfolder) &&
+ !camel_vee_folder_get_auto_update (vfolder) &&
+ g_hash_table_size (vfolder->priv->unmatched_add_changed) +
+ g_hash_table_size (vfolder->priv->unmatched_remove_changed) > 0) {
+ /* forget about these in cached updates */
+ g_hash_table_foreach_remove (vfolder->priv->unmatched_add_changed,
+ vee_folder_remove_from_unmatched_changed_cb, subfolder);
+ g_hash_table_foreach_remove (vfolder->priv->unmatched_remove_changed,
+ vee_folder_remove_from_unmatched_changed_cb, subfolder);
+ }
+
+ g_hash_table_destroy (uids);
+ }
+
+ if (vfolder->priv->parent_vee_store)
+ camel_vee_store_note_subfolder_unused (vfolder->priv->parent_vee_store, subfolder, vfolder);
+
+ camel_folder_thaw (v_folder);
+
+ /* do not notify about changes in vfolder which
+ * is removing its subfolders in dispose */
+ if (!vfolder->priv->destroyed &&
+ camel_folder_change_info_changed (changes))
+ camel_folder_changed (CAMEL_FOLDER (vfolder), changes);
+ camel_folder_change_info_free (changes);
+}
+
+static void
+vee_folder_folder_changed (CamelVeeFolder *vee_folder,
+ CamelFolder *subfolder,
+ CamelFolderChangeInfo *changes)
+{
+ CamelVeeFolderPrivate *p = vee_folder->priv;
+ FolderChangedData *data;
+ CamelFolder *folder;
+ CamelStore *parent_store;
+ CamelSession *session;
+
+ if (p->destroyed)
+ return;
+
+ folder = CAMEL_FOLDER (vee_folder);
+ parent_store = camel_folder_get_parent_store (folder);
+ session = camel_service_ref_session (CAMEL_SERVICE (parent_store));
+ if (!session)
+ return;
+
+ g_async_queue_lock (vee_folder->priv->change_queue);
+
+ data = vee_folder_changed_data_new (subfolder, changes);
+
+ g_async_queue_push_unlocked (vee_folder->priv->change_queue, data);
+
+ if (!vee_folder->priv->change_queue_busy) {
+ gchar *description;
+
+ description = g_strdup_printf ("Updating search folder '%s'", camel_folder_get_full_name (CAMEL_FOLDER (vee_folder)));
+
+ camel_session_submit_job (
+ session, description, (CamelSessionCallback)
+ vee_folder_process_changes,
+ g_object_ref (vee_folder),
+ (GDestroyNotify) g_object_unref);
+ vee_folder->priv->change_queue_busy = TRUE;
+
+ g_free (description);
+ }
+
+ g_async_queue_unlock (vee_folder->priv->change_queue);
+
+ g_object_unref (session);
+}
+
+static void
+camel_vee_folder_class_init (CamelVeeFolderClass *class)
+{
+ GObjectClass *object_class;
+ CamelFolderClass *folder_class;
+
+ g_type_class_add_private (class, sizeof (CamelVeeFolderPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = vee_folder_dispose;
+ object_class->finalize = vee_folder_finalize;
+ object_class->get_property = vee_folder_get_property;
+ object_class->set_property = vee_folder_set_property;
+
+ folder_class = CAMEL_FOLDER_CLASS (class);
+ folder_class->search_by_expression = vee_folder_search_by_expression;
+ folder_class->search_by_uids = vee_folder_search_by_uids;
+ folder_class->count_by_expression = vee_folder_count_by_expression;
+ folder_class->search_free = vee_folder_search_free;
+ folder_class->delete_ = vee_folder_delete;
+ folder_class->freeze = vee_folder_freeze;
+ folder_class->thaw = vee_folder_thaw;
+ folder_class->append_message_sync = vee_folder_append_message_sync;
+ folder_class->expunge_sync = vee_folder_expunge_sync;
+ folder_class->get_message_sync = vee_folder_get_message_sync;
+ folder_class->refresh_info_sync = vee_folder_refresh_info_sync;
+ folder_class->synchronize_sync = vee_folder_synchronize_sync;
+ folder_class->transfer_messages_to_sync = vee_folder_transfer_messages_to_sync;
+
+ class->set_expression = vee_folder_set_expression;
+ class->add_folder = vee_folder_add_folder;
+ class->remove_folder = vee_folder_remove_folder;
+ class->rebuild_folder = vee_folder_rebuild_folder;
+ class->folder_changed = vee_folder_folder_changed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_AUTO_UPDATE,
+ g_param_spec_boolean (
+ "auto-update",
+ "Auto Update",
+ _("Automatically _update on change in source folders"),
+ TRUE,
+ G_PARAM_READWRITE |
+ CAMEL_PARAM_PERSISTENT));
+}
+
+static void
+camel_vee_folder_init (CamelVeeFolder *vee_folder)
+{
+ CamelFolder *folder = CAMEL_FOLDER (vee_folder);
+
+ vee_folder->priv = CAMEL_VEE_FOLDER_GET_PRIVATE (vee_folder);
+
+ folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_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;
+
+ g_rec_mutex_init (&vee_folder->priv->subfolder_lock);
+ g_rec_mutex_init (&vee_folder->priv->changed_lock);
+
+ vee_folder->priv->auto_update = TRUE;
+ vee_folder->priv->ignore_changed = g_hash_table_new (g_direct_hash, g_direct_equal);
+ vee_folder->priv->skipped_changes = g_hash_table_new (g_direct_hash, g_direct_equal);
+ vee_folder->priv->unmatched_add_changed =
+ g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
+ vee_folder->priv->unmatched_remove_changed =
+ g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
+
+ vee_folder->priv->change_queue = g_async_queue_new_full (
+ (GDestroyNotify) vee_folder_changed_data_free);
+}
+
+void
+camel_vee_folder_construct (CamelVeeFolder *vf,
+ guint32 flags)
+{
+ CamelFolder *folder = (CamelFolder *) vf;
+ CamelStore *parent_store;
+
+ vf->flags = flags;
+
+ parent_store = camel_folder_get_parent_store (CAMEL_FOLDER (vf));
+ if (CAMEL_IS_VEE_STORE (parent_store))
+ vf->priv->parent_vee_store = CAMEL_VEE_STORE (parent_store);
+ else
+ vf->priv->vee_data_cache = camel_vee_data_cache_new ();
+
+ folder->summary = camel_vee_summary_new (folder);
+
+ /* only for subfolders of vee-store */
+ if (vf->priv->parent_vee_store) {
+ const gchar *user_data_dir;
+ gchar *state_file, *folder_name, *filename;
+
+ user_data_dir = camel_service_get_user_data_dir (CAMEL_SERVICE (parent_store));
+
+ folder_name = g_uri_escape_string (camel_folder_get_full_name (folder), NULL, TRUE);
+ filename = g_strconcat (folder_name, ".cmeta", NULL);
+ state_file = g_build_filename (user_data_dir, filename, NULL);
+
+ camel_object_set_state_filename (CAMEL_OBJECT (vf), state_file);
+
+ g_free (state_file);
+ g_free (filename);
+ g_free (folder_name);
+
+ /* set/load persistent state */
+ camel_object_state_read (CAMEL_OBJECT (vf));
+ }
+}
+
+/**
+ * camel_vee_folder_new:
+ * @parent_store: the parent CamelVeeStore
+ * @full: the full path to the vfolder.
+ * @flags: flags of some kind
+ *
+ * Create a new CamelVeeFolder object.
+ *
+ * Returns: A new CamelVeeFolder widget.
+ **/
+CamelFolder *
+camel_vee_folder_new (CamelStore *parent_store,
+ const gchar *full,
+ guint32 flags)
+{
+ CamelVeeFolder *vf;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (parent_store), NULL);
+ g_return_val_if_fail (full != NULL, NULL);
+
+ if (CAMEL_IS_VEE_STORE (parent_store) && strcmp (full, CAMEL_UNMATCHED_NAME) == 0) {
+ vf = camel_vee_store_get_unmatched_folder (CAMEL_VEE_STORE (parent_store));
+ if (vf)
+ g_object_ref (vf);
+ } else {
+ const gchar *name = strrchr (full, '/');
+
+ if (name == NULL)
+ name = full;
+ else
+ name++;
+ vf = g_object_new (
+ CAMEL_TYPE_VEE_FOLDER,
+ "display-name", name, "full-name", full,
+ "parent-store", parent_store, NULL);
+ camel_vee_folder_construct (vf, flags);
+ }
+
+ d (printf ("returning folder %s %p, count = %d\n", full, vf, camel_folder_get_message_count ((CamelFolder *) vf)));
+
+ return (CamelFolder *) vf;
+}
+
+void
+camel_vee_folder_set_expression (CamelVeeFolder *vfolder,
+ const gchar *expr)
+{
+ CAMEL_VEE_FOLDER_GET_CLASS (vfolder)->set_expression (vfolder, expr);
+}
+
+/**
+ * camel_vee_folder_get_expression:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+const gchar *
+camel_vee_folder_get_expression (CamelVeeFolder *vfolder)
+{
+ g_return_val_if_fail (CAMEL_IS_VEE_FOLDER (vfolder), NULL);
+
+ return vfolder->priv->expression;
+}
+
+/**
+ * camel_vee_folder_add_folder:
+ * @vfolder: Virtual Folder object
+ * @subfolder: source CamelFolder to add to @vfolder
+ *
+ * Adds @subfolder as a source folder to @vfolder.
+ **/
+void
+camel_vee_folder_add_folder (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ GCancellable *cancellable)
+{
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder));
+
+ if (vfolder == (CamelVeeFolder *) subfolder) {
+ g_warning ("Adding a virtual folder to itself as source, ignored");
+ return;
+ }
+
+ g_rec_mutex_lock (&vfolder->priv->subfolder_lock);
+
+ if (g_list_find (vfolder->priv->subfolders, subfolder) == NULL) {
+ gint freeze_count;
+
+ vfolder->priv->subfolders = g_list_append (vfolder->priv->subfolders, g_object_ref (subfolder));
+
+ freeze_count = camel_folder_get_frozen_count (CAMEL_FOLDER (vfolder));
+ while (freeze_count > 0) {
+ camel_folder_freeze (subfolder);
+ freeze_count--;
+ }
+ } else {
+ /* nothing to do, it's already there */
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+ return;
+ }
+
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+
+ g_signal_connect (
+ subfolder, "changed",
+ G_CALLBACK (subfolder_changed), vfolder);
+
+ g_signal_connect (
+ subfolder, "deleted",
+ G_CALLBACK (subfolder_deleted), vfolder);
+
+ CAMEL_VEE_FOLDER_GET_CLASS (vfolder)->add_folder (vfolder, subfolder, cancellable);
+}
+
+/**
+ * camel_vee_folder_remove_folder:
+ * @vfolder: Virtual Folder object
+ * @subfolder: source CamelFolder to remove from @vfolder
+ *
+ * Removed the source folder, @subfolder, from the virtual folder, @vfolder.
+ **/
+void
+camel_vee_folder_remove_folder (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ GCancellable *cancellable)
+{
+ gint freeze_count;
+
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder));
+
+ g_rec_mutex_lock (&vfolder->priv->subfolder_lock);
+
+ if (g_list_find (vfolder->priv->subfolders, subfolder) == NULL) {
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+ return;
+ }
+
+ g_signal_handlers_disconnect_by_func (subfolder, subfolder_changed, vfolder);
+ g_signal_handlers_disconnect_by_func (subfolder, subfolder_deleted, vfolder);
+
+ vfolder->priv->subfolders = g_list_remove (vfolder->priv->subfolders, subfolder);
+
+ freeze_count = camel_folder_get_frozen_count (CAMEL_FOLDER (vfolder));
+ while (freeze_count > 0) {
+ camel_folder_thaw (subfolder);
+ freeze_count--;
+ }
+
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+
+ CAMEL_VEE_FOLDER_GET_CLASS (vfolder)->remove_folder (vfolder, subfolder, cancellable);
+
+ g_object_unref (subfolder);
+}
+
+/**
+ * camel_vee_folder_rebuild_folder:
+ * @vfolder: Virtual Folder object
+ * @subfolder: source CamelFolder to add to @vfolder
+ * @cancellable:
+ *
+ * Rebuild the folder @subfolder, if it should be.
+ **/
+void
+camel_vee_folder_rebuild_folder (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ GCancellable *cancellable)
+{
+ vee_folder_propagate_skipped_changes (vfolder);
+
+ CAMEL_VEE_FOLDER_GET_CLASS (vfolder)->rebuild_folder (vfolder, subfolder, cancellable);
+}
+
+static void
+remove_folders (CamelFolder *folder,
+ CamelFolder *foldercopy,
+ CamelVeeFolder *vf)
+{
+ camel_vee_folder_remove_folder (vf, folder, NULL);
+ g_object_unref (folder);
+}
+
+/**
+ * camel_vee_folder_set_folders:
+ * @vf:
+ * @folders: (element-type CamelFolder) (transfer none):
+ *
+ * Set the whole list of folder sources on a vee folder.
+ **/
+void
+camel_vee_folder_set_folders (CamelVeeFolder *vf,
+ GList *folders,
+ GCancellable *cancellable)
+{
+ CamelVeeFolderPrivate *p = CAMEL_VEE_FOLDER_GET_PRIVATE (vf);
+ GHashTable *remove = g_hash_table_new (NULL, NULL);
+ GList *l, *to_add = NULL;
+ CamelFolder *folder;
+
+ /* setup a table of all folders we have currently */
+ g_rec_mutex_lock (&vf->priv->subfolder_lock);
+ l = p->subfolders;
+ while (l) {
+ g_hash_table_insert (remove, l->data, l->data);
+ g_object_ref (l->data);
+ l = l->next;
+ }
+ g_rec_mutex_unlock (&vf->priv->subfolder_lock);
+
+ camel_folder_freeze (CAMEL_FOLDER (vf));
+
+ /* if we already have the folder, ignore it, otherwise mark to add it */
+ l = folders;
+ while (l) {
+ if ((folder = g_hash_table_lookup (remove, l->data))) {
+ g_hash_table_remove (remove, folder);
+ g_object_unref (folder);
+ } else {
+ to_add = g_list_prepend (to_add, g_object_ref (l->data));
+ }
+ l = l->next;
+ }
+
+ /* first remove any we still have */
+ g_hash_table_foreach (remove, (GHFunc) remove_folders, vf);
+ g_hash_table_destroy (remove);
+
+ /* then add those new */
+ for (l = to_add; l; l = l->next) {
+ camel_vee_folder_add_folder (vf, l->data, cancellable);
+ }
+ g_list_free_full (to_add, g_object_unref);
+
+ camel_folder_thaw (CAMEL_FOLDER (vf));
+}
+
+/**
+ * camel_vee_folder_add_vuid:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_folder_add_vuid (CamelVeeFolder *vfolder,
+ CamelVeeMessageInfoData *mi_data,
+ CamelFolderChangeInfo *changes)
+{
+ CamelVeeSummary *vsummary;
+ CamelVeeSubfolderData *sf_data;
+ CamelFolder *subfolder;
+
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder));
+ g_return_if_fail (mi_data != NULL);
+ g_return_if_fail (vee_folder_is_unmatched (vfolder));
+
+ sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
+ subfolder = camel_vee_subfolder_data_get_folder (sf_data);
+
+ g_rec_mutex_lock (&vfolder->priv->changed_lock);
+ if (!camel_vee_folder_get_auto_update (vfolder) ||
+ g_hash_table_lookup (vfolder->priv->ignore_changed, subfolder) ||
+ g_hash_table_lookup (vfolder->priv->skipped_changes, subfolder)) {
+ g_hash_table_remove (vfolder->priv->unmatched_remove_changed, mi_data);
+
+ g_rec_mutex_lock (&vfolder->priv->subfolder_lock);
+
+ if (g_list_find (vfolder->priv->subfolders, subfolder)) {
+ /* postpone addition to the Unmatched folder, if the change was done
+ * in the Unmatched folder itself or auto-update is disabled */
+ g_hash_table_insert (
+ vfolder->priv->unmatched_add_changed,
+ g_object_ref (mi_data), GINT_TO_POINTER (1));
+ }
+
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+ g_rec_mutex_unlock (&vfolder->priv->changed_lock);
+
+ return;
+ }
+
+ g_rec_mutex_unlock (&vfolder->priv->changed_lock);
+
+ vsummary = CAMEL_VEE_SUMMARY (CAMEL_FOLDER (vfolder)->summary);
+ vee_folder_note_added_uid (vfolder, vsummary, mi_data, changes, FALSE);
+}
+
+/**
+ * camel_vee_folder_remove_vuid:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_folder_remove_vuid (CamelVeeFolder *vfolder,
+ CamelVeeMessageInfoData *mi_data,
+ CamelFolderChangeInfo *changes)
+{
+ CamelVeeSummary *vsummary;
+ CamelVeeSubfolderData *sf_data;
+ CamelVeeDataCache *data_cache;
+ CamelFolder *subfolder;
+
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder));
+ g_return_if_fail (mi_data != NULL);
+ g_return_if_fail (vee_folder_is_unmatched (vfolder));
+
+ sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
+ subfolder = camel_vee_subfolder_data_get_folder (sf_data);
+
+ g_rec_mutex_lock (&vfolder->priv->changed_lock);
+ if (!camel_vee_folder_get_auto_update (vfolder) ||
+ g_hash_table_lookup (vfolder->priv->ignore_changed, subfolder) ||
+ g_hash_table_lookup (vfolder->priv->skipped_changes, subfolder)) {
+ g_hash_table_remove (vfolder->priv->unmatched_add_changed, mi_data);
+
+ g_rec_mutex_lock (&vfolder->priv->subfolder_lock);
+
+ if (g_list_find (vfolder->priv->subfolders, subfolder)) {
+ /* postpone removal from the Unmatched folder, if the change was done
+ * in the Unmatched folder itself or auto-update is disabled */
+ g_hash_table_insert (
+ vfolder->priv->unmatched_remove_changed,
+ g_object_ref (mi_data), GINT_TO_POINTER (1));
+ }
+
+ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock);
+ g_rec_mutex_unlock (&vfolder->priv->changed_lock);
+
+ return;
+ }
+
+ g_rec_mutex_unlock (&vfolder->priv->changed_lock);
+
+ vsummary = CAMEL_VEE_SUMMARY (CAMEL_FOLDER (vfolder)->summary);
+ data_cache = vee_folder_get_data_cache (vfolder);
+
+ /* It can be NULL on dispose of the CamelVeeStore */
+ if (data_cache)
+ vee_folder_note_unmatch_uid (vfolder, vsummary, subfolder, data_cache, mi_data, changes);
+}
+
+/**
+ * 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)
+ *
+ * Returns: (transfer none):
+ **/
+CamelFolder *
+camel_vee_folder_get_location (CamelVeeFolder *vf,
+ const CamelVeeMessageInfo *vinfo,
+ gchar **realuid)
+{
+ CamelFolder *folder;
+
+ g_return_val_if_fail (CAMEL_IS_VEE_FOLDER (vf), NULL);
+ g_return_val_if_fail (vinfo != NULL, NULL);
+
+ folder = camel_folder_summary_get_folder (vinfo->orig_summary);
+
+ /* locking? yes? no? although the vfolderinfo is valid when obtained
+ * the folder in it might not necessarily be so ...? */
+ if (CAMEL_IS_VEE_FOLDER (folder)) {
+ CamelFolder *res;
+ const CamelVeeMessageInfo *vfinfo;
+
+ vfinfo = (CamelVeeMessageInfo *) camel_folder_get_message_info (folder, camel_message_info_get_uid (vinfo) + 8);
+ res = camel_vee_folder_get_location ((CamelVeeFolder *) folder, vfinfo, realuid);
+ camel_message_info_unref ((CamelMessageInfo *) vfinfo);
+ return res;
+ } else {
+ if (realuid)
+ *realuid = g_strdup (camel_message_info_get_uid (vinfo)+8);
+
+ return folder;
+ }
+}
+
+/**
+ * camel_vee_folder_get_vee_uid_folder:
+ *
+ * FIXME Document me!
+ *
+ * Returns: (transfer none):
+ *
+ * Since: 3.6
+ **/
+CamelFolder *
+camel_vee_folder_get_vee_uid_folder (CamelVeeFolder *vf,
+ const gchar *vee_message_uid)
+{
+ CamelFolder *res;
+ CamelVeeDataCache *data_cache;
+ CamelVeeMessageInfoData *mi_data;
+ CamelVeeSubfolderData *sf_data;
+
+ g_return_val_if_fail (CAMEL_IS_VEE_FOLDER (vf), NULL);
+ g_return_val_if_fail (vee_message_uid, NULL);
+
+ res = NULL;
+
+ data_cache = vee_folder_get_data_cache (vf);
+ g_return_val_if_fail (data_cache != NULL, NULL);
+
+ mi_data = camel_vee_data_cache_get_message_info_data_by_vuid (data_cache, vee_message_uid);
+ if (mi_data) {
+ sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
+ res = camel_vee_subfolder_data_get_folder (sf_data);
+ g_object_unref (mi_data);
+ }
+
+ return res;
+}
+
+/**
+ * camel_vee_folder_set_auto_update:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_folder_set_auto_update (CamelVeeFolder *vfolder,
+ gboolean auto_update)
+{
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder));
+
+ if (vfolder->priv->auto_update == auto_update)
+ return;
+
+ vfolder->priv->auto_update = auto_update;
+
+ g_object_notify (G_OBJECT (vfolder), "auto-update");
+}
+
+/**
+ * camel_vee_folder_get_auto_update:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+gboolean
+camel_vee_folder_get_auto_update (CamelVeeFolder *vfolder)
+{
+ g_return_val_if_fail (CAMEL_IS_VEE_FOLDER (vfolder), FALSE);
+
+ return vfolder->priv->auto_update;
+}
+
+/**
+ * camel_vee_folder_ignore_next_changed_event:
+ * @vfolder: a #CamelVeeFolder
+ * @subfolder: a #CamelFolder folder
+ *
+ * The next @subfolder-'s 'changed' event will be silently ignored. This
+ * is usually used in virtual folders when the change was done in them,
+ * but it is neither vTrash nor vJunk folder. Doing this avoids unnecessary
+ * removals of messages which don't satisfy search criteria anymore,
+ * which could be done on asynchronous delivery of folder's 'changed' signal.
+ * These ignored changes are accumulated and used on folder refresh.
+ *
+ * Since: 3.2
+ **/
+void
+camel_vee_folder_ignore_next_changed_event (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder)
+{
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder));
+ g_return_if_fail (subfolder != NULL);
+
+ g_rec_mutex_lock (&vfolder->priv->changed_lock);
+ g_hash_table_insert (vfolder->priv->ignore_changed, subfolder, GINT_TO_POINTER (1));
+ g_rec_mutex_unlock (&vfolder->priv->changed_lock);
+}
+
+/**
+ * camel_vee_folder_remove_from_ignore_changed_event:
+ * @vfolder: a #CamelVeeFolder
+ * @subfolder: a #CamelFolder folder
+ *
+ * Make sure the next @subfolder-'s 'changed' event will not be silently ignored.
+ * This is a counter-part function of camel_vee_folder_ignore_next_changed_event(),
+ * when there was expected a change, which did not happen, to take back the previous
+ * ignore event request.
+ *
+ * Since: 3.12
+ **/
+void
+camel_vee_folder_remove_from_ignore_changed_event (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder)
+{
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder));
+ g_return_if_fail (subfolder != NULL);
+
+ g_rec_mutex_lock (&vfolder->priv->changed_lock);
+ g_hash_table_remove (vfolder->priv->ignore_changed, subfolder);
+ g_rec_mutex_unlock (&vfolder->priv->changed_lock);
+}
diff --git a/src/camel/camel-vee-folder.h b/src/camel/camel-vee-folder.h
new file mode 100644
index 000000000..c1854bcfc
--- /dev/null
+++ b/src/camel/camel-vee-folder.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_VEE_FOLDER_H
+#define CAMEL_VEE_FOLDER_H
+
+#include <camel/camel-folder.h>
+#include <camel/camel-folder-search.h>
+#include <camel/camel-store.h>
+#include <camel/camel-vee-summary.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_VEE_FOLDER \
+ (camel_vee_folder_get_type ())
+#define CAMEL_VEE_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_VEE_FOLDER, CamelVeeFolder))
+#define CAMEL_VEE_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_VEE_FOLDER, CamelVeeFolderClass))
+#define CAMEL_IS_VEE_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_VEE_FOLDER))
+#define CAMEL_IS_VEE_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_VEE_FOLDER))
+#define CAMEL_VEE_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_VEE_FOLDER, CamelVeeFolderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelVeeFolder CamelVeeFolder;
+typedef struct _CamelVeeFolderClass CamelVeeFolderClass;
+typedef struct _CamelVeeFolderPrivate CamelVeeFolderPrivate;
+
+struct _CamelVeeFolder {
+ CamelFolder parent;
+ CamelVeeFolderPrivate *priv;
+
+ guint32 flags; /* folder open flags */
+};
+
+struct _CamelVeeFolderClass {
+ CamelFolderClass parent_class;
+
+ /* TODO: Some of this may need some additional work/thinking through, it works for now*/
+
+ void (*add_folder) (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ GCancellable *cancellable);
+ void (*remove_folder) (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ GCancellable *cancellable);
+ void (*rebuild_folder) (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ GCancellable *cancellable);
+
+ void (*set_expression) (CamelVeeFolder *vfolder,
+ const gchar *expression);
+
+ /* Called for a folder-changed event on a source folder */
+ void (*folder_changed) (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ CamelFolderChangeInfo *changes);
+};
+
+#define CAMEL_UNMATCHED_NAME "UNMATCHED"
+
+GType camel_vee_folder_get_type (void);
+CamelFolder * camel_vee_folder_new (CamelStore *parent_store,
+ const gchar *full,
+ guint32 flags);
+void camel_vee_folder_construct (CamelVeeFolder *vf,
+ guint32 flags);
+
+CamelFolder * camel_vee_folder_get_location (CamelVeeFolder *vf,
+ const struct _CamelVeeMessageInfo *vinfo,
+ gchar **realuid);
+CamelFolder * camel_vee_folder_get_vee_uid_folder (CamelVeeFolder *vf,
+ const gchar *vee_message_uid);
+void camel_vee_folder_set_auto_update (CamelVeeFolder *vfolder,
+ gboolean auto_update);
+gboolean camel_vee_folder_get_auto_update (CamelVeeFolder *vfolder);
+void camel_vee_folder_add_folder (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ GCancellable *cancellable);
+void camel_vee_folder_remove_folder (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ GCancellable *cancellable);
+void camel_vee_folder_set_folders (CamelVeeFolder *vf,
+ GList *folders,
+ GCancellable *cancellable);
+void camel_vee_folder_add_vuid (CamelVeeFolder *vfolder,
+ struct _CamelVeeMessageInfoData *mi_data,
+ CamelFolderChangeInfo *changes);
+void camel_vee_folder_remove_vuid (CamelVeeFolder *vfolder,
+ struct _CamelVeeMessageInfoData *mi_data,
+ CamelFolderChangeInfo *changes);
+
+void camel_vee_folder_rebuild_folder (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder,
+ GCancellable *cancellable);
+void camel_vee_folder_set_expression (CamelVeeFolder *vfolder,
+ const gchar *expr);
+const gchar * camel_vee_folder_get_expression (CamelVeeFolder *vfolder);
+
+void camel_vee_folder_ignore_next_changed_event
+ (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder);
+void camel_vee_folder_remove_from_ignore_changed_event
+ (CamelVeeFolder *vfolder,
+ CamelFolder *subfolder);
+
+G_END_DECLS
+
+#endif /* CAMEL_VEE_FOLDER_H */
diff --git a/src/camel/camel-vee-store.c b/src/camel/camel-vee-store.c
new file mode 100644
index 000000000..393207cbc
--- /dev/null
+++ b/src/camel/camel-vee-store.c
@@ -0,0 +1,1051 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include "camel-db.h"
+#include "camel-session.h"
+#include "camel-string-utils.h"
+#include "camel-vee-folder.h"
+#include "camel-vee-store.h"
+
+#define CAMEL_VEE_STORE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_VEE_STORE, CamelVeeStorePrivate))
+
+/* Translators: 'Unmatched' is a folder name under Search folders where are shown
+ * all messages not belonging into any other configured search folder */
+#define PRETTY_UNMATCHED_FOLDER_NAME _("Unmatched")
+
+#define d(x)
+
+/* flags
+ * 1 = delete (0 = add)
+ * 2 = noselect
+*/
+#define CHANGE_ADD (0)
+#define CHANGE_DELETE (1)
+#define CHANGE_NOSELECT (2)
+
+/* The custom property ID is a CamelArg artifact.
+ * It still identifies the property in state files. */
+enum {
+ PROP_0,
+ PROP_UNMATCHED_ENABLED = 0x2400
+};
+
+G_DEFINE_TYPE (CamelVeeStore, camel_vee_store, CAMEL_TYPE_STORE)
+
+struct _CamelVeeStorePrivate {
+ CamelVeeDataCache *vee_data_cache;
+ CamelVeeFolder *unmatched_folder;
+ gboolean unmatched_enabled;
+
+ GMutex sf_counts_mutex;
+ GHashTable *subfolder_usage_counts; /* CamelFolder * (subfolder) => gint of usages, for unmatched_folder */
+
+ GMutex vu_counts_mutex;
+ GHashTable *vuid_usage_counts; /* gchar * (vuid) => gint of usages, those with 0 comes to unmatched_folder */
+};
+
+static gint
+vee_folder_cmp (gconstpointer ap,
+ gconstpointer bp)
+{
+ const gchar *full_name_a;
+ const gchar *full_name_b;
+
+ full_name_a = camel_folder_get_full_name (((CamelFolder **) ap)[0]);
+ full_name_b = camel_folder_get_full_name (((CamelFolder **) bp)[0]);
+
+ return g_strcmp0 (full_name_a, full_name_b);
+}
+
+static void
+change_folder (CamelStore *store,
+ const gchar *name,
+ guint32 flags,
+ gint count)
+{
+ CamelFolderInfo *fi;
+ const gchar *tmp;
+
+ fi = camel_folder_info_new ();
+ fi->full_name = g_strdup (name);
+ tmp = strrchr (name, '/');
+ if (tmp == NULL)
+ tmp = name;
+ else
+ tmp++;
+ fi->display_name = g_strdup (tmp);
+ fi->unread = count;
+ fi->flags = CAMEL_FOLDER_VIRTUAL;
+ if (!(flags & CHANGE_DELETE))
+ fi->flags |= CAMEL_FOLDER_NOCHILDREN;
+ if (flags & CHANGE_NOSELECT)
+ fi->flags |= CAMEL_FOLDER_NOSELECT;
+ if (flags & CHANGE_DELETE)
+ camel_store_folder_deleted (store, fi);
+ else
+ camel_store_folder_created (store, fi);
+ camel_folder_info_free (fi);
+}
+
+static void
+vee_store_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_UNMATCHED_ENABLED:
+ camel_vee_store_set_unmatched_enabled (
+ CAMEL_VEE_STORE (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+vee_store_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_UNMATCHED_ENABLED:
+ g_value_set_boolean (
+ value,
+ camel_vee_store_get_unmatched_enabled (
+ CAMEL_VEE_STORE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+vee_store_dispose (GObject *object)
+{
+ CamelVeeStorePrivate *priv;
+
+ priv = CAMEL_VEE_STORE_GET_PRIVATE (object);
+
+ g_clear_object (&priv->vee_data_cache);
+ g_clear_object (&priv->unmatched_folder);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_vee_store_parent_class)->dispose (object);
+}
+
+static void
+vee_store_finalize (GObject *object)
+{
+ CamelVeeStorePrivate *priv;
+
+ priv = CAMEL_VEE_STORE_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->subfolder_usage_counts);
+ g_hash_table_destroy (priv->vuid_usage_counts);
+ g_mutex_clear (&priv->sf_counts_mutex);
+ g_mutex_clear (&priv->vu_counts_mutex);
+
+ /* Chain up to parent's finalize () method. */
+ G_OBJECT_CLASS (camel_vee_store_parent_class)->finalize (object);
+}
+
+static void
+vee_store_constructed (GObject *object)
+{
+ CamelVeeStore *vee_store;
+
+ vee_store = CAMEL_VEE_STORE (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (camel_vee_store_parent_class)->constructed (object);
+
+ /* Set up unmatched folder */
+ vee_store->priv->unmatched_folder = g_object_new (
+ CAMEL_TYPE_VEE_FOLDER,
+ "full-name", CAMEL_UNMATCHED_NAME,
+ "display-name", PRETTY_UNMATCHED_FOLDER_NAME,
+ "parent-store", vee_store, NULL);
+ camel_vee_folder_construct (
+ vee_store->priv->unmatched_folder, CAMEL_STORE_FOLDER_PRIVATE);
+ vee_store->priv->subfolder_usage_counts = g_hash_table_new (g_direct_hash, g_direct_equal);
+ vee_store->priv->vuid_usage_counts = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
+ g_mutex_init (&vee_store->priv->sf_counts_mutex);
+ g_mutex_init (&vee_store->priv->vu_counts_mutex);
+}
+
+static gchar *
+vee_store_get_name (CamelService *service,
+ gboolean brief)
+{
+ return g_strdup ("Virtual Folder Store");
+}
+
+static CamelFolder *
+vee_store_get_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelVeeFolder *vf;
+ CamelFolder *folder;
+ gchar *name, *p;
+ gsize name_len;
+
+ vf = (CamelVeeFolder *) camel_vee_folder_new (store, folder_name, flags);
+ if (vf && ((vf->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0)) {
+ const gchar *full_name;
+
+ full_name = camel_folder_get_full_name (CAMEL_FOLDER (vf));
+
+ /* Check that parents exist, if not, create dummy ones */
+ name_len = strlen (full_name) + 1;
+ name = alloca (name_len);
+ g_strlcpy (name, full_name, name_len);
+ 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 {
+ g_object_unref (folder);
+ }
+ *p++='/';
+ }
+
+ change_folder (store, full_name, CHANGE_ADD, camel_folder_get_message_count ((CamelFolder *) vf));
+ }
+
+ return (CamelFolder *) vf;
+}
+
+static CamelFolderInfo *
+vee_store_create_unmatched_fi (void)
+{
+ CamelFolderInfo *info;
+
+ info = camel_folder_info_new ();
+ info->full_name = g_strdup (CAMEL_UNMATCHED_NAME);
+ info->display_name = g_strdup (PRETTY_UNMATCHED_FOLDER_NAME);
+ info->unread = -1;
+ info->flags =
+ CAMEL_FOLDER_NOCHILDREN |
+ CAMEL_FOLDER_NOINFERIORS |
+ CAMEL_FOLDER_SYSTEM |
+ CAMEL_FOLDER_VIRTUAL;
+
+ return info;
+}
+
+static CamelFolderInfo *
+vee_store_get_folder_info_sync (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderInfo *info, *res = NULL, *tail;
+ GPtrArray *folders;
+ GHashTable *infos_hash;
+ gint 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];
+ const gchar *full_name;
+ const gchar *display_name;
+ gint add = FALSE;
+ gchar *pname, *tmp;
+ CamelFolderInfo *pinfo;
+
+ full_name = camel_folder_get_full_name (CAMEL_FOLDER (folder));
+ display_name = camel_folder_get_display_name (CAMEL_FOLDER (folder));
+
+ /* check we have to include this one */
+ if (top) {
+ gint namelen = strlen (full_name);
+ gint toplen = strlen (top);
+
+ add = ((namelen == toplen
+ && strcmp (full_name, top) == 0)
+ || ((namelen > toplen)
+ && strncmp (full_name, top, toplen) == 0
+ && full_name[toplen] == '/'
+ && ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)
+ || strchr (full_name + toplen + 1, '/') == NULL)));
+ } else {
+ add = (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)
+ || strchr (full_name, '/') == NULL;
+ }
+
+ if (add) {
+ gint32 unread;
+
+ unread = camel_folder_get_unread_message_count (
+ CAMEL_FOLDER (folder));
+
+ info = camel_folder_info_new ();
+ info->full_name = g_strdup (full_name);
+ info->display_name = g_strdup (display_name);
+ info->unread = unread;
+ 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 (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);
+ g_object_unref (folder);
+ }
+ g_ptr_array_free (folders, TRUE);
+ g_hash_table_destroy (infos_hash);
+
+ /* and add UNMATCHED, if scanning from top/etc and it's enabled */
+ if (camel_vee_store_get_unmatched_enabled (CAMEL_VEE_STORE (store)) &&
+ (top == NULL || top[0] == 0 || strncmp (top, CAMEL_UNMATCHED_NAME, strlen (CAMEL_UNMATCHED_NAME)) == 0)) {
+ info = vee_store_create_unmatched_fi ();
+
+ if (res == NULL)
+ res = info;
+ else {
+ tail = res;
+ while (tail->next)
+ tail = tail->next;
+ tail->next = info;
+ }
+ }
+
+ return res;
+}
+
+static CamelFolder *
+vee_store_get_junk_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return NULL;
+}
+
+static CamelFolder *
+vee_store_get_trash_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return NULL;
+}
+
+static gboolean
+vee_store_delete_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+
+ if (strcmp (folder_name, CAMEL_UNMATCHED_NAME) == 0) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Cannot delete folder: %s: Invalid operation"),
+ folder_name);
+ return FALSE;
+ }
+
+ folder = camel_object_bag_get (store->folders, folder_name);
+ if (folder) {
+ CamelObject *object = CAMEL_OBJECT (folder);
+ const gchar *state_filename;
+
+ state_filename = camel_object_get_state_filename (object);
+ if (state_filename != NULL) {
+ g_unlink (state_filename);
+ camel_object_set_state_filename (object, NULL);
+ }
+
+ if ((((CamelVeeFolder *) folder)->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0) {
+ /* what about now-empty parents? ignore? */
+ change_folder (store, folder_name, CHANGE_DELETE, -1);
+ }
+
+ g_object_unref (folder);
+ } else {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Cannot delete folder: %s: No such folder"),
+ folder_name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+vee_store_rename_folder_sync (CamelStore *store,
+ const gchar *old,
+ const gchar *new,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder, *oldfolder;
+ gchar *p, *name;
+ gsize name_len;
+
+ d (printf ("vee rename folder '%s' '%s'\n", old, new));
+
+ if (strcmp (old, CAMEL_UNMATCHED_NAME) == 0) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Cannot rename folder: %s: Invalid operation"), old);
+ return FALSE;
+ }
+
+ /* See if it exists, for vfolders, all folders are in the folders hash */
+ oldfolder = camel_object_bag_get (store->folders, old);
+ if (oldfolder == NULL) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Cannot rename folder: %s: No such folder"), old);
+ return FALSE;
+ }
+
+ /* Check that new parents exist, if not, create dummy ones */
+ name_len = strlen (new) + 1;
+ name = alloca (name_len);
+ g_strlcpy (name, new, name_len);
+ 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 {
+ g_object_unref (folder);
+ }
+ *p++='/';
+ }
+
+ g_object_unref (oldfolder);
+
+ return TRUE;
+}
+
+static void
+camel_vee_store_class_init (CamelVeeStoreClass *class)
+{
+ GObjectClass *object_class;
+ CamelServiceClass *service_class;
+ CamelStoreClass *store_class;
+
+ g_type_class_add_private (class, sizeof (CamelVeeStorePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = vee_store_set_property;
+ object_class->get_property = vee_store_get_property;
+ object_class->dispose = vee_store_dispose;
+ object_class->finalize = vee_store_finalize;
+ object_class->constructed = vee_store_constructed;
+
+ service_class = CAMEL_SERVICE_CLASS (class);
+ service_class->get_name = vee_store_get_name;
+
+ store_class = CAMEL_STORE_CLASS (class);
+ store_class->get_folder_sync = vee_store_get_folder_sync;
+ store_class->get_folder_info_sync = vee_store_get_folder_info_sync;
+ store_class->get_junk_folder_sync = vee_store_get_junk_folder_sync;
+ store_class->get_trash_folder_sync = vee_store_get_trash_folder_sync;
+ store_class->delete_folder_sync = vee_store_delete_folder_sync;
+ store_class->rename_folder_sync = vee_store_rename_folder_sync;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_UNMATCHED_ENABLED,
+ g_param_spec_boolean (
+ "unmatched-enabled",
+ "Unmatched Enabled",
+ _("Enable _Unmatched folder"),
+ TRUE,
+ G_PARAM_READWRITE));
+}
+
+static void
+camel_vee_store_init (CamelVeeStore *vee_store)
+{
+ CamelStore *store = CAMEL_STORE (vee_store);
+
+ vee_store->priv = CAMEL_VEE_STORE_GET_PRIVATE (vee_store);
+ vee_store->priv->vee_data_cache = camel_vee_data_cache_new ();
+ vee_store->priv->unmatched_enabled = TRUE;
+
+ /* we dont want a vtrash/vjunk on this one */
+ store->flags &= ~(CAMEL_STORE_VTRASH | CAMEL_STORE_VJUNK);
+}
+
+/**
+ * camel_vee_store_new:
+ *
+ * Create a new #CamelVeeStore object.
+ *
+ * Returns: new #CamelVeeStore object
+ **/
+CamelVeeStore *
+camel_vee_store_new (void)
+{
+ return g_object_new (CAMEL_TYPE_VEE_STORE, NULL);
+}
+
+/**
+ * camel_vee_store_get_vee_data_cache:
+ *
+ * FIXME Document me!
+ *
+ * Returns: (transfer none):
+ *
+ * Since: 3.6
+ **/
+CamelVeeDataCache *
+camel_vee_store_get_vee_data_cache (CamelVeeStore *vstore)
+{
+ g_return_val_if_fail (CAMEL_IS_VEE_STORE (vstore), NULL);
+
+ return vstore->priv->vee_data_cache;
+}
+
+/**
+ * camel_vee_store_get_unmatched_folder:
+ *
+ * FIXME Document me!
+ *
+ * Returns: (transfer none):
+ *
+ * Since: 3.6
+ **/
+CamelVeeFolder *
+camel_vee_store_get_unmatched_folder (CamelVeeStore *vstore)
+{
+ g_return_val_if_fail (CAMEL_IS_VEE_STORE (vstore), NULL);
+
+ if (!camel_vee_store_get_unmatched_enabled (vstore))
+ return NULL;
+
+ return vstore->priv->unmatched_folder;
+}
+
+/**
+ * camel_vee_store_get_unmatched_enabled:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+gboolean
+camel_vee_store_get_unmatched_enabled (CamelVeeStore *vstore)
+{
+ g_return_val_if_fail (CAMEL_IS_VEE_STORE (vstore), FALSE);
+
+ return vstore->priv->unmatched_enabled;
+}
+
+/**
+ * camel_vee_store_set_unmatched_enabled:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_store_set_unmatched_enabled (CamelVeeStore *vstore,
+ gboolean is_enabled)
+{
+ CamelFolderInfo *fi_unmatched;
+
+ g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
+
+ if (vstore->priv->unmatched_enabled == is_enabled)
+ return;
+
+ vstore->priv->unmatched_enabled = is_enabled;
+ g_object_notify (G_OBJECT (vstore), "unmatched-enabled");
+
+ fi_unmatched = vee_store_create_unmatched_fi ();
+
+ if (is_enabled) {
+ camel_store_folder_created (CAMEL_STORE (vstore), fi_unmatched);
+ camel_vee_store_rebuild_unmatched_folder (vstore, NULL, NULL);
+ } else {
+ camel_store_folder_deleted (CAMEL_STORE (vstore), fi_unmatched);
+ }
+
+ camel_folder_info_free (fi_unmatched);
+}
+
+struct AddToUnmatchedData {
+ CamelVeeFolder *unmatched_folder;
+ CamelFolderChangeInfo *changes;
+ gboolean unmatched_enabled;
+ GHashTable *vuid_usage_counts;
+};
+
+static void
+add_to_unmatched_folder_cb (CamelVeeMessageInfoData *mi_data,
+ CamelFolder *subfolder,
+ gpointer user_data)
+{
+ struct AddToUnmatchedData *atud = user_data;
+ const gchar *vuid;
+
+ g_return_if_fail (atud != NULL);
+
+ vuid = camel_vee_message_info_data_get_vee_message_uid (mi_data);
+ g_hash_table_insert (
+ atud->vuid_usage_counts,
+ (gpointer) camel_pstring_strdup (vuid),
+ GINT_TO_POINTER (0));
+
+ if (atud->unmatched_enabled)
+ camel_vee_folder_add_vuid (atud->unmatched_folder, mi_data, atud->changes);
+}
+
+/**
+ * camel_vee_store_note_subfolder_used:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_store_note_subfolder_used (CamelVeeStore *vstore,
+ CamelFolder *subfolder,
+ CamelVeeFolder *used_by)
+{
+ gint counts;
+
+ g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
+ g_return_if_fail (CAMEL_IS_FOLDER (subfolder));
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (used_by));
+
+ /* only real folders can be part of the unmatched folder */
+ if (CAMEL_IS_VEE_FOLDER (subfolder) ||
+ used_by == vstore->priv->unmatched_folder)
+ return;
+
+ g_mutex_lock (&vstore->priv->sf_counts_mutex);
+
+ counts = GPOINTER_TO_INT (g_hash_table_lookup (vstore->priv->subfolder_usage_counts, subfolder));
+ counts++;
+ g_hash_table_insert (
+ vstore->priv->subfolder_usage_counts,
+ subfolder, GINT_TO_POINTER (counts));
+
+ if (counts == 1) {
+ struct AddToUnmatchedData atud;
+ CamelFolder *unmatched_folder;
+
+ camel_vee_data_cache_add_subfolder (vstore->priv->vee_data_cache, subfolder);
+
+ g_mutex_lock (&vstore->priv->vu_counts_mutex);
+
+ /* all messages from the folder are unmatched at the beginning */
+ atud.unmatched_folder = vstore->priv->unmatched_folder;
+ atud.changes = camel_folder_change_info_new ();
+ atud.unmatched_enabled = camel_vee_store_get_unmatched_enabled (vstore);
+ atud.vuid_usage_counts = vstore->priv->vuid_usage_counts;
+
+ if (atud.unmatched_enabled)
+ camel_vee_folder_add_folder (vstore->priv->unmatched_folder, subfolder, NULL);
+
+ unmatched_folder = CAMEL_FOLDER (atud.unmatched_folder);
+
+ camel_folder_freeze (unmatched_folder);
+
+ camel_vee_data_cache_foreach_message_info_data (vstore->priv->vee_data_cache, subfolder,
+ add_to_unmatched_folder_cb, &atud);
+
+ camel_folder_thaw (unmatched_folder);
+ g_mutex_unlock (&vstore->priv->vu_counts_mutex);
+
+ if (camel_folder_change_info_changed (atud.changes))
+ camel_folder_changed (unmatched_folder, atud.changes);
+ camel_folder_change_info_free (atud.changes);
+ }
+
+ g_mutex_unlock (&vstore->priv->sf_counts_mutex);
+}
+
+static void
+remove_vuid_count_record_cb (CamelVeeMessageInfoData *mi_data,
+ CamelFolder *subfolder,
+ gpointer user_data)
+{
+ GHashTable *vuid_usage_counts = user_data;
+
+ g_return_if_fail (mi_data != NULL);
+ g_return_if_fail (user_data != NULL);
+
+ g_hash_table_remove (vuid_usage_counts, camel_vee_message_info_data_get_vee_message_uid (mi_data));
+}
+
+/**
+ * camel_vee_store_note_subfolder_unused:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_store_note_subfolder_unused (CamelVeeStore *vstore,
+ CamelFolder *subfolder,
+ CamelVeeFolder *unused_by)
+{
+ gint counts;
+
+ g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
+ g_return_if_fail (CAMEL_IS_FOLDER (subfolder));
+ g_return_if_fail (CAMEL_IS_VEE_FOLDER (unused_by));
+
+ /* only real folders can be part of the unmatched folder */
+ if (CAMEL_IS_VEE_FOLDER (subfolder) ||
+ unused_by == vstore->priv->unmatched_folder)
+ return;
+
+ g_mutex_lock (&vstore->priv->sf_counts_mutex);
+
+ counts = GPOINTER_TO_INT (g_hash_table_lookup (vstore->priv->subfolder_usage_counts, subfolder));
+ g_return_if_fail (counts > 0);
+
+ counts--;
+ if (counts == 0) {
+ g_hash_table_remove (vstore->priv->subfolder_usage_counts, subfolder);
+ if (camel_vee_store_get_unmatched_enabled (vstore))
+ camel_vee_folder_remove_folder (vstore->priv->unmatched_folder, subfolder, NULL);
+
+ g_mutex_lock (&vstore->priv->vu_counts_mutex);
+ camel_vee_data_cache_foreach_message_info_data (vstore->priv->vee_data_cache, subfolder,
+ remove_vuid_count_record_cb, vstore->priv->vuid_usage_counts);
+ g_mutex_unlock (&vstore->priv->vu_counts_mutex);
+
+ camel_vee_data_cache_remove_subfolder (vstore->priv->vee_data_cache, subfolder);
+ } else {
+ g_hash_table_insert (
+ vstore->priv->subfolder_usage_counts,
+ subfolder, GINT_TO_POINTER (counts));
+ }
+
+ g_mutex_unlock (&vstore->priv->sf_counts_mutex);
+}
+
+/**
+ * camel_vee_store_note_vuid_used:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_store_note_vuid_used (CamelVeeStore *vstore,
+ CamelVeeMessageInfoData *mi_data,
+ CamelVeeFolder *used_by)
+{
+ gint counts;
+ const gchar *vuid;
+ CamelFolder *subfolder;
+ CamelVeeSubfolderData *sf_data;
+
+ g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
+ g_return_if_fail (used_by != NULL);
+ g_return_if_fail (mi_data != NULL);
+
+ /* these notifications are ignored from Unmatched folder */
+ if (used_by == vstore->priv->unmatched_folder)
+ return;
+
+ /* unmatched folder holds only real folders */
+ sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
+ subfolder = camel_vee_subfolder_data_get_folder (sf_data);
+ if (CAMEL_IS_VEE_FOLDER (subfolder))
+ return;
+
+ g_mutex_lock (&vstore->priv->vu_counts_mutex);
+
+ vuid = camel_vee_message_info_data_get_vee_message_uid (mi_data);
+
+ counts = GPOINTER_TO_INT (g_hash_table_lookup (vstore->priv->vuid_usage_counts, vuid));
+ counts++;
+ g_hash_table_insert (
+ vstore->priv->vuid_usage_counts,
+ (gpointer) camel_pstring_strdup (vuid),
+ GINT_TO_POINTER (counts));
+
+ if (counts == 1 && camel_vee_store_get_unmatched_enabled (vstore)) {
+ CamelFolderChangeInfo *changes;
+
+ changes = camel_folder_change_info_new ();
+
+ camel_vee_folder_remove_vuid (vstore->priv->unmatched_folder, mi_data, changes);
+
+ if (camel_folder_change_info_changed (changes))
+ camel_folder_changed (CAMEL_FOLDER (vstore->priv->unmatched_folder), changes);
+ camel_folder_change_info_free (changes);
+ }
+
+ g_mutex_unlock (&vstore->priv->vu_counts_mutex);
+}
+
+/**
+ * camel_vee_store_note_vuid_unused:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_store_note_vuid_unused (CamelVeeStore *vstore,
+ CamelVeeMessageInfoData *mi_data,
+ CamelVeeFolder *unused_by)
+{
+ gint counts;
+ const gchar *vuid;
+ CamelFolder *subfolder;
+ CamelVeeSubfolderData *sf_data;
+
+ g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
+ g_return_if_fail (unused_by != NULL);
+ g_return_if_fail (mi_data != NULL);
+
+ /* these notifications are ignored from Unmatched folder */
+ if (unused_by == vstore->priv->unmatched_folder)
+ return;
+
+ /* unmatched folder holds only real folders */
+ sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
+ subfolder = camel_vee_subfolder_data_get_folder (sf_data);
+ if (CAMEL_IS_VEE_FOLDER (subfolder))
+ return;
+
+ g_mutex_lock (&vstore->priv->vu_counts_mutex);
+
+ vuid = camel_vee_message_info_data_get_vee_message_uid (mi_data);
+
+ counts = GPOINTER_TO_INT (g_hash_table_lookup (vstore->priv->vuid_usage_counts, vuid));
+ counts--;
+ if (counts < 0) {
+ g_mutex_unlock (&vstore->priv->vu_counts_mutex);
+ g_return_if_fail (counts >= 0);
+ return;
+ }
+
+ g_hash_table_insert (
+ vstore->priv->vuid_usage_counts,
+ (gpointer) camel_pstring_strdup (vuid),
+ GINT_TO_POINTER (counts));
+
+ if (counts == 0 && camel_vee_store_get_unmatched_enabled (vstore)) {
+ CamelFolderChangeInfo *changes;
+
+ changes = camel_folder_change_info_new ();
+
+ camel_vee_folder_add_vuid (vstore->priv->unmatched_folder, mi_data, changes);
+
+ if (camel_folder_change_info_changed (changes))
+ camel_folder_changed (CAMEL_FOLDER (vstore->priv->unmatched_folder), changes);
+ camel_folder_change_info_free (changes);
+ }
+
+ g_mutex_unlock (&vstore->priv->vu_counts_mutex);
+}
+
+struct RebuildUnmatchedData {
+ CamelVeeDataCache *data_cache;
+ CamelVeeFolder *unmatched_folder;
+ CamelFolderChangeInfo *changes;
+ GCancellable *cancellable;
+};
+
+static void
+rebuild_unmatched_folder_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ const gchar *vuid = key;
+ gint counts = GPOINTER_TO_INT (value);
+ struct RebuildUnmatchedData *rud = user_data;
+ CamelVeeSubfolderData *si_data;
+ CamelVeeMessageInfoData *mi_data;
+
+ g_return_if_fail (vuid != NULL);
+ g_return_if_fail (rud != NULL);
+
+ if (counts != 0 || g_cancellable_is_cancelled (rud->cancellable))
+ return;
+
+ mi_data = camel_vee_data_cache_get_message_info_data_by_vuid (rud->data_cache, vuid);
+ if (!mi_data)
+ return;
+
+ si_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
+
+ camel_vee_folder_add_folder (rud->unmatched_folder, camel_vee_subfolder_data_get_folder (si_data), NULL);
+ camel_vee_folder_add_vuid (rud->unmatched_folder, mi_data, rud->changes);
+
+ g_object_unref (mi_data);
+}
+
+static void
+vee_store_rebuild_unmatched_folder (CamelSession *session,
+ GCancellable *cancellable,
+ CamelVeeStore *vstore,
+ GError **error)
+{
+ struct RebuildUnmatchedData rud;
+ CamelVeeFolder *vunmatched;
+ CamelFolder *unmatched_folder;
+ CamelFolderChangeInfo *changes;
+
+ g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
+
+ vunmatched = camel_vee_store_get_unmatched_folder (vstore);
+ /* someone could disable it meanwhile */
+ if (!vunmatched)
+ return;
+
+ unmatched_folder = CAMEL_FOLDER (vunmatched);
+ g_return_if_fail (unmatched_folder != NULL);
+
+ camel_folder_freeze (unmatched_folder);
+
+ /* start from scratch, with empty folder */
+ camel_vee_folder_set_folders (vunmatched, NULL, cancellable);
+
+ changes = camel_folder_change_info_new ();
+
+ rud.data_cache = vstore->priv->vee_data_cache;
+ rud.unmatched_folder = vunmatched;
+ rud.changes = changes;
+ rud.cancellable = cancellable;
+
+ g_hash_table_foreach (vstore->priv->vuid_usage_counts, rebuild_unmatched_folder_cb, &rud);
+
+ camel_folder_thaw (unmatched_folder);
+
+ if (camel_folder_change_info_changed (changes))
+ camel_folder_changed (unmatched_folder, changes);
+ camel_folder_change_info_free (changes);
+
+ /* coverity[unchecked_value] */
+ g_cancellable_set_error_if_cancelled (cancellable, error);
+}
+
+/**
+ * camel_vee_store_rebuild_unmatched_folder:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_store_rebuild_unmatched_folder (CamelVeeStore *vstore,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_if_fail (CAMEL_IS_VEE_STORE (vstore));
+
+ /* this operation requires cancellable, thus if called
+ * without it then run in a dedicated thread */
+ if (!cancellable) {
+ CamelService *service;
+ CamelSession *session;
+
+ service = CAMEL_SERVICE (vstore);
+ session = camel_service_ref_session (service);
+
+ if (session) {
+ camel_session_submit_job (
+ session, _("Updating Unmatched search folder"), (CamelSessionCallback)
+ vee_store_rebuild_unmatched_folder,
+ g_object_ref (vstore),
+ g_object_unref);
+
+ g_object_unref (session);
+ }
+ } else {
+ vee_store_rebuild_unmatched_folder (NULL, cancellable, vstore, error);
+ }
+}
diff --git a/src/camel/camel-vee-store.h b/src/camel/camel-vee-store.h
new file mode 100644
index 000000000..d0fde3538
--- /dev/null
+++ b/src/camel/camel-vee-store.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_VEE_STORE_H
+#define CAMEL_VEE_STORE_H
+
+#include <camel/camel-store.h>
+#include <camel/camel-vee-data-cache.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_VEE_STORE \
+ (camel_vee_store_get_type ())
+#define CAMEL_VEE_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_VEE_STORE, CamelVeeStore))
+#define CAMEL_VEE_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_VEE_STORE, CamelVeeStoreClass))
+#define CAMEL_IS_VEE_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_VEE_STORE))
+#define CAMEL_IS_VEE_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_VEE_STORE))
+#define CAMEL_VEE_STORE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_VEE_STORE, CamelVeeStoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelVeeStore CamelVeeStore;
+typedef struct _CamelVeeStorePrivate CamelVeeStorePrivate;
+typedef struct _CamelVeeStoreClass CamelVeeStoreClass;
+
+struct _CamelVeeStore {
+ CamelStore parent;
+
+ CamelVeeStorePrivate *priv;
+};
+
+struct _CamelVeeStoreClass {
+ CamelStoreClass parent_class;
+};
+
+GType camel_vee_store_get_type (void);
+CamelVeeStore * camel_vee_store_new (void);
+CamelVeeDataCache * camel_vee_store_get_vee_data_cache (CamelVeeStore *vstore);
+struct _CamelVeeFolder *camel_vee_store_get_unmatched_folder (CamelVeeStore *vstore);
+gboolean camel_vee_store_get_unmatched_enabled (CamelVeeStore *vstore);
+void camel_vee_store_set_unmatched_enabled (CamelVeeStore *vstore,
+ gboolean is_enabled);
+void camel_vee_store_note_subfolder_used (CamelVeeStore *vstore,
+ CamelFolder *subfolder,
+ struct _CamelVeeFolder *used_by);
+void camel_vee_store_note_subfolder_unused (CamelVeeStore *vstore,
+ CamelFolder *subfolder,
+ struct _CamelVeeFolder *unused_by);
+void camel_vee_store_note_vuid_used (CamelVeeStore *vstore,
+ CamelVeeMessageInfoData *mi_data,
+ struct _CamelVeeFolder *used_by);
+void camel_vee_store_note_vuid_unused (CamelVeeStore *vstore,
+ CamelVeeMessageInfoData *mi_data,
+ struct _CamelVeeFolder *unused_by);
+void camel_vee_store_rebuild_unmatched_folder (CamelVeeStore *vstore,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_VEE_STORE_H */
diff --git a/src/camel/camel-vee-summary.c b/src/camel/camel-vee-summary.c
new file mode 100644
index 000000000..ddd527d7e
--- /dev/null
+++ b/src/camel/camel-vee-summary.c
@@ -0,0 +1,576 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "camel-db.h"
+#include "camel-debug.h"
+#include "camel-folder.h"
+#include "camel-store.h"
+#include "camel-vee-summary.h"
+#include "camel-vee-folder.h"
+#include "camel-vee-store.h"
+#include "camel-vtrash-folder.h"
+#include "camel-string-utils.h"
+
+#define d(x)
+
+#define CAMEL_VEE_SUMMARY_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_VEE_SUMMARY, CamelVeeSummaryPrivate))
+
+struct _CamelVeeSummaryPrivate {
+ /* CamelFolder * => GHashTable * of gchar *vuid */
+ GHashTable *vuids_by_subfolder;
+};
+
+G_DEFINE_TYPE (CamelVeeSummary, camel_vee_summary, CAMEL_TYPE_FOLDER_SUMMARY)
+
+static void
+vee_message_info_free (CamelFolderSummary *s,
+ CamelMessageInfo *info)
+{
+ CamelVeeMessageInfo *mi = (CamelVeeMessageInfo *) info;
+
+ g_object_unref (mi->orig_summary);
+
+ CAMEL_FOLDER_SUMMARY_CLASS (camel_vee_summary_parent_class)->message_info_free (s, info);
+}
+
+static CamelMessageInfo *
+vee_message_info_clone (CamelFolderSummary *s,
+ const CamelMessageInfo *mi)
+{
+ CamelVeeMessageInfo *to;
+ const CamelVeeMessageInfo *from = (const CamelVeeMessageInfo *) mi;
+
+ to = (CamelVeeMessageInfo *) camel_message_info_new (s);
+
+ to->orig_summary = g_object_ref (from->orig_summary);
+ to->info.summary = s;
+ to->info.uid = camel_pstring_strdup (from->info.uid);
+
+ return (CamelMessageInfo *) to;
+}
+
+#define HANDLE_NULL_INFO(value) if (!rmi) { d(g_warning (G_STRLOC ": real info is NULL for %s, safeguarding\n", mi->uid)); return value; }
+
+static gconstpointer
+vee_info_ptr (const CamelMessageInfo *mi,
+ gint id)
+{
+ CamelVeeMessageInfo *vmi = (CamelVeeMessageInfo *) mi;
+ CamelMessageInfo *rmi;
+ gpointer p;
+
+ rmi = camel_folder_summary_get (vmi->orig_summary, mi->uid + 8);
+ HANDLE_NULL_INFO (NULL);
+ p = (gpointer) camel_message_info_get_ptr (rmi, id);
+ camel_message_info_unref (rmi);
+
+ return p;
+}
+
+static guint32
+vee_info_uint32 (const CamelMessageInfo *mi,
+ gint id)
+{
+ CamelMessageInfo *rmi = camel_folder_summary_get (((CamelVeeMessageInfo *) mi)->orig_summary, mi->uid + 8);
+ guint32 ret;
+
+ HANDLE_NULL_INFO (0);
+ ret = camel_message_info_get_uint32 (rmi, id);
+ camel_message_info_unref (rmi);
+
+ return ret;
+
+}
+
+static time_t
+vee_info_time (const CamelMessageInfo *mi,
+ gint id)
+{
+ CamelMessageInfo *rmi = camel_folder_summary_get (((CamelVeeMessageInfo *) mi)->orig_summary, mi->uid + 8);
+ time_t ret;
+
+ HANDLE_NULL_INFO (0);
+ ret = camel_message_info_get_time (rmi, id);
+ camel_message_info_unref (rmi);
+
+ return ret;
+}
+
+static gboolean
+vee_info_user_flag (const CamelMessageInfo *mi,
+ const gchar *id)
+{
+ CamelMessageInfo *rmi = camel_folder_summary_get (((CamelVeeMessageInfo *) mi)->orig_summary, mi->uid + 8);
+ gboolean ret;
+
+ HANDLE_NULL_INFO (FALSE);
+ ret = camel_message_info_get_user_flag (rmi, id);
+ camel_message_info_unref (rmi);
+
+ return ret;
+}
+
+static const gchar *
+vee_info_user_tag (const CamelMessageInfo *mi,
+ const gchar *id)
+{
+ CamelMessageInfo *rmi = camel_folder_summary_get (((CamelVeeMessageInfo *) mi)->orig_summary, mi->uid + 8);
+ const gchar *ret;
+
+ HANDLE_NULL_INFO ("");
+ ret = camel_message_info_get_user_tag (rmi, id);
+ camel_message_info_unref (rmi);
+
+ return ret;
+}
+
+static void
+vee_summary_notify_mi_changed (CamelVeeFolder *vfolder,
+ CamelMessageInfo *mi)
+{
+ CamelFolderChangeInfo *changes;
+
+ g_return_if_fail (vfolder != NULL);
+ g_return_if_fail (mi != NULL);
+
+ changes = camel_folder_change_info_new ();
+
+ camel_folder_change_info_change_uid (changes, camel_message_info_get_uid (mi));
+ camel_folder_changed (CAMEL_FOLDER (vfolder), changes);
+ camel_folder_change_info_free (changes);
+}
+
+static gboolean
+vee_info_set_user_flag (CamelMessageInfo *mi,
+ const gchar *name,
+ gboolean value)
+{
+ gint res = FALSE;
+ CamelVeeFolder *vf = (CamelVeeFolder *) camel_folder_summary_get_folder (mi->summary);
+
+ if (mi->uid) {
+ CamelMessageInfo *rmi = camel_folder_summary_get (((CamelVeeMessageInfo *) mi)->orig_summary, mi->uid + 8);
+ gboolean ignore_changes = !CAMEL_IS_VTRASH_FOLDER (vf);
+
+ HANDLE_NULL_INFO (FALSE);
+
+ /* ignore changes done in the folder itself,
+ * unless it's a vTrash or vJunk folder */
+ if (ignore_changes)
+ camel_vee_folder_ignore_next_changed_event (vf, camel_folder_summary_get_folder (rmi->summary));
+
+ res = camel_message_info_set_user_flag (rmi, name, value);
+
+ if (ignore_changes) {
+ if (res)
+ vee_summary_notify_mi_changed (vf, mi);
+ else
+ camel_vee_folder_remove_from_ignore_changed_event (vf, camel_folder_summary_get_folder (rmi->summary));
+ }
+
+ camel_message_info_unref (rmi);
+ }
+
+ return res;
+}
+
+static gboolean
+vee_info_set_user_tag (CamelMessageInfo *mi,
+ const gchar *name,
+ const gchar *value)
+{
+ gint res = FALSE;
+
+ if (mi->uid) {
+ CamelMessageInfo *rmi = camel_folder_summary_get (((CamelVeeMessageInfo *) mi)->orig_summary, mi->uid + 8);
+ CamelVeeFolder *vf = (CamelVeeFolder *) camel_folder_summary_get_folder (mi->summary);
+ gboolean ignore_changes = !CAMEL_IS_VTRASH_FOLDER (vf);
+
+ HANDLE_NULL_INFO (FALSE);
+
+ /* ignore changes done in the folder itself,
+ * unless it's a vTrash or vJunk folder */
+ if (ignore_changes)
+ camel_vee_folder_ignore_next_changed_event (vf, camel_folder_summary_get_folder (rmi->summary));
+
+ res = camel_message_info_set_user_tag (rmi, name, value);
+
+ if (ignore_changes) {
+ if (res)
+ vee_summary_notify_mi_changed (vf, mi);
+ else
+ camel_vee_folder_remove_from_ignore_changed_event (vf, camel_folder_summary_get_folder (rmi->summary));
+ }
+
+ camel_message_info_unref (rmi);
+ }
+
+ return res;
+}
+
+static gboolean
+vee_info_set_flags (CamelMessageInfo *mi,
+ guint32 flags,
+ guint32 set)
+{
+ gint res = FALSE;
+ CamelVeeFolder *vf = CAMEL_VEE_FOLDER (camel_folder_summary_get_folder (mi->summary));
+
+ /* first update original message info... */
+ if (mi->uid) {
+ CamelMessageInfo *rmi = camel_folder_summary_get (((CamelVeeMessageInfo *) mi)->orig_summary, mi->uid + 8);
+ gboolean ignore_changes = !CAMEL_IS_VTRASH_FOLDER (vf);
+
+ HANDLE_NULL_INFO (FALSE);
+
+ /* ignore changes done in the folder itself,
+ * unless it's a vTrash or vJunk folder */
+ if (ignore_changes)
+ camel_vee_folder_ignore_next_changed_event (vf, camel_folder_summary_get_folder (rmi->summary));
+
+ res = camel_message_info_set_flags (rmi, flags, set);
+
+ if (res) {
+ /* update flags on itself too */
+ camel_folder_summary_replace_flags (mi->summary, mi);
+ }
+
+ if (ignore_changes) {
+ if (res)
+ vee_summary_notify_mi_changed (vf, mi);
+ else
+ camel_vee_folder_remove_from_ignore_changed_event (vf, camel_folder_summary_get_folder (rmi->summary));
+ }
+
+ camel_message_info_unref (rmi);
+ }
+
+ /* Do not call parent class' info_set_flags, to not do flood
+ * of change notifications, rather wait for a notification
+ * from original folder, and propagate the change in counts
+ * through camel_vee_summary_replace_flags().
+ */
+ /*if (res)
+ CAMEL_FOLDER_SUMMARY_CLASS (camel_vee_summary_parent_class)->info_set_flags (mi, flags, set);*/
+
+ return res;
+}
+
+static CamelMessageInfo *
+message_info_from_uid (CamelFolderSummary *s,
+ const gchar *uid)
+{
+ CamelMessageInfo *info;
+
+ info = camel_folder_summary_peek_loaded (s, uid);
+ if (!info) {
+ CamelVeeMessageInfo *vinfo;
+ CamelFolder *orig_folder;
+
+ /* This function isn't really nice. But no great way
+ * But in vfolder case, this may not be so bad, as vuid has the hash in first 8 bytes.
+ * So this just compares the entire string only if it belongs to the same folder.
+ * Otherwise, the first byte itself would return in strcmp, saving the CPU.
+ */
+ if (!camel_folder_summary_check_uid (s, uid)) {
+ d (
+ g_message ("Unable to find %s in the summary of %s", uid,
+ camel_folder_get_full_name (camel_folder_summary_get_folder (s->folder))));
+ return NULL;
+ }
+
+ /* Create the info and load it, its so easy. */
+ info = camel_message_info_new (s);
+ info->dirty = FALSE;
+ info->uid = camel_pstring_strdup (uid);
+
+ orig_folder = camel_vee_folder_get_vee_uid_folder (
+ (CamelVeeFolder *) camel_folder_summary_get_folder (s), uid);
+ g_return_val_if_fail (orig_folder != NULL, NULL);
+
+ vinfo = (CamelVeeMessageInfo *) info;
+ vinfo->orig_summary = orig_folder->summary;
+
+ g_object_ref (vinfo->orig_summary);
+ camel_message_info_ref (info);
+
+ camel_folder_summary_insert (s, info, FALSE);
+ }
+
+ return info;
+}
+
+static void
+vee_summary_finalize (GObject *object)
+{
+ CamelVeeSummaryPrivate *priv;
+
+ priv = CAMEL_VEE_SUMMARY_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->vuids_by_subfolder);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_vee_summary_parent_class)->finalize (object);
+}
+
+static void
+camel_vee_summary_class_init (CamelVeeSummaryClass *class)
+{
+ GObjectClass *object_class;
+ CamelFolderSummaryClass *folder_summary_class;
+
+ g_type_class_add_private (class, sizeof (CamelVeeSummaryPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = vee_summary_finalize;
+
+ folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (class);
+ folder_summary_class->message_info_size = sizeof (CamelVeeMessageInfo);
+ folder_summary_class->content_info_size = 0;
+ folder_summary_class->message_info_clone = vee_message_info_clone;
+ folder_summary_class->message_info_free = vee_message_info_free;
+ folder_summary_class->info_ptr = vee_info_ptr;
+ folder_summary_class->info_uint32 = vee_info_uint32;
+ folder_summary_class->info_time = vee_info_time;
+ folder_summary_class->info_user_flag = vee_info_user_flag;
+ folder_summary_class->info_user_tag = vee_info_user_tag;
+ folder_summary_class->info_set_user_flag = vee_info_set_user_flag;
+ folder_summary_class->info_set_user_tag = vee_info_set_user_tag;
+ folder_summary_class->info_set_flags = vee_info_set_flags;
+ folder_summary_class->message_info_from_uid = message_info_from_uid;
+}
+
+static void
+camel_vee_summary_init (CamelVeeSummary *vee_summary)
+{
+ vee_summary->priv = CAMEL_VEE_SUMMARY_GET_PRIVATE (vee_summary);
+
+ vee_summary->priv->vuids_by_subfolder = g_hash_table_new_full (
+ (GHashFunc) g_direct_hash,
+ (GEqualFunc) g_direct_equal,
+ (GDestroyNotify) NULL,
+ (GDestroyNotify) g_hash_table_destroy);
+}
+
+/**
+ * camel_vee_summary_new:
+ * @parent: Folder its attached to.
+ *
+ * This will create a new CamelVeeSummary object and read in the
+ * summary data from disk, if it exists.
+ *
+ * Returns: A new CamelVeeSummary object.
+ **/
+CamelFolderSummary *
+camel_vee_summary_new (CamelFolder *parent)
+{
+ CamelFolderSummary *summary;
+ CamelStore *parent_store;
+ const gchar *full_name;
+
+ summary = g_object_new (CAMEL_TYPE_VEE_SUMMARY, "folder", parent, NULL);
+ summary->flags |= CAMEL_FOLDER_SUMMARY_IN_MEMORY_ONLY;
+
+ /* not using DB for vee folder summaries, drop the table */
+ full_name = camel_folder_get_full_name (parent);
+ parent_store = camel_folder_get_parent_store (parent);
+ camel_db_delete_folder (parent_store->cdb_w, full_name, NULL);
+
+ return summary;
+}
+
+static void
+get_uids_for_subfolder (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ g_hash_table_insert (user_data, (gpointer) camel_pstring_strdup (key), GINT_TO_POINTER (1));
+}
+
+/**
+ * camel_vee_summary_get_uids_for_subfolder:
+ *
+ * FIXME Document me!
+ *
+ * Returns: (element-type utf8 utf8) (transfer container):
+ *
+ * Since: 3.6
+ **/
+GHashTable *
+camel_vee_summary_get_uids_for_subfolder (CamelVeeSummary *summary,
+ CamelFolder *subfolder)
+{
+ GHashTable *vuids, *known_uids;
+
+ g_return_val_if_fail (CAMEL_IS_VEE_SUMMARY (summary), NULL);
+ g_return_val_if_fail (CAMEL_IS_FOLDER (subfolder), NULL);
+
+ camel_folder_summary_lock (&summary->summary);
+
+ /* uses direct hash, because strings are supposed to be from the string pool */
+ known_uids = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
+
+ vuids = g_hash_table_lookup (summary->priv->vuids_by_subfolder, subfolder);
+ if (vuids) {
+ g_hash_table_foreach (vuids, get_uids_for_subfolder, known_uids);
+ }
+
+ camel_folder_summary_unlock (&summary->summary);
+
+ return known_uids;
+}
+
+/* unref returned pointer with camel_message_info_unref() */
+CamelVeeMessageInfo *
+camel_vee_summary_add (CamelVeeSummary *s,
+ CamelVeeMessageInfoData *mi_data)
+{
+ CamelVeeMessageInfo *vmi;
+ const gchar *vuid;
+ CamelVeeSubfolderData *sf_data;
+ CamelFolder *orig_folder;
+ GHashTable *vuids;
+
+ g_return_val_if_fail (CAMEL_IS_VEE_SUMMARY (s), NULL);
+ g_return_val_if_fail (CAMEL_IS_VEE_MESSAGE_INFO_DATA (mi_data), NULL);
+
+ camel_folder_summary_lock (&s->summary);
+
+ sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data);
+ vuid = camel_vee_message_info_data_get_vee_message_uid (mi_data);
+ orig_folder = camel_vee_subfolder_data_get_folder (sf_data);
+
+ vmi = (CamelVeeMessageInfo *) camel_folder_summary_peek_loaded (&s->summary, vuid);
+ if (vmi) {
+ /* Possible that the entry is loaded, see if it has the summary */
+ d (g_message ("%s - already there\n", vuid));
+ if (!vmi->orig_summary)
+ vmi->orig_summary = g_object_ref (orig_folder->summary);
+
+ camel_folder_summary_unlock (&s->summary);
+
+ return vmi;
+ }
+
+ vmi = (CamelVeeMessageInfo *) camel_message_info_new (&s->summary);
+ vmi->orig_summary = g_object_ref (orig_folder->summary);
+ vmi->info.uid = (gchar *) camel_pstring_strdup (vuid);
+
+ camel_message_info_ref (vmi);
+
+ vuids = g_hash_table_lookup (s->priv->vuids_by_subfolder, orig_folder);
+ if (vuids) {
+ g_hash_table_insert (vuids, (gpointer) camel_pstring_strdup (vuid), GINT_TO_POINTER (1));
+ } else {
+ vuids = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL);
+ g_hash_table_insert (vuids, (gpointer) camel_pstring_strdup (vuid), GINT_TO_POINTER (1));
+ g_hash_table_insert (s->priv->vuids_by_subfolder, orig_folder, vuids);
+ }
+
+ camel_folder_summary_insert (&s->summary, (CamelMessageInfo *) vmi, FALSE);
+ camel_folder_summary_unlock (&s->summary);
+
+ return vmi;
+}
+
+/**
+ * camel_vee_summary_remove:
+ *
+ * FIXME Document me!
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_summary_remove (CamelVeeSummary *summary,
+ const gchar *vuid,
+ CamelFolder *subfolder)
+{
+ CamelMessageInfo *mi;
+ GHashTable *vuids;
+
+ g_return_if_fail (CAMEL_IS_VEE_SUMMARY (summary));
+ g_return_if_fail (vuid != NULL);
+ g_return_if_fail (subfolder != NULL);
+
+ camel_folder_summary_lock (&summary->summary);
+
+ vuids = g_hash_table_lookup (summary->priv->vuids_by_subfolder, subfolder);
+ if (vuids) {
+ g_hash_table_remove (vuids, vuid);
+ if (!g_hash_table_size (vuids))
+ g_hash_table_remove (summary->priv->vuids_by_subfolder, subfolder);
+ }
+
+ mi = camel_folder_summary_peek_loaded (&summary->summary, vuid);
+
+ camel_folder_summary_remove_uid (&summary->summary, vuid);
+
+ if (mi) {
+ /* under twice, the first for camel_folder_summary_peek_loaded(),
+ * the second to actually free the mi */
+ camel_message_info_unref (mi);
+ camel_message_info_unref (mi);
+ }
+
+ camel_folder_summary_unlock (&summary->summary);
+}
+
+/**
+ * camel_vee_summary_replace_flags:
+ * @summary: a #CamelVeeSummary
+ * @uid: a message UID to update flags for
+ *
+ * Makes sure @summary flags on @uid corresponds to those
+ * in the subfolder of vee-folder, and updates internal counts
+ * on @summary as well.
+ *
+ * Since: 3.6
+ **/
+void
+camel_vee_summary_replace_flags (CamelVeeSummary *summary,
+ const gchar *uid)
+{
+ CamelMessageInfo *mi;
+
+ g_return_if_fail (CAMEL_IS_VEE_SUMMARY (summary));
+ g_return_if_fail (uid != NULL);
+
+ camel_folder_summary_lock (&summary->summary);
+
+ mi = camel_folder_summary_get (&summary->summary, uid);
+ if (!mi) {
+ camel_folder_summary_unlock (&summary->summary);
+ return;
+ }
+
+ camel_folder_summary_replace_flags (&summary->summary, mi);
+ camel_message_info_unref (mi);
+
+ camel_folder_summary_unlock (&summary->summary);
+}
diff --git a/src/camel/camel-vee-summary.h b/src/camel/camel-vee-summary.h
new file mode 100644
index 000000000..94a95dc4e
--- /dev/null
+++ b/src/camel/camel-vee-summary.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Dan Winship <danw@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_VEE_SUMMARY_H
+#define CAMEL_VEE_SUMMARY_H
+
+#include <camel/camel-folder-summary.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_VEE_SUMMARY \
+ (camel_vee_summary_get_type ())
+#define CAMEL_VEE_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_VEE_SUMMARY, CamelVeeSummary))
+#define CAMEL_VEE_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_VEE_SUMMARY, CamelVeeSummaryClass))
+#define CAMEL_IS_VEE_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_VEE_SUMMARY))
+#define CAMEL_IS_VEE_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_VEE_SUMMARY))
+#define CAMEL_VEE_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_VEE_SUMMARY, CamelVeeSummaryClass))
+
+G_BEGIN_DECLS
+
+struct _CamelVeeMessageInfoData;
+struct _CamelVeeFolder;
+struct _CamelFolder;
+
+typedef struct _CamelVeeSummary CamelVeeSummary;
+typedef struct _CamelVeeSummaryClass CamelVeeSummaryClass;
+typedef struct _CamelVeeSummaryPrivate CamelVeeSummaryPrivate;
+
+typedef struct _CamelVeeMessageInfo CamelVeeMessageInfo;
+
+struct _CamelVeeMessageInfo {
+ CamelMessageInfoBase info;
+ CamelFolderSummary *orig_summary;
+};
+
+struct _CamelVeeSummary {
+ CamelFolderSummary summary;
+
+ CamelVeeSummaryPrivate *priv;
+};
+
+struct _CamelVeeSummaryClass {
+ CamelFolderSummaryClass parent_class;
+};
+
+GType camel_vee_summary_get_type (void);
+CamelFolderSummary *
+ camel_vee_summary_new (struct _CamelFolder *parent);
+CamelVeeMessageInfo *
+ camel_vee_summary_add (CamelVeeSummary *s,
+ struct _CamelVeeMessageInfoData *mi_data);
+void camel_vee_summary_remove (CamelVeeSummary *summary,
+ const gchar *vuid,
+ CamelFolder *subfolder);
+void camel_vee_summary_replace_flags (CamelVeeSummary *summary,
+ const gchar *uid);
+GHashTable * camel_vee_summary_get_uids_for_subfolder
+ (CamelVeeSummary *summary,
+ CamelFolder *subfolder);
+
+G_END_DECLS
+
+#endif /* CAMEL_VEE_SUMMARY_H */
+
diff --git a/src/camel/camel-vtrash-folder.c b/src/camel/camel-vtrash-folder.c
new file mode 100644
index 000000000..b8eef4122
--- /dev/null
+++ b/src/camel/camel-vtrash-folder.c
@@ -0,0 +1,266 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-db.h"
+#include "camel-mime-message.h"
+#include "camel-store.h"
+#include "camel-vee-store.h"
+#include "camel-vtrash-folder.h"
+#include "camel-string-utils.h"
+
+static struct {
+ const gchar *full_name;
+ const gchar *name;
+ const gchar *expr;
+ guint32 bit;
+ guint32 flags;
+ const gchar *error_copy;
+ const gchar *db_col;
+} 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"), "deleted" },
+ { 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"), "junk" },
+};
+
+struct _transfer_data {
+ GCancellable *cancellable;
+ CamelFolder *folder;
+ CamelFolder *dest;
+ GPtrArray *uids;
+ gboolean delete;
+
+ CamelFolder *source_folder;
+ GPtrArray *source_uids;
+ guint32 sbit;
+};
+
+G_DEFINE_TYPE (CamelVTrashFolder, camel_vtrash_folder, CAMEL_TYPE_VEE_FOLDER)
+
+static void
+transfer_messages (CamelFolder *folder,
+ struct _transfer_data *md,
+ GError **error)
+{
+ gint i;
+
+ camel_folder_transfer_messages_to_sync (
+ md->folder, md->uids, md->dest,
+ md->delete, NULL, md->cancellable, error);
+
+ if (md->cancellable != NULL)
+ g_object_unref (md->cancellable);
+
+ /* set the bit back */
+ for (i = 0; i < md->source_uids->len; i++) {
+ CamelMessageInfo *mi = camel_folder_get_message_info (md->source_folder, md->source_uids->pdata[i]);
+ if (mi) {
+ camel_message_info_set_flags (mi, md->sbit, md->sbit);
+ camel_message_info_unref (mi);
+ }
+ }
+
+ camel_folder_thaw (md->folder);
+
+ for (i = 0; i < md->uids->len; i++)
+ g_free (md->uids->pdata[i]);
+
+ g_ptr_array_free (md->uids, TRUE);
+ g_ptr_array_free (md->source_uids, TRUE);
+ g_object_unref (md->folder);
+ g_free (md);
+}
+
+static gboolean
+vtrash_folder_append_message_sync (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, "%s",
+ _(vdata[((CamelVTrashFolder *) folder)->type].error_copy));
+
+ return FALSE;
+}
+
+static gboolean
+vtrash_folder_transfer_messages_to_sync (CamelFolder *source,
+ GPtrArray *uids,
+ CamelFolder *dest,
+ gboolean delete_originals,
+ GPtrArray **transferred_uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelVeeMessageInfo *mi;
+ gint i;
+ GHashTable *batch = NULL;
+ const gchar *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) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, "%s",
+ _(vdata[((CamelVTrashFolder *) dest)->type].error_copy));
+ return FALSE;
+ }
+
+ /* 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 TRUE;
+ }
+
+ /* 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.
+ */
+
+ camel_folder_freeze (source);
+ camel_folder_freeze (dest);
+
+ 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", (gchar *) uids->pdata[i]);
+ continue;
+ }
+
+ if (dest == camel_folder_summary_get_folder (mi->orig_summary)) {
+ /* 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, camel_folder_summary_get_folder (mi->orig_summary));
+ if (md == NULL) {
+ md = g_malloc0 (sizeof (*md));
+ md->cancellable = cancellable;
+ md->folder = g_object_ref (camel_folder_summary_get_folder (mi->orig_summary));
+ md->uids = g_ptr_array_new ();
+ md->dest = dest;
+ md->delete = delete_originals;
+ md->source_folder = source;
+ md->source_uids = g_ptr_array_new ();
+ md->sbit = sbit;
+ if (cancellable != NULL)
+ g_object_ref (cancellable);
+ camel_folder_freeze (md->folder);
+ g_hash_table_insert (batch, camel_folder_summary_get_folder (mi->orig_summary), md);
+ }
+
+ /* unset the bit temporarily */
+ camel_message_info_set_flags ((CamelMessageInfo *) mi, sbit, 0);
+
+ tuid = uids->pdata[i];
+ if (strlen (tuid) > 8)
+ tuid += 8;
+ g_ptr_array_add (md->uids, g_strdup (tuid));
+ g_ptr_array_add (md->source_uids, uids->pdata[i]);
+ }
+ camel_message_info_unref (mi);
+ }
+
+ if (batch) {
+ g_hash_table_foreach (batch, (GHFunc) transfer_messages, error);
+ g_hash_table_destroy (batch);
+ }
+
+ camel_folder_thaw (dest);
+ camel_folder_thaw (source);
+
+ return TRUE;
+}
+
+static void
+camel_vtrash_folder_class_init (CamelVTrashFolderClass *class)
+{
+ CamelFolderClass *folder_class;
+
+ folder_class = CAMEL_FOLDER_CLASS (class);
+ folder_class->append_message_sync = vtrash_folder_append_message_sync;
+ folder_class->transfer_messages_to_sync = vtrash_folder_transfer_messages_to_sync;
+}
+
+static void
+camel_vtrash_folder_init (CamelVTrashFolder *vtrash_folder)
+{
+}
+
+/**
+ * camel_vtrash_folder_new:
+ * @parent_store: the parent #CamelVeeStore object
+ * @type: type of vfolder, #CAMEL_VTRASH_FOLDER_TRASH or
+ * #CAMEL_VTRASH_FOLDER_JUNK currently.
+ *
+ * Create a new CamelVTrashFolder object.
+ *
+ * Returns: a new #CamelVTrashFolder object
+ **/
+CamelFolder *
+camel_vtrash_folder_new (CamelStore *parent_store,
+ CamelVTrashFolderType type)
+{
+ CamelVTrashFolder *vtrash;
+
+ g_return_val_if_fail (type < CAMEL_VTRASH_FOLDER_LAST, NULL);
+
+ vtrash = g_object_new (
+ CAMEL_TYPE_VTRASH_FOLDER,
+ "full-name", vdata[type].full_name,
+ "display-name", gettext (vdata[type].name),
+ "parent-store", parent_store, NULL);
+
+ camel_vee_folder_construct (
+ CAMEL_VEE_FOLDER (vtrash),
+ CAMEL_STORE_FOLDER_PRIVATE |
+ CAMEL_STORE_FOLDER_CREATE);
+
+ ((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;
+}
diff --git a/src/camel/camel-vtrash-folder.h b/src/camel/camel-vtrash-folder.h
new file mode 100644
index 000000000..bf0c6d46e
--- /dev/null
+++ b/src/camel/camel-vtrash-folder.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef CAMEL_VTRASH_FOLDER_H
+#define CAMEL_VTRASH_FOLDER_H
+
+#include <camel/camel-folder.h>
+#include <camel/camel-vee-folder.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_VTRASH_FOLDER \
+ (camel_vtrash_folder_get_type ())
+#define CAMEL_VTRASH_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_VTRASH_FOLDER, CamelVTrashFolder))
+#define CAMEL_VTRASH_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_VTRASH_FOLDER, CamelVTrashFolderClass))
+#define CAMEL_IS_VTRASH_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_VTRASH_FOLDER))
+#define CAMEL_IS_VTRASH_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_VTRASH_FOLDER))
+#define CAMEL_VTRASH_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_VTRASH_FOLDER, CamelVTrashFolderClass))
+
+#define CAMEL_VTRASH_NAME ".#evolution/Trash"
+#define CAMEL_VJUNK_NAME ".#evolution/Junk"
+
+G_BEGIN_DECLS
+
+typedef struct _CamelVTrashFolder CamelVTrashFolder;
+typedef struct _CamelVTrashFolderClass CamelVTrashFolderClass;
+
+typedef enum {
+ CAMEL_VTRASH_FOLDER_TRASH,
+ CAMEL_VTRASH_FOLDER_JUNK,
+ CAMEL_VTRASH_FOLDER_LAST
+} CamelVTrashFolderType;
+
+struct _CamelVTrashFolder {
+ CamelVeeFolder parent;
+
+ CamelVTrashFolderType type;
+ guint32 bit;
+};
+
+struct _CamelVTrashFolderClass {
+ CamelVeeFolderClass parent_class;
+};
+
+GType camel_vtrash_folder_get_type (void);
+CamelFolder * camel_vtrash_folder_new (CamelStore *parent_store,
+ CamelVTrashFolderType type);
+
+G_END_DECLS
+
+#endif /* CAMEL_VTRASH_FOLDER_H */
diff --git a/src/camel/camel-win32.c b/src/camel/camel-win32.c
new file mode 100644
index 000000000..b8323a385
--- /dev/null
+++ b/src/camel/camel-win32.c
@@ -0,0 +1,133 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-win32.c : Win32-specific bits
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Tor Lillqvist <tml@novell.com>
+ */
+
+#include <errno.h>
+#include <io.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include <windows.h>
+
+#include <glib/gstdio.h>
+
+#include "camel.h"
+
+static HMODULE hmodule;
+G_LOCK_DEFINE_STATIC (mutex);
+
+/* localedir uses system codepage as it is passed to the non-UTF8ified
+ * gettext library
+ */
+static const gchar *localedir = NULL;
+
+/* The others are in UTF-8 */
+static const gchar *prefix = NULL;
+static const gchar *cp_prefix;
+static const gchar *libexecdir;
+static const gchar *providerdir;
+
+static gchar *
+replace_prefix (const gchar *configure_time_prefix,
+ const gchar *runtime_prefix,
+ const gchar *configure_time_path)
+{
+ gchar *c_t_prefix_slash;
+ gchar *retval;
+
+ c_t_prefix_slash = g_strconcat (configure_time_prefix, "/", NULL);
+
+ if (runtime_prefix != NULL &&
+ g_str_has_prefix (configure_time_path, c_t_prefix_slash)) {
+ retval = g_strconcat (
+ runtime_prefix,
+ configure_time_path + strlen (configure_time_prefix),
+ NULL);
+ } else
+ retval = g_strdup (configure_time_path);
+
+ g_free (c_t_prefix_slash);
+
+ return retval;
+}
+
+static void
+setup (void)
+{
+ gchar *full_pfx;
+ gchar *cp_pfx;
+
+ G_LOCK (mutex);
+
+ if (prefix != NULL) {
+ G_UNLOCK (mutex);
+ return;
+ }
+ /* This requires that the libedataserver DLL is installed in $bindir */
+ full_pfx = g_win32_get_package_installation_directory_of_module (hmodule);
+ cp_pfx = g_win32_locale_filename_from_utf8 (full_pfx);
+
+ prefix = g_strdup (full_pfx);
+ cp_prefix = g_strdup (cp_pfx);
+
+ g_free (full_pfx);
+ g_free (cp_pfx);
+
+ localedir = replace_prefix (
+ E_DATA_SERVER_PREFIX, cp_prefix, LOCALEDIR);
+ libexecdir = replace_prefix (
+ E_DATA_SERVER_PREFIX, prefix, CAMEL_LIBEXECDIR);
+ providerdir = replace_prefix (
+ E_DATA_SERVER_PREFIX, prefix, CAMEL_PROVIDERDIR);
+
+ G_UNLOCK (mutex);
+}
+
+#include "camel-win32.h" /* For prototypes */
+
+#define GETTER(varbl) \
+const gchar * \
+_camel_get_##varbl (void) \
+{ \
+ setup (); \
+ return varbl; \
+}
+
+GETTER(localedir)
+GETTER(libexecdir)
+GETTER(providerdir)
+
+/* Silence gcc with a prototype. Yes, this is silly. */
+BOOL WINAPI DllMain (HINSTANCE hinstDLL,
+ DWORD fdwReason,
+ LPVOID lpvReserved);
+
+/* Minimal DllMain that just tucks away the DLL's HMODULE */
+BOOL WINAPI
+DllMain (HINSTANCE hinstDLL,
+ DWORD fdwReason,
+ LPVOID lpvReserved)
+{
+ switch (fdwReason) {
+ case DLL_PROCESS_ATTACH:
+ hmodule = hinstDLL;
+ break;
+ }
+ return TRUE;
+}
diff --git a/src/camel/camel-win32.h b/src/camel/camel-win32.h
new file mode 100644
index 000000000..d90605479
--- /dev/null
+++ b/src/camel/camel-win32.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-win32.h: Private info for win32.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_WIN32_H
+#define CAMEL_WIN32_H
+
+/* need a way to configure and save this data, if this header is to
+ be installed. For now, dont install it */
+
+#include "evolution-data-server-config.h"
+
+#include <glib.h>
+
+#ifdef G_OS_WIN32
+
+G_BEGIN_DECLS
+
+#define fsync(fd) _commit(fd)
+
+const gchar *_camel_get_localedir (void) G_GNUC_CONST;
+const gchar *_camel_get_libexecdir (void) G_GNUC_CONST;
+const gchar *_camel_get_providerdir (void) G_GNUC_CONST;
+
+#undef LOCALEDIR
+#define LOCALEDIR _camel_get_localedir ()
+
+#undef CAMEL_LIBEXECDIR
+#define CAMEL_LIBEXECDIR _camel_get_libexecdir ()
+
+#undef CAMEL_PROVIDERDIR
+#define CAMEL_PROVIDERDIR _camel_get_providerdir ()
+
+G_END_DECLS
+
+#endif /* G_OS_WIN32 */
+
+#endif /* CAMEL_WIN32_H */
diff --git a/src/camel/camel.c b/src/camel/camel.c
new file mode 100644
index 000000000..9a4564c2c
--- /dev/null
+++ b/src/camel/camel.c
@@ -0,0 +1,386 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ * Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <signal.h>
+
+#include <nspr.h>
+#include <prthread.h>
+#include "nss.h" /* Don't use <> here or it will include the system nss.h instead */
+#include <ssl.h>
+#include <sslproto.h>
+#include <errno.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel.h"
+#include "camel-certdb.h"
+#include "camel-debug.h"
+#include "camel-provider.h"
+#include "camel-win32.h"
+
+/* To protect NSS initialization and shutdown. This prevents
+ * concurrent calls to shutdown () and init () by different threads */
+PRLock *nss_initlock = NULL;
+
+/* Whether or not Camel has initialized the NSS library. We cannot
+ * unconditionally call NSS_Shutdown () if NSS was initialized by other
+ * library before. This boolean ensures that we only perform a cleanup
+ * if and only if Camel is the one that previously initialized NSS */
+volatile gboolean nss_initialized = FALSE;
+
+static gint initialised = FALSE;
+
+gint camel_application_is_exiting = FALSE;
+
+#define NSS_SYSTEM_DB "/etc/pki/nssdb"
+
+static gint
+nss_has_system_db (void)
+{
+ gint found = FALSE;
+#ifndef G_OS_WIN32
+ FILE *f;
+ gchar buf[80];
+
+ f = fopen (NSS_SYSTEM_DB "/pkcs11.txt", "r");
+ if (!f)
+ return FALSE;
+
+ /* Check whether the system NSS db is actually enabled */
+ while (fgets (buf, 80, f) && !found) {
+ if (!strcmp (buf, "library=libnsssysinit.so\n"))
+ found = TRUE;
+ }
+ fclose (f);
+#endif
+ return found;
+}
+
+gint
+camel_init (const gchar *configdir,
+ gboolean nss_init)
+{
+ CamelCertDB *certdb;
+ gchar *path;
+
+ if (initialised)
+ return 0;
+
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+ camel_debug_init ();
+
+ if (nss_init) {
+ static gchar v2_enabled = -1, weak_ciphers = -1;
+ gchar *nss_configdir = NULL;
+ gchar *nss_sql_configdir = NULL;
+ SECStatus status = SECFailure;
+
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 14)
+ /* NSS pre-3.14 has most of the ciphers disabled, thus enable
+ * weak ciphers, if it's compiled against such */
+ weak_ciphers = 1;
+#endif
+
+ /* check camel-tcp-stream-ssl.c for the same "CAMEL_SSL_V2_ENABLE" */
+ if (v2_enabled == -1)
+ v2_enabled = g_strcmp0 (g_getenv ("CAMEL_SSL_V2_ENABLE"), "1") == 0 ? 1 : 0;
+ if (weak_ciphers == -1)
+ weak_ciphers = g_strcmp0 (g_getenv ("CAMEL_SSL_WEAK_CIPHERS"), "1") == 0 ? 1 : 0;
+
+ if (nss_initlock == NULL) {
+ PR_Init (PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 10);
+ nss_initlock = PR_NewLock ();
+ }
+ PR_Lock (nss_initlock);
+
+ if (NSS_IsInitialized ())
+ goto skip_nss_init;
+
+#ifndef G_OS_WIN32
+ nss_configdir = g_strdup (configdir);
+#else
+ nss_configdir = g_win32_locale_filename_from_utf8 (configdir);
+#endif
+
+ if (nss_has_system_db ()) {
+ nss_sql_configdir = g_strdup ("sql:" NSS_SYSTEM_DB );
+ } else {
+ /* On Windows, we use the Evolution configdir. On other
+ * operating systems we use ~/.pki/nssdb/, which is where
+ * the user-specific part of the "shared system db" is
+ * stored and is what Chrome uses too.
+ *
+ * We have to create the configdir if it does not exist,
+ * to prevent camel from bailing out on first run. */
+#ifdef G_OS_WIN32
+ g_mkdir_with_parents (configdir, 0700);
+ nss_sql_configdir = g_strconcat (
+ "sql:", nss_configdir, NULL);
+#else
+ gchar *user_nss_dir = g_build_filename (
+ g_get_home_dir (), ".pki/nssdb", NULL );
+ if (g_mkdir_with_parents (user_nss_dir, 0700))
+ g_warning (
+ "Failed to create SQL "
+ "database directory %s: %s\n",
+ user_nss_dir, strerror (errno));
+
+ nss_sql_configdir = g_strconcat (
+ "sql:", user_nss_dir, NULL);
+ g_free (user_nss_dir);
+#endif
+ }
+
+#if NSS_VMAJOR > 3 || (NSS_VMAJOR == 3 && NSS_VMINOR >= 12)
+ /* See: https://wiki.mozilla.org/NSS_Shared_DB,
+ * particularly "Mode 3A". Note that the target
+ * directory MUST EXIST. */
+ status = NSS_InitWithMerge (
+ nss_sql_configdir, /* dest dir */
+ "", "", /* new DB name prefixes */
+ SECMOD_DB, /* secmod name */
+ nss_configdir, /* old DB dir */
+ "", "", /* old DB name prefixes */
+ nss_configdir, /* unique ID for old DB */
+ "Evolution S/MIME", /* UI name for old DB */
+ 0); /* flags */
+
+ if (status == SECFailure) {
+ g_warning (
+ "Failed to initialize NSS SQL database in %s: NSS error %d",
+ nss_sql_configdir, PORT_GetError ());
+ /* Fall back to opening the old DBM database */
+ }
+#endif
+ /* Support old versions of libnss, pre-sqlite support. */
+ if (status == SECFailure)
+ status = NSS_InitReadWrite (nss_configdir);
+ if (status == SECFailure) {
+ /* Fall back to using volatile dbs? */
+ status = NSS_NoDB_Init (nss_configdir);
+ if (status == SECFailure) {
+ g_free (nss_configdir);
+ g_free (nss_sql_configdir);
+ g_warning ("Failed to initialize NSS");
+ PR_Unlock (nss_initlock);
+ return -1;
+ }
+ }
+
+ nss_initialized = TRUE;
+skip_nss_init:
+
+ NSS_SetDomesticPolicy ();
+
+ if (weak_ciphers) {
+ PRUint16 indx;
+
+ /* enable SSL3/TLS cipher-suites */
+ for (indx = 0; indx < SSL_NumImplementedCiphers; indx++) {
+ if (!SSL_IS_SSL2_CIPHER (SSL_ImplementedCiphers[indx]) &&
+ SSL_ImplementedCiphers[indx] != SSL_RSA_WITH_NULL_SHA &&
+ SSL_ImplementedCiphers[indx] != SSL_RSA_WITH_NULL_MD5)
+ SSL_CipherPrefSetDefault (SSL_ImplementedCiphers[indx], PR_TRUE);
+ }
+ }
+
+ SSL_OptionSetDefault (SSL_ENABLE_SSL2, v2_enabled ? PR_TRUE : PR_FALSE);
+ SSL_OptionSetDefault (SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSetDefault (SSL_ENABLE_SSL3, PR_TRUE);
+ SSL_OptionSetDefault (SSL_ENABLE_TLS, PR_TRUE);
+
+ PR_Unlock (nss_initlock);
+
+ g_free (nss_configdir);
+ g_free (nss_sql_configdir);
+ }
+
+ path = g_strdup_printf ("%s/camel-cert.db", configdir);
+ certdb = camel_certdb_new ();
+ camel_certdb_set_filename (certdb, path);
+ g_free (path);
+
+ /* if we fail to load, who cares? it'll just be a volatile certdb */
+ camel_certdb_load (certdb);
+
+ /* set this certdb as the default db */
+ camel_certdb_set_default (certdb);
+
+ g_object_unref (certdb);
+
+ initialised = TRUE;
+
+ return 0;
+}
+
+/**
+ * camel_shutdown:
+ *
+ * Since: 2.24
+ **/
+void
+camel_shutdown (void)
+{
+ CamelCertDB *certdb;
+
+ if (!initialised)
+ return;
+
+ certdb = camel_certdb_get_default ();
+ if (certdb) {
+ camel_certdb_save (certdb);
+ camel_certdb_set_default (NULL);
+ }
+
+ /* These next calls must come last. */
+
+ if (nss_initlock != NULL) {
+ PR_Lock (nss_initlock);
+ if (nss_initialized)
+ NSS_Shutdown ();
+ PR_Unlock (nss_initlock);
+ }
+
+ initialised = FALSE;
+}
+
+static GRecMutex camel_binding_lock;
+
+/**
+ * camel_binding_bind_property:
+ *
+ * Thread safe variant of g_object_bind_property(). See its documentation
+ * for more information on arguments and return value.
+ *
+ * Returns: (transfer none):
+ *
+ * Since: 3.16
+ **/
+GBinding *
+camel_binding_bind_property (gpointer source,
+ const gchar *source_property,
+ gpointer target,
+ const gchar *target_property,
+ GBindingFlags flags)
+{
+ GBinding *binding;
+
+ g_rec_mutex_lock (&camel_binding_lock);
+
+ binding = g_object_bind_property (source, source_property, target, target_property, flags);
+
+ g_rec_mutex_unlock (&camel_binding_lock);
+
+ return binding;
+}
+
+/**
+ * camel_binding_bind_property_full:
+ * @source: (type GObject.Object): the source #GObject
+ * @source_property: the property on @source to bind
+ * @target: (type GObject.Object): the target #GObject
+ * @target_property: the property on @target to bind
+ * @flags: flags to pass to #GBinding
+ * @transform_to: (scope notified) (allow-none): the transformation function
+ * from the @source to the @target, or %NULL to use the default
+ * @transform_from: (scope notified) (allow-none): the transformation function
+ * from the @target to the @source, or %NULL to use the default
+ * @user_data: custom data to be passed to the transformation functions,
+ * or %NULL
+ * @notify: function to be called when disposing the binding, to free the
+ * resources used by the transformation functions
+ *
+ * Thread safe variant of g_object_bind_property_full(). See its documentation
+ * for more information on arguments and return value.
+ *
+ * Return value: (transfer none): the #GBinding instance representing the
+ * binding between the two #GObject instances. The binding is released
+ * whenever the #GBinding reference count reaches zero.
+ *
+ * Since: 3.16
+ **/
+GBinding *
+camel_binding_bind_property_full (gpointer source,
+ const gchar *source_property,
+ gpointer target,
+ const gchar *target_property,
+ GBindingFlags flags,
+ GBindingTransformFunc transform_to,
+ GBindingTransformFunc transform_from,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ GBinding *binding;
+
+ g_rec_mutex_lock (&camel_binding_lock);
+
+ binding = g_object_bind_property_full (source, source_property, target, target_property, flags,
+ transform_to, transform_from, user_data, notify);
+
+ g_rec_mutex_unlock (&camel_binding_lock);
+
+ return binding;
+}
+
+/**
+ * camel_binding_bind_property_with_closures: (rename-to camel_binding_bind_property_full)
+ * @source: (type GObject.Object): the source #GObject
+ * @source_property: the property on @source to bind
+ * @target: (type GObject.Object): the target #GObject
+ * @target_property: the property on @target to bind
+ * @flags: flags to pass to #GBinding
+ * @transform_to: a #GClosure wrapping the transformation function
+ * from the @source to the @target, or %NULL to use the default
+ * @transform_from: a #GClosure wrapping the transformation function
+ * from the @target to the @source, or %NULL to use the default
+ *
+ * Thread safe variant of g_object_bind_property_with_closures(). See its
+ * documentation for more information on arguments and return value.
+ *
+ * Return value: (transfer none): the #GBinding instance representing the
+ * binding between the two #GObject instances. The binding is released
+ * whenever the #GBinding reference count reaches zero.
+ *
+ * Since: 3.16
+ **/
+GBinding *
+camel_binding_bind_property_with_closures (gpointer source,
+ const gchar *source_property,
+ gpointer target,
+ const gchar *target_property,
+ GBindingFlags flags,
+ GClosure *transform_to,
+ GClosure *transform_from)
+{
+ GBinding *binding;
+
+ g_rec_mutex_lock (&camel_binding_lock);
+
+ binding = g_object_bind_property_with_closures (source, source_property, target, target_property, flags,
+ transform_to, transform_from);
+
+ g_rec_mutex_unlock (&camel_binding_lock);
+
+ return binding;
+}
diff --git a/src/camel/camel.h b/src/camel/camel.h
new file mode 100644
index 000000000..5710717dd
--- /dev/null
+++ b/src/camel/camel.h
@@ -0,0 +1,171 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
+ */
+
+#ifndef CAMEL_H
+#define CAMEL_H
+
+#define __CAMEL_H_INSIDE__
+
+#include <camel/camel-address.h>
+#include <camel/camel-async-closure.h>
+#include <camel/camel-block-file.h>
+#include <camel/camel-certdb.h>
+#include <camel/camel-charset-map.h>
+#include <camel/camel-cipher-context.h>
+#include <camel/camel-data-cache.h>
+#include <camel/camel-data-wrapper.h>
+#include <camel/camel-db.h>
+#include <camel/camel-debug.h>
+#include <camel/camel-enums.h>
+#include <camel/camel-enumtypes.h>
+#include <camel/camel-file-utils.h>
+#include <camel/camel-filter-driver.h>
+#include <camel/camel-filter-input-stream.h>
+#include <camel/camel-filter-output-stream.h>
+#include <camel/camel-filter-search.h>
+#include <camel/camel-folder.h>
+#include <camel/camel-folder-search.h>
+#include <camel/camel-folder-summary.h>
+#include <camel/camel-folder-thread.h>
+#include <camel/camel-gpg-context.h>
+#include <camel/camel-html-parser.h>
+#include <camel/camel-iconv.h>
+#include <camel/camel-index.h>
+#include <camel/camel-internet-address.h>
+#include <camel/camel-junk-filter.h>
+#include <camel/camel-local-settings.h>
+#include <camel/camel-lock.h>
+#include <camel/camel-lock-client.h>
+#include <camel/camel-lock-helper.h>
+#include <camel/camel-medium.h>
+#include <camel/camel-memchunk.h>
+#include <camel/camel-mempool.h>
+#include <camel/camel-mime-filter.h>
+#include <camel/camel-mime-filter-basic.h>
+#include <camel/camel-mime-filter-bestenc.h>
+#include <camel/camel-mime-filter-canon.h>
+#include <camel/camel-mime-filter-charset.h>
+#include <camel/camel-mime-filter-crlf.h>
+#include <camel/camel-mime-filter-enriched.h>
+#include <camel/camel-mime-filter-from.h>
+#include <camel/camel-mime-filter-gzip.h>
+#include <camel/camel-mime-filter-html.h>
+#include <camel/camel-mime-filter-index.h>
+#include <camel/camel-mime-filter-linewrap.h>
+#include <camel/camel-mime-filter-pgp.h>
+#include <camel/camel-mime-filter-progress.h>
+#include <camel/camel-mime-filter-tohtml.h>
+#include <camel/camel-mime-filter-windows.h>
+#include <camel/camel-mime-filter-yenc.h>
+#include <camel/camel-mime-message.h>
+#include <camel/camel-mime-parser.h>
+#include <camel/camel-mime-part.h>
+#include <camel/camel-mime-part-utils.h>
+#include <camel/camel-mime-utils.h>
+#include <camel/camel-movemail.h>
+#include <camel/camel-msgport.h>
+#include <camel/camel-multipart.h>
+#include <camel/camel-multipart-encrypted.h>
+#include <camel/camel-multipart-signed.h>
+#include <camel/camel-net-utils.h>
+#include <camel/camel-network-service.h>
+#include <camel/camel-nntp-address.h>
+#include <camel/camel-null-output-stream.h>
+#include <camel/camel-object.h>
+#include <camel/camel-object-bag.h>
+#include <camel/camel-offline-folder.h>
+#include <camel/camel-offline-settings.h>
+#include <camel/camel-offline-store.h>
+#include <camel/camel-operation.h>
+#include <camel/camel-partition-table.h>
+#include <camel/camel-provider.h>
+#include <camel/camel-sasl.h>
+#include <camel/camel-sasl-anonymous.h>
+#include <camel/camel-sasl-cram-md5.h>
+#include <camel/camel-sasl-digest-md5.h>
+#include <camel/camel-sasl-gssapi.h>
+#include <camel/camel-sasl-login.h>
+#include <camel/camel-sasl-ntlm.h>
+#include <camel/camel-sasl-plain.h>
+#include <camel/camel-sasl-popb4smtp.h>
+#include <camel/camel-service.h>
+#include <camel/camel-session.h>
+#include <camel/camel-settings.h>
+#include <camel/camel-sexp.h>
+#include <camel/camel-smime-context.h>
+#include <camel/camel-store.h>
+#include <camel/camel-store-settings.h>
+#include <camel/camel-store-summary.h>
+#include <camel/camel-stream.h>
+#include <camel/camel-stream-buffer.h>
+#include <camel/camel-stream-filter.h>
+#include <camel/camel-stream-fs.h>
+#include <camel/camel-stream-mem.h>
+#include <camel/camel-stream-null.h>
+#include <camel/camel-stream-process.h>
+#include <camel/camel-string-utils.h>
+#include <camel/camel-subscribable.h>
+#include <camel/camel-text-index.h>
+#include <camel/camel-transport.h>
+#include <camel/camel-trie.h>
+#include <camel/camel-uid-cache.h>
+#include <camel/camel-url.h>
+#include <camel/camel-url-scanner.h>
+#include <camel/camel-utf8.h>
+#include <camel/camel-vee-data-cache.h>
+#include <camel/camel-vee-folder.h>
+#include <camel/camel-vee-store.h>
+#include <camel/camel-vee-summary.h>
+#include <camel/camel-vtrash-folder.h>
+
+#undef __CAMEL_H_INSIDE__
+
+G_BEGIN_DECLS
+
+extern gint camel_application_is_exiting;
+
+gint camel_init (const gchar *certdb_dir, gboolean nss_init);
+void camel_shutdown (void);
+
+GBinding * camel_binding_bind_property (gpointer source,
+ const gchar *source_property,
+ gpointer target,
+ const gchar *target_property,
+ GBindingFlags flags);
+GBinding * camel_binding_bind_property_full(gpointer source,
+ const gchar *source_property,
+ gpointer target,
+ const gchar *target_property,
+ GBindingFlags flags,
+ GBindingTransformFunc transform_to,
+ GBindingTransformFunc transform_from,
+ gpointer user_data,
+ GDestroyNotify notify);
+GBinding * camel_binding_bind_property_with_closures
+ (gpointer source,
+ const gchar *source_property,
+ gpointer target,
+ const gchar *target_property,
+ GBindingFlags flags,
+ GClosure *transform_to,
+ GClosure *transform_from);
+
+G_END_DECLS
+
+#endif /* CAMEL_H */
diff --git a/src/camel/camel.pc.in b/src/camel/camel.pc.in
new file mode 100644
index 000000000..55e1099a9
--- /dev/null
+++ b/src/camel/camel.pc.in
@@ -0,0 +1,15 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+libdir=@LIB_INSTALL_DIR@
+includedir=@INCLUDE_INSTALL_DIR@
+
+privlibdir=@privlibdir@
+privincludedir=@privincludedir@
+
+camel_providerdir=@camel_providerdir@
+
+Name: camel
+Description: the Evolution MIME message handling library
+Version: @PROJECT_VERSION@
+Requires: gio-2.0 sqlite3 @mozilla_nss@ @mozilla_nspr@
+Libs: -L${libdir} -lcamel-@API_VERSION@ @MANUAL_NSS_LIBS@
+Cflags: -I${privincludedir} @MANUAL_NSS_CFLAGS@
diff --git a/src/camel/devel-docs/camel-index.txt b/src/camel/devel-docs/camel-index.txt
new file mode 100644
index 000000000..0ebc250db
--- /dev/null
+++ b/src/camel/devel-docs/camel-index.txt
@@ -0,0 +1,407 @@
+
+The history of text-indexing in evolution.
+
+CamelTextIndex was written to address several shortcomings in the
+existing libibex (referred to as libibex2), which had been written to
+address shortcomings in the original libibex.
+
+Mail indexing characteristics
+
+First, i'll cover some of the scenarios that a mail indexing system
+must cover. They are slightly different from other indexing systems,
+at least we wanted them to be.
+
+1. Indexing a few new messages, they may potentially reference most of
+ the alphabet in the index.
+2. Indexing a whole mailbox for the first time
+3. Unindexing anywhere from a few to all existing messages during expunge.
+4. Searching.
+
+Cases 1, 3, and 4 occur the most often, however 2 is the most noticeable
+at first use, or if the index must be reset. So the code needs to
+work fast in all cases, which generally leads to trade-offs being
+made. Each implementation aimed to address or ignore these
+requirements in different ways, with the final implementation probably
+having the best balance so far.
+
+The main issue is that the indexing be performed real time. We index
+as we add the messages. We index before we open the mailbox. We
+index as we remove messages. Because of this we need to approach
+things differently to many other indexing systems; most of which work
+with static data in an off-line mode. This allows them to index the
+whole body of content and use as much memory and cpu time as required.
+
+We probably need to look at doing offline, or at least delayed
+indexing in the future - but this introduces some coherency problems
+with vFolders and any body searches. However, having the indexing
+library a base part of Camel helps in implementing a mechanism to
+achieve this.
+
+Ibex the first
+
+The original ibex used a memory-based hash table to store words. This made
+the index very fast for both lookups and modifications. However any
+queries required a full load of the index into memory, and any updates
+required a full write of the index to disk. After about 5-10 000
+messages occasionaly adding to the index became appreciably slower as
+the whole index needed to be loaded into memory first. This obviously
+took a toll on memory as well.
+
+I wont cover the algorithms used, they were all pretty basic, the only
+real smarts were that index deletes were only flagged and that data
+not written to disk when the index saved.
+
+Evolution 1.x, ibex 2.
+
+In an attempt to rectify the incremental update performance of
+libibex, it was completely rewritten to use an on-disk block-based
+filesystem.
+
+Note: the first attempt used libdb - however performance was so slow
+and the indices were so large it was dropped in favour of a custom
+filesystem-like data file.
+
+The motivation was that a few extra disk lookups during
+retrieval wouldn't be noticeably slower, however it should be able to
+scale up to many more messages with lower memory overhead and slower
+startup time.
+
+The block filesystem contains 3 major components:
+
+1. A hash table that mapped message's to a word id sequence list.
+2. A hash table that mapped word's to a message id sequence list.
+3. A sequence filesystem that stored sequences of id's.
+
+The id's are 32 bit identifiers that are unique for each word or
+message. They are also designed to be reversible and static.
+That is, given the id, you can map it to the string identifier that it
+represents directly, without having to look it up in the hash table.
+
+Other features of this design is that the database file should be
+kept in sync at all times with the state of the index. The message to
+wordid tables are used to remove the messageid's from the word's it
+contains when the message is expunged, and so on.
+
+Indexing operation
+
+The indexing operation consists of the basic steps:
+
+1. Lookup the messageid from the message name, using the messageid table.
+2. Generate a list of words in the message
+3. For each word:
+4. Lookup the wordid and sequence information
+5. If the word doesn't exist, create a new word/wordid
+6. Add the messageid to the word sequence.
+7. Add the wordid to the message sequence.
+
+The initial implementation only used caching at the disk-block level.
+Unfortunately, the simple hash table design chosen (fixed sized base
+table with chained buckets) scaled very poorly above about 10 000
+messages. So this approach proved to be too i/o intensive for
+practical use, and several other caches were added to improve
+performance:
+
+1. Stage (1) above is done entirely in memory. At initial startup
+ the whole list of potential names is read into an in-memory hash
+ table.
+2. Stage (4) above is also done entirely in memory. Even a large
+ cache provided little benefit due to wide distribution of potential
+ words. This cache is only created when adding to the index.
+3. Stage (6) uses the table from stage (4) and concatenates upto
+ approximately one disk blocks worth of messageid's before writing
+ them out to the word sequence.
+4. Stage (7) concatenates all wordid's for a given message before
+ writing them out at once.
+
+As you can see, the added complexity meant we nearly have to cache as
+much as the original version! This also almost removed all of the
+startup-time benefit for incremental update of the index, as the table
+was not stored as compactly on disk as the original version.
+
+However, we only ever stored a subset of the index in memory, and only
+during updates, with some tricks to reduce memory usage for very rare
+words, so the overall memory use was still much lower.
+
+Removing a message
+
+Removing a message is fairly involved:
+
+1. Lookup the messageid and word sequence list from the messageid table.
+2. For each wordid in the sequence list
+3. Lookup the message sequence list directly from the wordid table.
+4. Scan each block in the sequence, and remove any instances of the
+ messageid.
+5. Remove the message to messageid mapping in the messageid table.
+
+Unfortunately caching helped very little here, particularly if many
+messages were removed. Also note that the file could never shrink as
+the data could be spread randomly over it. Removal is an extremely
+expensive an unbounded process. Deleting all of the messages in a
+mailbox is extremely i/o intensive, with blocks potentially being
+accessed dozens of times.
+
+Performing a query
+
+Performing a query is fast:
+
+1. Lookup the messageid sequence list from the wordid table.
+2. For each messageid
+3. Lookup the message name directly from the messageid table.
+
+Even without caching this performs at a very acceptable level.
+
+Summary
+
+This index performs reasonably well upto about 10 000 messages for a
+complete re-index. However with incremental updates it degrads much
+faster, only a few thousand messages added and it becomes tiresomely
+slow and i/o bound. The index becomes more fragmented with random
+updates and removals and heavily bogs down the system as you go much
+beyond those few thousand messages.
+
+The code is also very complicated and hard to follow. There are too
+many special cases, and it is buggy. Detected on-disk structure
+errors result in the index being reset, which although it shrinks the
+index, is very slow.
+
+The indices created are bulky, and never shrink. Because of the
+reverse index used for message removal, there is 50% redundant data at
+all times. Some overly tricky techniques (very much like ReiserFS's
+tail packing) are used to waste as little space as possible, with a
+great impact on performance.
+
+One other problem is that because the index is disk based, we
+use a file descriptor continuously. With some users having
+>100 folders, they quickly run out of process file descriptors and
+evolution fails. To get around this a cache of least recently used
+index files is used to flush away and free file descriptors so they
+can be re-used. This makes it hard to lock the files; this problem
+still exists with the next implementation.
+
+Anyway, a better solution is required.
+
+CamelIndex
+
+The first problem to address was the api. It was starting to age.
+Although adequate, the api wasn't terribly clean, reusable, or
+scalable. The first thing was to objectise the library, and since we
+needed to use it in Camel, the best way was to create a CamelObject.
+
+CamelIndex was born. A mostly abstract class that provides a simple
+common interface for accessing indices, including cursors and utility
+and maintenance functions.
+
+In addition, a number of the features in libibex2 were simplified or
+rewritten and abstracted into the re-usable classes that follow.
+
+By providing simple cursors, more complex queries were easier to write
+and can execute more efficiently; camel searching now does sub-string
+searches for all body queries, and still runs at a very healthy speed
+and uses less memory than before.
+
+CamelBlockFile
+
+This is basically the same block filesystem used in libibex2. It
+handles disk i/o based on blocks (CamelBlock), flushing modified
+blocks to disk, and caching of recently accessed blocks. It was
+enhanced slightly to allow blocks to be locked in memory.
+
+CamelKeyFile
+
+This is a simple reverse-linked list of sequences of keyid's.
+
+The main property of this file is that updates are only ever appended
+to the end of the file, which improves i/o characteristics markedly.
+
+When an existing keyid sequence is updated, it simply points back to
+the start of the previous one, and provides a pointer to the new
+entry. i.e. a simple linked list.
+
+CamelKeyTable
+
+This is taken from the libibex2 code for mapping keys, with few
+changes. It uses a CamelBlockFile for its i/o.
+
+The key table is a linked list of blocks (CamelKeyBlock) which contain
+key strings and and a data pointer and flags for each key. Each block
+is a packed array of string descriptors (CamelKeyKey's).
+
+A keyid (camel_key_t) is a 32 bit descriptor which identifies this key
+in a reversible way. In this case the bottom 10 bits are used to
+identify the index of the key within the key block, and the top 22
+bits are used to identify the key block itself. In this way, given
+the 32 bit key id, we can reference the block containing the key
+directly (with at most 1 access), and access the flags and key string
+using the key index.
+
+Keys can potentially be removed and their keyid's reused by simply
+re-packing the key block. This was used in libibex2, but not in
+CamelIndex.
+
+[diagram - camelkeyblock]
+
+CamelPartitionTable
+
+An implementation of a scalable, on-disk 'perfect' hash table. It
+uses the CamelBlockFile to handle its i/o. This is a completely new
+hash table implementation which was not present in libibex2.
+
+[FIXME: Reference the original paper the algorithm is based on.]
+
+A partition table consists of a list of mapping blocks
+(CamelPartitionMapBlock), which is a compact table that maps a range
+of hashid's to a partition block (CamelPartitionKeyBlock), which
+contains hashid's of that range.
+
+[diagram - camelpartitiontable]
+
+The partition block only maps the hashid to a keyid (see CamelKeyTable)
+which means it can store a lot of keys in each block.
+
+To add a new value to the partition table:
+
+1. Calculate the hash value of the key
+2. Find out which partition block the key will fit into, using the
+ partition table.
+3. If the partition block is full:
+4. If there is room in the next or previous block:
+5. Merge the 2 blocks together, and split at the half-way point
+6. Update the partition table hash indices to match the blocks
+7. Else
+8. Create a new block, and split the existing block across it
+9. Insert the new block into the partition table
+10. Else
+11. Just add the key to the end of the block.
+
+Steps 5 and 8 perform a sorting of the partition key entries by hashid
+to find the midpoint. It may be beneficial to store the hashid's
+sorted always, it would then not require a sort to split the blocks.
+This would also benefit key lookups by being able to use a binary
+search. However, the incremental sort may be more expensive.
+
+If the partition table itself fills up, then perform a similar
+splitting function on its blocks, and store it over multiple blocks.
+With a block size of 1024 bytes, we can fit 127 blocks pointers, each
+with 127 keys in it - around 16000 keys. So we only need 1024 bytes
+of memory for each 16000 on-disk keys (assuming full tables).
+
+Removal is basically the same, but if we end up with an empty block we
+just remove it from the partition table. CamelTextIndex doesn't
+actually use removal although it is implemented in
+CamelPartitionTable.
+
+Lookup is very simple. We basically follow steps 1 and 2, and then
+perform a linear search through the block to find a matching hash id.
+That is our key. This is assuming a perfect hash, additionally the
+code could use the keyid to lookup in a keytable to verify the key is
+indeed the right one. This would require having to support duplicate
+hashid's and would make block splitting slightly more complex, but
+only by a couple of lines of code. This is something that will
+probably have to be addressed in the future.
+
+Using a partition table means that we can tell with 1 disk access
+whether or not a key exists (assuming a perfect hash function), and 1
+more access to look up all of the details of the key since the keyid
+is reversible. Another feature is that the partition table is always
+self-balancing for any data processed in any order.
+
+Yet one more feature is that it is quite easy to order the writes to
+the partition table so that its structure is always consistent, even
+in the event of program failure. Although this has been disabled in
+the current code to take maximal advantage of the block cache.
+
+CamelTextIndex
+
+CamelTextIndex is the implementation of CamelIndex now used by camel
+for indexing mail. It shares some features with the second
+incarnation of libibex, but is generally simpler. It uses the
+previously described classes to implement the CamelIndex interface.
+
+Indexing operation
+
+Indexing operation is similar to libibex2, but without the requirement
+to maintain the reverse index.
+
+1. Lookup the messageid from the message name, using the messageid
+ partition table.
+2. Generate a list of words in the message
+3. For each word
+4. Lookup the wordid and sequence information.
+5. Append the messageid to the word sequence.
+
+In practice we also have a word cache which caches upto 32 messageid's
+for each word before it is written to the key file.
+
+Removing a message
+
+Removal is not immediate. This is one of the major performance
+improvements in CamelIndex.
+
+1. Lookup the messageid from the message name partition table
+2. Use the messageid to set a flag in the message key table to
+ indicate the message has been deleted.
+3. Remove the key hash from the partition table.
+
+This comes down to a maximum of 2 disk reads and 2 disk writes.
+libibex2 had unbounded maximums, depending on the number of words in a
+given message. The key file is not changed.
+
+Because data is not removed from the files at all, an additional
+optional step is required, that of compressing the indices.
+
+Performing a query
+
+Performing a query is much the same as with libibex2. We usually have
+slightly less disk i/o because of a more efficient and scalable hash
+table implementation, and improved locality of reference of the key
+table data.
+
+1. Lookup the messageid from the message name partition table
+2. Use the messageid to get the data pointer directly from the key
+ table.
+3. Iterate through the key file, reading blocks backwards through the
+ file.
+
+Compressing
+
+Although it could have benefited from it, libibex2 did not ever
+compress indices - the only way to compress an index was to remove it
+and have it be rebuilt.
+
+CamelIndex requires a compression stage as data is never removed from
+it otherwise. Because of the much greater locality of reference, the
+compression stage is actually much faster than an incremental removal
+of data inside the data files.
+
+Compressing comprises the following steps:
+
+1. Open a new temporary index, an index block file and an index key
+ file.
+2. For each message in the message partition table
+3. If the message is not marked deleted, add it to the new message
+ partition table, and recored the old messageid to new messageid
+ mapping.
+4. For each word in the word partition table
+5. For each messageid's in the word sequence list
+6. If the messageid maps to a new messageid, remap the messageid,
+ else discard it.
+7. Concatenate upto 256 messageid's in a row before writing to the
+ key file, to improve lookups.
+8. Create a new word in the new word key table
+9. Add the wordid and new sequence id to the word partition table.
+
+Note that at step 8 we could (should?) also check if the word has any
+messages associated with it, and discard the word from the new index.
+
+After compression, the name partition index only contains names which
+are not deleted, and the key file is compressed into larger blocks
+which takes up less space and is faster to retrieve.
+
+During index operations a number of statistics are taken which trigger
+an automatic compress when the file fragmentation or number of deleted
+messages exceed a threshold. So the index maintains itself, and does
+not need manual compression.
+
+
+
+
diff --git a/src/camel/devel-docs/camel_data_wrapper.dia b/src/camel/devel-docs/camel_data_wrapper.dia
new file mode 100644
index 000000000..301563d1e
--- /dev/null
+++ b/src/camel/devel-docs/camel_data_wrapper.dia
Binary files differ
diff --git a/src/camel/devel-docs/camel_parser_states.dia b/src/camel/devel-docs/camel_parser_states.dia
new file mode 100644
index 000000000..556a5b1c4
--- /dev/null
+++ b/src/camel/devel-docs/camel_parser_states.dia
Binary files differ
diff --git a/src/camel/devel-docs/camel_stream.dia b/src/camel/devel-docs/camel_stream.dia
new file mode 100644
index 000000000..d91d1bb15
--- /dev/null
+++ b/src/camel/devel-docs/camel_stream.dia
Binary files differ
diff --git a/src/camel/gentables.pl b/src/camel/gentables.pl
new file mode 100755
index 000000000..c07c1815a
--- /dev/null
+++ b/src/camel/gentables.pl
@@ -0,0 +1,105 @@
+#!/usr/bin/perl
+
+%special = (
+ "CHARS_LWSP", " \t\n\r",
+ "CHARS_TSPECIAL", "()<>@,;:\\\"/[]?=",
+ "CHARS_SPECIAL", "()<>@,;:\\\".[]",
+ "CHARS_CSPECIAL", "()\\\r", # not in comments
+ "CHARS_DSPECIAL", "[]\\\r \t", # not in domains
+ "CHARS_ATTRCHAR", "*\'% " ); # extra non-included attribute-chars
+
+%bits = (
+ CAMEL_MIME_IS_CTRL , 1<<0,
+ CAMEL_MIME_IS_LWSP , 1<<1,
+ CAMEL_MIME_IS_TSPECIAL , 1<<2,
+ CAMEL_MIME_IS_SPECIAL , 1<<3,
+ CAMEL_MIME_IS_SPACE , 1<<4,
+ CAMEL_MIME_IS_DSPECIAL , 1<<5,
+ CAMEL_MIME_IS_QPSAFE , 1<<6,
+ CAMEL_MIME_IS_ESAFE , 1<<7, #/* encoded word safe */
+ CAMEL_MIME_IS_PSAFE , 1<<8, #/* encoded word in phrase safe */
+ CAMEL_MIME_IS_ATTRCHAR , 1<<9, #/* attribute-char safe (rfc2184) */
+);
+
+@table = ();
+
+# set bit in character positions
+sub add_bits {
+ my $bit = $_[0];
+ my $str = $_[1];
+ my $ch;
+
+ foreach $ch (split(/.{0}/, $str)) {
+ $table[ord($ch)] |= $bits{$bit};
+ }
+};
+
+# remove bit in character positions
+sub rem_bits {
+ my $bit = $_[0];
+ my $str = $_[1];
+ my $ch;
+
+ foreach $ch (split(/.{0}/, $str)) {
+ $table[ord($ch)] &= ~($bits{$bit});
+ }
+};
+
+# set up base ranges
+foreach $i (0 .. 255) {
+ $table[$i] = 0;
+ if ($i<32 || $i==127) {
+ $table[$i] |= $bits{CAMEL_MIME_IS_CTRL} | $bits{CAMEL_MIME_IS_TSPECIAL};
+ } elsif ($i < 127) {
+ $table[$i] |= $bits{CAMEL_MIME_IS_ATTRCHAR};
+ }
+ if (($i>=32 && $i<=60) || ($i>=62 && $i<=126) || $i==9) {
+ $table[$i] |= ($bits{CAMEL_MIME_IS_QPSAFE} | $bits{CAMEL_MIME_IS_ESAFE});
+ }
+ if (($i>=0x30 && $i<=0x39) || ($i>=0x61 && $i<=0x7a) || ($i>=0x41 && $i<= 0x5a)) {
+ $table[$i] |= $bits{CAMEL_MIME_IS_PSAFE};
+ }
+}
+
+$table[0x20] |= $bits{CAMEL_MIME_IS_SPACE};
+
+add_bits('CAMEL_MIME_IS_LWSP', " \t\n\r");
+add_bits('CAMEL_MIME_IS_TSPECIAL', "()<>@,;:\\\"/[]?=");
+add_bits('CAMEL_MIME_IS_SPECIAL', "()<>@,;:\\\".[]");
+# not in domains
+add_bits('CAMEL_MIME_IS_DSPECIAL', "[]\\\r \t");
+
+# list of characters that must be encoded.
+# encoded word in text specials: rfc 2047 5(1)
+rem_bits('CAMEL_MIME_IS_ESAFE', "()<>@,;:\"/[]?.=_");
+
+# non-included attribute-chars (tspecial + extra)
+rem_bits('CAMEL_MIME_IS_ATTRCHAR', "*\'% "."()<>@,;:\\\"/[]?=");
+
+# list of additional characters that can be left unencoded.
+# encoded word in phrase specials: rfc 2047 5(3)
+add_bits('CAMEL_MIME_IS_PSAFE', "!*+-/");
+
+
+#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);
+
+# output
+print "const unsigned short camel_mime_special_table[256] = {\n\t";
+foreach $i (0..255) {
+ printf "0x%04x,", $table[$i];
+ if (($i & 0x7) == 0x7) {
+ print "\n";
+ if ($i != 0xff) {
+ print "\t";
+ }
+ } else {
+ print " ";
+ }
+}
+print "};\n";
diff --git a/src/camel/providers/CMakeLists.txt b/src/camel/providers/CMakeLists.txt
new file mode 100644
index 000000000..c1e5859d8
--- /dev/null
+++ b/src/camel/providers/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_subdirectory(imapx)
+add_subdirectory(local)
+add_subdirectory(nntp)
+add_subdirectory(pop3)
+add_subdirectory(smtp)
+
+if(NOT WIN32)
+ add_subdirectory(sendmail)
+endif(NOT WIN32) \ No newline at end of file
diff --git a/src/camel/providers/imapx/CMakeLists.txt b/src/camel/providers/imapx/CMakeLists.txt
new file mode 100644
index 000000000..5395f241c
--- /dev/null
+++ b/src/camel/providers/imapx/CMakeLists.txt
@@ -0,0 +1,93 @@
+set(SOURCES
+ camel-imapx-provider.c
+ camel-imapx-command.c
+ camel-imapx-command.h
+ camel-imapx-conn-manager.c
+ camel-imapx-conn-manager.h
+ camel-imapx-folder.c
+ camel-imapx-folder.h
+ camel-imapx-input-stream.c
+ camel-imapx-input-stream.h
+ camel-imapx-job.c
+ camel-imapx-job.h
+ camel-imapx-list-response.c
+ camel-imapx-list-response.h
+ camel-imapx-logger.c
+ camel-imapx-logger.h
+ camel-imapx-mailbox.c
+ camel-imapx-mailbox.h
+ camel-imapx-namespace.c
+ camel-imapx-namespace.h
+ camel-imapx-namespace-response.c
+ camel-imapx-namespace-response.h
+ camel-imapx-search.c
+ camel-imapx-search.h
+ camel-imapx-server.c
+ camel-imapx-server.h
+ camel-imapx-settings.c
+ camel-imapx-settings.h
+ camel-imapx-status-response.c
+ camel-imapx-status-response.h
+ camel-imapx-store.c
+ camel-imapx-store.h
+ camel-imapx-store-summary.c
+ camel-imapx-store-summary.h
+ camel-imapx-summary.c
+ camel-imapx-summary.h
+ camel-imapx-tokenise.h
+ camel-imapx-utils.c
+ camel-imapx-utils.h
+)
+
+set(DEPENDENCIES
+ camel
+)
+
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/camel-imapx-tokenise.h
+ COMMAND ${GPERF} -H imapx_hash -N imapx_tokenise_struct -L ANSI-C -o -t -k1,$$ ${CMAKE_CURRENT_SOURCE_DIR}/camel-imapx-tokens.txt --output-file=${CMAKE_CURRENT_BINARY_DIR}/camel-imapx-tokenise.h
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/camel-imapx-tokens.txt
+)
+
+add_library(camelimapx MODULE ${SOURCES})
+
+add_dependencies(camelimapx
+ ${DEPENDENCIES}
+)
+
+target_compile_definitions(camelimapx PRIVATE
+ -DG_LOG_DOMAIN=\"camel-imapx-provider\"
+)
+
+target_compile_options(camelimapx PUBLIC
+ ${CAMEL_CFLAGS}
+ ${GIO_UNIX_CFLAGS}
+ ${CALENDAR_CFLAGS}
+)
+
+target_include_directories(camelimapx PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/camel
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_SOURCE_DIR}/src/camel
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CAMEL_INCLUDE_DIRS}
+ ${GIO_UNIX_INCLUDE_DIRS}
+ ${CALENDAR_INCLUDE_DIRS}
+)
+
+target_link_libraries(camelimapx
+ ${DEPENDENCIES}
+ ${CAMEL_LDFLAGS}
+ ${GIO_UNIX_LDFLAGS}
+ ${CALENDAR_LDFLAGS}
+)
+
+install(TARGETS camelimapx
+ DESTINATION ${camel_providerdir}
+)
+
+install(FILES libcamelimapx.urls
+ DESTINATION ${camel_providerdir}
+)
diff --git a/src/camel/providers/imapx/camel-imapx-command.c b/src/camel/providers/imapx/camel-imapx-command.c
new file mode 100644
index 000000000..cf54416c7
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-command.c
@@ -0,0 +1,465 @@
+/*
+ * camel-imapx-command.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-imapx-job.h"
+#include "camel-imapx-server.h"
+#include "camel-imapx-store.h"
+
+#include "camel-imapx-command.h"
+
+#define c(...) camel_imapx_debug(command, __VA_ARGS__)
+
+typedef struct _CamelIMAPXRealCommand CamelIMAPXRealCommand;
+
+/* CamelIMAPXCommand + some private bits */
+struct _CamelIMAPXRealCommand {
+ CamelIMAPXCommand public;
+
+ volatile gint ref_count;
+
+ /* For building the part. */
+ GString *buffer;
+
+ /* For network/parse errors. */
+ GError *error;
+};
+
+/* Safe to cast to a GQueue. */
+struct _CamelIMAPXCommandQueue {
+ GQueue g_queue;
+};
+
+CamelIMAPXCommand *
+camel_imapx_command_new (CamelIMAPXServer *is,
+ guint32 job_kind,
+ const gchar *format,
+ ...)
+{
+ CamelIMAPXRealCommand *real_ic;
+ static gint tag = 0;
+ va_list ap;
+
+ real_ic = g_slice_new0 (CamelIMAPXRealCommand);
+
+ /* Initialize private bits. */
+ real_ic->ref_count = 1;
+ real_ic->buffer = g_string_sized_new (512);
+
+ /* Initialize public bits. */
+ real_ic->public.is = is;
+ real_ic->public.tag = tag++;
+ real_ic->public.job_kind = job_kind;
+ real_ic->public.status = NULL;
+ real_ic->public.completed = FALSE;
+ g_queue_init (&real_ic->public.parts);
+
+ if (format != NULL && *format != '\0') {
+ va_start (ap, format);
+ camel_imapx_command_addv (
+ (CamelIMAPXCommand *) real_ic, format, ap);
+ va_end (ap);
+ }
+
+ return (CamelIMAPXCommand *) real_ic;
+}
+
+CamelIMAPXCommand *
+camel_imapx_command_ref (CamelIMAPXCommand *ic)
+{
+ CamelIMAPXRealCommand *real_ic;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_COMMAND (ic), NULL);
+
+ real_ic = (CamelIMAPXRealCommand *) ic;
+
+ g_atomic_int_inc (&real_ic->ref_count);
+
+ return ic;
+}
+
+void
+camel_imapx_command_unref (CamelIMAPXCommand *ic)
+{
+ CamelIMAPXRealCommand *real_ic;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_COMMAND (ic));
+
+ real_ic = (CamelIMAPXRealCommand *) ic;
+
+ if (g_atomic_int_dec_and_test (&real_ic->ref_count)) {
+ CamelIMAPXCommandPart *cp;
+
+ /* Free the public stuff. */
+
+ imapx_free_status (ic->status);
+
+ while ((cp = g_queue_pop_head (&ic->parts)) != NULL) {
+ g_free (cp->data);
+ if (cp->ob) {
+ switch (cp->type & CAMEL_IMAPX_COMMAND_MASK) {
+ case CAMEL_IMAPX_COMMAND_FILE:
+ case CAMEL_IMAPX_COMMAND_STRING:
+ g_free (cp->ob);
+ break;
+ default:
+ g_object_unref (cp->ob);
+ }
+ }
+ g_free (cp);
+ }
+
+ /* Free the private stuff. */
+
+ g_string_free (real_ic->buffer, TRUE);
+
+ g_clear_error (&real_ic->error);
+
+ /* Fill the memory with a bit pattern before releasing
+ * it back to the slab allocator, so we can more easily
+ * identify dangling CamelIMAPXCommand pointers. */
+ memset (real_ic, 0xaa, sizeof (CamelIMAPXRealCommand));
+
+ /* But leave the reference count set to zero, so
+ * CAMEL_IS_IMAPX_COMMAND can identify it as bad. */
+ real_ic->ref_count = 0;
+
+ g_slice_free (CamelIMAPXRealCommand, real_ic);
+ }
+}
+
+gboolean
+camel_imapx_command_check (CamelIMAPXCommand *ic)
+{
+ CamelIMAPXRealCommand *real_ic;
+
+ real_ic = (CamelIMAPXRealCommand *) ic;
+
+ return (real_ic != NULL && real_ic->ref_count > 0);
+}
+
+void
+camel_imapx_command_add (CamelIMAPXCommand *ic,
+ const gchar *format,
+ ...)
+{
+ va_list ap;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_COMMAND (ic));
+
+ if (format != NULL && *format != '\0') {
+ va_start (ap, format);
+ camel_imapx_command_addv (ic, format, ap);
+ va_end (ap);
+ }
+}
+
+void
+camel_imapx_command_addv (CamelIMAPXCommand *ic,
+ const gchar *format,
+ va_list ap)
+{
+ const gchar *p, *ps, *start;
+ guchar c;
+ guint width;
+ gchar ch;
+ gint llong;
+ gchar *s;
+ gchar *P;
+ gint d;
+ glong l;
+ guint32 f;
+ CamelFlag *F;
+ CamelDataWrapper *D;
+ CamelSasl *A;
+ gchar literal_format[16];
+ CamelIMAPXMailbox *mailbox;
+ GString *buffer;
+ gchar *utf7_name = NULL;
+ const gchar *name;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_COMMAND (ic));
+
+ c (camel_imapx_server_get_tagprefix (ic->is), "adding command, format = '%s'\n", format);
+
+ buffer = ((CamelIMAPXRealCommand *) ic)->buffer;
+
+ p = format;
+ ps = format;
+ while ((c = *p++) != '\0') {
+ switch (c) {
+ case '%':
+ if (*p == '%') {
+ g_string_append_len (buffer, ps, p - ps);
+ p++;
+ ps = p;
+ continue;
+ }
+
+ g_string_append_len (buffer, ps, p - ps - 1);
+ start = p - 1;
+ width = 0;
+ llong = 0;
+
+ do {
+ c = *p++;
+ if (c == '0')
+ ;
+ else if ( c== '-')
+ ;
+ else
+ break;
+ } while (c);
+
+ do {
+ if (g_ascii_isdigit (c))
+ width = width * 10 + (c - '0');
+ else
+ break;
+ } while ((c = *p++));
+
+ while (c == 'l') {
+ llong++;
+ c = *p++;
+ }
+
+ switch (c) {
+ case 'A': /* auth object - sasl auth, treat as special kind of continuation */
+ A = va_arg (ap, CamelSasl *);
+ camel_imapx_command_add_part (ic, CAMEL_IMAPX_COMMAND_AUTH, A);
+ break;
+ case 'D': /* datawrapper */
+ D = va_arg (ap, CamelDataWrapper *);
+ c (camel_imapx_server_get_tagprefix (ic->is), "got data wrapper '%p'\n", D);
+ camel_imapx_command_add_part (ic, CAMEL_IMAPX_COMMAND_DATAWRAPPER, D);
+ break;
+ case 'P': /* filename path */
+ P = va_arg (ap, gchar *);
+ c (camel_imapx_server_get_tagprefix (ic->is), "got file path '%s'\n", P);
+ camel_imapx_command_add_part (ic, CAMEL_IMAPX_COMMAND_FILE, P);
+ break;
+ case 't': /* token */
+ s = va_arg (ap, gchar *);
+ g_string_append (buffer, s);
+ break;
+ case 's': /* simple string */
+ s = va_arg (ap, gchar *);
+ c (camel_imapx_server_get_tagprefix (ic->is), "got string '%s'\n", g_str_has_prefix (format, "LOGIN") ? "***" : s);
+ output_string:
+ if (s && *s) {
+ guchar mask = imapx_is_mask (s);
+
+ if (mask & IMAPX_TYPE_ATOM_CHAR)
+ g_string_append (buffer, s);
+ else if (mask & IMAPX_TYPE_TEXT_CHAR) {
+ g_string_append_c (buffer, '"');
+ while (*s) {
+ gchar *start = s;
+
+ while (*s && imapx_is_quoted_char (*s))
+ s++;
+ g_string_append_len (buffer, start, s - start);
+ if (*s) {
+ g_string_append_c (buffer, '\\');
+ g_string_append_c (buffer, *s);
+ s++;
+ }
+ }
+ g_string_append_c (buffer, '"');
+ } else {
+ camel_imapx_command_add_part (ic, CAMEL_IMAPX_COMMAND_STRING, s);
+ }
+ } else {
+ g_string_append (buffer, "\"\"");
+ }
+ g_free (utf7_name);
+ utf7_name = NULL;
+ break;
+ case 'M': /* CamelIMAPXMailbox */
+ mailbox = va_arg (ap, CamelIMAPXMailbox *);
+ name = camel_imapx_mailbox_get_name (mailbox);
+ utf7_name = camel_utf8_utf7 (name);
+ s = utf7_name;
+ goto output_string;
+ case 'm': /* UTF-8 mailbox name */
+ name = va_arg (ap, gchar *);
+ utf7_name = camel_utf8_utf7 (name);
+ s = utf7_name;
+ goto output_string;
+ case 'F': /* IMAP flags set */
+ f = va_arg (ap, guint32);
+ F = va_arg (ap, CamelFlag *);
+ imapx_write_flags (buffer, f, F);
+ break;
+ case 'c':
+ d = va_arg (ap, gint);
+ ch = d;
+ g_string_append_c (buffer, ch);
+ break;
+ case 'd': /* int/unsigned */
+ case 'u':
+ if (llong == 1) {
+ l = va_arg (ap, glong);
+ c (camel_imapx_server_get_tagprefix (ic->is), "got glong '%d'\n", (gint) l);
+ memcpy (literal_format, start, p - start);
+ literal_format[p - start] = 0;
+ g_string_append_printf (buffer, literal_format, l);
+ } else if (llong == 2) {
+ guint64 i64 = va_arg (ap, guint64);
+ c (camel_imapx_server_get_tagprefix (ic->is), "got guint64 '%d'\n", (gint) i64);
+ memcpy (literal_format, start, p - start);
+ literal_format[p - start] = 0;
+ g_string_append_printf (buffer, literal_format, i64);
+ } else {
+ d = va_arg (ap, gint);
+ c (camel_imapx_server_get_tagprefix (ic->is), "got gint '%d'\n", d);
+ memcpy (literal_format, start, p - start);
+ literal_format[p - start] = 0;
+ g_string_append_printf (buffer, literal_format, d);
+ }
+ break;
+ }
+
+ ps = p;
+ break;
+
+ case '\\': /* only for \\ really, we dont support \n\r etc at all */
+ c = *p;
+ if (c) {
+ g_warn_if_fail (c == '\\');
+ g_string_append_len (buffer, ps, p - ps);
+ p++;
+ ps = p;
+ }
+ }
+ }
+
+ g_string_append_len (buffer, ps, p - ps - 1);
+}
+
+void
+camel_imapx_command_add_part (CamelIMAPXCommand *ic,
+ CamelIMAPXCommandPartType type,
+ gpointer data)
+{
+ CamelIMAPXCommandPart *cp;
+ GString *buffer;
+ guint ob_size = 0;
+
+ buffer = ((CamelIMAPXRealCommand *) ic)->buffer;
+
+ /* TODO: literal+? */
+
+ switch (type & CAMEL_IMAPX_COMMAND_MASK) {
+ case CAMEL_IMAPX_COMMAND_DATAWRAPPER: {
+ CamelObject *ob = data;
+ GOutputStream *stream;
+
+ stream = camel_null_output_stream_new ();
+ camel_data_wrapper_write_to_output_stream_sync (
+ CAMEL_DATA_WRAPPER (ob), stream, NULL, NULL);
+ type |= CAMEL_IMAPX_COMMAND_LITERAL_PLUS;
+ g_object_ref (ob);
+ ob_size = camel_null_output_stream_get_bytes_written (
+ CAMEL_NULL_OUTPUT_STREAM (stream));
+ g_object_unref (stream);
+ break;
+ }
+ case CAMEL_IMAPX_COMMAND_AUTH: {
+ CamelObject *ob = data;
+ const gchar *mechanism;
+
+ /* we presume we'll need to get additional data only if we're not authenticated yet */
+ g_object_ref (ob);
+ mechanism = camel_sasl_get_mechanism (CAMEL_SASL (ob));
+ if (g_strcmp0 (mechanism, "Google") == 0)
+ mechanism = "XOAUTH2";
+ g_string_append (buffer, mechanism);
+ if (!camel_sasl_get_authenticated ((CamelSasl *) ob))
+ type |= CAMEL_IMAPX_COMMAND_CONTINUATION;
+ break;
+ }
+ case CAMEL_IMAPX_COMMAND_FILE: {
+ gchar *path = data;
+ struct stat st;
+
+ if (g_stat (path, &st) == 0) {
+ data = g_strdup (data);
+ ob_size = st.st_size;
+ } else
+ data = NULL;
+
+ type |= CAMEL_IMAPX_COMMAND_LITERAL_PLUS;
+ break;
+ }
+ case CAMEL_IMAPX_COMMAND_STRING:
+ data = g_strdup (data);
+ ob_size = strlen (data);
+ type |= CAMEL_IMAPX_COMMAND_LITERAL_PLUS;
+ break;
+ default:
+ ob_size = 0;
+ }
+
+ if (type & CAMEL_IMAPX_COMMAND_LITERAL_PLUS) {
+ g_string_append_c (buffer, '{');
+ g_string_append_printf (buffer, "%u", ob_size);
+ if (camel_imapx_server_have_capability (ic->is, IMAPX_CAPABILITY_LITERALPLUS)) {
+ g_string_append_c (buffer, '+');
+ } else {
+ type &= ~CAMEL_IMAPX_COMMAND_LITERAL_PLUS;
+ type |= CAMEL_IMAPX_COMMAND_CONTINUATION;
+ }
+ g_string_append_c (buffer, '}');
+ }
+
+ cp = g_malloc0 (sizeof (*cp));
+ cp->type = type;
+ cp->ob_size = ob_size;
+ cp->ob = data;
+ cp->data_size = buffer->len;
+ cp->data = g_strdup (buffer->str);
+
+ g_string_set_size (buffer, 0);
+
+ g_queue_push_tail (&ic->parts, cp);
+}
+
+void
+camel_imapx_command_close (CamelIMAPXCommand *ic)
+{
+ GString *buffer;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_COMMAND (ic));
+
+ buffer = ((CamelIMAPXRealCommand *) ic)->buffer;
+
+ if (buffer->len > 5 && g_ascii_strncasecmp (buffer->str, "LOGIN", 5) == 0) {
+ c (camel_imapx_server_get_tagprefix (ic->is), "completing command buffer is [%d] 'LOGIN...'\n", (gint) buffer->len);
+ } else {
+ c (camel_imapx_server_get_tagprefix (ic->is), "completing command buffer is [%d] '%.*s'\n", (gint) buffer->len, (gint) buffer->len, buffer->str);
+ }
+ if (buffer->len > 0)
+ camel_imapx_command_add_part (ic, CAMEL_IMAPX_COMMAND_SIMPLE, NULL);
+
+ g_string_set_size (buffer, 0);
+}
diff --git a/src/camel/providers/imapx/camel-imapx-command.h b/src/camel/providers/imapx/camel-imapx-command.h
new file mode 100644
index 000000000..836ac45da
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-command.h
@@ -0,0 +1,102 @@
+/*
+ * camel-imapx-command.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_COMMAND_H
+#define CAMEL_IMAPX_COMMAND_H
+
+#include "camel-imapx-utils.h"
+
+#define CAMEL_IS_IMAPX_COMMAND(command) \
+ (camel_imapx_command_check (command))
+
+G_BEGIN_DECLS
+
+/* Avoid a circular reference. */
+struct _CamelIMAPXServer;
+
+typedef struct _CamelIMAPXCommand CamelIMAPXCommand;
+typedef struct _CamelIMAPXCommandPart CamelIMAPXCommandPart;
+
+typedef void (*CamelIMAPXCommandFunc) (struct _CamelIMAPXServer *is,
+ CamelIMAPXCommand *ic);
+
+typedef enum {
+ CAMEL_IMAPX_COMMAND_SIMPLE = 0,
+ CAMEL_IMAPX_COMMAND_DATAWRAPPER,
+ CAMEL_IMAPX_COMMAND_AUTH,
+ CAMEL_IMAPX_COMMAND_FILE,
+ CAMEL_IMAPX_COMMAND_STRING,
+ CAMEL_IMAPX_COMMAND_MASK = 0xff,
+
+ /* Continuation with LITERAL+ */
+ CAMEL_IMAPX_COMMAND_LITERAL_PLUS = 1 << 14,
+
+ /* Does this command expect continuation? */
+ CAMEL_IMAPX_COMMAND_CONTINUATION = 1 << 15
+
+} CamelIMAPXCommandPartType;
+
+struct _CamelIMAPXCommandPart {
+ gint data_size;
+ gchar *data;
+
+ CamelIMAPXCommandPartType type;
+
+ gint ob_size;
+ gpointer ob;
+};
+
+struct _CamelIMAPXCommand {
+ struct _CamelIMAPXServer *is;
+ gint pri;
+
+ guint32 job_kind; /* CamelIMAPXJobKind */
+
+ /* Status for command. */
+ struct _status_info *status;
+
+ guint32 tag;
+ gboolean completed;
+
+ GQueue parts;
+ GList *current_part;
+};
+
+CamelIMAPXCommand *
+ camel_imapx_command_new (struct _CamelIMAPXServer *is,
+ guint32 job_kind,
+ const gchar *format,
+ ...);
+CamelIMAPXCommand *
+ camel_imapx_command_ref (CamelIMAPXCommand *ic);
+void camel_imapx_command_unref (CamelIMAPXCommand *ic);
+gboolean camel_imapx_command_check (CamelIMAPXCommand *ic);
+void camel_imapx_command_add (CamelIMAPXCommand *ic,
+ const gchar *format,
+ ...);
+void camel_imapx_command_addv (CamelIMAPXCommand *ic,
+ const gchar *format,
+ va_list ap);
+void camel_imapx_command_add_part (CamelIMAPXCommand *ic,
+ CamelIMAPXCommandPartType type,
+ gpointer data);
+void camel_imapx_command_close (CamelIMAPXCommand *ic);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_COMMAND_H */
+
diff --git a/src/camel/providers/imapx/camel-imapx-conn-manager.c b/src/camel/providers/imapx/camel-imapx-conn-manager.c
new file mode 100644
index 000000000..983c69b66
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-conn-manager.c
@@ -0,0 +1,2797 @@
+/*-*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-imap-conn-manager.h
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Chenthill Palanisamy <pchenthill@novell.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-imapx-conn-manager.h"
+#include "camel-imapx-folder.h"
+#include "camel-imapx-job.h"
+#include "camel-imapx-settings.h"
+#include "camel-imapx-store.h"
+#include "camel-imapx-utils.h"
+
+#define c(...) camel_imapx_debug(conman, __VA_ARGS__)
+
+#define CON_READ_LOCK(x) \
+ (g_rw_lock_reader_lock (&(x)->priv->rw_lock))
+#define CON_READ_UNLOCK(x) \
+ (g_rw_lock_reader_unlock (&(x)->priv->rw_lock))
+#define CON_WRITE_LOCK(x) \
+ (g_rw_lock_writer_lock (&(x)->priv->rw_lock))
+#define CON_WRITE_UNLOCK(x) \
+ (g_rw_lock_writer_unlock (&(x)->priv->rw_lock))
+
+#define JOB_QUEUE_LOCK(x) g_rec_mutex_lock (&(x)->priv->job_queue_lock)
+#define JOB_QUEUE_UNLOCK(x) g_rec_mutex_unlock (&(x)->priv->job_queue_lock)
+
+#define CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_IMAPX_CONN_MANAGER, CamelIMAPXConnManagerPrivate))
+
+typedef struct _ConnectionInfo ConnectionInfo;
+
+struct _CamelIMAPXConnManagerPrivate {
+ GList *connections; /* ConnectionInfo * */
+ GWeakRef store;
+ GRWLock rw_lock;
+ guint limit_max_connections;
+
+ GMutex pending_connections_lock;
+ GSList *pending_connections; /* GCancellable * */
+
+ gchar last_tagprefix;
+
+ GRecMutex job_queue_lock;
+ GSList *job_queue; /* CamelIMAPXJob * */
+
+ GMutex busy_connections_lock;
+ GCond busy_connections_cond;
+
+ GMutex busy_mailboxes_lock; /* used for both busy_mailboxes and idle_mailboxes */
+ GHashTable *busy_mailboxes; /* CamelIMAPXMailbox ~> gint */
+ GHashTable *idle_mailboxes; /* CamelIMAPXMailbox ~> gint */
+};
+
+struct _ConnectionInfo {
+ GMutex lock;
+ CamelIMAPXServer *is;
+ gboolean busy;
+ gulong refresh_mailbox_handler_id;
+ volatile gint ref_count;
+};
+
+enum {
+ PROP_0,
+ PROP_STORE
+};
+
+enum {
+ CONNECTION_CREATED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (
+ CamelIMAPXConnManager,
+ camel_imapx_conn_manager,
+ G_TYPE_OBJECT)
+
+static gboolean
+imapx_conn_manager_copy_message_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ CamelIMAPXMailbox *destination,
+ GPtrArray *uids,
+ gboolean delete_originals,
+ gboolean remove_deleted_flags,
+ gboolean skip_sync_changes,
+ GCancellable *cancellable,
+ GError **error);
+
+typedef struct _MailboxRefreshData {
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox;
+} MailboxRefreshData;
+
+static void
+mailbox_refresh_data_free (MailboxRefreshData *data)
+{
+ if (data) {
+ g_clear_object (&data->conn_man);
+ g_clear_object (&data->mailbox);
+ g_free (data);
+ }
+}
+
+static gpointer
+imapx_conn_manager_idle_mailbox_refresh_thread (gpointer user_data)
+{
+ MailboxRefreshData *data = user_data;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (data != NULL, NULL);
+
+ /* passing NULL cancellable means to use only the job's abort cancellable */
+ if (!camel_imapx_conn_manager_refresh_info_sync (data->conn_man, data->mailbox, NULL, &local_error)) {
+ c ('*', "%s: Failed to refresh mailbox '%s': %s\n", G_STRFUNC,
+ camel_imapx_mailbox_get_name (data->mailbox),
+ local_error ? local_error->message : "Unknown error");
+ }
+
+ mailbox_refresh_data_free (data);
+ g_clear_error (&local_error);
+
+ return NULL;
+}
+
+static void
+imapx_conn_manager_refresh_mailbox_cb (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ CamelIMAPXConnManager *conn_man)
+{
+ MailboxRefreshData *data;
+ GThread *thread;
+ GError *local_error = NULL;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_SERVER (is));
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+
+ data = g_new0 (MailboxRefreshData, 1);
+ data->conn_man = g_object_ref (conn_man);
+ data->mailbox = g_object_ref (mailbox);
+
+ thread = g_thread_try_new (NULL, imapx_conn_manager_idle_mailbox_refresh_thread, data, &local_error);
+ if (!thread) {
+ g_warning ("%s: Failed to create IDLE mailbox refresh thread: %s", G_STRFUNC, local_error ? local_error->message : "Unknown error");
+ mailbox_refresh_data_free (data);
+ } else {
+ g_thread_unref (thread);
+ }
+
+ g_clear_error (&local_error);
+}
+
+static ConnectionInfo *
+connection_info_new (CamelIMAPXServer *is)
+{
+ ConnectionInfo *cinfo;
+
+ cinfo = g_slice_new0 (ConnectionInfo);
+ g_mutex_init (&cinfo->lock);
+ cinfo->is = g_object_ref (is);
+ cinfo->ref_count = 1;
+
+ return cinfo;
+}
+
+static ConnectionInfo *
+connection_info_ref (ConnectionInfo *cinfo)
+{
+ g_return_val_if_fail (cinfo != NULL, NULL);
+ g_return_val_if_fail (cinfo->ref_count > 0, NULL);
+
+ g_atomic_int_inc (&cinfo->ref_count);
+
+ return cinfo;
+}
+
+static void
+connection_info_unref (ConnectionInfo *cinfo)
+{
+ g_return_if_fail (cinfo != NULL);
+ g_return_if_fail (cinfo->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test (&cinfo->ref_count)) {
+ if (cinfo->refresh_mailbox_handler_id)
+ g_signal_handler_disconnect (cinfo->is, cinfo->refresh_mailbox_handler_id);
+
+ g_mutex_clear (&cinfo->lock);
+ g_object_unref (cinfo->is);
+
+ g_slice_free (ConnectionInfo, cinfo);
+ }
+}
+
+static gboolean
+connection_info_try_reserve (ConnectionInfo *cinfo)
+{
+ gboolean reserved = FALSE;
+
+ g_return_val_if_fail (cinfo != NULL, FALSE);
+
+ g_mutex_lock (&cinfo->lock);
+
+ if (!cinfo->busy) {
+ cinfo->busy = TRUE;
+ reserved = TRUE;
+ }
+
+ g_mutex_unlock (&cinfo->lock);
+
+ return reserved;
+}
+
+static gboolean
+connection_info_get_busy (ConnectionInfo *cinfo)
+{
+ gboolean busy;
+
+ g_return_val_if_fail (cinfo != NULL, FALSE);
+
+ g_mutex_lock (&cinfo->lock);
+
+ busy = cinfo->busy;
+
+ g_mutex_unlock (&cinfo->lock);
+
+ return busy;
+}
+
+static void
+connection_info_set_busy (ConnectionInfo *cinfo,
+ gboolean busy)
+{
+ g_return_if_fail (cinfo != NULL);
+
+ g_mutex_lock (&cinfo->lock);
+
+ cinfo->busy = busy;
+
+ g_mutex_unlock (&cinfo->lock);
+}
+
+static void
+imapx_conn_manager_signal_busy_connections (CamelIMAPXConnManager *conn_man)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+
+ g_mutex_lock (&conn_man->priv->busy_connections_lock);
+ g_cond_broadcast (&conn_man->priv->busy_connections_cond);
+ g_mutex_unlock (&conn_man->priv->busy_connections_lock);
+}
+
+static void
+imapx_conn_manager_unmark_busy (CamelIMAPXConnManager *conn_man,
+ ConnectionInfo *cinfo)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+ g_return_if_fail (cinfo != NULL);
+ g_return_if_fail (connection_info_get_busy (cinfo));
+
+ connection_info_set_busy (cinfo, FALSE);
+
+ imapx_conn_manager_signal_busy_connections (conn_man);
+}
+
+static gboolean
+imapx_conn_manager_remove_info (CamelIMAPXConnManager *conn_man,
+ ConnectionInfo *cinfo)
+{
+ GList *list, *link;
+ gboolean removed = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+ g_return_val_if_fail (cinfo != NULL, FALSE);
+
+ CON_WRITE_LOCK (conn_man);
+
+ list = conn_man->priv->connections;
+ link = g_list_find (list, cinfo);
+
+ if (link != NULL) {
+ list = g_list_delete_link (list, link);
+ connection_info_unref (cinfo);
+ removed = TRUE;
+ }
+
+ conn_man->priv->connections = list;
+
+ CON_WRITE_UNLOCK (conn_man);
+
+ if (removed)
+ imapx_conn_manager_signal_busy_connections (conn_man);
+
+ return removed;
+}
+
+static void
+imapx_conn_manager_cancel_pending_connections (CamelIMAPXConnManager *conn_man)
+{
+ GSList *link;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+
+ g_mutex_lock (&conn_man->priv->pending_connections_lock);
+ for (link = conn_man->priv->pending_connections; link; link = g_slist_next (link)) {
+ GCancellable *cancellable = link->data;
+
+ if (cancellable)
+ g_cancellable_cancel (cancellable);
+ }
+ g_mutex_unlock (&conn_man->priv->pending_connections_lock);
+}
+
+static void
+imapx_conn_manager_abort_jobs (CamelIMAPXConnManager *conn_man)
+{
+ GSList *link;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+
+ JOB_QUEUE_LOCK (conn_man);
+
+ for (link = conn_man->priv->job_queue; link; link = g_slist_next (link)) {
+ CamelIMAPXJob *job = link->data;
+
+ if (job)
+ camel_imapx_job_abort (job);
+ }
+
+ JOB_QUEUE_UNLOCK (conn_man);
+}
+
+static CamelFolder *
+imapx_conn_manager_ref_folder_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXStore *store;
+ CamelFolder *folder;
+ gchar *folder_path;
+
+ store = camel_imapx_conn_manager_ref_store (conn_man);
+ folder_path = camel_imapx_mailbox_dup_folder_path (mailbox);
+
+ folder = camel_store_get_folder_sync (CAMEL_STORE (store), folder_path, 0, cancellable, NULL);
+ if (folder)
+ camel_imapx_folder_set_mailbox (CAMEL_IMAPX_FOLDER (folder), mailbox);
+
+ g_free (folder_path);
+ g_clear_object (&store);
+
+ return folder;
+}
+
+static void
+imapx_conn_manager_inc_mailbox_hash (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GHashTable *mailboxes_hash)
+{
+ gint count;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+ g_return_if_fail (mailboxes_hash != NULL);
+
+ g_mutex_lock (&conn_man->priv->busy_mailboxes_lock);
+
+ count = GPOINTER_TO_INT (g_hash_table_lookup (mailboxes_hash, mailbox));
+ count++;
+
+ g_hash_table_insert (mailboxes_hash, g_object_ref (mailbox), GINT_TO_POINTER (count));
+
+ g_mutex_unlock (&conn_man->priv->busy_mailboxes_lock);
+}
+
+static void
+imapx_conn_manager_dec_mailbox_hash (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GHashTable *mailboxes_hash)
+{
+ gint count;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+ g_return_if_fail (mailboxes_hash != NULL);
+
+ g_mutex_lock (&conn_man->priv->busy_mailboxes_lock);
+
+ count = GPOINTER_TO_INT (g_hash_table_lookup (mailboxes_hash, mailbox));
+ if (!count) {
+ g_mutex_unlock (&conn_man->priv->busy_mailboxes_lock);
+ return;
+ }
+
+ count--;
+
+ if (count)
+ g_hash_table_insert (mailboxes_hash, g_object_ref (mailbox), GINT_TO_POINTER (count));
+ else
+ g_hash_table_remove (mailboxes_hash, mailbox);
+
+ g_mutex_unlock (&conn_man->priv->busy_mailboxes_lock);
+}
+
+static gboolean
+imapx_conn_manager_is_mailbox_hash (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GHashTable *mailboxes_hash)
+{
+ gint count;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+ g_return_val_if_fail (mailboxes_hash != NULL, FALSE);
+
+ g_mutex_lock (&conn_man->priv->busy_mailboxes_lock);
+
+ count = GPOINTER_TO_INT (g_hash_table_lookup (mailboxes_hash, mailbox));
+
+ g_mutex_unlock (&conn_man->priv->busy_mailboxes_lock);
+
+ return count > 0;
+}
+
+static void
+imapx_conn_manager_clear_mailboxes_hashes (CamelIMAPXConnManager *conn_man)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+
+ g_mutex_lock (&conn_man->priv->busy_mailboxes_lock);
+
+ g_hash_table_remove_all (conn_man->priv->busy_mailboxes);
+ g_hash_table_remove_all (conn_man->priv->idle_mailboxes);
+
+ g_mutex_unlock (&conn_man->priv->busy_mailboxes_lock);
+}
+
+static void
+imapx_conn_manager_inc_mailbox_busy (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ imapx_conn_manager_inc_mailbox_hash (conn_man, mailbox, conn_man->priv->busy_mailboxes);
+}
+
+static void
+imapx_conn_manager_dec_mailbox_busy (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ imapx_conn_manager_dec_mailbox_hash (conn_man, mailbox, conn_man->priv->busy_mailboxes);
+}
+
+static gboolean
+imapx_conn_manager_is_mailbox_busy (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ return imapx_conn_manager_is_mailbox_hash (conn_man, mailbox, conn_man->priv->busy_mailboxes);
+}
+
+static void
+imapx_conn_manager_inc_mailbox_idle (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ imapx_conn_manager_inc_mailbox_hash (conn_man, mailbox, conn_man->priv->idle_mailboxes);
+}
+
+static void
+imapx_conn_manager_dec_mailbox_idle (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ imapx_conn_manager_dec_mailbox_hash (conn_man, mailbox, conn_man->priv->idle_mailboxes);
+}
+
+static gboolean
+imapx_conn_manager_is_mailbox_idle (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ return imapx_conn_manager_is_mailbox_hash (conn_man, mailbox, conn_man->priv->idle_mailboxes);
+}
+
+static gboolean
+imapx_conn_manager_has_inbox_idle (CamelIMAPXConnManager *conn_man)
+{
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXMailbox *inbox_mailbox;
+ gboolean is_idle;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ imapx_store = camel_imapx_conn_manager_ref_store (conn_man);
+ inbox_mailbox = imapx_store ? camel_imapx_store_ref_mailbox (imapx_store, "INBOX") : NULL;
+
+ g_clear_object (&imapx_store);
+
+ is_idle = inbox_mailbox && imapx_conn_manager_is_mailbox_idle (conn_man, inbox_mailbox);
+
+ g_clear_object (&inbox_mailbox);
+
+ return is_idle;
+}
+
+static void
+imapx_conn_manager_set_store (CamelIMAPXConnManager *conn_man,
+ CamelStore *store)
+{
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ g_weak_ref_set (&conn_man->priv->store, store);
+}
+
+static void
+imapx_conn_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_STORE:
+ imapx_conn_manager_set_store (
+ CAMEL_IMAPX_CONN_MANAGER (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_conn_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_STORE:
+ g_value_take_object (
+ value,
+ camel_imapx_conn_manager_ref_store (
+ CAMEL_IMAPX_CONN_MANAGER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_conn_manager_dispose (GObject *object)
+{
+ CamelIMAPXConnManager *conn_man;
+
+ conn_man = CAMEL_IMAPX_CONN_MANAGER (object);
+
+ imapx_conn_manager_cancel_pending_connections (conn_man);
+ imapx_conn_manager_abort_jobs (conn_man);
+
+ g_list_free_full (
+ conn_man->priv->connections,
+ (GDestroyNotify) connection_info_unref);
+ conn_man->priv->connections = NULL;
+
+ g_weak_ref_set (&conn_man->priv->store, NULL);
+
+ g_mutex_lock (&conn_man->priv->busy_mailboxes_lock);
+ g_hash_table_remove_all (conn_man->priv->busy_mailboxes);
+ g_hash_table_remove_all (conn_man->priv->idle_mailboxes);
+ g_mutex_unlock (&conn_man->priv->busy_mailboxes_lock);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_imapx_conn_manager_parent_class)->dispose (object);
+}
+
+static void
+imapx_conn_manager_finalize (GObject *object)
+{
+ CamelIMAPXConnManagerPrivate *priv;
+
+ priv = CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE (object);
+
+ g_warn_if_fail (priv->pending_connections == NULL);
+ g_warn_if_fail (priv->job_queue == NULL);
+
+ g_rw_lock_clear (&priv->rw_lock);
+ g_rec_mutex_clear (&priv->job_queue_lock);
+ g_mutex_clear (&priv->pending_connections_lock);
+ g_mutex_clear (&priv->busy_connections_lock);
+ g_cond_clear (&priv->busy_connections_cond);
+ g_weak_ref_clear (&priv->store);
+ g_mutex_clear (&priv->busy_mailboxes_lock);
+ g_hash_table_destroy (priv->busy_mailboxes);
+ g_hash_table_destroy (priv->idle_mailboxes);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_imapx_conn_manager_parent_class)->finalize (object);
+}
+
+static void
+camel_imapx_conn_manager_class_init (CamelIMAPXConnManagerClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelIMAPXConnManagerPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = imapx_conn_manager_set_property;
+ object_class->get_property = imapx_conn_manager_get_property;
+ object_class->dispose = imapx_conn_manager_dispose;
+ object_class->finalize = imapx_conn_manager_finalize;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_STORE,
+ g_param_spec_object (
+ "store",
+ "Store",
+ "The CamelIMAPXStore to which we belong",
+ CAMEL_TYPE_IMAPX_STORE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ signals[CONNECTION_CREATED] = g_signal_new (
+ "connection-created",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CamelIMAPXConnManagerClass, connection_created),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ CAMEL_TYPE_IMAPX_SERVER);
+}
+
+static void
+camel_imapx_conn_manager_init (CamelIMAPXConnManager *conn_man)
+{
+ conn_man->priv = CAMEL_IMAPX_CONN_MANAGER_GET_PRIVATE (conn_man);
+
+ g_rw_lock_init (&conn_man->priv->rw_lock);
+ g_rec_mutex_init (&conn_man->priv->job_queue_lock);
+ g_mutex_init (&conn_man->priv->pending_connections_lock);
+ g_mutex_init (&conn_man->priv->busy_connections_lock);
+ g_cond_init (&conn_man->priv->busy_connections_cond);
+ g_weak_ref_init (&conn_man->priv->store, NULL);
+ g_mutex_init (&conn_man->priv->busy_mailboxes_lock);
+
+ conn_man->priv->last_tagprefix = 'A' - 1;
+ conn_man->priv->busy_mailboxes = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
+ conn_man->priv->idle_mailboxes = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
+}
+
+static gchar
+imapx_conn_manager_get_next_free_tagprefix_unlocked (CamelIMAPXConnManager *conn_man)
+{
+ gchar adept;
+ gint ii;
+ GList *iter;
+
+ adept = conn_man->priv->last_tagprefix + 1;
+
+ /* the 'Z' is dedicated to auth types query */
+ if (adept >= 'Z')
+ adept = 'A';
+ else if (adept < 'A')
+ adept = 'A';
+
+ for (ii = 0; ii < 26; ii++) {
+ for (iter = conn_man->priv->connections; iter; iter = g_list_next (iter)) {
+ ConnectionInfo *cinfo = iter->data;
+
+ if (!cinfo || !cinfo->is)
+ continue;
+
+ if (camel_imapx_server_get_tagprefix (cinfo->is) == adept)
+ break;
+ }
+
+ /* Read all current active connections and none has the same tag prefix */
+ if (!iter)
+ break;
+
+ adept++;
+ if (adept >= 'Z')
+ adept = 'A';
+ }
+
+ g_return_val_if_fail (adept >= 'A' && adept < 'Z', 'Z');
+
+ conn_man->priv->last_tagprefix = adept;
+
+ return adept;
+}
+
+static ConnectionInfo *
+imapx_create_new_connection_unlocked (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXServer *is = NULL;
+ CamelIMAPXStore *imapx_store;
+ ConnectionInfo *cinfo = NULL;
+ gboolean success;
+
+ /* Caller must be holding CON_WRITE_LOCK. */
+
+ /* Check if we got cancelled while we were waiting. */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return NULL;
+
+ imapx_store = camel_imapx_conn_manager_ref_store (conn_man);
+ g_return_val_if_fail (imapx_store != NULL, NULL);
+
+ is = camel_imapx_server_new (imapx_store);
+ camel_imapx_server_set_tagprefix (is, imapx_conn_manager_get_next_free_tagprefix_unlocked (conn_man));
+
+ g_signal_emit (conn_man, signals[CONNECTION_CREATED], 0, is);
+
+ /* XXX As part of the connect operation the CamelIMAPXServer will
+ * have to call camel_session_authenticate_sync(), but it has
+ * no way to pass itself through in that call so the service
+ * knows which CamelIMAPXServer is trying to authenticate.
+ *
+ * IMAPX is the only provider that does multiple connections
+ * like this, so I didn't want to pollute the CamelSession and
+ * CamelService authentication APIs with an extra argument.
+ * Instead we do this little hack so the service knows which
+ * CamelIMAPXServer to act on in its authenticate_sync() method.
+ *
+ * Because we're holding the CAMEL_SERVICE_REC_CONNECT_LOCK
+ * we should not have multiple IMAPX connections trying to
+ * authenticate at once, so this should be thread-safe.
+ */
+ camel_imapx_store_set_connecting_server (imapx_store, is, conn_man->priv->connections != NULL);
+ success = camel_imapx_server_connect_sync (is, cancellable, error);
+ camel_imapx_store_set_connecting_server (imapx_store, NULL, FALSE);
+
+ if (!success)
+ goto exit;
+
+ cinfo = connection_info_new (is);
+
+ cinfo->refresh_mailbox_handler_id = g_signal_connect (
+ is, "refresh-mailbox", G_CALLBACK (imapx_conn_manager_refresh_mailbox_cb), conn_man);
+
+ /* Takes ownership of the ConnectionInfo. */
+ conn_man->priv->connections = g_list_append (conn_man->priv->connections, cinfo);
+
+ c (camel_imapx_server_get_tagprefix (is), "Created new connection %p (server:%p) for %s; total connections %d\n",
+ cinfo, cinfo->is,
+ mailbox ? camel_imapx_mailbox_get_name (mailbox) : "[null]",
+ g_list_length (conn_man->priv->connections));
+
+exit:
+ g_object_unref (imapx_store);
+ g_clear_object (&is);
+
+ return cinfo;
+}
+
+static gint
+imapx_conn_manager_get_max_connections (CamelIMAPXConnManager *conn_man)
+{
+ CamelIMAPXStore *imapx_store;
+ CamelSettings *settings;
+ gint max_connections;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), -1);
+
+ imapx_store = camel_imapx_conn_manager_ref_store (conn_man);
+ if (!imapx_store)
+ return -1;
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (imapx_store));
+
+ max_connections = camel_imapx_settings_get_concurrent_connections (CAMEL_IMAPX_SETTINGS (settings));
+
+ if (conn_man->priv->limit_max_connections > 0 &&
+ conn_man->priv->limit_max_connections < max_connections)
+ max_connections = conn_man->priv->limit_max_connections;
+
+ g_object_unref (settings);
+ g_object_unref (imapx_store);
+
+ return max_connections > 0 ? max_connections : 1;
+}
+
+static void
+imapx_conn_manager_connection_wait_cancelled_cb (GCancellable *cancellable,
+ CamelIMAPXConnManager *conn_man)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+
+ imapx_conn_manager_signal_busy_connections (conn_man);
+}
+
+static ConnectionInfo *
+camel_imapx_conn_manager_ref_connection (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ gboolean *out_is_new_connection,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ConnectionInfo *cinfo = NULL;
+ CamelIMAPXStore *imapx_store;
+ CamelSession *session;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), NULL);
+
+ if (out_is_new_connection)
+ *out_is_new_connection = FALSE;
+
+ imapx_store = camel_imapx_conn_manager_ref_store (conn_man);
+ if (!imapx_store)
+ return NULL;
+
+ session = camel_service_ref_session (CAMEL_SERVICE (imapx_store));
+
+ if (camel_offline_store_get_online (CAMEL_OFFLINE_STORE (imapx_store)) &&
+ session && camel_session_get_online (session)) {
+
+ g_mutex_lock (&conn_man->priv->pending_connections_lock);
+ if (cancellable) {
+ g_object_ref (cancellable);
+ } else {
+ cancellable = g_cancellable_new ();
+ }
+ conn_man->priv->pending_connections = g_slist_prepend (conn_man->priv->pending_connections, cancellable);
+ g_mutex_unlock (&conn_man->priv->pending_connections_lock);
+
+ /* Hold the writer lock while we requisition a CamelIMAPXServer
+ * to prevent other threads from adding or removing connections. */
+ CON_READ_LOCK (conn_man);
+
+ /* Check if we've got cancelled while waiting for the lock. */
+ while (!cinfo && !g_cancellable_set_error_if_cancelled (cancellable, &local_error)) {
+ gint opened_connections, max_connections;
+ GList *link;
+
+ for (link = conn_man->priv->connections; link; link = g_list_next (link)) {
+ ConnectionInfo *candidate = link->data;
+
+ if (candidate && connection_info_try_reserve (candidate)) {
+ cinfo = connection_info_ref (candidate);
+ break;
+ }
+ }
+
+ if (cinfo)
+ break;
+
+ opened_connections = g_list_length (conn_man->priv->connections);
+ max_connections = imapx_conn_manager_get_max_connections (conn_man);
+
+ if (max_connections <= 0)
+ break;
+
+ if (!cinfo && opened_connections < max_connections) {
+ GError *local_error_2 = NULL;
+
+ CON_READ_UNLOCK (conn_man);
+ CON_WRITE_LOCK (conn_man);
+ cinfo = imapx_create_new_connection_unlocked (conn_man, mailbox, cancellable, &local_error_2);
+ if (cinfo)
+ connection_info_set_busy (cinfo, TRUE);
+ CON_WRITE_UNLOCK (conn_man);
+ CON_READ_LOCK (conn_man);
+
+ if (!cinfo) {
+ gboolean limit_connections =
+ g_error_matches (local_error_2, CAMEL_IMAPX_SERVER_ERROR,
+ CAMEL_IMAPX_SERVER_ERROR_CONCURRENT_CONNECT_FAILED) &&
+ conn_man->priv->connections;
+
+ c ('*', "Failed to open a new connection, while having %d opened, with error: %s; will limit connections: %s\n",
+ g_list_length (conn_man->priv->connections),
+ local_error_2 ? local_error_2->message : "Unknown error",
+ limit_connections ? "yes" : "no");
+
+ if (limit_connections) {
+ /* limit to one-less than current connection count - be nice to the server */
+ conn_man->priv->limit_max_connections = g_list_length (conn_man->priv->connections) - 1;
+ if (!conn_man->priv->limit_max_connections)
+ conn_man->priv->limit_max_connections = 1;
+
+ g_clear_error (&local_error_2);
+ } else {
+ if (local_error_2)
+ g_propagate_error (&local_error, local_error_2);
+ break;
+ }
+ } else {
+ connection_info_ref (cinfo);
+
+ if (out_is_new_connection)
+ *out_is_new_connection = TRUE;
+ }
+ }
+
+ if (!cinfo) {
+ gulong handler_id;
+
+ CON_READ_UNLOCK (conn_man);
+
+ handler_id = g_cancellable_connect (cancellable, G_CALLBACK (imapx_conn_manager_connection_wait_cancelled_cb), conn_man, NULL);
+
+ g_mutex_lock (&conn_man->priv->busy_connections_lock);
+ g_cond_wait (&conn_man->priv->busy_connections_cond, &conn_man->priv->busy_connections_lock);
+ g_mutex_unlock (&conn_man->priv->busy_connections_lock);
+
+ if (handler_id)
+ g_cancellable_disconnect (cancellable, handler_id);
+
+ CON_READ_LOCK (conn_man);
+ }
+ }
+
+ CON_READ_UNLOCK (conn_man);
+
+ g_mutex_lock (&conn_man->priv->pending_connections_lock);
+ conn_man->priv->pending_connections = g_slist_remove (conn_man->priv->pending_connections, cancellable);
+ g_object_unref (cancellable);
+ g_mutex_unlock (&conn_man->priv->pending_connections_lock);
+ }
+
+ g_clear_object (&imapx_store);
+ g_clear_object (&session);
+
+ if (!cinfo && (!local_error || local_error->domain == G_RESOLVER_ERROR)) {
+ if (local_error) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation (%s)"),
+ local_error->message);
+
+ g_clear_error (&local_error);
+ } else {
+ g_set_error_literal (
+ &local_error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ }
+ }
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return cinfo;
+}
+
+/****************************/
+
+CamelIMAPXConnManager *
+camel_imapx_conn_manager_new (CamelStore *store)
+{
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+
+ return g_object_new (
+ CAMEL_TYPE_IMAPX_CONN_MANAGER, "store", store, NULL);
+}
+
+CamelIMAPXStore *
+camel_imapx_conn_manager_ref_store (CamelIMAPXConnManager *conn_man)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), NULL);
+
+ return g_weak_ref_get (&conn_man->priv->store);
+}
+
+gboolean
+camel_imapx_conn_manager_connect_sync (CamelIMAPXConnManager *conn_man,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ConnectionInfo *cinfo;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ CON_READ_LOCK (conn_man);
+ if (conn_man->priv->connections) {
+ CON_READ_UNLOCK (conn_man);
+ return TRUE;
+ }
+ CON_READ_UNLOCK (conn_man);
+
+ imapx_conn_manager_clear_mailboxes_hashes (conn_man);
+
+ cinfo = camel_imapx_conn_manager_ref_connection (conn_man, NULL, NULL, cancellable, error);
+ if (cinfo) {
+ imapx_conn_manager_unmark_busy (conn_man, cinfo);
+ connection_info_unref (cinfo);
+ }
+
+ return cinfo != NULL;
+}
+
+gboolean
+camel_imapx_conn_manager_disconnect_sync (CamelIMAPXConnManager *conn_man,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GList *link, *connections;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ /* Do this before acquiring the write lock, because any pending
+ connection holds the write lock, thus makes this request starve. */
+ imapx_conn_manager_cancel_pending_connections (conn_man);
+ imapx_conn_manager_abort_jobs (conn_man);
+
+ CON_WRITE_LOCK (conn_man);
+
+ c ('*', "Disconnecting all %d connections\n", g_list_length (conn_man->priv->connections));
+
+ connections = conn_man->priv->connections;
+ conn_man->priv->connections = NULL;
+
+ CON_WRITE_UNLOCK (conn_man);
+
+ for (link = connections; link; link = g_list_next (link)) {
+ ConnectionInfo *cinfo = link->data;
+ GError *local_error = NULL;
+
+ if (!cinfo)
+ continue;
+
+ if (!camel_imapx_server_disconnect_sync (cinfo->is, cancellable, &local_error)) {
+ c (camel_imapx_server_get_tagprefix (cinfo->is), " Failed to disconnect from the server: %s\n",
+ local_error ? local_error->message : "Unknown error");
+ }
+
+ connection_info_unref (cinfo);
+ g_clear_error (&local_error);
+ }
+
+ g_list_free (connections);
+
+ imapx_conn_manager_clear_mailboxes_hashes (conn_man);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_conn_manager_should_wait_for (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXJob *new_job,
+ CamelIMAPXJob *queued_job)
+{
+ guint32 job_kind;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+ g_return_val_if_fail (queued_job != NULL, FALSE);
+
+ if (camel_imapx_job_get_kind (new_job) == CAMEL_IMAPX_JOB_GET_MESSAGE)
+ return FALSE;
+
+ job_kind = camel_imapx_job_get_kind (queued_job);
+
+ /* List jobs with high priority. */
+ return job_kind == CAMEL_IMAPX_JOB_GET_MESSAGE ||
+ job_kind == CAMEL_IMAPX_JOB_COPY_MESSAGE ||
+ job_kind == CAMEL_IMAPX_JOB_MOVE_MESSAGE ||
+ job_kind == CAMEL_IMAPX_JOB_EXPUNGE;
+}
+
+gboolean
+camel_imapx_conn_manager_run_job_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXJob *job,
+ CamelIMAPXJobMatchesFunc finish_before_job,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GSList *link;
+ ConnectionInfo *cinfo;
+ gboolean success = FALSE, is_new_connection = FALSE;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+ g_return_val_if_fail (job != NULL, FALSE);
+
+ JOB_QUEUE_LOCK (conn_man);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ JOB_QUEUE_UNLOCK (conn_man);
+ return FALSE;
+ }
+
+ link = conn_man->priv->job_queue;
+ while (link) {
+ CamelIMAPXJob *queued_job = link->data;
+ gboolean matches;
+
+ g_warn_if_fail (queued_job != NULL);
+ g_warn_if_fail (queued_job != job);
+
+ if (!queued_job) {
+ link = g_slist_next (link);
+ continue;
+ }
+
+ matches = camel_imapx_job_matches (job, queued_job);
+ if (matches || (finish_before_job && finish_before_job (job, queued_job)) ||
+ imapx_conn_manager_should_wait_for (conn_man, job, queued_job)) {
+ camel_imapx_job_ref (queued_job);
+
+ JOB_QUEUE_UNLOCK (conn_man);
+
+ camel_imapx_job_wait_sync (queued_job, cancellable);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ camel_imapx_job_unref (queued_job);
+ return FALSE;
+ }
+
+ if (matches) {
+ gpointer result = NULL;
+ GDestroyNotify destroy_result = NULL;
+
+ /* Do not inherit cancelled errors, just try again */
+ if (!camel_imapx_job_was_cancelled (queued_job) &&
+ camel_imapx_job_copy_result (queued_job, &success, &result, &local_error, &destroy_result)) {
+ camel_imapx_job_set_result (job, success, result, local_error, destroy_result);
+ camel_imapx_job_unref (queued_job);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+ }
+ }
+
+ JOB_QUEUE_LOCK (conn_man);
+
+ camel_imapx_job_unref (queued_job);
+
+ /* The queue could change, start from the beginning. */
+ link = conn_man->priv->job_queue;
+ } else {
+ link = g_slist_next (link);
+ }
+ }
+
+ conn_man->priv->job_queue = g_slist_prepend (conn_man->priv->job_queue, job);
+
+ JOB_QUEUE_UNLOCK (conn_man);
+
+ do {
+ g_clear_error (&local_error);
+
+ cinfo = camel_imapx_conn_manager_ref_connection (conn_man, camel_imapx_job_get_mailbox (job), &is_new_connection, cancellable, error);
+ if (cinfo) {
+ CamelIMAPXMailbox *job_mailbox;
+
+ job_mailbox = camel_imapx_job_get_mailbox (job);
+
+ if (job_mailbox)
+ imapx_conn_manager_inc_mailbox_busy (conn_man, job_mailbox);
+
+ if (camel_imapx_server_is_in_idle (cinfo->is)) {
+ CamelIMAPXMailbox *idle_mailbox;
+
+ idle_mailbox = camel_imapx_server_ref_idle_mailbox (cinfo->is);
+ if (idle_mailbox)
+ imapx_conn_manager_dec_mailbox_idle (conn_man, idle_mailbox);
+ g_clear_object (&idle_mailbox);
+ }
+
+ success = camel_imapx_server_stop_idle_sync (cinfo->is, cancellable, &local_error);
+
+ if (success && camel_imapx_server_can_use_idle (cinfo->is)) {
+ GList *link, *connection_infos, *disconnected_infos = NULL;
+
+ CON_READ_LOCK (conn_man);
+ connection_infos = g_list_copy (conn_man->priv->connections);
+ g_list_foreach (connection_infos, (GFunc) connection_info_ref, NULL);
+ CON_READ_UNLOCK (conn_man);
+
+ /* Stop IDLE on all connections serving the same mailbox,
+ to avoid notifications for changes done by itself */
+ for (link = connection_infos; link && !g_cancellable_is_cancelled (cancellable); link = g_list_next (link)) {
+ ConnectionInfo *other_cinfo = link->data;
+ CamelIMAPXMailbox *other_mailbox;
+
+ if (!other_cinfo || other_cinfo == cinfo || connection_info_get_busy (other_cinfo) ||
+ !camel_imapx_server_is_in_idle (other_cinfo->is))
+ continue;
+
+ other_mailbox = camel_imapx_server_ref_idle_mailbox (other_cinfo->is);
+ if (job_mailbox == other_mailbox) {
+ if (!camel_imapx_server_stop_idle_sync (other_cinfo->is, cancellable, &local_error)) {
+ c (camel_imapx_server_get_tagprefix (other_cinfo->is),
+ "Failed to stop IDLE call (will be removed) on connection %p (server:%p) due to error: %s\n",
+ other_cinfo, other_cinfo->is, local_error ? local_error->message : "Unknown error");
+
+ camel_imapx_server_disconnect_sync (other_cinfo->is, cancellable, NULL);
+
+ disconnected_infos = g_list_prepend (disconnected_infos, connection_info_ref (other_cinfo));
+ } else {
+ imapx_conn_manager_dec_mailbox_idle (conn_man, other_mailbox);
+ }
+
+ g_clear_error (&local_error);
+ }
+
+ g_clear_object (&other_mailbox);
+ }
+
+ for (link = disconnected_infos; link; link = g_list_next (link)) {
+ ConnectionInfo *other_cinfo = link->data;
+
+ imapx_conn_manager_remove_info (conn_man, other_cinfo);
+ }
+
+ g_list_free_full (disconnected_infos, (GDestroyNotify) connection_info_unref);
+ g_list_free_full (connection_infos, (GDestroyNotify) connection_info_unref);
+ }
+
+ if (success)
+ success = camel_imapx_job_run_sync (job, cinfo->is, cancellable, &local_error);
+
+ if (job_mailbox)
+ imapx_conn_manager_dec_mailbox_busy (conn_man, job_mailbox);
+
+ if (success) {
+ CamelIMAPXMailbox *idle_mailbox = NULL;
+
+ if (!imapx_conn_manager_has_inbox_idle (conn_man)) {
+ CamelIMAPXStore *imapx_store;
+
+ imapx_store = camel_imapx_conn_manager_ref_store (conn_man);
+ idle_mailbox = imapx_store ? camel_imapx_store_ref_mailbox (imapx_store, "INBOX") : NULL;
+
+ g_clear_object (&imapx_store);
+ }
+
+ if (!idle_mailbox)
+ idle_mailbox = camel_imapx_server_ref_selected (cinfo->is);
+
+ /* Can start IDLE on the connection only if the IDLE folder is not busy
+ and not in IDLE already, to avoid multiple IDLE notifications on the same mailbox */
+ if (idle_mailbox && camel_imapx_server_can_use_idle (cinfo->is) &&
+ !imapx_conn_manager_is_mailbox_busy (conn_man, idle_mailbox) &&
+ !imapx_conn_manager_is_mailbox_idle (conn_man, idle_mailbox)) {
+ camel_imapx_server_schedule_idle_sync (cinfo->is, idle_mailbox, cancellable, NULL);
+
+ if (camel_imapx_server_is_in_idle (cinfo->is)) {
+ g_clear_object (&idle_mailbox);
+
+ idle_mailbox = camel_imapx_server_ref_idle_mailbox (cinfo->is);
+ if (idle_mailbox)
+ imapx_conn_manager_inc_mailbox_idle (conn_man, idle_mailbox);
+ }
+ }
+
+ g_clear_object (&idle_mailbox);
+
+ imapx_conn_manager_unmark_busy (conn_man, cinfo);
+ } else if (!local_error || ((local_error->domain == G_IO_ERROR || local_error->domain == G_TLS_ERROR || local_error->domain == CAMEL_IMAPX_ERROR ||
+ g_error_matches (local_error, CAMEL_IMAPX_SERVER_ERROR, CAMEL_IMAPX_SERVER_ERROR_TRY_RECONNECT)) &&
+ !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))) {
+ c (camel_imapx_server_get_tagprefix (cinfo->is), "Removed connection %p (server:%p) due to error: %s\n",
+ cinfo, cinfo->is, local_error ? local_error->message : "Unknown error");
+
+ camel_imapx_server_disconnect_sync (cinfo->is, cancellable, NULL);
+ imapx_conn_manager_remove_info (conn_man, cinfo);
+
+ if (!local_error ||
+ g_error_matches (local_error, G_TLS_ERROR, G_TLS_ERROR_MISC) ||
+ g_error_matches (local_error, G_TLS_ERROR, G_TLS_ERROR_EOF) ||
+ g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CLOSED)) {
+ GError *tmp = local_error;
+
+ local_error = NULL;
+
+ /* This message won't get into UI. */
+ g_set_error (&local_error, CAMEL_IMAPX_SERVER_ERROR, CAMEL_IMAPX_SERVER_ERROR_TRY_RECONNECT,
+ "Reconnect after failure: %s", tmp ? tmp->message : "Unknown error");
+
+ g_clear_error (&tmp);
+ }
+ } else {
+ c (camel_imapx_server_get_tagprefix (cinfo->is), "Unmark connection %p (server:%p) busy after failure, error: %s\n",
+ cinfo, cinfo->is, local_error ? local_error->message : "Unknown error");
+
+ imapx_conn_manager_unmark_busy (conn_man, cinfo);
+ }
+
+ connection_info_unref (cinfo);
+ }
+
+ /* If there's a reconnect required for a new connection, then there happened
+ something really wrong, thus rather give up. */
+ } while (!success && !is_new_connection && g_error_matches (local_error, CAMEL_IMAPX_SERVER_ERROR, CAMEL_IMAPX_SERVER_ERROR_TRY_RECONNECT));
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ JOB_QUEUE_LOCK (conn_man);
+ conn_man->priv->job_queue = g_slist_remove (conn_man->priv->job_queue, job);
+ JOB_QUEUE_UNLOCK (conn_man);
+
+ camel_imapx_job_done (job);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_nothing_matches (CamelIMAPXJob *job,
+ CamelIMAPXJob *other_job)
+{
+ /* For jobs where none can match. */
+ return FALSE;
+}
+
+static gboolean
+imapx_conn_manager_matches_sync_changes_or_refresh_info (CamelIMAPXJob *job,
+ CamelIMAPXJob *other_job)
+{
+ CamelIMAPXJobKind other_job_kind;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (other_job != NULL, FALSE);
+ g_return_val_if_fail (job != other_job, FALSE);
+
+ if (camel_imapx_job_get_mailbox (job) != camel_imapx_job_get_mailbox (other_job))
+ return FALSE;
+
+ other_job_kind = camel_imapx_job_get_kind (other_job);
+
+ return other_job_kind == CAMEL_IMAPX_JOB_SYNC_CHANGES ||
+ other_job_kind == CAMEL_IMAPX_JOB_REFRESH_INFO;
+}
+
+struct ListJobData {
+ gchar *pattern;
+ CamelStoreGetFolderInfoFlags flags;
+};
+
+static void
+list_job_data_free (gpointer ptr)
+{
+ struct ListJobData *job_data = ptr;
+
+ if (job_data) {
+ g_free (job_data->pattern);
+ g_free (job_data);
+ }
+}
+
+static gboolean
+imapx_conn_manager_list_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct ListJobData *job_data;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ job_data = camel_imapx_job_get_user_data (job);
+ g_return_val_if_fail (job_data != NULL, FALSE);
+
+ return camel_imapx_server_list_sync (server, job_data->pattern, job_data->flags, cancellable, error);
+}
+
+static gboolean
+imapx_conn_manager_list_matches (CamelIMAPXJob *job,
+ CamelIMAPXJob *other_job)
+{
+ struct ListJobData *job_data, *other_job_data;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (other_job != NULL, FALSE);
+
+ if (camel_imapx_job_get_kind (job) != CAMEL_IMAPX_JOB_LIST ||
+ camel_imapx_job_get_kind (job) != camel_imapx_job_get_kind (other_job))
+ return FALSE;
+
+ job_data = camel_imapx_job_get_user_data (job);
+ other_job_data = camel_imapx_job_get_user_data (other_job);
+
+ if (!job_data || !other_job_data)
+ return FALSE;
+
+ return job_data->flags == other_job_data->flags &&
+ g_strcmp0 (job_data->pattern, other_job_data->pattern) == 0;
+}
+
+gboolean
+camel_imapx_conn_manager_list_sync (CamelIMAPXConnManager *conn_man,
+ const gchar *pattern,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ struct ListJobData *job_data;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_LIST, NULL,
+ imapx_conn_manager_list_run_sync,
+ imapx_conn_manager_list_matches,
+ NULL);
+
+ job_data = g_new0 (struct ListJobData, 1);
+ job_data->pattern = g_strdup (pattern);
+ job_data->flags = flags;
+
+ camel_imapx_job_set_user_data (job, job_data, list_job_data_free);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job, NULL, cancellable, error);
+ if (success)
+ camel_imapx_job_copy_result (job, &success, NULL, error, NULL);
+
+ camel_imapx_job_unref (job);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_refresh_info_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXMailbox *mailbox;
+ gboolean success;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox = camel_imapx_job_get_mailbox (job);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ success = camel_imapx_server_refresh_info_sync (server, mailbox, cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, success, NULL, local_error, NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+gboolean
+camel_imapx_conn_manager_refresh_info_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ if (!camel_imapx_conn_manager_sync_changes_sync (conn_man, mailbox, cancellable, error))
+ return FALSE;
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_REFRESH_INFO, mailbox,
+ imapx_conn_manager_refresh_info_run_sync, NULL, NULL);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job,
+ imapx_conn_manager_matches_sync_changes_or_refresh_info,
+ cancellable, error);
+
+ camel_imapx_job_unref (job);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_move_to_real_junk_sync (CamelIMAPXConnManager *conn_man,
+ CamelFolder *folder,
+ GCancellable *cancellable,
+ gboolean *out_need_to_expunge,
+ GError **error)
+{
+ CamelIMAPXFolder *imapx_folder;
+ CamelIMAPXMailbox *mailbox;
+ CamelIMAPXSettings *settings;
+ GPtrArray *uids_to_copy;
+ gchar *real_junk_path = NULL;
+ gboolean success = TRUE;
+
+ *out_need_to_expunge = FALSE;
+
+ /* Caller already obtained the mailbox from the folder,
+ * so the folder should still have it readily available. */
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+ mailbox = camel_imapx_folder_ref_mailbox (imapx_folder);
+ g_return_val_if_fail (mailbox != NULL, FALSE);
+
+ uids_to_copy = g_ptr_array_new_with_free_func (
+ (GDestroyNotify) camel_pstring_free);
+
+ settings = CAMEL_IMAPX_SETTINGS (camel_service_ref_settings (CAMEL_SERVICE (camel_folder_get_parent_store (folder))));
+ if (camel_imapx_settings_get_use_real_junk_path (settings)) {
+ real_junk_path = camel_imapx_settings_dup_real_junk_path (settings);
+ camel_imapx_folder_claim_move_to_real_junk_uids (imapx_folder, uids_to_copy);
+ }
+ g_object_unref (settings);
+
+ if (uids_to_copy->len > 0) {
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXMailbox *destination = NULL;
+
+ imapx_store = camel_imapx_conn_manager_ref_store (conn_man);
+
+ if (real_junk_path != NULL) {
+ folder = camel_store_get_folder_sync (
+ CAMEL_STORE (imapx_store),
+ real_junk_path, 0,
+ cancellable, error);
+ } else {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID_PATH,
+ _("No destination folder specified"));
+ folder = NULL;
+ }
+
+ if (folder != NULL) {
+ destination = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (folder),
+ cancellable, error);
+ g_object_unref (folder);
+ }
+
+ /* Avoid duplicating messages in the Junk folder. */
+ if (destination == mailbox) {
+ success = TRUE;
+ } else if (destination != NULL) {
+ success = imapx_conn_manager_copy_message_sync (
+ conn_man, mailbox, destination,
+ uids_to_copy, TRUE, FALSE, TRUE,
+ cancellable, error);
+ *out_need_to_expunge = success;
+ } else {
+ success = FALSE;
+ }
+
+ if (!success) {
+ g_prefix_error (
+ error, "%s: ",
+ _("Unable to move junk messages"));
+ }
+
+ g_clear_object (&destination);
+ g_clear_object (&imapx_store);
+ }
+
+ g_ptr_array_unref (uids_to_copy);
+ g_free (real_junk_path);
+
+ g_clear_object (&mailbox);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_move_to_real_trash_sync (CamelIMAPXConnManager *conn_man,
+ CamelFolder *folder,
+ GCancellable *cancellable,
+ gboolean *out_need_to_expunge,
+ GError **error)
+{
+ CamelIMAPXFolder *imapx_folder;
+ CamelIMAPXMailbox *mailbox, *destination = NULL;
+ CamelIMAPXSettings *settings;
+ CamelIMAPXStore *imapx_store;
+ GPtrArray *uids_to_copy;
+ gchar *real_trash_path = NULL;
+ guint32 folder_deleted_count = 0;
+ gboolean success = TRUE;
+
+ *out_need_to_expunge = FALSE;
+
+ /* Caller already obtained the mailbox from the folder,
+ * so the folder should still have it readily available. */
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+ mailbox = camel_imapx_folder_ref_mailbox (imapx_folder);
+ g_return_val_if_fail (mailbox != NULL, FALSE);
+
+ uids_to_copy = g_ptr_array_new_with_free_func (
+ (GDestroyNotify) camel_pstring_free);
+
+ settings = CAMEL_IMAPX_SETTINGS (camel_service_ref_settings (CAMEL_SERVICE (camel_folder_get_parent_store (folder))));
+ if (camel_imapx_settings_get_use_real_trash_path (settings)) {
+ real_trash_path = camel_imapx_settings_dup_real_trash_path (settings);
+ camel_imapx_folder_claim_move_to_real_trash_uids (CAMEL_IMAPX_FOLDER (folder), uids_to_copy);
+ }
+ g_object_unref (settings);
+
+ imapx_store = camel_imapx_conn_manager_ref_store (conn_man);
+
+ if (real_trash_path != NULL) {
+ folder = camel_store_get_folder_sync (
+ CAMEL_STORE (imapx_store),
+ real_trash_path, 0,
+ cancellable, error);
+ } else {
+ if (uids_to_copy->len > 0) {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID_PATH,
+ _("No destination folder specified"));
+ }
+
+ folder = NULL;
+ }
+
+ if (folder != NULL) {
+ destination = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (folder),
+ cancellable, error);
+ folder_deleted_count = camel_folder_summary_get_deleted_count (folder->summary);
+ g_object_unref (folder);
+ }
+
+ /* Avoid duplicating messages in the Trash folder. */
+ if (destination == mailbox) {
+ success = TRUE;
+ /* Deleted messages in the real Trash folder will be permanently deleted immediately. */
+ *out_need_to_expunge = folder_deleted_count > 0 || uids_to_copy->len > 0;
+ } else if (destination != NULL) {
+ if (uids_to_copy->len > 0) {
+ success = imapx_conn_manager_copy_message_sync (
+ conn_man, mailbox, destination,
+ uids_to_copy, TRUE, TRUE, TRUE,
+ cancellable, error);
+ *out_need_to_expunge = success;
+ }
+ } else if (uids_to_copy->len > 0) {
+ success = FALSE;
+ }
+
+ if (!success) {
+ g_prefix_error (
+ error, "%s: ",
+ _("Unable to move deleted messages"));
+ }
+
+ g_ptr_array_unref (uids_to_copy);
+ g_free (real_trash_path);
+
+ g_clear_object (&imapx_store);
+ g_clear_object (&destination);
+ g_clear_object (&mailbox);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_expunge_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ gboolean skip_sync_changes,
+ GCancellable *cancellable,
+ GError **error);
+
+static gboolean
+imapx_conn_manager_sync_changes_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXMailbox *mailbox;
+ GError *local_error = NULL;
+ gboolean can_influence_flags, success;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox = camel_imapx_job_get_mailbox (job);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ can_influence_flags = GPOINTER_TO_INT (camel_imapx_job_get_user_data (job)) == 1;
+
+ success = camel_imapx_server_sync_changes_sync (server, mailbox, can_influence_flags, cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, success, NULL, local_error, NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_sync_changes_matches (CamelIMAPXJob *job,
+ CamelIMAPXJob *other_job)
+{
+ gboolean job_can_influence_flags, other_job_can_influence_flags;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (other_job != NULL, FALSE);
+
+ if (camel_imapx_job_get_kind (job) != CAMEL_IMAPX_JOB_SYNC_CHANGES ||
+ camel_imapx_job_get_kind (job) != camel_imapx_job_get_kind (other_job))
+ return FALSE;
+
+ job_can_influence_flags = GPOINTER_TO_INT (camel_imapx_job_get_user_data (job)) == 1;
+ other_job_can_influence_flags = GPOINTER_TO_INT (camel_imapx_job_get_user_data (other_job)) == 1;
+
+ return job_can_influence_flags == other_job_can_influence_flags;
+}
+
+gboolean
+camel_imapx_conn_manager_sync_changes_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ CamelFolder *folder = NULL;
+ gboolean need_to_expunge = FALSE, expunge = FALSE;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_SYNC_CHANGES, mailbox,
+ imapx_conn_manager_sync_changes_run_sync,
+ imapx_conn_manager_sync_changes_matches, NULL);
+
+ /* Skip store of the \Deleted flag */
+ camel_imapx_job_set_user_data (job, GINT_TO_POINTER (1), NULL);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job,
+ imapx_conn_manager_matches_sync_changes_or_refresh_info,
+ cancellable, error);
+
+ camel_imapx_job_unref (job);
+
+ if (success) {
+ folder = imapx_conn_manager_ref_folder_sync (conn_man, mailbox, cancellable, error);
+ if (!folder)
+ success = FALSE;
+ }
+
+ if (success) {
+ success = imapx_conn_manager_move_to_real_junk_sync (
+ conn_man, folder, cancellable,
+ &need_to_expunge, error);
+ expunge |= need_to_expunge;
+ }
+
+ if (success) {
+ success = imapx_conn_manager_move_to_real_trash_sync (
+ conn_man, folder, cancellable,
+ &need_to_expunge, error);
+ expunge |= need_to_expunge;
+ }
+
+ if (success && expunge) {
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_SYNC_CHANGES, mailbox,
+ imapx_conn_manager_sync_changes_run_sync,
+ imapx_conn_manager_sync_changes_matches, NULL);
+
+ /* Store also the \Deleted flag */
+ camel_imapx_job_set_user_data (job, GINT_TO_POINTER (0), NULL);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job,
+ imapx_conn_manager_matches_sync_changes_or_refresh_info,
+ cancellable, error);
+
+ camel_imapx_job_unref (job);
+
+ success = imapx_conn_manager_expunge_sync (conn_man, mailbox, TRUE, cancellable, error);
+ }
+
+ g_clear_object (&folder);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_expunge_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXMailbox *mailbox;
+ GError *local_error = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox = camel_imapx_job_get_mailbox (job);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ success = camel_imapx_server_expunge_sync (server, mailbox, cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, success, NULL, local_error, NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_expunge_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ gboolean skip_sync_changes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ if (!skip_sync_changes && !camel_imapx_conn_manager_sync_changes_sync (conn_man, mailbox, cancellable, error))
+ return FALSE;
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_EXPUNGE, mailbox,
+ imapx_conn_manager_expunge_run_sync, NULL, NULL);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job, NULL, cancellable, error);
+
+ camel_imapx_job_unref (job);
+
+ return success;
+}
+
+gboolean
+camel_imapx_conn_manager_expunge_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return imapx_conn_manager_expunge_sync (conn_man, mailbox, FALSE, cancellable, error);
+}
+
+struct GetMessageJobData {
+ CamelFolderSummary *summary;
+ CamelDataCache *message_cache;
+ gchar *message_uid;
+};
+
+static void
+get_message_job_data_free (gpointer ptr)
+{
+ struct GetMessageJobData *job_data = ptr;
+
+ if (job_data) {
+ g_clear_object (&job_data->summary);
+ g_clear_object (&job_data->message_cache);
+ g_free (job_data->message_uid);
+ g_free (job_data);
+ }
+}
+
+static gboolean
+imapx_conn_manager_get_message_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct GetMessageJobData *job_data;
+ CamelIMAPXMailbox *mailbox;
+ CamelStream *result;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox = camel_imapx_job_get_mailbox (job);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ job_data = camel_imapx_job_get_user_data (job);
+ g_return_val_if_fail (job_data != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (job_data->summary), FALSE);
+ g_return_val_if_fail (CAMEL_IS_DATA_CACHE (job_data->message_cache), FALSE);
+ g_return_val_if_fail (job_data->message_uid != NULL, FALSE);
+
+ result = camel_imapx_server_get_message_sync (
+ server, mailbox, job_data->summary, job_data->message_cache, job_data->message_uid,
+ cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, result != NULL, result, local_error, result ? g_object_unref : NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return result != NULL;
+}
+
+static gboolean
+imapx_conn_manager_get_message_matches (CamelIMAPXJob *job,
+ CamelIMAPXJob *other_job)
+{
+ struct GetMessageJobData *job_data, *other_job_data;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (other_job != NULL, FALSE);
+
+ if (camel_imapx_job_get_kind (job) != CAMEL_IMAPX_JOB_GET_MESSAGE ||
+ camel_imapx_job_get_kind (job) != camel_imapx_job_get_kind (other_job))
+ return FALSE;
+
+ job_data = camel_imapx_job_get_user_data (job);
+ other_job_data = camel_imapx_job_get_user_data (other_job);
+
+ if (!job_data || !other_job_data)
+ return FALSE;
+
+ return g_strcmp0 (job_data->message_uid, other_job_data->message_uid) == 0;
+}
+
+static void
+imapx_conn_manager_get_message_copy_result (CamelIMAPXJob *job,
+ gconstpointer set_result,
+ gpointer *out_result)
+{
+ if (!set_result || !*out_result)
+ return;
+
+ *out_result = g_object_ref ((gpointer) set_result);
+}
+
+CamelStream *
+camel_imapx_conn_manager_get_message_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolderSummary *summary,
+ CamelDataCache *message_cache,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ struct GetMessageJobData *job_data;
+ CamelStream *result;
+ gpointer result_data = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), NULL);
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_GET_MESSAGE, mailbox,
+ imapx_conn_manager_get_message_run_sync,
+ imapx_conn_manager_get_message_matches,
+ imapx_conn_manager_get_message_copy_result);
+
+ job_data = g_new0 (struct GetMessageJobData, 1);
+ job_data->summary = g_object_ref (summary);
+ job_data->message_cache = g_object_ref (message_cache);
+ job_data->message_uid = g_strdup (message_uid);
+
+ camel_imapx_job_set_user_data (job, job_data, get_message_job_data_free);
+
+ if (camel_imapx_conn_manager_run_job_sync (conn_man, job, NULL, cancellable, error) &&
+ camel_imapx_job_take_result_data (job, &result_data)) {
+ result = result_data;
+ } else {
+ result = NULL;
+ }
+
+ camel_imapx_job_unref (job);
+
+ return result;
+}
+
+struct CopyMessageJobData {
+ CamelIMAPXMailbox *destination;
+ GPtrArray *uids;
+ gboolean delete_originals;
+ gboolean remove_deleted_flags;
+};
+
+static void
+copy_message_job_data_free (gpointer ptr)
+{
+ struct CopyMessageJobData *job_data = ptr;
+
+ if (job_data) {
+ g_clear_object (&job_data->destination);
+ g_ptr_array_free (job_data->uids, TRUE);
+ g_free (job_data);
+ }
+}
+
+static gboolean
+imapx_conn_manager_copy_message_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct CopyMessageJobData *job_data;
+ CamelIMAPXMailbox *mailbox;
+ GError *local_error = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox = camel_imapx_job_get_mailbox (job);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ job_data = camel_imapx_job_get_user_data (job);
+ g_return_val_if_fail (job_data != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (job_data->destination), FALSE);
+ g_return_val_if_fail (job_data->uids != NULL, FALSE);
+
+ success = camel_imapx_server_copy_message_sync (
+ server, mailbox, job_data->destination, job_data->uids, job_data->delete_originals,
+ job_data->remove_deleted_flags, cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, success, NULL, local_error, NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_copy_message_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ CamelIMAPXMailbox *destination,
+ GPtrArray *uids,
+ gboolean delete_originals,
+ gboolean remove_deleted_flags,
+ gboolean skip_sync_changes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ struct CopyMessageJobData *job_data;
+ gboolean success;
+ gint ii;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ if (!skip_sync_changes && !camel_imapx_conn_manager_sync_changes_sync (conn_man, mailbox, cancellable, error))
+ return FALSE;
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_COPY_MESSAGE, mailbox,
+ imapx_conn_manager_copy_message_run_sync,
+ imapx_conn_manager_nothing_matches,
+ NULL);
+
+ job_data = g_new0 (struct CopyMessageJobData, 1);
+ job_data->destination = g_object_ref (destination);
+ job_data->uids = g_ptr_array_new_full (uids->len, (GDestroyNotify) camel_pstring_free);
+ job_data->delete_originals = delete_originals;
+ job_data->remove_deleted_flags = remove_deleted_flags;
+
+ for (ii = 0; ii < uids->len; ii++) {
+ g_ptr_array_add (job_data->uids, (gpointer) camel_pstring_strdup (uids->pdata[ii]));
+ }
+
+ camel_imapx_job_set_user_data (job, job_data, copy_message_job_data_free);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job, NULL, cancellable, error);
+
+ camel_imapx_job_unref (job);
+
+ if (success) {
+ CamelFolder *dest;
+
+ dest = imapx_conn_manager_ref_folder_sync (conn_man, destination, cancellable, NULL);
+
+ /* Update destination folder only if it's not frozen,
+ * to avoid updating for each "move" action on a single
+ * message while filtering. */
+ if (dest && !camel_folder_is_frozen (dest)) {
+ /* Ignore errors here */
+ camel_imapx_conn_manager_refresh_info_sync (conn_man, destination, cancellable, NULL);
+ }
+
+ g_clear_object (&dest);
+ }
+
+ return success;
+}
+
+gboolean
+camel_imapx_conn_manager_copy_message_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ CamelIMAPXMailbox *destination,
+ GPtrArray *uids,
+ gboolean delete_originals,
+ gboolean remove_deleted_flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return imapx_conn_manager_copy_message_sync (conn_man, mailbox, destination, uids,
+ delete_originals, remove_deleted_flags, FALSE, cancellable, error);
+}
+
+struct AppendMessageJobData {
+ CamelFolderSummary *summary;
+ CamelDataCache *message_cache;
+ CamelMimeMessage *message;
+ const CamelMessageInfo *mi;
+};
+
+static void
+append_message_job_data_free (gpointer ptr)
+{
+ struct AppendMessageJobData *job_data = ptr;
+
+ if (job_data) {
+ g_clear_object (&job_data->summary);
+ g_clear_object (&job_data->message_cache);
+ g_clear_object (&job_data->message);
+ g_free (job_data);
+ }
+}
+
+static gboolean
+imapx_conn_manager_append_message_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct AppendMessageJobData *job_data;
+ CamelIMAPXMailbox *mailbox;
+ gchar *appended_uid = NULL;
+ GError *local_error = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox = camel_imapx_job_get_mailbox (job);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ job_data = camel_imapx_job_get_user_data (job);
+ g_return_val_if_fail (job_data != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (job_data->summary), FALSE);
+ g_return_val_if_fail (CAMEL_IS_DATA_CACHE (job_data->message_cache), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (job_data->message), FALSE);
+
+ success = camel_imapx_server_append_message_sync (server, mailbox, job_data->summary, job_data->message_cache,
+ job_data->message, job_data->mi, &appended_uid, cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, success, appended_uid, local_error, appended_uid ? g_free : NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+gboolean
+camel_imapx_conn_manager_append_message_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolderSummary *summary,
+ CamelDataCache *message_cache,
+ CamelMimeMessage *message,
+ const CamelMessageInfo *mi,
+ gchar **append_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ struct AppendMessageJobData *job_data;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_APPEND_MESSAGE, mailbox,
+ imapx_conn_manager_append_message_run_sync,
+ imapx_conn_manager_nothing_matches,
+ NULL);
+
+ job_data = g_new0 (struct AppendMessageJobData, 1);
+ job_data->summary = g_object_ref (summary);
+ job_data->message_cache = g_object_ref (message_cache);
+ job_data->message = g_object_ref (message);
+ job_data->mi = mi;
+
+ camel_imapx_job_set_user_data (job, job_data, append_message_job_data_free);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job, NULL, cancellable, error);
+ if (success) {
+ gpointer result_data = NULL;
+
+ success = camel_imapx_job_take_result_data (job, &result_data);
+ if (success && append_uid)
+ *append_uid = result_data;
+ else
+ g_free (result_data);
+ }
+
+ camel_imapx_job_unref (job);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_sync_message_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct GetMessageJobData *job_data;
+ CamelIMAPXMailbox *mailbox;
+ GError *local_error = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox = camel_imapx_job_get_mailbox (job);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ job_data = camel_imapx_job_get_user_data (job);
+ g_return_val_if_fail (job_data != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (job_data->summary), FALSE);
+ g_return_val_if_fail (CAMEL_IS_DATA_CACHE (job_data->message_cache), FALSE);
+ g_return_val_if_fail (job_data->message_uid != NULL, FALSE);
+
+ success = camel_imapx_server_sync_message_sync (
+ server, mailbox, job_data->summary, job_data->message_cache, job_data->message_uid,
+ cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, success, NULL, local_error, NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+gboolean
+camel_imapx_conn_manager_sync_message_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolderSummary *summary,
+ CamelDataCache *message_cache,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ struct GetMessageJobData *job_data;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_SYNC_MESSAGE, mailbox,
+ imapx_conn_manager_sync_message_run_sync,
+ imapx_conn_manager_get_message_matches,
+ NULL);
+
+ job_data = g_new0 (struct GetMessageJobData, 1);
+ job_data->summary = g_object_ref (summary);
+ job_data->message_cache = g_object_ref (message_cache);
+ job_data->message_uid = g_strdup (message_uid);
+
+ camel_imapx_job_set_user_data (job, job_data, get_message_job_data_free);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job, NULL, cancellable, error);
+
+ camel_imapx_job_unref (job);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_create_mailbox_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *mailbox_name;
+ GError *local_error = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox_name = camel_imapx_job_get_user_data (job);
+ g_return_val_if_fail (mailbox_name != NULL, FALSE);
+
+ success = camel_imapx_server_create_mailbox_sync (server, mailbox_name, cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, success, NULL, local_error, NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+gboolean
+camel_imapx_conn_manager_create_mailbox_sync (CamelIMAPXConnManager *conn_man,
+ const gchar *mailbox_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_CREATE_MAILBOX, NULL,
+ imapx_conn_manager_create_mailbox_run_sync,
+ imapx_conn_manager_nothing_matches,
+ NULL);
+
+ camel_imapx_job_set_user_data (job, g_strdup (mailbox_name), g_free);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job, NULL, cancellable, error);
+
+ camel_imapx_job_unref (job);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_delete_mailbox_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXMailbox *mailbox;
+ GError *local_error = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox = camel_imapx_job_get_mailbox (job);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ success = camel_imapx_server_delete_mailbox_sync (server, mailbox, cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, success, NULL, local_error, NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+gboolean
+camel_imapx_conn_manager_delete_mailbox_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_DELETE_MAILBOX, mailbox,
+ imapx_conn_manager_delete_mailbox_run_sync,
+ imapx_conn_manager_nothing_matches,
+ NULL);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job, NULL, cancellable, error);
+
+ camel_imapx_job_unref (job);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_rename_mailbox_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXMailbox *mailbox;
+ const gchar *new_mailbox_name;
+ GError *local_error = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox = camel_imapx_job_get_mailbox (job);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ new_mailbox_name = camel_imapx_job_get_user_data (job);
+ g_return_val_if_fail (new_mailbox_name != NULL, FALSE);
+
+ success = camel_imapx_server_rename_mailbox_sync (server, mailbox, new_mailbox_name, cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, success, NULL, local_error, NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+gboolean
+camel_imapx_conn_manager_rename_mailbox_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ const gchar *new_mailbox_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_RENAME_MAILBOX, mailbox,
+ imapx_conn_manager_rename_mailbox_run_sync,
+ imapx_conn_manager_nothing_matches,
+ NULL);
+
+ camel_imapx_job_set_user_data (job, g_strdup (new_mailbox_name), g_free);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job, NULL, cancellable, error);
+
+ camel_imapx_job_unref (job);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_subscribe_mailbox_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXMailbox *mailbox;
+ GError *local_error = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox = camel_imapx_job_get_mailbox (job);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ success = camel_imapx_server_subscribe_mailbox_sync (server, mailbox, cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, success, NULL, local_error, NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+gboolean
+camel_imapx_conn_manager_subscribe_mailbox_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_SUBSCRIBE_MAILBOX, mailbox,
+ imapx_conn_manager_subscribe_mailbox_run_sync, NULL, NULL);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job, NULL, cancellable, error);
+
+ camel_imapx_job_unref (job);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_unsubscribe_mailbox_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXMailbox *mailbox;
+ GError *local_error = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox = camel_imapx_job_get_mailbox (job);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ success = camel_imapx_server_unsubscribe_mailbox_sync (server, mailbox, cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, success, NULL, local_error, NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+gboolean
+camel_imapx_conn_manager_unsubscribe_mailbox_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_UNSUBSCRIBE_MAILBOX, mailbox,
+ imapx_conn_manager_unsubscribe_mailbox_run_sync, NULL, NULL);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job, NULL, cancellable, error);
+
+ camel_imapx_job_unref (job);
+
+ return success;
+}
+
+static gboolean
+imapx_conn_manager_update_quota_info_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXMailbox *mailbox;
+ GError *local_error = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox = camel_imapx_job_get_mailbox (job);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ success = camel_imapx_server_update_quota_info_sync (server, mailbox, cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, success, NULL, local_error, NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+gboolean
+camel_imapx_conn_manager_update_quota_info_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXJob *job;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), FALSE);
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_UPDATE_QUOTA_INFO, mailbox,
+ imapx_conn_manager_update_quota_info_run_sync, NULL, NULL);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job, NULL, cancellable, error);
+
+ camel_imapx_job_unref (job);
+
+ return success;
+}
+
+static gchar **
+imapx_copy_strv (const gchar * const *words)
+{
+ gchar **copy;
+ gint ii;
+
+ if (!words || !*words)
+ return NULL;
+
+ copy = g_new0 (gchar *, g_strv_length ((gchar **) words) + 1);
+
+ for (ii = 0; words[ii]; ii++) {
+ copy[ii] = g_strdup (words[ii]);
+ }
+
+ copy[ii] = NULL;
+
+ return copy;
+}
+
+static gboolean
+imapx_equal_strv (const gchar * const *words1,
+ const gchar * const *words2)
+{
+ gint ii;
+
+ if (words1 == words2)
+ return TRUE;
+
+ if (!words1 || !words2)
+ return FALSE;
+
+ for (ii = 0; words1[ii] && words2[ii]; ii++) {
+ if (g_strcmp0 (words1[ii], words2[ii]) != 0)
+ return FALSE;
+ }
+
+ return !words1[ii] && !words2[ii];
+}
+
+struct UidSearchJobData {
+ gchar *criteria_prefix;
+ gchar *search_key;
+ gchar **words;
+};
+
+static void
+uid_search_job_data_free (gpointer ptr)
+{
+ struct UidSearchJobData *job_data = ptr;
+
+ if (ptr) {
+ g_free (job_data->criteria_prefix);
+ g_free (job_data->search_key);
+ g_strfreev (job_data->words);
+ g_free (job_data);
+ }
+}
+
+static gboolean
+imapx_conn_manager_uid_search_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct UidSearchJobData *job_data;
+ CamelIMAPXMailbox *mailbox;
+ GPtrArray *uids = NULL;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+
+ mailbox = camel_imapx_job_get_mailbox (job);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ job_data = camel_imapx_job_get_user_data (job);
+ g_return_val_if_fail (job_data != NULL, FALSE);
+
+ uids = camel_imapx_server_uid_search_sync (server, mailbox, job_data->criteria_prefix,
+ job_data->search_key, (const gchar * const *) job_data->words, cancellable, &local_error);
+
+ camel_imapx_job_set_result (job, uids != NULL, uids, local_error, uids ? (GDestroyNotify) g_ptr_array_free : NULL);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return uids != NULL;
+}
+
+static gboolean
+imapx_conn_manager_uid_search_matches (CamelIMAPXJob *job,
+ CamelIMAPXJob *other_job)
+{
+ struct UidSearchJobData *job_data, *other_job_data;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (other_job != NULL, FALSE);
+
+ if (camel_imapx_job_get_kind (job) != CAMEL_IMAPX_JOB_UID_SEARCH ||
+ camel_imapx_job_get_kind (job) != camel_imapx_job_get_kind (other_job))
+ return FALSE;
+
+ job_data = camel_imapx_job_get_user_data (job);
+ other_job_data = camel_imapx_job_get_user_data (other_job);
+
+ if (!job_data || !other_job_data)
+ return job_data == other_job_data;
+
+ return g_strcmp0 (job_data->criteria_prefix, other_job_data->criteria_prefix) == 0 &&
+ g_strcmp0 (job_data->search_key, other_job_data->search_key) == 0 &&
+ imapx_equal_strv ((const gchar * const *) job_data->words, (const gchar * const *) other_job_data->words);
+}
+
+GPtrArray *
+camel_imapx_conn_manager_uid_search_sync (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ const gchar *criteria_prefix,
+ const gchar *search_key,
+ const gchar * const *words,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct UidSearchJobData *job_data;
+ GPtrArray *uids = NULL;
+ CamelIMAPXJob *job;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man), NULL);
+
+ job_data = g_new0 (struct UidSearchJobData, 1);
+ job_data->criteria_prefix = g_strdup (criteria_prefix);
+ job_data->search_key = g_strdup (search_key);
+ job_data->words = imapx_copy_strv (words);
+
+ job = camel_imapx_job_new (CAMEL_IMAPX_JOB_UID_SEARCH, mailbox,
+ imapx_conn_manager_uid_search_run_sync,
+ imapx_conn_manager_uid_search_matches,
+ NULL);
+
+ camel_imapx_job_set_user_data (job, job_data, uid_search_job_data_free);
+
+ success = camel_imapx_conn_manager_run_job_sync (conn_man, job, NULL, cancellable, error);
+ if (success) {
+ gpointer result_data = NULL;
+
+ success = camel_imapx_job_take_result_data (job, &result_data);
+ if (success)
+ uids = result_data;
+ }
+
+ camel_imapx_job_unref (job);
+
+ return uids;
+}
+
+/* for debugging purposes only */
+void
+camel_imapx_conn_manager_dump_queue_status (CamelIMAPXConnManager *conn_man)
+{
+ GList *llink;
+ GSList *slink;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_CONN_MANAGER (conn_man));
+
+ CON_READ_LOCK (conn_man);
+
+ printf ("%s: opened connections:%d\n", G_STRFUNC, g_list_length (conn_man->priv->connections));
+
+ for (llink = conn_man->priv->connections; llink != NULL; llink = g_list_next (llink)) {
+ ConnectionInfo *cinfo = llink->data;
+ CamelIMAPXCommand *cmd = NULL;
+
+ if (cinfo)
+ cmd = cinfo->is ? camel_imapx_server_ref_current_command (cinfo->is) : NULL;
+
+ printf (" connection:%p server:[%c] %p busy:%d command:%s\n", cinfo,
+ cinfo && cinfo->is ? camel_imapx_server_get_tagprefix (cinfo->is) : '?',
+ cinfo ? cinfo->is : NULL, cinfo ? cinfo->busy : FALSE,
+ cmd ? camel_imapx_job_get_kind_name (cmd->job_kind) : "[null]");
+
+ if (cmd)
+ camel_imapx_command_unref (cmd);
+ }
+
+ CON_READ_UNLOCK (conn_man);
+
+ JOB_QUEUE_LOCK (conn_man);
+
+ printf ("Queued jobs:%d\n", g_slist_length (conn_man->priv->job_queue));
+ for (slink = conn_man->priv->job_queue; slink; slink = g_slist_next (slink)) {
+ CamelIMAPXJob *job = slink->data;
+
+ printf (" job:%p kind:%s mailbox:%s\n", job,
+ job ? camel_imapx_job_get_kind_name (camel_imapx_job_get_kind (job)) : "[null]",
+ job && camel_imapx_job_get_mailbox (job) ? camel_imapx_mailbox_get_name (camel_imapx_job_get_mailbox (job)) : "[null]");
+ }
+
+ JOB_QUEUE_UNLOCK (conn_man);
+}
diff --git a/src/camel/providers/imapx/camel-imapx-conn-manager.h b/src/camel/providers/imapx/camel-imapx-conn-manager.h
new file mode 100644
index 000000000..f0edd54ef
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-conn-manager.h
@@ -0,0 +1,190 @@
+/*-*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-imap-conn-manager.h
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Chenthill Palanisamy <pchenthill@novell.com>
+ */
+
+#ifndef _CAMEL_IMAPX_CONN_MANAGER_H
+#define _CAMEL_IMAPX_CONN_MANAGER_H
+
+#include "camel-imapx-job.h"
+#include "camel-imapx-mailbox.h"
+#include "camel-imapx-server.h"
+
+G_BEGIN_DECLS
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_CONN_MANAGER \
+ (camel_imapx_conn_manager_get_type ())
+#define CAMEL_IMAPX_CONN_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_CONN_MANAGER, CamelIMAPXConnManager))
+#define CAMEL_IMAPX_CONN_MANAGER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_CONN_MANAGER, CamelIMAPXConnManagerClass))
+#define CAMEL_IS_IMAPX_CONN_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_CONN_MANAGER))
+#define CAMEL_IS_IMAPX_CONN_MANAGER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_CONN_MANAGER))
+#define CAMEL_IMAPX_CONN_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_CONN_MANAGER, CamelIMAPXConnManagerClass))
+
+struct _CamelIMAPXStore;
+
+typedef struct _CamelIMAPXConnManager CamelIMAPXConnManager;
+typedef struct _CamelIMAPXConnManagerClass CamelIMAPXConnManagerClass;
+typedef struct _CamelIMAPXConnManagerPrivate CamelIMAPXConnManagerPrivate;
+
+struct _CamelIMAPXConnManager {
+ GObject parent;
+
+ CamelIMAPXConnManagerPrivate *priv;
+};
+
+struct _CamelIMAPXConnManagerClass {
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (* connection_created) (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXServer *server);
+};
+
+GType camel_imapx_conn_manager_get_type (void);
+CamelIMAPXConnManager *
+ camel_imapx_conn_manager_new (CamelStore *store);
+struct _CamelIMAPXStore *
+ camel_imapx_conn_manager_ref_store
+ (CamelIMAPXConnManager *conn_man);
+gboolean camel_imapx_conn_manager_connect_sync
+ (CamelIMAPXConnManager *conn_man,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_disconnect_sync
+ (CamelIMAPXConnManager *conn_man,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_run_job_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXJob *job,
+ CamelIMAPXJobMatchesFunc finish_before_job,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_list_sync
+ (CamelIMAPXConnManager *conn_man,
+ const gchar *pattern,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_refresh_info_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_sync_changes_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_expunge_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+CamelStream * camel_imapx_conn_manager_get_message_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolderSummary *summary,
+ CamelDataCache *message_cache,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_copy_message_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ CamelIMAPXMailbox *destination,
+ GPtrArray *uids,
+ gboolean delete_originals,
+ gboolean remove_deleted_flags,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_append_message_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolderSummary *summary,
+ CamelDataCache *message_cache,
+ CamelMimeMessage *message,
+ const CamelMessageInfo *mi,
+ gchar **append_uid,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_sync_message_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolderSummary *summary,
+ CamelDataCache *message_cache,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_create_mailbox_sync
+ (CamelIMAPXConnManager *conn_man,
+ const gchar *mailbox_name,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_delete_mailbox_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_rename_mailbox_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ const gchar *new_mailbox_name,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_subscribe_mailbox_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_unsubscribe_mailbox_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_conn_manager_update_quota_info_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+GPtrArray * camel_imapx_conn_manager_uid_search_sync
+ (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXMailbox *mailbox,
+ const gchar *criteria_prefix,
+ const gchar *search_key,
+ const gchar * const *words,
+ GCancellable *cancellable,
+ GError **error);
+
+/* for debugging purposes only */
+void camel_imapx_conn_manager_dump_queue_status
+ (CamelIMAPXConnManager *conn_man);
+G_END_DECLS
+
+#endif /* _CAMEL_IMAPX_SERVER_H */
diff --git a/src/camel/providers/imapx/camel-imapx-folder.c b/src/camel/providers/imapx/camel-imapx-folder.c
new file mode 100644
index 000000000..0d7328762
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-folder.c
@@ -0,0 +1,1482 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-imap-folder.c : class for a imap folder
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-imapx-folder.h"
+#include "camel-imapx-search.h"
+#include "camel-imapx-server.h"
+#include "camel-imapx-settings.h"
+#include "camel-imapx-store.h"
+#include "camel-imapx-summary.h"
+#include "camel-imapx-utils.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#define d(...) camel_imapx_debug(debug, '?', __VA_ARGS__)
+
+#define CAMEL_IMAPX_FOLDER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_IMAPX_FOLDER, CamelIMAPXFolderPrivate))
+
+struct _CamelIMAPXFolderPrivate {
+ GMutex property_lock;
+ GWeakRef mailbox;
+
+ GMutex move_to_hash_table_lock;
+ GHashTable *move_to_real_junk_uids;
+ GHashTable *move_to_real_trash_uids;
+
+ gboolean check_folder;
+};
+
+/* The custom property ID is a CamelArg artifact.
+ * It still identifies the property in state files. */
+enum {
+ PROP_0,
+ PROP_MAILBOX,
+ PROP_APPLY_FILTERS = 0x2501,
+ PROP_CHECK_FOLDER = 0x2502
+};
+
+G_DEFINE_TYPE (CamelIMAPXFolder, camel_imapx_folder, CAMEL_TYPE_OFFLINE_FOLDER)
+
+static gboolean imapx_folder_get_apply_filters (CamelIMAPXFolder *folder);
+
+void
+camel_imapx_folder_claim_move_to_real_junk_uids (CamelIMAPXFolder *folder,
+ GPtrArray *out_uids_to_copy)
+{
+ GList *keys;
+
+ g_mutex_lock (&folder->priv->move_to_hash_table_lock);
+
+ keys = g_hash_table_get_keys (folder->priv->move_to_real_junk_uids);
+ g_hash_table_steal_all (folder->priv->move_to_real_junk_uids);
+
+ g_mutex_unlock (&folder->priv->move_to_hash_table_lock);
+
+ while (keys != NULL) {
+ g_ptr_array_add (out_uids_to_copy, keys->data);
+ keys = g_list_delete_link (keys, keys);
+ }
+}
+
+void
+camel_imapx_folder_claim_move_to_real_trash_uids (CamelIMAPXFolder *folder,
+ GPtrArray *out_uids_to_copy)
+{
+ GList *keys;
+
+ g_mutex_lock (&folder->priv->move_to_hash_table_lock);
+
+ keys = g_hash_table_get_keys (folder->priv->move_to_real_trash_uids);
+ g_hash_table_steal_all (folder->priv->move_to_real_trash_uids);
+
+ g_mutex_unlock (&folder->priv->move_to_hash_table_lock);
+
+ while (keys != NULL) {
+ g_ptr_array_add (out_uids_to_copy, keys->data);
+ keys = g_list_delete_link (keys, keys);
+ }
+}
+
+static gboolean
+imapx_folder_get_apply_filters (CamelIMAPXFolder *folder)
+{
+ g_return_val_if_fail (folder != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_FOLDER (folder), FALSE);
+
+ return folder->apply_filters;
+}
+
+static void
+imapx_folder_set_apply_filters (CamelIMAPXFolder *folder,
+ gboolean apply_filters)
+{
+ g_return_if_fail (folder != NULL);
+ g_return_if_fail (CAMEL_IS_IMAPX_FOLDER (folder));
+
+ if (folder->apply_filters == apply_filters)
+ return;
+
+ folder->apply_filters = apply_filters;
+
+ g_object_notify (G_OBJECT (folder), "apply-filters");
+}
+
+static void
+imapx_folder_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_APPLY_FILTERS:
+ imapx_folder_set_apply_filters (
+ CAMEL_IMAPX_FOLDER (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_CHECK_FOLDER:
+ camel_imapx_folder_set_check_folder (
+ CAMEL_IMAPX_FOLDER (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_MAILBOX:
+ camel_imapx_folder_set_mailbox (
+ CAMEL_IMAPX_FOLDER (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_folder_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_APPLY_FILTERS:
+ g_value_set_boolean (
+ value,
+ imapx_folder_get_apply_filters (
+ CAMEL_IMAPX_FOLDER (object)));
+ return;
+
+ case PROP_CHECK_FOLDER:
+ g_value_set_boolean (
+ value,
+ camel_imapx_folder_get_check_folder (
+ CAMEL_IMAPX_FOLDER (object)));
+ return;
+
+ case PROP_MAILBOX:
+ g_value_take_object (
+ value,
+ camel_imapx_folder_ref_mailbox (
+ CAMEL_IMAPX_FOLDER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_folder_dispose (GObject *object)
+{
+ CamelIMAPXFolder *folder = CAMEL_IMAPX_FOLDER (object);
+ CamelStore *store;
+
+ if (folder->cache != NULL) {
+ g_object_unref (folder->cache);
+ folder->cache = NULL;
+ }
+
+ if (folder->search != NULL) {
+ g_object_unref (folder->search);
+ folder->search = NULL;
+ }
+
+ store = camel_folder_get_parent_store (CAMEL_FOLDER (folder));
+ if (store != NULL) {
+ camel_store_summary_disconnect_folder_summary (
+ CAMEL_IMAPX_STORE (store)->summary,
+ CAMEL_FOLDER (folder)->summary);
+ }
+
+ g_weak_ref_set (&folder->priv->mailbox, NULL);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_imapx_folder_parent_class)->dispose (object);
+}
+
+static void
+imapx_folder_finalize (GObject *object)
+{
+ CamelIMAPXFolder *folder = CAMEL_IMAPX_FOLDER (object);
+
+ g_mutex_clear (&folder->search_lock);
+ g_mutex_clear (&folder->stream_lock);
+
+ g_mutex_clear (&folder->priv->property_lock);
+
+ g_mutex_clear (&folder->priv->move_to_hash_table_lock);
+ g_hash_table_destroy (folder->priv->move_to_real_junk_uids);
+ g_hash_table_destroy (folder->priv->move_to_real_trash_uids);
+
+ g_weak_ref_clear (&folder->priv->mailbox);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_imapx_folder_parent_class)->finalize (object);
+}
+
+/* Algorithm for selecting a folder:
+ *
+ * - If uidvalidity == old uidvalidity
+ * and exsists == old exists
+ * and recent == old recent
+ * and unseen == old unseen
+ * Assume our summary is correct
+ * for each summary item
+ * mark the summary item as 'old/not updated'
+ * rof
+ * fetch flags from 1:*
+ * for each fetch response
+ * info = summary[index]
+ * if info.uid != uid
+ * info = summary_by_uid[uid]
+ * fi
+ * if info == NULL
+ * create new info @ index
+ * fi
+ * if got.flags
+ * update flags
+ * fi
+ * if got.header
+ * update based on header
+ * mark as retrieved
+ * else if got.body
+ * update based on imap body
+ * mark as retrieved
+ * fi
+ *
+ * Async fetch response:
+ * info = summary[index]
+ * if info == null
+ * if uid == null
+ * force resync/select?
+ * info = empty @ index
+ * else if uid && info.uid != uid
+ * force a resync?
+ * return
+ * fi
+ *
+ * if got.flags {
+ * info.flags = flags
+ * }
+ * if got.header {
+ * info.init (header)
+ * info.empty = false
+ * }
+ *
+ * info.state - 2 bit field in flags
+ * 0 = empty, nothing set
+ * 1 = uid & flags set
+ * 2 = update required
+ * 3 = up to date
+ */
+
+static void
+imapx_search_free (CamelFolder *folder,
+ GPtrArray *uids)
+{
+ CamelIMAPXFolder *imapx_folder;
+
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+
+ g_return_if_fail (imapx_folder->search);
+
+ g_mutex_lock (&imapx_folder->search_lock);
+
+ camel_folder_search_free_result (imapx_folder->search, uids);
+
+ g_mutex_unlock (&imapx_folder->search_lock);
+}
+
+static GPtrArray *
+imapx_search_by_uids (CamelFolder *folder,
+ const gchar *expression,
+ GPtrArray *uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXFolder *imapx_folder;
+ CamelIMAPXSearch *imapx_search;
+ GPtrArray *matches;
+
+ if (uids->len == 0)
+ return g_ptr_array_new ();
+
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+
+ g_mutex_lock (&imapx_folder->search_lock);
+
+ imapx_search = CAMEL_IMAPX_SEARCH (imapx_folder->search);
+
+ camel_folder_search_set_folder (imapx_folder->search, folder);
+ camel_imapx_search_set_cancellable_and_error (imapx_search, cancellable, error);
+
+ matches = camel_folder_search_search (
+ imapx_folder->search, expression, uids, cancellable, error);
+
+ camel_imapx_search_set_cancellable_and_error (imapx_search, NULL, NULL);
+
+ g_mutex_unlock (&imapx_folder->search_lock);
+
+ return matches;
+}
+
+static guint32
+imapx_count_by_expression (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXFolder *imapx_folder;
+ CamelIMAPXSearch *imapx_search;
+ guint32 matches;
+
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+
+ g_mutex_lock (&imapx_folder->search_lock);
+
+ imapx_search = CAMEL_IMAPX_SEARCH (imapx_folder->search);
+
+ camel_folder_search_set_folder (imapx_folder->search, folder);
+ camel_imapx_search_set_cancellable_and_error (imapx_search, cancellable, error);
+
+ matches = camel_folder_search_count (
+ imapx_folder->search, expression, cancellable, error);
+
+ camel_imapx_search_set_cancellable_and_error (imapx_search, NULL, NULL);
+
+ g_mutex_unlock (&imapx_folder->search_lock);
+
+ return matches;
+}
+
+static GPtrArray *
+imapx_search_by_expression (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXFolder *imapx_folder;
+ CamelIMAPXSearch *imapx_search;
+ GPtrArray *matches;
+
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+
+ g_mutex_lock (&imapx_folder->search_lock);
+
+ imapx_search = CAMEL_IMAPX_SEARCH (imapx_folder->search);
+
+ camel_folder_search_set_folder (imapx_folder->search, folder);
+ camel_imapx_search_set_cancellable_and_error (imapx_search, cancellable, error);
+
+ matches = camel_folder_search_search (
+ imapx_folder->search, expression, NULL, cancellable, error);
+
+ camel_imapx_search_set_cancellable_and_error (imapx_search, NULL, NULL);
+
+ g_mutex_unlock (&imapx_folder->search_lock);
+
+ return matches;
+}
+
+static gchar *
+imapx_get_filename (CamelFolder *folder,
+ const gchar *uid,
+ GError **error)
+{
+ CamelIMAPXFolder *imapx_folder;
+
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+
+ return camel_data_cache_get_filename (
+ imapx_folder->cache, "cache", uid);
+}
+
+static gboolean
+imapx_append_message_sync (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *store;
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox = NULL;
+ gboolean success = FALSE;
+
+ if (appended_uid != NULL)
+ *appended_uid = NULL;
+
+ store = camel_folder_get_parent_store (folder);
+
+ imapx_store = CAMEL_IMAPX_STORE (store);
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+
+ mailbox = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (folder), cancellable, error);
+
+ if (mailbox == NULL)
+ goto exit;
+
+ success = camel_imapx_conn_manager_append_message_sync (
+ conn_man, mailbox, folder->summary,
+ CAMEL_IMAPX_FOLDER (folder)->cache, message,
+ info, appended_uid, cancellable, error);
+
+exit:
+ g_clear_object (&mailbox);
+
+ return success;
+}
+
+static gboolean
+imapx_expunge_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *store;
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox = NULL;
+ GError *local_error = NULL;
+ gboolean success = FALSE;
+
+ store = camel_folder_get_parent_store (folder);
+
+ imapx_store = CAMEL_IMAPX_STORE (store);
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+
+ mailbox = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (folder), cancellable, error);
+
+ if (mailbox == NULL)
+ goto exit;
+
+ if ((store->flags & CAMEL_STORE_VTRASH) == 0) {
+ CamelFolder *trash;
+ const gchar *full_name;
+
+ full_name = camel_folder_get_full_name (folder);
+
+ trash = camel_store_get_trash_folder_sync (store, cancellable, &local_error);
+
+ if (local_error == NULL && trash && (folder == trash || g_ascii_strcasecmp (full_name, camel_folder_get_full_name (trash)) == 0)) {
+ CamelMessageInfo *info;
+ GPtrArray *known_uids;
+ gint ii;
+
+ camel_folder_summary_lock (folder->summary);
+
+ camel_folder_summary_prepare_fetch_all (folder->summary, NULL);
+ known_uids = camel_folder_summary_get_array (folder->summary);
+
+ /* it's a real trash folder, thus delete all mails from there */
+ for (ii = 0; known_uids && ii < known_uids->len; ii++) {
+ info = camel_folder_summary_get (folder->summary, g_ptr_array_index (known_uids, ii));
+ if (info) {
+ camel_message_info_set_flags (info, CAMEL_MESSAGE_DELETED, CAMEL_MESSAGE_DELETED);
+ camel_message_info_unref (info);
+ }
+ }
+
+ camel_folder_summary_unlock (folder->summary);
+
+ camel_folder_summary_free_array (known_uids);
+ }
+
+ g_clear_object (&trash);
+ g_clear_error (&local_error);
+ }
+
+ success = camel_imapx_conn_manager_expunge_sync (conn_man, mailbox, cancellable, error);
+
+exit:
+ g_clear_object (&mailbox);
+
+ return success;
+}
+
+static CamelMimeMessage *
+imapx_get_message_cached (CamelFolder *folder,
+ const gchar *message_uid,
+ GCancellable *cancellable)
+{
+ CamelIMAPXFolder *imapx_folder;
+ CamelMimeMessage *msg = NULL;
+ CamelStream *stream = NULL;
+ GIOStream *base_stream;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_FOLDER (folder), NULL);
+ g_return_val_if_fail (message_uid != NULL, NULL);
+
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+
+ base_stream = camel_data_cache_get (imapx_folder->cache, "cur", message_uid, NULL);
+ if (base_stream != NULL) {
+ stream = camel_stream_new (base_stream);
+ g_object_unref (base_stream);
+ }
+
+ if (stream != NULL) {
+ gboolean success;
+
+ msg = camel_mime_message_new ();
+
+ g_mutex_lock (&imapx_folder->stream_lock);
+ success = camel_data_wrapper_construct_from_stream_sync (
+ CAMEL_DATA_WRAPPER (msg), stream, cancellable, NULL);
+ if (!success) {
+ g_object_unref (msg);
+ msg = NULL;
+ }
+ g_mutex_unlock (&imapx_folder->stream_lock);
+ g_object_unref (stream);
+ }
+
+ return msg;
+}
+
+static CamelMimeMessage *
+imapx_get_message_sync (CamelFolder *folder,
+ const gchar *uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimeMessage *msg = NULL;
+ CamelStream *stream;
+ CamelStore *store;
+ CamelIMAPXFolder *imapx_folder;
+ GIOStream *base_stream;
+ const gchar *path = NULL;
+ gboolean offline_message = FALSE;
+
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+ store = camel_folder_get_parent_store (folder);
+
+ if (!strchr (uid, '-'))
+ path = "cur";
+ else {
+ path = "new";
+ offline_message = TRUE;
+ }
+
+ base_stream = camel_data_cache_get (
+ imapx_folder->cache, path, uid, NULL);
+ if (base_stream != NULL) {
+ stream = camel_stream_new (base_stream);
+ g_object_unref (base_stream);
+ } else {
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox;
+
+ if (offline_message) {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID_UID,
+ "Offline message vanished from disk: %s", uid);
+ return NULL;
+ }
+
+ conn_man = camel_imapx_store_get_conn_manager (CAMEL_IMAPX_STORE (store));
+
+ mailbox = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (folder), cancellable, error);
+
+ if (mailbox == NULL)
+ return NULL;
+
+ stream = camel_imapx_conn_manager_get_message_sync (
+ conn_man, mailbox, folder->summary,
+ CAMEL_IMAPX_FOLDER (folder)->cache, uid,
+ cancellable, error);
+
+ g_clear_object (&mailbox);
+ }
+
+ if (stream != NULL) {
+ gboolean success;
+
+ msg = camel_mime_message_new ();
+
+ g_mutex_lock (&imapx_folder->stream_lock);
+ success = camel_data_wrapper_construct_from_stream_sync (
+ CAMEL_DATA_WRAPPER (msg), stream, cancellable, error);
+ if (!success) {
+ g_object_unref (msg);
+ msg = NULL;
+ }
+ g_mutex_unlock (&imapx_folder->stream_lock);
+ g_object_unref (stream);
+ }
+
+ if (msg != NULL) {
+ CamelMessageInfo *mi;
+
+ mi = camel_folder_summary_get (folder->summary, uid);
+ if (mi != NULL) {
+ CamelMessageFlags flags;
+ gboolean has_attachment;
+
+ flags = camel_message_info_get_flags (mi);
+ has_attachment = camel_mime_message_has_attachment (msg);
+ if (((flags & CAMEL_MESSAGE_ATTACHMENTS) && !has_attachment) ||
+ ((flags & CAMEL_MESSAGE_ATTACHMENTS) == 0 && has_attachment)) {
+ camel_message_info_set_flags (
+ mi, CAMEL_MESSAGE_ATTACHMENTS,
+ has_attachment ? CAMEL_MESSAGE_ATTACHMENTS : 0);
+ }
+
+ camel_message_info_unref (mi);
+ }
+ }
+
+ return msg;
+}
+
+static CamelFolderQuotaInfo *
+imapx_get_quota_info_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *store;
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox = NULL;
+ CamelFolderQuotaInfo *quota_info = NULL;
+ gchar **quota_roots;
+ gboolean success = FALSE;
+
+ store = camel_folder_get_parent_store (folder);
+
+ imapx_store = CAMEL_IMAPX_STORE (store);
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+
+ mailbox = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (folder), cancellable, error);
+ if (mailbox == NULL)
+ goto exit;
+
+ success = camel_imapx_conn_manager_update_quota_info_sync (conn_man, mailbox, cancellable, error);
+
+ if (!success)
+ goto exit;
+
+ quota_roots = camel_imapx_mailbox_dup_quota_roots (mailbox);
+
+ /* XXX Just return info for the first quota root, I guess. */
+ if (quota_roots != NULL && quota_roots[0] != NULL) {
+ quota_info = camel_imapx_store_dup_quota_info (
+ CAMEL_IMAPX_STORE (store), quota_roots[0]);
+ }
+
+ g_strfreev (quota_roots);
+
+ if (quota_info == NULL)
+ g_set_error (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ _("No quota information available for folder '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (store)),
+ camel_folder_get_full_name (folder));
+
+exit:
+ g_clear_object (&mailbox);
+
+ return quota_info;
+}
+
+static gboolean
+imapx_purge_message_cache_sync (CamelFolder *folder,
+ gchar *start_uid,
+ gchar *end_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* Not Implemented for now. */
+ return TRUE;
+}
+
+static gboolean
+imapx_refresh_info_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *store;
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox = NULL;
+ gboolean success = FALSE;
+
+ store = camel_folder_get_parent_store (folder);
+
+ /* Not connected, thus skip the operation */
+ if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store)))
+ return TRUE;
+
+ imapx_store = CAMEL_IMAPX_STORE (store);
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+
+ mailbox = camel_imapx_folder_list_mailbox (CAMEL_IMAPX_FOLDER (folder), cancellable, error);
+
+ if (mailbox) {
+ success = camel_imapx_conn_manager_refresh_info_sync (conn_man, mailbox, cancellable, error);
+ }
+
+ g_clear_object (&mailbox);
+
+ return success;
+}
+
+static gboolean
+imapx_synchronize_sync (CamelFolder *folder,
+ gboolean expunge,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *store;
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox = NULL;
+ gboolean success = FALSE;
+
+ store = camel_folder_get_parent_store (folder);
+
+ /* Not connected, thus skip the operation */
+ if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store)))
+ return TRUE;
+
+ imapx_store = CAMEL_IMAPX_STORE (store);
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+
+ mailbox = camel_imapx_folder_list_mailbox (CAMEL_IMAPX_FOLDER (folder), cancellable, error);
+
+ /* Do not update mailboxes on exit which were not entered yet */
+ if (mailbox == NULL || (camel_application_is_exiting &&
+ camel_imapx_mailbox_get_permanentflags (mailbox) == ~0)) {
+ success = mailbox != NULL;
+ } else {
+ success = camel_imapx_conn_manager_sync_changes_sync (conn_man, mailbox, cancellable, error);
+ if (success && expunge && camel_folder_summary_get_deleted_count (folder->summary) > 0) {
+ success = camel_imapx_conn_manager_expunge_sync (conn_man, mailbox, cancellable, error);
+ }
+ }
+
+ g_clear_object (&mailbox);
+
+ return success;
+}
+
+static gboolean
+imapx_synchronize_message_sync (CamelFolder *folder,
+ const gchar *uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *store;
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox = NULL;
+ gboolean success = FALSE;
+
+ store = camel_folder_get_parent_store (folder);
+
+ imapx_store = CAMEL_IMAPX_STORE (store);
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+
+ mailbox = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (folder), cancellable, error);
+
+ if (mailbox == NULL)
+ goto exit;
+
+ success = camel_imapx_conn_manager_sync_message_sync (
+ conn_man, mailbox, folder->summary,
+ CAMEL_IMAPX_FOLDER (folder)->cache, uid,
+ cancellable, error);
+
+exit:
+ g_clear_object (&mailbox);
+
+ return success;
+}
+
+static gboolean
+imapx_transfer_messages_to_sync (CamelFolder *source,
+ GPtrArray *uids,
+ CamelFolder *dest,
+ gboolean delete_originals,
+ GPtrArray **transferred_uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *store;
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *src_mailbox = NULL;
+ CamelIMAPXMailbox *dst_mailbox = NULL;
+ gboolean success = FALSE;
+
+ store = camel_folder_get_parent_store (source);
+
+ imapx_store = CAMEL_IMAPX_STORE (store);
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+
+ src_mailbox = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (source), cancellable, error);
+
+ if (src_mailbox == NULL)
+ goto exit;
+
+ dst_mailbox = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (dest), cancellable, error);
+
+ if (dst_mailbox == NULL)
+ goto exit;
+
+ success = camel_imapx_conn_manager_copy_message_sync (
+ conn_man, src_mailbox, dst_mailbox, uids,
+ delete_originals, FALSE, cancellable, error);
+
+exit:
+ g_clear_object (&src_mailbox);
+ g_clear_object (&dst_mailbox);
+
+ return success;
+}
+
+static void
+imapx_folder_changed (CamelFolder *folder,
+ CamelFolderChangeInfo *info)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_FOLDER (folder));
+
+ if (info && info->uid_removed && info->uid_removed->len) {
+ CamelIMAPXFolder *imapx_folder;
+ guint ii;
+
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+
+ g_mutex_lock (&imapx_folder->priv->move_to_hash_table_lock);
+
+ for (ii = 0; ii < info->uid_removed->len; ii++) {
+ const gchar *message_uid = info->uid_removed->pdata[ii];
+
+ if (!message_uid)
+ continue;
+
+ g_hash_table_remove (imapx_folder->priv->move_to_real_trash_uids, message_uid);
+ g_hash_table_remove (imapx_folder->priv->move_to_real_junk_uids, message_uid);
+ }
+
+ g_mutex_unlock (&imapx_folder->priv->move_to_hash_table_lock);
+ }
+
+ /* Chain up to parent's method. */
+ CAMEL_FOLDER_CLASS (camel_imapx_folder_parent_class)->changed (folder, info);
+}
+
+static void
+imapx_rename (CamelFolder *folder,
+ const gchar *new_name)
+{
+ CamelStore *store;
+ CamelIMAPXStore *imapx_store;
+ const gchar *folder_name;
+
+ store = camel_folder_get_parent_store (folder);
+ imapx_store = CAMEL_IMAPX_STORE (store);
+
+ camel_store_summary_disconnect_folder_summary (
+ imapx_store->summary, folder->summary);
+
+ /* Chain up to parent's rename() method. */
+ CAMEL_FOLDER_CLASS (camel_imapx_folder_parent_class)->
+ rename (folder, new_name);
+
+ folder_name = camel_folder_get_full_name (folder);
+
+ camel_store_summary_connect_folder_summary (
+ imapx_store->summary, folder_name, folder->summary);
+}
+
+static void
+camel_imapx_folder_class_init (CamelIMAPXFolderClass *class)
+{
+ GObjectClass *object_class;
+ CamelFolderClass *folder_class;
+
+ g_type_class_add_private (class, sizeof (CamelIMAPXFolderPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = imapx_folder_set_property;
+ object_class->get_property = imapx_folder_get_property;
+ object_class->dispose = imapx_folder_dispose;
+ object_class->finalize = imapx_folder_finalize;
+
+ folder_class = CAMEL_FOLDER_CLASS (class);
+ folder_class->rename = imapx_rename;
+ folder_class->search_by_expression = imapx_search_by_expression;
+ folder_class->search_by_uids = imapx_search_by_uids;
+ folder_class->count_by_expression = imapx_count_by_expression;
+ folder_class->search_free = imapx_search_free;
+ folder_class->get_filename = imapx_get_filename;
+ folder_class->append_message_sync = imapx_append_message_sync;
+ folder_class->expunge_sync = imapx_expunge_sync;
+ folder_class->get_message_cached = imapx_get_message_cached;
+ folder_class->get_message_sync = imapx_get_message_sync;
+ folder_class->get_quota_info_sync = imapx_get_quota_info_sync;
+ folder_class->purge_message_cache_sync = imapx_purge_message_cache_sync;
+ folder_class->refresh_info_sync = imapx_refresh_info_sync;
+ folder_class->synchronize_sync = imapx_synchronize_sync;
+ folder_class->synchronize_message_sync = imapx_synchronize_message_sync;
+ folder_class->transfer_messages_to_sync = imapx_transfer_messages_to_sync;
+ folder_class->changed = imapx_folder_changed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_APPLY_FILTERS,
+ g_param_spec_boolean (
+ "apply-filters",
+ "Apply Filters",
+ _("Apply message _filters to this folder"),
+ FALSE,
+ G_PARAM_READWRITE |
+ CAMEL_PARAM_PERSISTENT));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CHECK_FOLDER,
+ g_param_spec_boolean (
+ "check-folder",
+ "Check Folder",
+ _("Always check for _new mail in this folder"),
+ FALSE,
+ G_PARAM_READWRITE |
+ CAMEL_PARAM_PERSISTENT));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_MAILBOX,
+ g_param_spec_object (
+ "mailbox",
+ "Mailbox",
+ "IMAP mailbox for this folder",
+ CAMEL_TYPE_IMAPX_MAILBOX,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_imapx_folder_init (CamelIMAPXFolder *imapx_folder)
+{
+ CamelFolder *folder = CAMEL_FOLDER (imapx_folder);
+ GHashTable *move_to_real_junk_uids;
+ GHashTable *move_to_real_trash_uids;
+
+ move_to_real_junk_uids = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) camel_pstring_free,
+ (GDestroyNotify) NULL);
+
+ move_to_real_trash_uids = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) camel_pstring_free,
+ (GDestroyNotify) NULL);
+
+ imapx_folder->priv = CAMEL_IMAPX_FOLDER_GET_PRIVATE (imapx_folder);
+
+ folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY;
+
+ folder->permanent_flags =
+ CAMEL_MESSAGE_ANSWERED |
+ CAMEL_MESSAGE_DELETED |
+ CAMEL_MESSAGE_DRAFT |
+ CAMEL_MESSAGE_FLAGGED |
+ CAMEL_MESSAGE_SEEN |
+ CAMEL_MESSAGE_USER;
+
+ camel_folder_set_lock_async (folder, TRUE);
+
+ g_mutex_init (&imapx_folder->priv->property_lock);
+
+ g_mutex_init (&imapx_folder->priv->move_to_hash_table_lock);
+ imapx_folder->priv->move_to_real_junk_uids = move_to_real_junk_uids;
+ imapx_folder->priv->move_to_real_trash_uids = move_to_real_trash_uids;
+
+ g_mutex_init (&imapx_folder->search_lock);
+ g_mutex_init (&imapx_folder->stream_lock);
+
+ g_weak_ref_init (&imapx_folder->priv->mailbox, NULL);
+}
+
+CamelFolder *
+camel_imapx_folder_new (CamelStore *store,
+ const gchar *folder_dir,
+ const gchar *folder_name,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelService *service;
+ CamelSettings *settings;
+ CamelIMAPXFolder *imapx_folder;
+ const gchar *short_name;
+ gchar *state_file;
+ gboolean filter_all;
+ gboolean filter_inbox;
+ gboolean filter_junk;
+ gboolean filter_junk_inbox;
+
+ d ("opening imap folder '%s'\n", folder_dir);
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ g_object_get (
+ settings,
+ "filter-all", &filter_all,
+ "filter-inbox", &filter_inbox,
+ "filter-junk", &filter_junk,
+ "filter-junk-inbox", &filter_junk_inbox,
+ NULL);
+
+ g_object_unref (settings);
+
+ short_name = strrchr (folder_name, '/');
+ if (short_name)
+ short_name++;
+ else
+ short_name = folder_name;
+
+ folder = g_object_new (
+ CAMEL_TYPE_IMAPX_FOLDER,
+ "display-name", short_name,
+ "full_name", folder_name,
+ "parent-store", store, NULL);
+
+ folder->summary = camel_imapx_summary_new (folder);
+ if (folder->summary == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Could not create folder summary for %s"),
+ short_name);
+ return NULL;
+ }
+
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+ imapx_folder->cache = camel_data_cache_new (folder_dir, error);
+ if (imapx_folder->cache == NULL) {
+ g_prefix_error (
+ error, _("Could not create cache for %s: "),
+ short_name);
+ return NULL;
+ }
+
+ state_file = g_build_filename (folder_dir, "cmeta", NULL);
+ camel_object_set_state_filename (CAMEL_OBJECT (folder), state_file);
+ g_free (state_file);
+ camel_object_state_read (CAMEL_OBJECT (folder));
+
+ if (camel_offline_folder_can_downsync (CAMEL_OFFLINE_FOLDER (folder))) {
+ /* Ensure cache will never expire, otherwise
+ * it causes redownload of messages. */
+ camel_data_cache_set_expire_age (imapx_folder->cache, -1);
+ camel_data_cache_set_expire_access (imapx_folder->cache, -1);
+ } else {
+ /* Set cache expiration for one week. */
+ camel_data_cache_set_expire_age (imapx_folder->cache, 60 * 60 * 24 * 7);
+ camel_data_cache_set_expire_access (imapx_folder->cache, 60 * 60 * 24 * 7);
+ }
+
+ imapx_folder->search = camel_imapx_search_new (CAMEL_IMAPX_STORE (store));
+
+ if (filter_all)
+ folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
+
+ if (camel_imapx_mailbox_is_inbox (folder_name)) {
+ if (filter_inbox)
+ folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
+
+ if (filter_junk)
+ folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
+ } else {
+ if (filter_junk && !filter_junk_inbox)
+ folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
+
+ if (imapx_folder_get_apply_filters (imapx_folder))
+ folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
+ }
+
+ camel_store_summary_connect_folder_summary (
+ CAMEL_IMAPX_STORE (store)->summary,
+ folder_name, folder->summary);
+
+ return folder;
+}
+
+/**
+ * camel_imapx_folder_ref_mailbox:
+ * @folder: a #CamelIMAPXFolder
+ *
+ * Returns the #CamelIMAPXMailbox for @folder from the current IMAP server
+ * connection, or %NULL if @folder's #CamelFolder:parent-store is disconnected
+ * from the IMAP server.
+ *
+ * The returned #CamelIMAPXMailbox is referenced for thread-safety and
+ * should be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelIMAPXMailbox, or %NULL
+ *
+ * Since: 3.12
+ **/
+CamelIMAPXMailbox *
+camel_imapx_folder_ref_mailbox (CamelIMAPXFolder *folder)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_FOLDER (folder), NULL);
+
+ return g_weak_ref_get (&folder->priv->mailbox);
+}
+
+/**
+ * camel_imapx_folder_set_mailbox:
+ * @folder: a #CamelIMAPXFolder
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Sets the #CamelIMAPXMailbox for @folder from the current IMAP server
+ * connection. Note that #CamelIMAPXFolder only holds a weak reference
+ * on its #CamelIMAPXMailbox so that when the IMAP server connection is
+ * lost, all mailbox instances are automatically destroyed.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_folder_set_mailbox (CamelIMAPXFolder *folder,
+ CamelIMAPXMailbox *mailbox)
+{
+ CamelIMAPXSummary *imapx_summary;
+ guint32 uidvalidity;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_FOLDER (folder));
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ g_weak_ref_set (&folder->priv->mailbox, mailbox);
+
+ imapx_summary = CAMEL_IMAPX_SUMMARY (CAMEL_FOLDER (folder)->summary);
+ uidvalidity = camel_imapx_mailbox_get_uidvalidity (mailbox);
+
+ if (uidvalidity > 0 && uidvalidity != imapx_summary->validity)
+ camel_imapx_folder_invalidate_local_cache (folder, uidvalidity);
+
+ g_object_notify (G_OBJECT (folder), "mailbox");
+}
+
+/**
+ * camel_imapx_folder_list_mailbox:
+ * @folder: a #CamelIMAPXFolder
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Ensures that @folder's #CamelIMAPXFolder:mailbox property is set,
+ * going so far as to issue a LIST command if necessary (but should
+ * be a rarely needed last resort).
+ *
+ * If @folder's #CamelFolder:parent-store is disconnected from the IMAP
+ * server or an error occurs during the LIST command, the function sets
+ * @error and returns %NULL.
+ *
+ * The returned #CamelIMAPXMailbox is referenced for thread-safety and
+ * should be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelIMAPXMailbox, or %NULL
+ *
+ * Since: 3.12
+ **/
+CamelIMAPXMailbox *
+camel_imapx_folder_list_mailbox (CamelIMAPXFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox;
+ CamelStore *parent_store;
+ CamelStoreInfo *store_info;
+ CamelIMAPXStoreInfo *imapx_store_info;
+ gchar *folder_path = NULL;
+ gchar *mailbox_name = NULL;
+ gchar *pattern;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_FOLDER (folder), FALSE);
+
+ /* First check if we already have a mailbox. */
+
+ mailbox = camel_imapx_folder_ref_mailbox (folder);
+ if (mailbox != NULL)
+ goto exit;
+
+ /* Obtain the mailbox name from the store summary. */
+
+ folder_path = camel_folder_dup_full_name (CAMEL_FOLDER (folder));
+ parent_store = camel_folder_get_parent_store (CAMEL_FOLDER (folder));
+
+ imapx_store = CAMEL_IMAPX_STORE (parent_store);
+
+ store_info = camel_store_summary_path (
+ imapx_store->summary, folder_path);
+
+ /* This should never fail. We needed the CamelStoreInfo
+ * to instantiate the CamelIMAPXFolder in the first place. */
+ g_return_val_if_fail (store_info != NULL, FALSE);
+
+ imapx_store_info = (CamelIMAPXStoreInfo *) store_info;
+ mailbox_name = g_strdup (imapx_store_info->mailbox_name);
+
+ camel_store_summary_info_unref (imapx_store->summary, store_info);
+
+ /* See if the CamelIMAPXStore already has the mailbox. */
+
+ mailbox = camel_imapx_store_ref_mailbox (imapx_store, mailbox_name);
+ if (mailbox != NULL) {
+ camel_imapx_folder_set_mailbox (folder, mailbox);
+ goto exit;
+ }
+
+ /* Last resort is to issue a LIST command. Maintainer should
+ * monitor IMAP logs to make sure this is rarely if ever used. */
+
+ pattern = camel_utf8_utf7 (mailbox_name);
+
+ /* This creates a mailbox instance from the LIST response. */
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+ success = camel_imapx_conn_manager_list_sync (conn_man, pattern, 0, cancellable, error);
+
+ g_free (pattern);
+
+ if (!success)
+ goto exit;
+
+ /* This might still return NULL if the mailbox has a
+ * /NonExistent attribute. Otherwise this should work. */
+ mailbox = camel_imapx_store_ref_mailbox (imapx_store, mailbox_name);
+ if (mailbox != NULL) {
+ camel_imapx_folder_set_mailbox (folder, mailbox);
+ } else {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID_STATE,
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ _("No IMAP mailbox available for folder '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (parent_store)),
+ camel_folder_get_full_name (CAMEL_FOLDER (folder)));
+ }
+
+exit:
+ g_free (folder_path);
+ g_free (mailbox_name);
+
+ return mailbox;
+}
+
+/**
+ * camel_imapx_folder_copy_message_map:
+ * @folder: a #CamelIMAPXFolder
+ *
+ * Returns a #GSequence of 32-bit integers representing the locally cached
+ * mapping of message sequence numbers to unique identifiers.
+ *
+ * Free the returns #GSequence with g_sequence_free().
+ *
+ * Returns: a #GSequence
+ *
+ * Since: 3.12
+ **/
+GSequence *
+camel_imapx_folder_copy_message_map (CamelIMAPXFolder *folder)
+{
+ CamelFolderSummary *summary;
+ GSequence *message_map;
+ GPtrArray *array;
+ guint ii;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_FOLDER (folder), NULL);
+
+ summary = CAMEL_FOLDER (folder)->summary;
+ array = camel_folder_summary_get_array (summary);
+ camel_folder_sort_uids (CAMEL_FOLDER (folder), array);
+
+ message_map = g_sequence_new (NULL);
+
+ for (ii = 0; ii < array->len; ii++) {
+ guint32 uid = strtoul (array->pdata[ii], NULL, 10);
+ g_sequence_append (message_map, GUINT_TO_POINTER (uid));
+ }
+
+ camel_folder_summary_free_array (array);
+
+ return message_map;
+}
+
+/**
+ * camel_imapx_folder_add_move_to_real_junk:
+ * @folder: a #CamelIMAPXFolder
+ * @message_uid: a message UID
+ *
+ * Adds @message_uid to a pool of messages to be moved to a real junk
+ * folder the next time @folder is explicitly synchronized by way of
+ * camel_folder_synchronize() or camel_folder_synchronize_sync().
+ *
+ * This only applies when using a real folder to track junk messages,
+ * as specified by the #CamelIMAPXSettings:use-real-junk-path setting.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_folder_add_move_to_real_junk (CamelIMAPXFolder *folder,
+ const gchar *message_uid)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_FOLDER (folder));
+ g_return_if_fail (message_uid != NULL);
+ g_return_if_fail (camel_folder_summary_check_uid (CAMEL_FOLDER (folder)->summary, message_uid));
+
+ g_mutex_lock (&folder->priv->move_to_hash_table_lock);
+
+ g_hash_table_add (
+ folder->priv->move_to_real_junk_uids,
+ (gpointer) camel_pstring_strdup (message_uid));
+
+ g_mutex_unlock (&folder->priv->move_to_hash_table_lock);
+}
+
+/**
+ * camel_imapx_folder_add_move_to_real_trash:
+ * @folder: a #CamelIMAPXFolder
+ * @message_uid: a message UID
+ *
+ * Adds @message_uid to a pool of messages to be moved to a real trash
+ * folder the next time @folder is explicitly synchronized by way of
+ * camel_folder_synchronize() or camel_folder_synchronize_sync().
+ *
+ * This only applies when using a real folder to track deleted messages,
+ * as specified by the #CamelIMAPXSettings:use-real-trash-path setting.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_folder_add_move_to_real_trash (CamelIMAPXFolder *folder,
+ const gchar *message_uid)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_FOLDER (folder));
+ g_return_if_fail (message_uid != NULL);
+ g_return_if_fail (camel_folder_summary_check_uid (CAMEL_FOLDER (folder)->summary, message_uid));
+
+ g_mutex_lock (&folder->priv->move_to_hash_table_lock);
+
+ g_hash_table_add (
+ folder->priv->move_to_real_trash_uids,
+ (gpointer) camel_pstring_strdup (message_uid));
+
+ g_mutex_unlock (&folder->priv->move_to_hash_table_lock);
+}
+
+/**
+ * camel_imapx_folder_invalidate_local_cache:
+ * @folder: a #CamelIMAPXFolder
+ * @new_uidvalidity: the new UIDVALIDITY value
+ *
+ * Call this function when the IMAP server reports a different UIDVALIDITY
+ * value than what is presently cached. This means all cached message UIDs
+ * are now invalid and must be discarded.
+ *
+ * The local cache for @folder is reset and the @new_uidvalidity value is
+ * recorded in the newly-reset cache.
+ *
+ * Since: 3.10
+ **/
+void
+camel_imapx_folder_invalidate_local_cache (CamelIMAPXFolder *folder,
+ guint64 new_uidvalidity)
+{
+ CamelFolderSummary *summary;
+ CamelFolderChangeInfo *changes;
+ GPtrArray *array;
+ guint ii;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_FOLDER (folder));
+ g_return_if_fail (new_uidvalidity > 0);
+
+ summary = CAMEL_FOLDER (folder)->summary;
+
+ changes = camel_folder_change_info_new ();
+ array = camel_folder_summary_get_array (summary);
+
+ for (ii = 0; ii < array->len; ii++) {
+ const gchar *uid = array->pdata[ii];
+ camel_folder_change_info_change_uid (changes, uid);
+ }
+
+ CAMEL_IMAPX_SUMMARY (summary)->validity = new_uidvalidity;
+ camel_folder_summary_touch (summary);
+ camel_folder_summary_save_to_db (summary, NULL);
+
+ camel_data_cache_clear (folder->cache, "cache");
+ camel_data_cache_clear (folder->cache, "cur");
+
+ camel_folder_changed (CAMEL_FOLDER (folder), changes);
+
+ camel_folder_change_info_free (changes);
+ camel_folder_summary_free_array (array);
+}
+
+gboolean
+camel_imapx_folder_get_check_folder (CamelIMAPXFolder *folder)
+{
+ g_return_val_if_fail (folder != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_FOLDER (folder), FALSE);
+
+ return folder->priv->check_folder;
+}
+
+void
+camel_imapx_folder_set_check_folder (CamelIMAPXFolder *folder,
+ gboolean check_folder)
+{
+ g_return_if_fail (folder != NULL);
+ g_return_if_fail (CAMEL_IS_IMAPX_FOLDER (folder));
+
+ if (folder->priv->check_folder == check_folder)
+ return;
+
+ folder->priv->check_folder = check_folder;
+
+ g_object_notify (G_OBJECT (folder), "check-folder");
+}
diff --git a/src/camel/providers/imapx/camel-imapx-folder.h b/src/camel/providers/imapx/camel-imapx-folder.h
new file mode 100644
index 000000000..12c3d9b31
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-folder.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-imap-folder.h : Class for a IMAP folder
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_IMAPX_FOLDER_H
+#define CAMEL_IMAPX_FOLDER_H
+
+#include <camel/camel.h>
+
+#include "camel-imapx-mailbox.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_FOLDER \
+ (camel_imapx_folder_get_type ())
+#define CAMEL_IMAPX_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_FOLDER, CamelIMAPXFolder))
+#define CAMEL_IMAPX_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_FOLDER, CamelIMAPXFolderClass))
+#define CAMEL_IS_IMAPX_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_FOLDER))
+#define CAMEL_IS_IMAPX_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_FOLDER))
+#define CAMEL_IMAPX_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_FOLDER, CamelIMAPXFolderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAPXFolder CamelIMAPXFolder;
+typedef struct _CamelIMAPXFolderClass CamelIMAPXFolderClass;
+typedef struct _CamelIMAPXFolderPrivate CamelIMAPXFolderPrivate;
+
+struct _CamelIMAPXFolder {
+ CamelOfflineFolder parent;
+ CamelIMAPXFolderPrivate *priv;
+
+ CamelDataCache *cache;
+ CamelFolderSearch *search;
+
+ GMutex search_lock;
+ GMutex stream_lock;
+
+ gboolean apply_filters; /* persistent property */
+};
+
+struct _CamelIMAPXFolderClass {
+ CamelOfflineFolderClass parent_class;
+};
+
+GType camel_imapx_folder_get_type (void);
+CamelFolder * camel_imapx_folder_new (CamelStore *parent,
+ const gchar *path,
+ const gchar *raw,
+ GError **error);
+CamelIMAPXMailbox *
+ camel_imapx_folder_ref_mailbox (CamelIMAPXFolder *folder);
+void camel_imapx_folder_set_mailbox (CamelIMAPXFolder *folder,
+ CamelIMAPXMailbox *mailbox);
+CamelIMAPXMailbox *
+ camel_imapx_folder_list_mailbox (CamelIMAPXFolder *folder,
+ GCancellable *cancellable,
+ GError **error);
+GSequence * camel_imapx_folder_copy_message_map
+ (CamelIMAPXFolder *folder);
+void camel_imapx_folder_add_move_to_real_junk
+ (CamelIMAPXFolder *folder,
+ const gchar *message_uid);
+void camel_imapx_folder_add_move_to_real_trash
+ (CamelIMAPXFolder *folder,
+ const gchar *message_uid);
+void camel_imapx_folder_invalidate_local_cache
+ (CamelIMAPXFolder *folder,
+ guint64 new_uidvalidity);
+gboolean camel_imapx_folder_get_check_folder
+ (CamelIMAPXFolder *folder);
+void camel_imapx_folder_set_check_folder
+ (CamelIMAPXFolder *folder,
+ gboolean check_folder);
+void camel_imapx_folder_claim_move_to_real_junk_uids
+ (CamelIMAPXFolder *folder,
+ GPtrArray *out_uids_to_copy);
+void camel_imapx_folder_claim_move_to_real_trash_uids
+ (CamelIMAPXFolder *folder,
+ GPtrArray *out_uids_to_copy);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_FOLDER_H */
diff --git a/src/camel/providers/imapx/camel-imapx-input-stream.c b/src/camel/providers/imapx/camel-imapx-input-stream.c
new file mode 100644
index 000000000..b71bd74b0
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-input-stream.c
@@ -0,0 +1,1053 @@
+/*
+ * camel-imapx-input-stream.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel.h>
+
+#include "camel-imapx-server.h"
+#include "camel-imapx-utils.h"
+
+#include "camel-imapx-input-stream.h"
+
+#define CAMEL_IMAPX_INPUT_STREAM_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_IMAPX_INPUT_STREAM, CamelIMAPXInputStreamPrivate))
+
+struct _CamelIMAPXInputStreamPrivate {
+ guchar *buf, *ptr, *end;
+ guint literal;
+
+ guint unget;
+ camel_imapx_token_t unget_tok;
+ guchar *unget_token;
+ guint unget_len;
+
+ guchar *tokenbuf;
+ guint bufsize;
+};
+
+/* Forward Declarations */
+static void camel_imapx_input_stream_pollable_init
+ (GPollableInputStreamInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ CamelIMAPXInputStream,
+ camel_imapx_input_stream,
+ G_TYPE_FILTER_INPUT_STREAM,
+ G_IMPLEMENT_INTERFACE (
+ G_TYPE_POLLABLE_INPUT_STREAM,
+ camel_imapx_input_stream_pollable_init))
+
+static gint
+imapx_input_stream_fill (CamelIMAPXInputStream *is,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GInputStream *base_stream;
+ gint left = 0;
+
+ if (g_cancellable_is_cancelled (cancellable))
+ return -1;
+
+ base_stream = g_filter_input_stream_get_base_stream (
+ G_FILTER_INPUT_STREAM (is));
+
+ if (error && *error) {
+ g_warning ("%s: Avoiding GIO call with a filled error '%s'", G_STRFUNC, (*error)->message);
+ error = NULL;
+ }
+
+ left = is->priv->end - is->priv->ptr;
+ memcpy (is->priv->buf, is->priv->ptr, left);
+ is->priv->end = is->priv->buf + left;
+ is->priv->ptr = is->priv->buf;
+ left = g_input_stream_read (
+ base_stream,
+ is->priv->end,
+ is->priv->bufsize - (is->priv->end - is->priv->buf),
+ cancellable, error);
+ if (left > 0) {
+ is->priv->end += left;
+ return is->priv->end - is->priv->ptr;
+ } else {
+ /* If returning zero, camel_stream_read() doesn't consider
+ * that to be an error. But we do -- we should only be here
+ * if we *know* there are data to receive. So set the error
+ * accordingly */
+ if (!left)
+ g_set_error (
+ error, CAMEL_IMAPX_SERVER_ERROR, CAMEL_IMAPX_SERVER_ERROR_TRY_RECONNECT,
+ _("Source stream returned no data"));
+ return -1;
+ }
+}
+
+static void
+imapx_input_stream_finalize (GObject *object)
+{
+ CamelIMAPXInputStreamPrivate *priv;
+
+ priv = CAMEL_IMAPX_INPUT_STREAM_GET_PRIVATE (object);
+
+ g_free (priv->buf);
+ g_free (priv->tokenbuf);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_imapx_input_stream_parent_class)->
+ finalize (object);
+}
+
+static gssize
+imapx_input_stream_read (GInputStream *stream,
+ gpointer buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXInputStreamPrivate *priv;
+ GInputStream *base_stream;
+ gssize max;
+
+ priv = CAMEL_IMAPX_INPUT_STREAM_GET_PRIVATE (stream);
+
+ base_stream = g_filter_input_stream_get_base_stream (
+ G_FILTER_INPUT_STREAM (stream));
+
+ if (priv->literal == 0 || count == 0)
+ return 0;
+
+ max = priv->end - priv->ptr;
+ if (max > 0) {
+ max = MIN (max, priv->literal);
+ max = MIN (max, count);
+ memcpy (buffer, priv->ptr, max);
+ priv->ptr += max;
+ } else {
+ if (error && *error) {
+ g_warning ("%s: Avoiding GIO call with a filled error '%s'", G_STRFUNC, (*error)->message);
+ error = NULL;
+ }
+
+ max = MIN (priv->literal, count);
+ max = g_input_stream_read (
+ base_stream, buffer, max, cancellable, error);
+ if (max <= 0)
+ return max;
+ }
+
+ priv->literal -= max;
+
+ return max;
+}
+
+static gboolean
+imapx_input_stream_can_poll (GPollableInputStream *pollable_stream)
+{
+ GInputStream *base_stream;
+
+ base_stream = g_filter_input_stream_get_base_stream (
+ G_FILTER_INPUT_STREAM (pollable_stream));
+
+ pollable_stream = G_POLLABLE_INPUT_STREAM (base_stream);
+
+ return g_pollable_input_stream_can_poll (pollable_stream);
+}
+
+static gboolean
+imapx_input_stream_is_readable (GPollableInputStream *pollable_stream)
+{
+ GInputStream *base_stream;
+
+ base_stream = g_filter_input_stream_get_base_stream (
+ G_FILTER_INPUT_STREAM (pollable_stream));
+
+ pollable_stream = G_POLLABLE_INPUT_STREAM (base_stream);
+
+ return g_pollable_input_stream_is_readable (pollable_stream);
+}
+
+static GSource *
+imapx_input_stream_create_source (GPollableInputStream *pollable_stream,
+ GCancellable *cancellable)
+{
+ GInputStream *base_stream;
+
+ base_stream = g_filter_input_stream_get_base_stream (
+ G_FILTER_INPUT_STREAM (pollable_stream));
+
+ pollable_stream = G_POLLABLE_INPUT_STREAM (base_stream);
+
+ return g_pollable_input_stream_create_source (
+ pollable_stream, cancellable);
+}
+
+static gssize
+imapx_input_stream_read_nonblocking (GPollableInputStream *pollable_stream,
+ gpointer buffer,
+ gsize count,
+ GError **error)
+{
+ GInputStream *base_stream;
+
+ base_stream = g_filter_input_stream_get_base_stream (
+ G_FILTER_INPUT_STREAM (pollable_stream));
+
+ pollable_stream = G_POLLABLE_INPUT_STREAM (base_stream);
+
+ if (error && *error) {
+ g_warning ("%s: Avoiding GIO call with a filled error '%s'", G_STRFUNC, (*error)->message);
+ error = NULL;
+ }
+
+ /* XXX The function takes a GCancellable but the class method
+ * does not. Should be okay to pass NULL here since this
+ * is just a pass-through. */
+ return g_pollable_input_stream_read_nonblocking (
+ pollable_stream, buffer, count, NULL, error);
+}
+
+static void
+camel_imapx_input_stream_class_init (CamelIMAPXInputStreamClass *class)
+{
+ GObjectClass *object_class;
+ GInputStreamClass *input_stream_class;
+
+ g_type_class_add_private (
+ class, sizeof (CamelIMAPXInputStreamPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = imapx_input_stream_finalize;
+
+ input_stream_class = G_INPUT_STREAM_CLASS (class);
+ input_stream_class->read_fn = imapx_input_stream_read;
+}
+
+static void
+camel_imapx_input_stream_pollable_init (GPollableInputStreamInterface *iface)
+{
+ iface->can_poll = imapx_input_stream_can_poll;
+ iface->is_readable = imapx_input_stream_is_readable;
+ iface->create_source = imapx_input_stream_create_source;
+ iface->read_nonblocking = imapx_input_stream_read_nonblocking;
+}
+
+static void
+camel_imapx_input_stream_init (CamelIMAPXInputStream *is)
+{
+ is->priv = CAMEL_IMAPX_INPUT_STREAM_GET_PRIVATE (is);
+
+ /* +1 is room for appending a 0 if we need to for a token */
+ is->priv->bufsize = 4096;
+ is->priv->buf = g_malloc (is->priv->bufsize + 1);
+ is->priv->ptr = is->priv->end = is->priv->buf;
+ is->priv->tokenbuf = g_malloc (is->priv->bufsize + 1);
+}
+
+static void
+camel_imapx_input_stream_grow (CamelIMAPXInputStream *is,
+ guint len,
+ guchar **bufptr,
+ guchar **tokptr)
+{
+ guchar *oldtok = is->priv->tokenbuf;
+ guchar *oldbuf = is->priv->buf;
+
+ do {
+ is->priv->bufsize <<= 1;
+ } while (is->priv->bufsize <= len);
+
+ is->priv->tokenbuf = g_realloc (
+ is->priv->tokenbuf,
+ is->priv->bufsize + 1);
+ if (tokptr)
+ *tokptr = is->priv->tokenbuf + (*tokptr - oldtok);
+ if (is->priv->unget)
+ is->priv->unget_token =
+ is->priv->tokenbuf +
+ (is->priv->unget_token - oldtok);
+
+ is->priv->buf = g_realloc (is->priv->buf, is->priv->bufsize + 1);
+ is->priv->ptr = is->priv->buf + (is->priv->ptr - oldbuf);
+ is->priv->end = is->priv->buf + (is->priv->end - oldbuf);
+ if (bufptr)
+ *bufptr = is->priv->buf + (*bufptr - oldbuf);
+}
+
+G_DEFINE_QUARK (camel-imapx-error-quark, camel_imapx_error)
+
+/**
+ * camel_imapx_input_stream_new:
+ * @base_stream: a pollable #GInputStream
+ *
+ * Creates a new #CamelIMAPXInputStream which wraps @base_stream and
+ * parses incoming IMAP lines into tokens.
+ *
+ * Returns: a #CamelIMAPXInputStream
+ *
+ * Since: 3.12
+ **/
+GInputStream *
+camel_imapx_input_stream_new (GInputStream *base_stream)
+{
+ /* We implement GPollableInputStream by forwarding method calls
+ * to the base stream, so the base stream needs to be pollable. */
+ g_return_val_if_fail (G_IS_POLLABLE_INPUT_STREAM (base_stream), NULL);
+
+ return g_object_new (
+ CAMEL_TYPE_IMAPX_INPUT_STREAM,
+ "base-stream", base_stream, NULL);
+}
+
+/* Returns if there is any data buffered that is ready for processing */
+gint
+camel_imapx_input_stream_buffered (CamelIMAPXInputStream *is)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (is), 0);
+
+ return is->priv->end - is->priv->ptr;
+}
+
+/* FIXME: these should probably handle it themselves,
+ * and get rid of the token interface? */
+gboolean
+camel_imapx_input_stream_atom (CamelIMAPXInputStream *is,
+ guchar **data,
+ guint *lenp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_imapx_token_t tok;
+ guchar *p, c;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (is), FALSE);
+ g_return_val_if_fail (data != NULL, FALSE);
+ g_return_val_if_fail (lenp != NULL, FALSE);
+
+ /* this is only 'approximate' atom */
+ tok = camel_imapx_input_stream_token (is, data, lenp, cancellable, error);
+
+ switch (tok) {
+ case IMAPX_TOK_ERROR:
+ return FALSE;
+
+ case IMAPX_TOK_TOKEN:
+ p = *data;
+ while ((c = *p))
+ *p++ = toupper(c);
+ return TRUE;
+
+ case IMAPX_TOK_INT:
+ return TRUE;
+
+ default:
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "expecting atom");
+ return FALSE;
+ }
+}
+
+/* gets an atom, a quoted_string, or a literal */
+gboolean
+camel_imapx_input_stream_astring (CamelIMAPXInputStream *is,
+ guchar **data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_imapx_token_t tok;
+ guchar *p, *e, *start, c;
+ guint len, inlen;
+ gint ret;
+
+ g_return_val_if_fail (CAMEL_IMAPX_INPUT_STREAM (is), FALSE);
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ p = is->priv->ptr;
+ e = is->priv->end;
+
+ if (is->priv->unget) {
+ tok = camel_imapx_input_stream_token (is, data, &len, cancellable, error);
+ } else {
+ /* skip whitespace/prefill buffer */
+ do {
+ while (p >= e ) {
+ is->priv->ptr = p;
+ if (imapx_input_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
+ return FALSE;
+ p = is->priv->ptr;
+ e = is->priv->end;
+ }
+ c = *p++;
+ } while (c == ' ' || c == '\r');
+
+ if (c == '\"' || c == '{') {
+ tok = camel_imapx_input_stream_token (is, data, &len, cancellable, error);
+ } else {
+ guchar *o, *oe;
+
+ tok = IMAPX_TOK_STRING;
+
+ /* <any %x01-7F except "(){ " / %x00-1F / %x7F > */
+ o = is->priv->tokenbuf;
+ oe = is->priv->tokenbuf + is->priv->bufsize - 1;
+ *o++ = c;
+ while (1) {
+ while (p < e) {
+ c = *p++;
+ if (c <= 0x1f || c == 0x7f || c == '(' || c == ')' || c == '{' || c == ' ') {
+ if (c == ' ' || c == '\r')
+ is->priv->ptr = p;
+ else
+ is->priv->ptr = p - 1;
+ *o = 0;
+ *data = is->priv->tokenbuf;
+ return TRUE;
+ }
+
+ if (o >= oe) {
+ camel_imapx_input_stream_grow (is, 0, &p, &o);
+ oe = is->priv->tokenbuf + is->priv->bufsize - 1;
+ e = is->priv->end;
+ }
+ *o++ = c;
+ }
+ is->priv->ptr = p;
+ if (imapx_input_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
+ return FALSE;
+ p = is->priv->ptr;
+ e = is->priv->end;
+ }
+ }
+ }
+
+ switch (tok) {
+ case IMAPX_TOK_ERROR:
+ return FALSE;
+
+ case IMAPX_TOK_TOKEN:
+ case IMAPX_TOK_STRING:
+ case IMAPX_TOK_INT:
+ return TRUE;
+
+ case IMAPX_TOK_LITERAL:
+ if (len >= is->priv->bufsize)
+ camel_imapx_input_stream_grow (is, len, NULL, NULL);
+ p = is->priv->tokenbuf;
+ camel_imapx_input_stream_set_literal (is, len);
+ do {
+ ret = camel_imapx_input_stream_getl (
+ is, &start, &inlen, cancellable, error);
+ if (ret < 0)
+ return FALSE;
+ memcpy (p, start, inlen);
+ p += inlen;
+ } while (ret > 0);
+ *p = 0;
+ *data = is->priv->tokenbuf;
+ return TRUE;
+
+ default:
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "expecting astring");
+ return FALSE;
+ }
+}
+
+/* check for NIL or (small) quoted_string or literal */
+gboolean
+camel_imapx_input_stream_nstring (CamelIMAPXInputStream *is,
+ guchar **data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_imapx_token_t tok;
+ guchar *p, *start;
+ guint len, inlen;
+ gint ret;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (is), FALSE);
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ tok = camel_imapx_input_stream_token (is, data, &len, cancellable, error);
+
+ switch (tok) {
+ case IMAPX_TOK_ERROR:
+ return FALSE;
+
+ case IMAPX_TOK_STRING:
+ return TRUE;
+
+ case IMAPX_TOK_LITERAL:
+ if (len >= is->priv->bufsize)
+ camel_imapx_input_stream_grow (is, len, NULL, NULL);
+ p = is->priv->tokenbuf;
+ camel_imapx_input_stream_set_literal (is, len);
+ do {
+ ret = camel_imapx_input_stream_getl (
+ is, &start, &inlen, cancellable, error);
+ if (ret < 0)
+ return FALSE;
+ memcpy (p, start, inlen);
+ p += inlen;
+ } while (ret > 0);
+ *p = 0;
+ *data = is->priv->tokenbuf;
+ return TRUE;
+
+ case IMAPX_TOK_TOKEN:
+ p = *data;
+ if (toupper (p[0]) == 'N' &&
+ toupper (p[1]) == 'I' &&
+ toupper (p[2]) == 'L' &&
+ p[3] == 0) {
+ *data = NULL;
+ return TRUE;
+ }
+ /* fall through */
+
+ default:
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "expecting nstring");
+ return FALSE;
+ }
+}
+
+/* parse an nstring as a GBytes */
+gboolean
+camel_imapx_input_stream_nstring_bytes (CamelIMAPXInputStream *is,
+ GBytes **out_bytes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_imapx_token_t tok;
+ guchar *token;
+ guint len;
+ GOutputStream *output_stream;
+ gssize bytes_written;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (is), FALSE);
+ g_return_val_if_fail (out_bytes != NULL, FALSE);
+
+ *out_bytes = NULL;
+
+ tok = camel_imapx_input_stream_token (
+ is, &token, &len, cancellable, error);
+
+ switch (tok) {
+ case IMAPX_TOK_ERROR:
+ return FALSE;
+
+ case IMAPX_TOK_STRING:
+ *out_bytes = g_bytes_new (token, len);
+ return TRUE;
+
+ case IMAPX_TOK_LITERAL:
+ /* If len is big, we could
+ * automatically use a file backing. */
+ camel_imapx_input_stream_set_literal (is, len);
+ output_stream =
+ g_memory_output_stream_new_resizable ();
+ bytes_written = g_output_stream_splice (
+ output_stream,
+ G_INPUT_STREAM (is),
+ G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ cancellable, error);
+ success = (bytes_written >= 0);
+ if (success) {
+ *out_bytes =
+ g_memory_output_stream_steal_as_bytes (
+ G_MEMORY_OUTPUT_STREAM (output_stream));
+ }
+ g_object_unref (output_stream);
+ return success;
+
+ case IMAPX_TOK_TOKEN:
+ if (toupper (token[0]) == 'N' &&
+ toupper (token[1]) == 'I' &&
+ toupper (token[2]) == 'L' &&
+ token[3] == 0) {
+ *out_bytes = NULL;
+ return TRUE;
+ }
+ /* fall through */
+
+ default:
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "nstring: token not string");
+ return FALSE;
+ }
+}
+
+gboolean
+camel_imapx_input_stream_number (CamelIMAPXInputStream *is,
+ guint64 *number,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_imapx_token_t tok;
+ guchar *token;
+ guint len;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (is), FALSE);
+ g_return_val_if_fail (number != NULL, FALSE);
+
+ tok = camel_imapx_input_stream_token (
+ is, &token, &len, cancellable, error);
+
+ switch (tok) {
+ case IMAPX_TOK_ERROR:
+ return FALSE;
+
+ case IMAPX_TOK_INT:
+ *number = g_ascii_strtoull ((gchar *) token, 0, 10);
+ return TRUE;
+
+ default:
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "expecting number");
+ return FALSE;
+ }
+}
+
+gboolean
+camel_imapx_input_stream_text (CamelIMAPXInputStream *is,
+ guchar **text,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GByteArray *build = g_byte_array_new ();
+ guchar *token;
+ guint len;
+ gint tok;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (is), FALSE);
+ g_return_val_if_fail (text != NULL, FALSE);
+
+ while (is->priv->unget > 0) {
+ switch (is->priv->unget_tok) {
+ case IMAPX_TOK_TOKEN:
+ case IMAPX_TOK_STRING:
+ case IMAPX_TOK_INT:
+ g_byte_array_append (
+ build, (guint8 *)
+ is->priv->unget_token,
+ is->priv->unget_len);
+ g_byte_array_append (
+ build, (guint8 *) " ", 1);
+ default: /* invalid, but we'll ignore */
+ break;
+ }
+ is->priv->unget--;
+ }
+
+ do {
+ tok = camel_imapx_input_stream_gets (
+ is, &token, &len, cancellable, error);
+ if (tok < 0) {
+ *text = NULL;
+ g_byte_array_free (build, TRUE);
+ return FALSE;
+ }
+ if (len)
+ g_byte_array_append (build, token, len);
+ } while (tok > 0);
+
+ g_byte_array_append (build, (guint8 *) "", 1);
+ *text = build->data;
+ g_byte_array_free (build, FALSE);
+
+ return TRUE;
+}
+
+/* Get one token from the imap stream */
+camel_imapx_token_t
+camel_imapx_input_stream_token (CamelIMAPXInputStream *is,
+ guchar **data,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error)
+{
+ register guchar c, *oe;
+ guchar *o, *p, *e;
+ guint literal;
+ gint digits;
+ gboolean is_literal8 = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (is), IMAPX_TOK_ERROR);
+ g_return_val_if_fail (data != NULL, IMAPX_TOK_ERROR);
+ g_return_val_if_fail (len != NULL, IMAPX_TOK_ERROR);
+
+ if (is->priv->unget > 0) {
+ is->priv->unget--;
+ *data = is->priv->unget_token;
+ *len = is->priv->unget_len;
+ return is->priv->unget_tok;
+ }
+
+ *data = NULL;
+ *len = 0;
+
+ if (is->priv->literal > 0 && !g_cancellable_is_cancelled (cancellable))
+ g_warning (
+ "stream_token called with literal %d",
+ is->priv->literal);
+
+ p = is->priv->ptr;
+ e = is->priv->end;
+
+ /* skip whitespace/prefill buffer */
+ do {
+ while (p >= e ) {
+ is->priv->ptr = p;
+ if (imapx_input_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
+ return IMAPX_TOK_ERROR;
+ p = is->priv->ptr;
+ e = is->priv->end;
+ }
+ c = *p++;
+ } while (c == ' ' || c == '\r');
+
+ if (c == '~') {
+ if (p >= e) {
+ is->priv->ptr = p;
+ if (imapx_input_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
+ return IMAPX_TOK_ERROR;
+ p = is->priv->ptr;
+ e = is->priv->end;
+ }
+
+ if (*p == '{') {
+ c = *p++;
+ is_literal8 = TRUE;
+ }
+ }
+
+ /*strchr("\n*()[]+", c)*/
+ if (imapx_is_token_char (c)) {
+ is->priv->ptr = p;
+ return c;
+ } else if (c == '{') {
+ literal = 0;
+ *data = p;
+ while (1) {
+ while (p < e) {
+ c = *p++;
+ if (isdigit (c) && literal < (UINT_MAX / 10)) {
+ literal = literal * 10 + (c - '0');
+ } else if (is_literal8 && c == '+') {
+ if (p >= e) {
+ is->priv->ptr = p;
+ if (imapx_input_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
+ return IMAPX_TOK_ERROR;
+ p = is->priv->ptr;
+ e = is->priv->end;
+ }
+
+ /* The '+' can be only at the end of the literal8 token */
+ if (*p != '}')
+ goto protocol_error;
+ } else if (c == '}') {
+ while (1) {
+ while (p < e) {
+ c = *p++;
+ if (c == '\n') {
+ *len = literal;
+ is->priv->ptr = p;
+ is->priv->literal = literal;
+ return IMAPX_TOK_LITERAL;
+ }
+ }
+ is->priv->ptr = p;
+ if (imapx_input_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
+ return IMAPX_TOK_ERROR;
+ p = is->priv->ptr;
+ e = is->priv->end;
+ }
+ } else {
+ goto protocol_error;
+ }
+ }
+ is->priv->ptr = p;
+ if (imapx_input_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
+ return IMAPX_TOK_ERROR;
+ p = is->priv->ptr;
+ e = is->priv->end;
+ }
+ } else if (c == '"') {
+ o = is->priv->tokenbuf;
+ oe = is->priv->tokenbuf + is->priv->bufsize - 1;
+ while (1) {
+ while (p < e) {
+ c = *p++;
+ if (c == '\\') {
+ while (p >= e) {
+ is->priv->ptr = p;
+ if (imapx_input_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
+ return IMAPX_TOK_ERROR;
+ p = is->priv->ptr;
+ e = is->priv->end;
+ }
+ c = *p++;
+ } else if (c == '\"') {
+ is->priv->ptr = p;
+ *o = 0;
+ *data = is->priv->tokenbuf;
+ *len = o - is->priv->tokenbuf;
+ return IMAPX_TOK_STRING;
+ }
+ if (c == '\n' || c == '\r')
+ goto protocol_error;
+ if (o >= oe) {
+ camel_imapx_input_stream_grow (is, 0, &p, &o);
+ oe = is->priv->tokenbuf + is->priv->bufsize - 1;
+ e = is->priv->end;
+ }
+ *o++ = c;
+ }
+ is->priv->ptr = p;
+ if (imapx_input_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
+ return IMAPX_TOK_ERROR;
+ p = is->priv->ptr;
+ e = is->priv->end;
+ }
+ } else {
+ o = is->priv->tokenbuf;
+ oe = is->priv->tokenbuf + is->priv->bufsize - 1;
+ digits = isdigit (c);
+ *o++ = c;
+ while (1) {
+ while (p < e) {
+ c = *p++;
+ /*if (strchr(" \r\n*()[]+", c) != NULL) {*/
+ if (imapx_is_notid_char (c)) {
+ if (c == ' ' || c == '\r')
+ is->priv->ptr = p;
+ else
+ is->priv->ptr = p - 1;
+ *o = 0;
+ *data = is->priv->tokenbuf;
+ *len = o - is->priv->tokenbuf;
+ return digits ? IMAPX_TOK_INT : IMAPX_TOK_TOKEN;
+ }
+
+ if (o >= oe) {
+ camel_imapx_input_stream_grow (is, 0, &p, &o);
+ oe = is->priv->tokenbuf + is->priv->bufsize - 1;
+ e = is->priv->end;
+ }
+ digits &= isdigit (c);
+ *o++ = c;
+ }
+ is->priv->ptr = p;
+ if (imapx_input_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
+ return IMAPX_TOK_ERROR;
+ p = is->priv->ptr;
+ e = is->priv->end;
+ }
+ }
+
+protocol_error:
+
+ /* Protocol error, skip until next lf? */
+ if (c == '\n')
+ is->priv->ptr = p - 1;
+ else
+ is->priv->ptr = p;
+
+ g_set_error (error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED, "protocol error");
+
+ return IMAPX_TOK_ERROR;
+}
+
+void
+camel_imapx_input_stream_ungettoken (CamelIMAPXInputStream *is,
+ camel_imapx_token_t tok,
+ guchar *token,
+ guint len)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (is));
+
+ is->priv->unget_tok = tok;
+ is->priv->unget_token = token;
+ is->priv->unget_len = len;
+ is->priv->unget++;
+}
+
+/* returns -1 on error, 0 if last lot of data, >0 if more remaining */
+gint
+camel_imapx_input_stream_gets (CamelIMAPXInputStream *is,
+ guchar **start,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint max;
+ guchar *end;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (is), -1);
+ g_return_val_if_fail (start != NULL, -1);
+ g_return_val_if_fail (len != NULL, -1);
+
+ *len = 0;
+
+ max = is->priv->end - is->priv->ptr;
+ if (max == 0) {
+ max = imapx_input_stream_fill (is, cancellable, error);
+ if (max <= 0)
+ return max;
+ }
+
+ *start = is->priv->ptr;
+ end = memchr (is->priv->ptr, '\n', max);
+ if (end)
+ max = (end - is->priv->ptr) + 1;
+ *start = is->priv->ptr;
+ *len = max;
+ is->priv->ptr += max;
+
+ return end == NULL ? 1 : 0;
+}
+
+void
+camel_imapx_input_stream_set_literal (CamelIMAPXInputStream *is,
+ guint literal)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (is));
+
+ is->priv->literal = literal;
+}
+
+/* returns -1 on erorr, 0 if last data, >0 if more data left */
+gint
+camel_imapx_input_stream_getl (CamelIMAPXInputStream *is,
+ guchar **start,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint max;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (is), -1);
+ g_return_val_if_fail (start != NULL, -1);
+ g_return_val_if_fail (len != NULL, -1);
+
+ *len = 0;
+
+ if (is->priv->literal > 0) {
+ max = is->priv->end - is->priv->ptr;
+ if (max == 0) {
+ max = imapx_input_stream_fill (is, cancellable, error);
+ if (max <= 0)
+ return max;
+ }
+
+ max = MIN (max, is->priv->literal);
+ *start = is->priv->ptr;
+ *len = max;
+ is->priv->ptr += max;
+ is->priv->literal -= max;
+ }
+
+ if (is->priv->literal > 0)
+ return 1;
+
+ return 0;
+}
+
+/* skip the rest of the line of tokens */
+gboolean
+camel_imapx_input_stream_skip (CamelIMAPXInputStream *is,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_imapx_token_t tok;
+ guchar *token;
+ guint len;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (is), FALSE);
+
+ do {
+ tok = camel_imapx_input_stream_token (
+ is, &token, &len, cancellable, error);
+
+ if (tok == IMAPX_TOK_ERROR)
+ return FALSE;
+
+ if (tok == IMAPX_TOK_LITERAL) {
+ camel_imapx_input_stream_set_literal (is, len);
+ do {
+ tok = camel_imapx_input_stream_getl (
+ is, &token, &len, cancellable, error);
+ } while (tok > 0);
+ }
+ } while (tok != '\n' && tok >= 0);
+
+ return (tok != IMAPX_TOK_ERROR);
+}
+
+gboolean
+camel_imapx_input_stream_skip_until (CamelIMAPXInputStream *is,
+ const gchar *delimiters,
+ GCancellable *cancellable,
+ GError **error)
+{
+ register guchar c;
+ guchar *p, *e;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (is), FALSE);
+
+ if (is->priv->unget > 0) {
+ is->priv->unget--;
+ return TRUE;
+ }
+
+ if (is->priv->literal > 0) {
+ is->priv->literal--;
+ return TRUE;
+ }
+
+ p = is->priv->ptr;
+ e = is->priv->end;
+
+ do {
+ while (p >= e ) {
+ is->priv->ptr = p;
+ if (imapx_input_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
+ return FALSE;
+ p = is->priv->ptr;
+ e = is->priv->end;
+ }
+ c = *p++;
+ } while (c && c != ' ' && c != '\r' && c != '\n' && (!delimiters || !strchr (delimiters, c)));
+
+ is->priv->ptr = p;
+
+ return TRUE;
+}
diff --git a/src/camel/providers/imapx/camel-imapx-input-stream.h b/src/camel/providers/imapx/camel-imapx-input-stream.h
new file mode 100644
index 000000000..e379a3aaf
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-input-stream.h
@@ -0,0 +1,156 @@
+/*
+ * camel-imapx-input-stream.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_INPUT_STREAM_H
+#define CAMEL_IMAPX_INPUT_STREAM_H
+
+#include <gio/gio.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_INPUT_STREAM \
+ (camel_imapx_input_stream_get_type ())
+#define CAMEL_IMAPX_INPUT_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_INPUT_STREAM, CamelIMAPXInputStream))
+#define CAMEL_IMAPX_INPUT_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_INPUT_STREAM, CamelIMAPXInputStreamClass))
+#define CAMEL_IS_IMAPX_INPUT_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_INPUT_STREAM))
+#define CAMEL_IS_IMAPX_INPUT_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_INPUT_STREAM))
+#define CAMEL_IMAPX_INPUT_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_INPUT_STREAM, CamelIMAPXInputStreamClass))
+
+#define CAMEL_IMAPX_ERROR \
+ (camel_imapx_error_quark ())
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAPXInputStream CamelIMAPXInputStream;
+typedef struct _CamelIMAPXInputStreamClass CamelIMAPXInputStreamClass;
+typedef struct _CamelIMAPXInputStreamPrivate CamelIMAPXInputStreamPrivate;
+
+typedef enum {
+ CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED = 1,
+ CAMEL_IMAPX_ERROR_IGNORE /* may ignore such errors */
+} CamelIMAPXError;
+
+typedef enum {
+ IMAPX_TOK_ERROR = -1,
+ IMAPX_TOK_TOKEN = 256,
+ IMAPX_TOK_STRING,
+ IMAPX_TOK_INT,
+ IMAPX_TOK_LITERAL,
+} camel_imapx_token_t;
+
+struct _CamelIMAPXInputStream {
+ GFilterInputStream parent;
+ CamelIMAPXInputStreamPrivate *priv;
+};
+
+struct _CamelIMAPXInputStreamClass {
+ GFilterInputStreamClass parent_class;
+};
+
+GQuark camel_imapx_error_quark (void) G_GNUC_CONST;
+GType camel_imapx_input_stream_get_type
+ (void) G_GNUC_CONST;
+GInputStream * camel_imapx_input_stream_new (GInputStream *base_stream);
+gint camel_imapx_input_stream_buffered
+ (CamelIMAPXInputStream *is);
+
+camel_imapx_token_t
+ camel_imapx_input_stream_token (CamelIMAPXInputStream *is,
+ guchar **start,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error);
+
+void camel_imapx_input_stream_ungettoken
+ (CamelIMAPXInputStream *is,
+ camel_imapx_token_t tok,
+ guchar *token,
+ guint len);
+void camel_imapx_input_stream_set_literal
+ (CamelIMAPXInputStream *is,
+ guint literal);
+gint camel_imapx_input_stream_gets (CamelIMAPXInputStream *is,
+ guchar **start,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error);
+gint camel_imapx_input_stream_getl (CamelIMAPXInputStream *is,
+ guchar **start,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error);
+
+/* gets an atom, upper-cases */
+gboolean camel_imapx_input_stream_atom (CamelIMAPXInputStream *is,
+ guchar **start,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error);
+/* gets an atom or string */
+gboolean camel_imapx_input_stream_astring
+ (CamelIMAPXInputStream *is,
+ guchar **start,
+ GCancellable *cancellable,
+ GError **error);
+/* gets a NIL or a string, start==NULL if NIL */
+gboolean camel_imapx_input_stream_nstring
+ (CamelIMAPXInputStream *is,
+ guchar **start,
+ GCancellable *cancellable,
+ GError **error);
+/* gets a NIL or string into a GBytes, bytes==NULL if NIL */
+gboolean camel_imapx_input_stream_nstring_bytes
+ (CamelIMAPXInputStream *is,
+ GBytes **out_bytes,
+ GCancellable *cancellable,
+ GError **error);
+/* gets 'text' */
+gboolean camel_imapx_input_stream_text (CamelIMAPXInputStream *is,
+ guchar **text,
+ GCancellable *cancellable,
+ GError **error);
+
+/* gets a 'number' */
+gboolean camel_imapx_input_stream_number
+ (CamelIMAPXInputStream *is,
+ guint64 *number,
+ GCancellable *cancellable,
+ GError **error);
+
+/* skips the rest of a line, including literals, etc */
+gboolean camel_imapx_input_stream_skip (CamelIMAPXInputStream *is,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean camel_imapx_input_stream_skip_until
+ (CamelIMAPXInputStream *is,
+ const gchar *delimiters,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_INPUT_STREAM_H */
diff --git a/src/camel/providers/imapx/camel-imapx-job.c b/src/camel/providers/imapx/camel-imapx-job.c
new file mode 100644
index 000000000..ed9c45ade
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-job.c
@@ -0,0 +1,544 @@
+/*
+ * camel-imapx-job.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include "camel-imapx-job.h"
+
+G_LOCK_DEFINE_STATIC (get_kind_name_funcs);
+static GSList *get_kind_name_funcs = NULL;
+
+const gchar *
+camel_imapx_job_get_kind_name (guint32 job_kind)
+{
+ GSList *link;
+
+ switch ((CamelIMAPXJobKind) job_kind) {
+ case CAMEL_IMAPX_JOB_UNKNOWN:
+ return "UNKNOWN";
+ case CAMEL_IMAPX_JOB_CAPABILITY:
+ return "CAPABILITY";
+ case CAMEL_IMAPX_JOB_STARTTLS:
+ return "STARTTLS";
+ case CAMEL_IMAPX_JOB_AUTHENTICATE:
+ return "AUTHENTICATE";
+ case CAMEL_IMAPX_JOB_LOGIN:
+ return "LOGIN";
+ case CAMEL_IMAPX_JOB_NAMESPACE:
+ return "NAMESPACE";
+ case CAMEL_IMAPX_JOB_SELECT:
+ return "SELECT";
+ case CAMEL_IMAPX_JOB_STATUS:
+ return "STATUS";
+ case CAMEL_IMAPX_JOB_ENABLE:
+ return "ENABLE";
+ case CAMEL_IMAPX_JOB_NOTIFY:
+ return "NOTIFY";
+ case CAMEL_IMAPX_JOB_GET_MESSAGE:
+ return "GET_MESSAGE";
+ case CAMEL_IMAPX_JOB_SYNC_MESSAGE:
+ return "SYNC_MESSAGE";
+ case CAMEL_IMAPX_JOB_APPEND_MESSAGE:
+ return "APPEND_MESSAGE";
+ case CAMEL_IMAPX_JOB_COPY_MESSAGE:
+ return "COPY_MESSAGE";
+ case CAMEL_IMAPX_JOB_MOVE_MESSAGE:
+ return "MOVE_MESSAGE";
+ case CAMEL_IMAPX_JOB_FETCH_NEW_MESSAGES:
+ return "FETCH_NEW_MESSAGES";
+ case CAMEL_IMAPX_JOB_REFRESH_INFO:
+ return "REFRESH_INFO";
+ case CAMEL_IMAPX_JOB_SYNC_CHANGES:
+ return "SYNC_CHANGES";
+ case CAMEL_IMAPX_JOB_EXPUNGE:
+ return "EXPUNGE";
+ case CAMEL_IMAPX_JOB_NOOP:
+ return "NOOP";
+ case CAMEL_IMAPX_JOB_IDLE:
+ return "IDLE";
+ case CAMEL_IMAPX_JOB_DONE:
+ return "DONE";
+ case CAMEL_IMAPX_JOB_LIST:
+ return "LIST";
+ case CAMEL_IMAPX_JOB_LSUB:
+ return "LSUB";
+ case CAMEL_IMAPX_JOB_CREATE_MAILBOX:
+ return "CREATE_MAILBOX";
+ case CAMEL_IMAPX_JOB_DELETE_MAILBOX:
+ return "DELETE_MAILBOX";
+ case CAMEL_IMAPX_JOB_RENAME_MAILBOX:
+ return "RENAME_MAILBOX";
+ case CAMEL_IMAPX_JOB_SUBSCRIBE_MAILBOX:
+ return "SUBSCRIBE_MAILBOX";
+ case CAMEL_IMAPX_JOB_UNSUBSCRIBE_MAILBOX:
+ return "UNSUBSCRIBE_MAILBOX";
+ case CAMEL_IMAPX_JOB_UPDATE_QUOTA_INFO:
+ return "UPDATE_QUOTA_INFO";
+ case CAMEL_IMAPX_JOB_UID_SEARCH:
+ return "UID_SEARCH";
+ case CAMEL_IMAPX_JOB_LAST:
+ break;
+ }
+
+ G_LOCK (get_kind_name_funcs);
+
+ for (link = get_kind_name_funcs; link; link = g_slist_next (link)) {
+ CamelIMAPXJobGetKindNameFunc get_kind_name = link->data;
+
+ if (get_kind_name) {
+ const gchar *name = get_kind_name (job_kind);
+
+ if (name) {
+ G_UNLOCK (get_kind_name_funcs);
+ return name;
+ }
+ }
+ }
+
+ G_UNLOCK (get_kind_name_funcs);
+
+ if (job_kind == CAMEL_IMAPX_JOB_LAST)
+ return "LAST";
+
+ return "???";
+}
+
+void
+camel_imapx_job_register_get_kind_name_func (CamelIMAPXJobGetKindNameFunc get_kind_name)
+{
+ g_return_if_fail (get_kind_name != NULL);
+
+ G_LOCK (get_kind_name_funcs);
+
+ if (!g_slist_find (get_kind_name_funcs, get_kind_name))
+ get_kind_name_funcs = g_slist_prepend (get_kind_name_funcs, get_kind_name);
+
+ G_UNLOCK (get_kind_name_funcs);
+}
+
+void
+camel_imapx_job_unregister_get_kind_name_func (CamelIMAPXJobGetKindNameFunc get_kind_name)
+{
+ g_return_if_fail (get_kind_name != NULL);
+
+ G_LOCK (get_kind_name_funcs);
+
+ g_warn_if_fail (g_slist_find (get_kind_name_funcs, get_kind_name));
+ get_kind_name_funcs = g_slist_remove (get_kind_name_funcs, get_kind_name);
+
+ G_UNLOCK (get_kind_name_funcs);
+}
+
+struct _CamelIMAPXJob {
+ volatile gint ref_count;
+
+ guint32 job_kind;
+ CamelIMAPXMailbox *mailbox;
+
+ CamelIMAPXJobRunSyncFunc run_sync;
+ CamelIMAPXJobMatchesFunc matches;
+ CamelIMAPXJobCopyResultFunc copy_result;
+
+ /* Extra job-specific data. */
+ gpointer user_data;
+ GDestroyNotify destroy_user_data;
+
+ gboolean result_is_set;
+ gboolean result_success;
+ gpointer result_data;
+ GError *result_error;
+ GDestroyNotify destroy_result_data;
+
+ GCond done_cond;
+ GMutex done_mutex;
+ gboolean is_done;
+
+ GCancellable *abort_cancellable;
+};
+
+CamelIMAPXJob *
+camel_imapx_job_new (guint32 job_kind,
+ CamelIMAPXMailbox *mailbox,
+ CamelIMAPXJobRunSyncFunc run_sync,
+ CamelIMAPXJobMatchesFunc matches,
+ CamelIMAPXJobCopyResultFunc copy_result)
+{
+ CamelIMAPXJob *job;
+
+ g_return_val_if_fail (run_sync != NULL, NULL);
+
+ job = g_new0 (CamelIMAPXJob, 1);
+ job->ref_count = 1;
+ job->job_kind = job_kind;
+ job->mailbox = mailbox ? g_object_ref (mailbox) : NULL;
+ job->run_sync = run_sync;
+ job->matches = matches;
+ job->copy_result = copy_result;
+ job->abort_cancellable = camel_operation_new ();
+ job->is_done = FALSE;
+
+ g_cond_init (&job->done_cond);
+ g_mutex_init (&job->done_mutex);
+
+ return job;
+}
+
+CamelIMAPXJob *
+camel_imapx_job_ref (CamelIMAPXJob *job)
+{
+ g_return_val_if_fail (job != NULL, NULL);
+
+ g_atomic_int_inc (&job->ref_count);
+
+ return job;
+}
+
+void
+camel_imapx_job_unref (CamelIMAPXJob *job)
+{
+ g_return_if_fail (job != NULL);
+
+ if (g_atomic_int_dec_and_test (&job->ref_count)) {
+ if (job->destroy_user_data)
+ job->destroy_user_data (job->user_data);
+
+ if (job->result_is_set && job->destroy_result_data)
+ job->destroy_result_data (job->result_data);
+
+ g_clear_object (&job->mailbox);
+ g_clear_object (&job->abort_cancellable);
+ g_clear_error (&job->result_error);
+
+ g_cond_clear (&job->done_cond);
+ g_mutex_clear (&job->done_mutex);
+
+ job->ref_count = 0xdeadbeef;
+
+ g_free (job);
+ }
+}
+
+guint32
+camel_imapx_job_get_kind (CamelIMAPXJob *job)
+{
+ g_return_val_if_fail (job != NULL, CAMEL_IMAPX_JOB_UNKNOWN);
+
+ return job->job_kind;
+}
+
+CamelIMAPXMailbox *
+camel_imapx_job_get_mailbox (CamelIMAPXJob *job)
+{
+ g_return_val_if_fail (job != NULL, NULL);
+
+ return job->mailbox;
+}
+
+gpointer
+camel_imapx_job_get_user_data (CamelIMAPXJob *job)
+{
+ g_return_val_if_fail (job != NULL, NULL);
+
+ return job->user_data;
+}
+
+void
+camel_imapx_job_set_user_data (CamelIMAPXJob *job,
+ gpointer user_data,
+ GDestroyNotify destroy_user_data)
+{
+ g_return_if_fail (job != NULL);
+
+ if (job->destroy_user_data)
+ job->destroy_user_data (job->user_data);
+
+ job->user_data = user_data;
+ job->destroy_user_data = destroy_user_data;
+}
+
+gboolean
+camel_imapx_job_was_cancelled (CamelIMAPXJob *job)
+{
+ g_return_val_if_fail (job != NULL, FALSE);
+
+ if (!job->result_is_set)
+ return FALSE;
+
+ return g_error_matches (job->result_error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+}
+
+void
+camel_imapx_job_set_result (CamelIMAPXJob *job,
+ gboolean success,
+ gpointer result,
+ const GError *error,
+ GDestroyNotify destroy_result)
+{
+ g_return_if_fail (job != NULL);
+
+ if (job->result_is_set) {
+ if (job->destroy_result_data)
+ job->destroy_result_data (job->result_data);
+ g_clear_error (&job->result_error);
+ }
+
+ job->result_is_set = TRUE;
+ job->result_success = success;
+ job->result_data = result;
+ job->destroy_result_data = destroy_result;
+
+ if (error)
+ job->result_error = g_error_copy (error);
+}
+
+/* This doesn't return whether the job succeeded, but whether the result
+ was set for the job, thus some result copied. All out-arguments are optional. */
+gboolean
+camel_imapx_job_copy_result (CamelIMAPXJob *job,
+ gboolean *out_success,
+ gpointer *out_result,
+ GError **out_error,
+ GDestroyNotify *out_destroy_result)
+{
+ g_return_val_if_fail (job != NULL, FALSE);
+
+ if (!job->result_is_set)
+ return FALSE;
+
+ if (out_success)
+ *out_success = job->result_success;
+
+ if (out_result) {
+ *out_result = NULL;
+
+ if (job->copy_result) {
+ job->copy_result (job, job->result_data, out_result);
+ } else if (job->result_data) {
+ g_warn_if_reached ();
+ }
+ }
+
+ if (out_error) {
+ g_warn_if_fail (*out_error == NULL);
+
+ if (job->result_error)
+ *out_error = g_error_copy (job->result_error);
+ }
+
+ if (out_destroy_result)
+ *out_destroy_result = job->destroy_result_data;
+
+ return TRUE;
+}
+
+/* Similar to camel_imapx_job_copy_result() except it gives result data
+ to the caller and unsets (not frees) the data in the job. */
+gboolean
+camel_imapx_job_take_result_data (CamelIMAPXJob *job,
+ gpointer *out_result)
+{
+ g_return_val_if_fail (job != NULL, FALSE);
+
+ if (!job->result_is_set)
+ return FALSE;
+
+ if (out_result) {
+ *out_result = job->result_data;
+ } else if (job->destroy_result_data) {
+ job->destroy_result_data (job->result_data);
+ }
+
+ job->result_data = NULL;
+ g_clear_error (&job->result_error);
+
+ job->result_is_set = FALSE;
+
+ return TRUE;
+}
+
+gboolean
+camel_imapx_job_matches (CamelIMAPXJob *job,
+ CamelIMAPXJob *other_job)
+{
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (other_job != NULL, FALSE);
+
+ if (job->job_kind != other_job->job_kind)
+ return FALSE;
+
+ if (job->mailbox != other_job->mailbox)
+ return FALSE;
+
+ if (job->matches)
+ return job->matches (job, other_job);
+
+ return TRUE;
+}
+
+static void
+imapx_job_cancelled_cb (GCancellable *cancellable,
+ CamelIMAPXJob *job)
+{
+ camel_imapx_job_abort (job);
+}
+
+static void
+imapx_job_push_message_cb (CamelOperation *operation,
+ const gchar *message,
+ GCancellable *job_cancellable)
+{
+ g_return_if_fail (CAMEL_IS_OPERATION (operation));
+ g_return_if_fail (CAMEL_IS_OPERATION (job_cancellable));
+
+ camel_operation_push_message (job_cancellable, "%s", message);
+}
+
+static void
+imapx_job_pop_message_cb (CamelOperation *operation,
+ GCancellable *job_cancellable)
+{
+ g_return_if_fail (CAMEL_IS_OPERATION (operation));
+ g_return_if_fail (CAMEL_IS_OPERATION (job_cancellable));
+
+ camel_operation_pop_message (job_cancellable);
+}
+
+static void
+imapx_job_progress_cb (CamelOperation *operation,
+ gint percent,
+ GCancellable *job_cancellable)
+{
+ g_return_if_fail (CAMEL_IS_OPERATION (operation));
+ g_return_if_fail (CAMEL_IS_OPERATION (job_cancellable));
+
+ camel_operation_progress (job_cancellable, percent);
+}
+
+gboolean
+camel_imapx_job_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GError *local_error = NULL;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), FALSE);
+ g_return_val_if_fail (job->run_sync != NULL, FALSE);
+
+ g_mutex_lock (&job->done_mutex);
+ job->is_done = FALSE;
+ g_mutex_unlock (&job->done_mutex);
+
+ g_cancellable_reset (job->abort_cancellable);
+
+ if (!g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ gulong cancelled_handler_id = 0;
+ gulong push_message_handler_id = 0;
+ gulong pop_message_handler_id = 0;
+ gulong progress_handler_id = 0;
+
+ /* Proxy signals between job's cancellable and the abort_cancellable */
+ if (cancellable)
+ cancelled_handler_id = g_cancellable_connect (cancellable,
+ G_CALLBACK (imapx_job_cancelled_cb), job, NULL);
+
+ if (CAMEL_IS_OPERATION (cancellable)) {
+ push_message_handler_id = g_signal_connect (job->abort_cancellable, "push-message",
+ G_CALLBACK (imapx_job_push_message_cb), cancellable);
+ pop_message_handler_id = g_signal_connect (job->abort_cancellable, "pop-message",
+ G_CALLBACK (imapx_job_pop_message_cb), cancellable);
+ progress_handler_id = g_signal_connect (job->abort_cancellable, "progress",
+ G_CALLBACK (imapx_job_progress_cb), cancellable);
+ }
+
+ success = job->run_sync (job, server, job->abort_cancellable, &local_error);
+
+ if (push_message_handler_id)
+ g_signal_handler_disconnect (job->abort_cancellable, push_message_handler_id);
+ if (pop_message_handler_id)
+ g_signal_handler_disconnect (job->abort_cancellable, pop_message_handler_id);
+ if (progress_handler_id)
+ g_signal_handler_disconnect (job->abort_cancellable, progress_handler_id);
+ if (cancelled_handler_id)
+ g_cancellable_disconnect (cancellable, cancelled_handler_id);
+ }
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+void
+camel_imapx_job_done (CamelIMAPXJob *job)
+{
+ g_return_if_fail (job != NULL);
+
+ g_mutex_lock (&job->done_mutex);
+ job->is_done = TRUE;
+ g_cond_broadcast (&job->done_cond);
+ g_mutex_unlock (&job->done_mutex);
+}
+
+void
+camel_imapx_job_abort (CamelIMAPXJob *job)
+{
+ g_return_if_fail (job != NULL);
+
+ g_cancellable_cancel (job->abort_cancellable);
+}
+
+static void
+camel_imapx_job_wait_cancelled_cb (GCancellable *cancellable,
+ gpointer user_data)
+{
+ CamelIMAPXJob *job = user_data;
+
+ g_return_if_fail (job != NULL);
+
+ g_mutex_lock (&job->done_mutex);
+ g_cond_broadcast (&job->done_cond);
+ g_mutex_unlock (&job->done_mutex);
+}
+
+void
+camel_imapx_job_wait_sync (CamelIMAPXJob *job,
+ GCancellable *cancellable)
+{
+ gulong handler_id = 0;
+
+ g_return_if_fail (job != NULL);
+
+ if (g_cancellable_is_cancelled (cancellable))
+ return;
+
+ if (cancellable)
+ handler_id = g_cancellable_connect (cancellable, G_CALLBACK (camel_imapx_job_wait_cancelled_cb), job, NULL);
+
+ g_mutex_lock (&job->done_mutex);
+ while (!job->is_done && !g_cancellable_is_cancelled (cancellable)) {
+ g_cond_wait (&job->done_cond, &job->done_mutex);
+ }
+ g_mutex_unlock (&job->done_mutex);
+
+ if (handler_id)
+ g_cancellable_disconnect (cancellable, handler_id);
+}
diff --git a/src/camel/providers/imapx/camel-imapx-job.h b/src/camel/providers/imapx/camel-imapx-job.h
new file mode 100644
index 000000000..de0c5d133
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-job.h
@@ -0,0 +1,124 @@
+/*
+ * camel-imapx-job.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_JOB_H
+#define CAMEL_IMAPX_JOB_H
+
+#include "camel-imapx-server.h"
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAPXJob CamelIMAPXJob;
+
+struct _CamelIMAPXJob;
+
+typedef enum {
+ CAMEL_IMAPX_JOB_UNKNOWN = 0,
+ CAMEL_IMAPX_JOB_CAPABILITY,
+ CAMEL_IMAPX_JOB_STARTTLS,
+ CAMEL_IMAPX_JOB_AUTHENTICATE,
+ CAMEL_IMAPX_JOB_LOGIN,
+ CAMEL_IMAPX_JOB_NAMESPACE,
+ CAMEL_IMAPX_JOB_SELECT,
+ CAMEL_IMAPX_JOB_STATUS,
+ CAMEL_IMAPX_JOB_ENABLE,
+ CAMEL_IMAPX_JOB_NOTIFY,
+ CAMEL_IMAPX_JOB_GET_MESSAGE,
+ CAMEL_IMAPX_JOB_SYNC_MESSAGE,
+ CAMEL_IMAPX_JOB_APPEND_MESSAGE,
+ CAMEL_IMAPX_JOB_COPY_MESSAGE,
+ CAMEL_IMAPX_JOB_MOVE_MESSAGE,
+ CAMEL_IMAPX_JOB_FETCH_NEW_MESSAGES,
+ CAMEL_IMAPX_JOB_REFRESH_INFO,
+ CAMEL_IMAPX_JOB_SYNC_CHANGES,
+ CAMEL_IMAPX_JOB_EXPUNGE,
+ CAMEL_IMAPX_JOB_NOOP,
+ CAMEL_IMAPX_JOB_IDLE,
+ CAMEL_IMAPX_JOB_DONE,
+ CAMEL_IMAPX_JOB_LIST,
+ CAMEL_IMAPX_JOB_LSUB,
+ CAMEL_IMAPX_JOB_CREATE_MAILBOX,
+ CAMEL_IMAPX_JOB_DELETE_MAILBOX,
+ CAMEL_IMAPX_JOB_RENAME_MAILBOX,
+ CAMEL_IMAPX_JOB_SUBSCRIBE_MAILBOX,
+ CAMEL_IMAPX_JOB_UNSUBSCRIBE_MAILBOX,
+ CAMEL_IMAPX_JOB_UPDATE_QUOTA_INFO,
+ CAMEL_IMAPX_JOB_UID_SEARCH,
+ CAMEL_IMAPX_JOB_LAST
+} CamelIMAPXJobKind;
+
+typedef const gchar * (* CamelIMAPXJobGetKindNameFunc)(guint32 job_kind);
+
+const gchar * camel_imapx_job_get_kind_name (guint32 job_kind);
+void camel_imapx_job_register_get_kind_name_func
+ (CamelIMAPXJobGetKindNameFunc get_kind_name);
+void camel_imapx_job_unregister_get_kind_name_func
+ (CamelIMAPXJobGetKindNameFunc get_kind_name);
+
+typedef gboolean (* CamelIMAPXJobRunSyncFunc) (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error);
+typedef gboolean (* CamelIMAPXJobMatchesFunc) (CamelIMAPXJob *job,
+ CamelIMAPXJob *other_job);
+typedef void (* CamelIMAPXJobCopyResultFunc) (CamelIMAPXJob *job,
+ gconstpointer set_result,
+ gpointer *out_result);
+
+CamelIMAPXJob * camel_imapx_job_new (guint32 job_kind,
+ CamelIMAPXMailbox *mailbox,
+ CamelIMAPXJobRunSyncFunc run_sync,
+ CamelIMAPXJobMatchesFunc matches,
+ CamelIMAPXJobCopyResultFunc copy_result);
+CamelIMAPXJob * camel_imapx_job_ref (CamelIMAPXJob *job);
+void camel_imapx_job_unref (CamelIMAPXJob *job);
+guint32 camel_imapx_job_get_kind (CamelIMAPXJob *job);
+CamelIMAPXMailbox *
+ camel_imapx_job_get_mailbox (CamelIMAPXJob *job);
+gpointer camel_imapx_job_get_user_data (CamelIMAPXJob *job);
+void camel_imapx_job_set_user_data (CamelIMAPXJob *job,
+ gpointer user_data,
+ GDestroyNotify destroy_user_data);
+gboolean camel_imapx_job_was_cancelled (CamelIMAPXJob *job);
+void camel_imapx_job_set_result (CamelIMAPXJob *job,
+ gboolean success,
+ gpointer result,
+ const GError *error,
+ GDestroyNotify destroy_result);
+gboolean camel_imapx_job_copy_result (CamelIMAPXJob *job,
+ gboolean *out_success,
+ gpointer *out_result,
+ GError **out_error,
+ GDestroyNotify *out_destroy_result);
+gboolean camel_imapx_job_take_result_data
+ (CamelIMAPXJob *job,
+ gpointer *out_result);
+gboolean camel_imapx_job_matches (CamelIMAPXJob *job,
+ CamelIMAPXJob *other_job);
+gboolean camel_imapx_job_run_sync (CamelIMAPXJob *job,
+ CamelIMAPXServer *server,
+ GCancellable *cancellable,
+ GError **error);
+void camel_imapx_job_done (CamelIMAPXJob *job);
+void camel_imapx_job_abort (CamelIMAPXJob *job);
+void camel_imapx_job_wait_sync (CamelIMAPXJob *job,
+ GCancellable *cancellable);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_JOB_H */
+
diff --git a/src/camel/providers/imapx/camel-imapx-list-response.c b/src/camel/providers/imapx/camel-imapx-list-response.c
new file mode 100644
index 000000000..4ff31dc86
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-list-response.c
@@ -0,0 +1,841 @@
+/*
+ * camel-imapx-list-response.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * SECTION: camel-imapx-list-response
+ * @include: camel/camel.h
+ * @short_description: Stores an IMAP LIST response
+ *
+ * #CamelIMAPXListResponse encapsulates an IMAP LIST response, which consists
+ * of a set of mailbox attributes, a mailbox separator character, and the
+ * mailbox name. (Extended information for LIST responses, as described in
+ * <ulink url="http://tools.ietf.org/html/rfc5258">RFC 5258</ulink>, to be
+ * supported at a later date.)
+ *
+ * #CamelIMAPXListResponse can also convert the mailbox attributes to
+ * a #CamelStoreInfoFlags / #CamelFolderInfoFlags value for use with
+ * camel_store_get_folder_info().
+ **/
+
+#include "camel-imapx-list-response.h"
+
+#include "camel-imapx-utils.h"
+
+#define CAMEL_IMAPX_LIST_RESPONSE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_IMAPX_LIST_RESPONSE, CamelIMAPXListResponsePrivate))
+
+struct _CamelIMAPXListResponsePrivate {
+ gchar *mailbox_name;
+ gchar separator;
+ GHashTable *attributes;
+ GHashTable *extended_items;
+};
+
+G_DEFINE_TYPE (
+ CamelIMAPXListResponse,
+ camel_imapx_list_response,
+ G_TYPE_OBJECT)
+
+/* These are internalized on class initialization. */
+static const gchar *known_attributes[] = {
+ CAMEL_IMAPX_LIST_ATTR_MARKED,
+ CAMEL_IMAPX_LIST_ATTR_NOINFERIORS,
+ CAMEL_IMAPX_LIST_ATTR_NOSELECT,
+ CAMEL_IMAPX_LIST_ATTR_UNMARKED,
+ CAMEL_IMAPX_LIST_ATTR_HASCHILDREN,
+ CAMEL_IMAPX_LIST_ATTR_HASNOCHILDREN,
+ CAMEL_IMAPX_LIST_ATTR_NONEXISTENT,
+ CAMEL_IMAPX_LIST_ATTR_REMOTE,
+ CAMEL_IMAPX_LIST_ATTR_SUBSCRIBED,
+ CAMEL_IMAPX_LIST_ATTR_ALL,
+ CAMEL_IMAPX_LIST_ATTR_ARCHIVE,
+ CAMEL_IMAPX_LIST_ATTR_DRAFTS,
+ CAMEL_IMAPX_LIST_ATTR_FLAGGED,
+ CAMEL_IMAPX_LIST_ATTR_JUNK,
+ CAMEL_IMAPX_LIST_ATTR_SENT,
+ CAMEL_IMAPX_LIST_ATTR_TRASH
+};
+
+static void
+imapx_list_response_finalize (GObject *object)
+{
+ CamelIMAPXListResponsePrivate *priv;
+
+ priv = CAMEL_IMAPX_LIST_RESPONSE_GET_PRIVATE (object);
+
+ g_free (priv->mailbox_name);
+
+ g_hash_table_destroy (priv->attributes);
+ g_hash_table_destroy (priv->extended_items);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_imapx_list_response_parent_class)->
+ finalize (object);
+}
+
+static void
+camel_imapx_list_response_class_init (CamelIMAPXListResponseClass *class)
+{
+ GObjectClass *object_class;
+ gint ii;
+
+ g_type_class_add_private (
+ class, sizeof (CamelIMAPXListResponsePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = imapx_list_response_finalize;
+
+ /* Internalize known mailbox attribute names. */
+ for (ii = 0; ii < G_N_ELEMENTS (known_attributes); ii++) {
+ const gchar *string = known_attributes[ii];
+ known_attributes[ii] = g_intern_static_string (string);
+ }
+}
+
+static void
+camel_imapx_list_response_init (CamelIMAPXListResponse *response)
+{
+ GHashTable *attributes;
+ GHashTable *extended_items;
+
+ /* Set of internalized attribute strings. */
+ attributes = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
+
+ extended_items = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_variant_unref);
+
+ response->priv = CAMEL_IMAPX_LIST_RESPONSE_GET_PRIVATE (response);
+ response->priv->attributes = attributes;
+ response->priv->extended_items = extended_items;
+}
+
+/* Helper for camel_imapx_list_response_new() */
+static GVariant *
+imapx_list_response_parse_childinfo (CamelIMAPXInputStream *stream,
+ CamelIMAPXListResponse *response,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVariantBuilder builder;
+ GVariant *value;
+ camel_imapx_token_t tok;
+ guchar *token;
+ guint len;
+ gboolean success;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY);
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+ if (tok != '(') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "list childinfo: expecting ')'");
+ goto fail;
+ }
+
+repeat:
+ success = camel_imapx_input_stream_astring (
+ stream, &token, cancellable, error);
+
+ if (!success)
+ goto fail;
+
+ value = g_variant_new_string ((gchar *) token);
+ g_variant_builder_add_value (&builder, value);
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+ if (tok != ')') {
+ camel_imapx_input_stream_ungettoken (stream, tok, token, len);
+ goto repeat;
+ }
+
+ return g_variant_builder_end (&builder);
+
+fail:
+ g_variant_builder_clear (&builder);
+
+ return NULL;
+}
+
+/* Helper for camel_imapx_list_response_new() */
+static GVariant *
+imapx_list_response_parse_oldname (CamelIMAPXInputStream *stream,
+ CamelIMAPXListResponse *response,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_imapx_token_t tok;
+ guchar *token;
+ guint len;
+ gchar *mailbox_name = NULL;
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+ if (tok != '(') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "list oldname: expecting ')'");
+ goto fail;
+ }
+
+ /* The separator character should be known by now. */
+ mailbox_name = camel_imapx_parse_mailbox (
+ stream, response->priv->separator, cancellable, error);
+ if (mailbox_name == NULL)
+ goto fail;
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+ if (tok != ')') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "list oldname: expecting ')'");
+ goto fail;
+ }
+
+ return g_variant_new_string (mailbox_name);
+
+fail:
+ g_free (mailbox_name);
+
+ return NULL;
+}
+
+/* Helper for camel_imapx_list_response_new() */
+static gboolean
+imapx_list_response_parse_extended_item (CamelIMAPXInputStream *stream,
+ CamelIMAPXListResponse *response,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guchar *token;
+ gchar *item_tag;
+ GVariant *item_value = NULL;
+ gboolean success;
+
+ /* Parse the extended item tag. */
+
+ success = camel_imapx_input_stream_astring (
+ stream, &token, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ item_tag = g_strdup ((gchar *) token);
+
+ /* Parse the extended item value if we recognize the tag,
+ * otherwise skip to the end. This is easier than trying
+ * to anticipate all possible extensions ahead of time.
+ *
+ * XXX If we had a real LALR(1) parser then we could at
+ * least parse it into an abstract syntax tree, and
+ * pick out the items we support. Alas, our ad-hoc
+ * IMAP parser makes this more difficult. */
+
+ /* RFC 5258 "LIST-EXTENDED" */
+ if (item_tag && g_ascii_strcasecmp (item_tag, "CHILDINFO") == 0) {
+ item_value = imapx_list_response_parse_childinfo (
+ stream, response, cancellable, error);
+ success = (item_value != NULL);
+
+ /* RFC 5465 "NOTIFY" */
+ } else if (item_tag && g_ascii_strcasecmp (item_tag, "OLDNAME") == 0) {
+ item_value = imapx_list_response_parse_oldname (
+ stream, response, cancellable, error);
+ success = (item_value != NULL);
+
+ } else {
+ success = camel_imapx_input_stream_skip_until (
+ stream, ")", cancellable, error);
+ }
+
+ if (item_value != NULL) {
+ /* Takes ownership of the item_tag string. */
+ g_hash_table_insert (
+ response->priv->extended_items,
+ item_tag, g_variant_ref_sink (item_value));
+ } else {
+ g_free (item_tag);
+ }
+
+ return success;
+}
+
+/**
+ * camel_imapx_list_response_new:
+ * @stream: a #CamelIMAPXInputStream
+ * @cancellable: a #GCancellable
+ * @error: return location for a #GError, or %NULL
+ *
+ * Attempts to parse an IMAP LIST response from @stream and, if successful,
+ * stores the response data in a new #CamelIMAPXListResponse. If an error
+ * occurs, the function sets @error and returns %NULL.
+ *
+ * Returns: a #CamelIMAPXListResponse, or %NULL
+ *
+ * Since: 3.10
+ **/
+CamelIMAPXListResponse *
+camel_imapx_list_response_new (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXListResponse *response;
+ camel_imapx_token_t tok;
+ guchar *token;
+ guint len;
+ const gchar *attribute;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (stream), NULL);
+
+ response = g_object_new (CAMEL_TYPE_IMAPX_LIST_RESPONSE, NULL);
+
+ /* Parse attributes. */
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+ if (tok != '(') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "list: expecting '('");
+ goto fail;
+ }
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ while (tok == IMAPX_TOK_STRING || tok == IMAPX_TOK_TOKEN) {
+ camel_imapx_list_response_add_attribute (
+ response, (gchar *) token);
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ }
+
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+ if (tok != ')') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "list: expecting ')'");
+ goto fail;
+ }
+
+ /* Add implied attributes (see RFC 5258 section 3.4). */
+
+ /* "\NoInferiors" implies "\HasNoChildren" */
+ attribute = CAMEL_IMAPX_LIST_ATTR_NOINFERIORS;
+ if (camel_imapx_list_response_has_attribute (response, attribute)) {
+ attribute = CAMEL_IMAPX_LIST_ATTR_HASNOCHILDREN;
+ camel_imapx_list_response_add_attribute (response, attribute);
+ }
+
+ /* "\NonExistent" implies "\NoSelect" */
+ attribute = CAMEL_IMAPX_LIST_ATTR_NONEXISTENT;
+ if (camel_imapx_list_response_has_attribute (response, attribute)) {
+ attribute = CAMEL_IMAPX_LIST_ATTR_NOSELECT;
+ camel_imapx_list_response_add_attribute (response, attribute);
+ }
+
+ /* Parse separator. */
+
+ success = camel_imapx_input_stream_nstring (
+ stream, &token, cancellable, error);
+
+ if (!success)
+ goto fail;
+
+ if (token != NULL)
+ response->priv->separator = *token;
+ else
+ response->priv->separator = '\0';
+
+ /* Parse mailbox name. */
+
+ response->priv->mailbox_name = camel_imapx_parse_mailbox (
+ stream, response->priv->separator, cancellable, error);
+ if (response->priv->mailbox_name == NULL)
+ goto fail;
+
+ /* Parse extended info (optional). */
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+ if (tok == '(') {
+ gboolean success;
+
+extended_item_repeat:
+ success = imapx_list_response_parse_extended_item (
+ stream, response, cancellable, error);
+ if (!success)
+ goto fail;
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+ if (tok != ')') {
+ camel_imapx_input_stream_ungettoken (
+ stream, tok, token, len);
+ goto extended_item_repeat;
+ }
+
+ } else if (tok == '\n') {
+ camel_imapx_input_stream_ungettoken (stream, tok, token, len);
+
+ } else {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "list: expecting '(' or NEWLINE");
+ goto fail;
+ }
+
+ return response;
+
+fail:
+ g_clear_object (&response);
+
+ return NULL;
+}
+
+/**
+ * camel_imapx_list_response_hash:
+ * @response: a #CamelIMAPXListResponse
+ *
+ * Generates a hash value for @response based on the mailbox name. This
+ * function is intended for easily hashing a #CamelIMAPXListResponse to
+ * add to a #GHashTable or similar data structure.
+ *
+ * Returns: a hash value for @response
+ *
+ * Since: 3.10
+ **/
+guint
+camel_imapx_list_response_hash (CamelIMAPXListResponse *response)
+{
+ const gchar *mailbox_name;
+
+ mailbox_name = camel_imapx_list_response_get_mailbox_name (response);
+
+ return g_str_hash (mailbox_name);
+}
+
+/**
+ * camel_imapx_list_response_equal:
+ * @response_a: the first #CamelIMAPXListResponse
+ * @response_b: the second #CamelIMAPXListResponse
+ *
+ * Checks two #CamelIMAPXListResponse instances for equality based on
+ * their mailbox names.
+ *
+ * Returns: %TRUE if @response_a and @response_b are equal
+ *
+ * Since: 3.10
+ **/
+gboolean
+camel_imapx_list_response_equal (CamelIMAPXListResponse *response_a,
+ CamelIMAPXListResponse *response_b)
+{
+ const gchar *mailbox_a;
+ const gchar *mailbox_b;
+
+ mailbox_a = camel_imapx_list_response_get_mailbox_name (response_a);
+ mailbox_b = camel_imapx_list_response_get_mailbox_name (response_b);
+
+ return g_str_equal (mailbox_a, mailbox_b);
+}
+
+/**
+ * camel_imapx_list_response_compare:
+ * @response_a: the first #CamelIMAPXListResponse
+ * @response_b: the second #CamelIMAPXListResponse
+ *
+ * Compares two #CamelIMAPXListResponse instances by their mailbox names.
+ *
+ * Returns: a negative value if @response_a compares before @response_b,
+ * zero if they compare equal, or a positive value if @response_a
+ * compares after @response_b
+ *
+ * Since: 3.10
+ **/
+gint
+camel_imapx_list_response_compare (CamelIMAPXListResponse *response_a,
+ CamelIMAPXListResponse *response_b)
+{
+ const gchar *mailbox_a;
+ const gchar *mailbox_b;
+
+ mailbox_a = camel_imapx_list_response_get_mailbox_name (response_a);
+ mailbox_b = camel_imapx_list_response_get_mailbox_name (response_b);
+
+ return g_strcmp0 (mailbox_a, mailbox_b);
+}
+
+/**
+ * camel_imapx_list_response_get_mailbox_name:
+ * @response: a #CamelIMAPXListResponse
+ *
+ * Returns the mailbox name for @response.
+ *
+ * Returns: the mailbox name
+ *
+ * Since: 3.10
+ **/
+const gchar *
+camel_imapx_list_response_get_mailbox_name (CamelIMAPXListResponse *response)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response), NULL);
+
+ return response->priv->mailbox_name;
+}
+
+/**
+ * camel_imapx_list_response_get_separator:
+ * @response: a #CamelIMAPXListResponse
+ *
+ * Returns the mailbox path separator character for @response.
+ *
+ * Returns: the mailbox path separator character
+ *
+ * Since: 3.10
+ **/
+gchar
+camel_imapx_list_response_get_separator (CamelIMAPXListResponse *response)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response), '\0');
+
+ return response->priv->separator;
+}
+
+/* Flag macros appear here in the reference manual,
+ * so also documenting them here in the source code
+ * since the documentation is so repetitive. */
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_NOINFERIORS:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc3501#section-7.2.2">
+ * RFC 3501 section 7.2.2</ulink>.
+ *
+ * Since: 3.10
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_NOSELECT:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc3501#section-7.2.2">
+ * RFC 3501 section 7.2.2</ulink>.
+ *
+ * Since: 3.10
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_MARKED:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc3501#section-7.2.2">
+ * RFC 3501 section 7.2.2</ulink>.
+ *
+ * Since: 3.10
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_UNMARKED:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc3501#section-7.2.2">
+ * RFC 3501 section 7.2.2</ulink>.
+ *
+ * Since: 3.10
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_NONEXISTENT:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc5258#section-3">
+ * RFC 5258 section 3</ulink>.
+ *
+ * Since: 3.10
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_SUBSCRIBED:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc5258#section-3.1">
+ * RFC 5258 section 3.1</ulink>.
+ *
+ * Since: 3.10
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_REMOTE:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc5258#section-3.1">
+ * RFC 5258 section 3.1</ulink>.
+ *
+ * Since: 3.10
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_HASCHILDREN:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc5258#section-4">
+ * RFC 5258 section 4</ulink>.
+ *
+ * Since: 3.10
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_HASNOCHILDREN:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc5258#section-4">
+ * RFC 5258 section 4</ulink>.
+ *
+ * Since: 3.10
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_ALL:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc6154#section-2">
+ * RFC 6154 section 2</ulink>.
+ *
+ * Since: 3.12
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_ARCHIVE:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc6154#section-2">
+ * RFC 6154 section 2</ulink>.
+ *
+ * Since: 3.12
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_DRAFTS:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc6154#section-2">
+ * RFC 6154 section 2</ulink>.
+ *
+ * Since: 3.12
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_FLAGGED:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc6154#section-2">
+ * RFC 6154 section 2</ulink>.
+ *
+ * Since: 3.12
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_JUNK:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc6154#section-2">
+ * RFC 6154 section 2</ulink>.
+ *
+ * Since: 3.12
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_SENT:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc6154#section-2">
+ * RFC 6154 section 2</ulink>.
+ *
+ * Since: 3.12
+ **/
+
+/**
+ * CAMEL_IMAPX_LIST_ATTR_TRASH:
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc6154#section-2">
+ * RFC 6154 section 2</ulink>.
+ *
+ * Since: 3.12
+ **/
+
+/**
+ * camel_imapx_list_response_add_attribute:
+ * @response: a #CamelIMAPXListResponse
+ * @attribute: a mailbox attribute
+ *
+ * Adds a mailbox attribute to @response. The @attribute should be one of
+ * the LIST attribute macros defined above.
+ *
+ * Since: 3.10
+ **/
+void
+camel_imapx_list_response_add_attribute (CamelIMAPXListResponse *response,
+ const gchar *attribute)
+{
+ const gchar *canonical = NULL;
+ gint ii;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response));
+ g_return_if_fail (attribute != NULL);
+
+ /* Try normalizing the attribute to match one of our
+ * pre-defined macros. */
+ for (ii = 0; ii < G_N_ELEMENTS (known_attributes); ii++) {
+ if (g_ascii_strcasecmp (attribute, known_attributes[ii]) == 0) {
+ canonical = known_attributes[ii];
+ break;
+ }
+ }
+
+ if (canonical == NULL)
+ canonical = g_intern_string (attribute);
+
+ g_hash_table_add (response->priv->attributes, (gpointer) canonical);
+}
+
+/**
+ * camel_imapx_list_response_has_attribute:
+ * @response: a #CamelIMAPXListResponse
+ * @attribute: a mailbox attribute
+ *
+ * Returns whether @response includes the given mailbox attribute. The
+ * @attribute should be one of the LIST attribute macros defined above.
+ *
+ * Returns: %TRUE if @response has @attribute, or else %FALSE
+ *
+ * Since: 3.10
+ **/
+gboolean
+camel_imapx_list_response_has_attribute (CamelIMAPXListResponse *response,
+ const gchar *attribute)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response), FALSE);
+ g_return_val_if_fail (attribute != NULL, FALSE);
+
+ return g_hash_table_contains (response->priv->attributes, attribute);
+}
+
+/**
+ * camel_imapx_list_response_dup_attributes:
+ * @response: a #CamelIMAPXListResponse
+ *
+ * Returns a #GHashTable of mailbox attributes to be used as a set.
+ * Use g_hash_table_contains() and g_hash_table_get_keys() to query
+ * for and list all mailbox attributes, respectively.
+ *
+ * The hash table keys are all internalized strings and must not be freed.
+ * Free the returned #GHashTable with g_hash_table_destroy() when finished
+ * with it.
+ *
+ * Returns: a newly-created #GHashTable
+ *
+ * Since: 3.10
+ **/
+GHashTable *
+camel_imapx_list_response_dup_attributes (CamelIMAPXListResponse *response)
+{
+ GHashTable *hash_table;
+ GHashTableIter iter;
+ gpointer key;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response), NULL);
+
+ hash_table = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
+
+ g_hash_table_iter_init (&iter, response->priv->attributes);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ g_hash_table_add (hash_table, key);
+
+ return hash_table;
+}
+
+/**
+ * camel_imapx_list_response_ref_extended_item:
+ * @response: a #CamelIMAPXListResponse
+ * @extended_item_tag: an extended item tag
+ *
+ * Returns the extended item value for @extended_item_tag as a #GVariant.
+ * The type of the #GVariant depends on the extended item. If no value
+ * for @extended_item_tag exists, the function returns %NULL.
+ *
+ * The returned #GVariant is referenced for thread-safety and should
+ * be unreferenced with g_variant_unref() when finished with it.
+ *
+ * Returns: a #GVariant, or %NULL
+ *
+ * Since: 3.10
+ **/
+GVariant *
+camel_imapx_list_response_ref_extended_item (CamelIMAPXListResponse *response,
+ const gchar *extended_item_tag)
+{
+ GHashTable *extended_items;
+ GVariant *value;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response), NULL);
+ g_return_val_if_fail (extended_item_tag != NULL, NULL);
+
+ extended_items = response->priv->extended_items;
+
+ value = g_hash_table_lookup (extended_items, extended_item_tag);
+
+ return (value != NULL) ? g_variant_ref (value) : NULL;
+}
+
+/**
+ * camel_imapx_list_response_get_oldname:
+ * @response: a #CamelIMAPXListResponse
+ *
+ * Convenience function returns the value of the "OLDNAME" extended data
+ * item, or %NULL if no such extended data item is present.
+ *
+ * The presence of this extended data item indicates the mailbox has been
+ * renamed. See <ulink url="http://tools.ietf.org/html/rfc5465#section-5.4">
+ * RFC 5465 Section 5.4</ulink> for further details.
+ *
+ * Returns: the old mailbox name, or %NULL
+ *
+ * Since: 3.12
+ **/
+const gchar *
+camel_imapx_list_response_get_oldname (CamelIMAPXListResponse *response)
+{
+ GHashTable *extended_items;
+ GVariant *value;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response), NULL);
+
+ extended_items = response->priv->extended_items;
+
+ value = g_hash_table_lookup (extended_items, "OLDNAME");
+
+ return (value != NULL) ? g_variant_get_string (value, NULL) : NULL;
+}
+
diff --git a/src/camel/providers/imapx/camel-imapx-list-response.h b/src/camel/providers/imapx/camel-imapx-list-response.h
new file mode 100644
index 000000000..b0419bf62
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-list-response.h
@@ -0,0 +1,125 @@
+/*
+ * camel-imapx-list-response.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_LIST_RESPONSE_H
+#define CAMEL_IMAPX_LIST_RESPONSE_H
+
+#include <gio/gio.h>
+
+#include "camel-imapx-input-stream.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_LIST_RESPONSE \
+ (camel_imapx_list_response_get_type ())
+#define CAMEL_IMAPX_LIST_RESPONSE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_LIST_RESPONSE, CamelIMAPXListResponse))
+#define CAMEL_IMAPX_LIST_RESPONSE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_LIST_RESPONSE, CamelIMAPXListResponseClass))
+#define CAMEL_IS_IMAPX_LIST_RESPONSE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_LIST_RESPONSE))
+#define CAMEL_IS_IMAPX_LIST_RESPONSE_CLASS(cls) \
+ (G_TYPE_CHECK_INSTANCE_CLASS \
+ ((cls), CAMEL_TYPE_IMAPX_LIST_RESPONSE))
+#define CAMEL_IMAPX_LIST_RESPONSE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_LIST_RESPONSE, CamelIMAPXListResponseClass))
+
+/* RFC 3501 Standard Flags */
+#define CAMEL_IMAPX_LIST_ATTR_MARKED "\\Marked"
+#define CAMEL_IMAPX_LIST_ATTR_NOINFERIORS "\\NoInferiors"
+#define CAMEL_IMAPX_LIST_ATTR_NOSELECT "\\NoSelect"
+#define CAMEL_IMAPX_LIST_ATTR_UNMARKED "\\Unmarked"
+
+/* RFC 5258 "LIST-EXTENDED" Flags */
+#define CAMEL_IMAPX_LIST_ATTR_HASCHILDREN "\\HasChildren"
+#define CAMEL_IMAPX_LIST_ATTR_HASNOCHILDREN "\\HasNoChildren"
+#define CAMEL_IMAPX_LIST_ATTR_NONEXISTENT "\\NonExistent"
+#define CAMEL_IMAPX_LIST_ATTR_REMOTE "\\Remote"
+#define CAMEL_IMAPX_LIST_ATTR_SUBSCRIBED "\\Subscribed"
+
+/* RFC 6154 "SPECIAL-USE" Flags */
+#define CAMEL_IMAPX_LIST_ATTR_ALL "\\All"
+#define CAMEL_IMAPX_LIST_ATTR_ARCHIVE "\\Archive"
+#define CAMEL_IMAPX_LIST_ATTR_DRAFTS "\\Drafts"
+#define CAMEL_IMAPX_LIST_ATTR_FLAGGED "\\Flagged"
+#define CAMEL_IMAPX_LIST_ATTR_JUNK "\\Junk"
+#define CAMEL_IMAPX_LIST_ATTR_SENT "\\Sent"
+#define CAMEL_IMAPX_LIST_ATTR_TRASH "\\Trash"
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAPXListResponse CamelIMAPXListResponse;
+typedef struct _CamelIMAPXListResponseClass CamelIMAPXListResponseClass;
+typedef struct _CamelIMAPXListResponsePrivate CamelIMAPXListResponsePrivate;
+
+/**
+ * CamelIMAPXListResponse:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.10
+ **/
+struct _CamelIMAPXListResponse {
+ GObject parent;
+ CamelIMAPXListResponsePrivate *priv;
+};
+
+struct _CamelIMAPXListResponseClass {
+ GObjectClass parent_class;
+};
+
+GType camel_imapx_list_response_get_type
+ (void) G_GNUC_CONST;
+CamelIMAPXListResponse *
+ camel_imapx_list_response_new
+ (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+guint camel_imapx_list_response_hash
+ (CamelIMAPXListResponse *response);
+gboolean camel_imapx_list_response_equal
+ (CamelIMAPXListResponse *response_a,
+ CamelIMAPXListResponse *response_b);
+gint camel_imapx_list_response_compare
+ (CamelIMAPXListResponse *response_a,
+ CamelIMAPXListResponse *response_b);
+const gchar * camel_imapx_list_response_get_mailbox_name
+ (CamelIMAPXListResponse *response);
+gchar camel_imapx_list_response_get_separator
+ (CamelIMAPXListResponse *response);
+void camel_imapx_list_response_add_attribute
+ (CamelIMAPXListResponse *response,
+ const gchar *attribute);
+gboolean camel_imapx_list_response_has_attribute
+ (CamelIMAPXListResponse *response,
+ const gchar *attribute);
+GHashTable * camel_imapx_list_response_dup_attributes
+ (CamelIMAPXListResponse *response);
+GVariant * camel_imapx_list_response_ref_extended_item
+ (CamelIMAPXListResponse *response,
+ const gchar *extended_item_tag);
+const gchar * camel_imapx_list_response_get_oldname
+ (CamelIMAPXListResponse *response);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_LIST_RESPONSE_H */
+
diff --git a/src/camel/providers/imapx/camel-imapx-logger.c b/src/camel/providers/imapx/camel-imapx-logger.c
new file mode 100644
index 000000000..084bc27c1
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-logger.c
@@ -0,0 +1,240 @@
+/*
+ * camel-imapx-logger.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * SECTION: camel-imapx-logger
+ * @include: camel-imapx-logger.h
+ * @short_description: Log input/output streams
+ *
+ * #CamelIMAPXLogger is a simple #GConverter that just echos data to standard
+ * output if the I/O debugging setting is enabled ('CAMEL_DEBUG=imapx:io').
+ * Attaches to the #GInputStream and #GOutputStream.
+ **/
+
+#include "camel-imapx-logger.h"
+
+#include <string.h>
+
+#include "camel-imapx-utils.h"
+
+#define CAMEL_IMAPX_LOGGER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_IMAPX_LOGGER, CamelIMAPXLoggerPrivate))
+
+struct _CamelIMAPXLoggerPrivate {
+ gchar prefix;
+};
+
+enum {
+ PROP_0,
+ PROP_PREFIX
+};
+
+/* Forward Declarations */
+static void camel_imapx_logger_interface_init
+ (GConverterIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ CamelIMAPXLogger,
+ camel_imapx_logger,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (
+ G_TYPE_CONVERTER,
+ camel_imapx_logger_interface_init))
+
+static void
+imapx_logger_set_prefix (CamelIMAPXLogger *logger,
+ gchar prefix)
+{
+ logger->priv->prefix = prefix;
+}
+
+static void
+imapx_logger_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PREFIX:
+ imapx_logger_set_prefix (
+ CAMEL_IMAPX_LOGGER (object),
+ g_value_get_schar (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_logger_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PREFIX:
+ g_value_set_schar (
+ value,
+ camel_imapx_logger_get_prefix (
+ CAMEL_IMAPX_LOGGER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static GConverterResult
+imapx_logger_convert (GConverter *converter,
+ gconstpointer inbuf,
+ gsize inbuf_size,
+ gpointer outbuf,
+ gsize outbuf_size,
+ GConverterFlags flags,
+ gsize *bytes_read,
+ gsize *bytes_written,
+ GError **error)
+{
+ CamelIMAPXLoggerPrivate *priv;
+ GConverterResult result;
+ gsize min_size;
+ const gchar *login_start;
+
+ priv = CAMEL_IMAPX_LOGGER_GET_PRIVATE (converter);
+
+ min_size = MIN (inbuf_size, outbuf_size);
+
+ memcpy (outbuf, inbuf, min_size);
+ *bytes_read = *bytes_written = min_size;
+
+ login_start = g_strstr_len (outbuf, min_size, " LOGIN ");
+ if (login_start > (const gchar *) outbuf) {
+ const gchar *space = g_strstr_len (outbuf, min_size, " ");
+
+ if (space == login_start) {
+ camel_imapx_debug (
+ io, priv->prefix, "I/O: '%.*s ...'\n",
+ (gint) (login_start - ((const gchar *) outbuf) + 6), (gchar *) outbuf);
+ } else {
+ /* To print the command the other way */
+ login_start = NULL;
+ }
+ }
+
+ if (!login_start) {
+ /* Skip ending '\n' '\r'; it may sometimes show wrong data,
+ when the input is divided into wrong chunks, but it will
+ usually work as is needed, no extra new-lines in the log */
+ while (min_size > 0 && (((gchar *) outbuf)[min_size - 1] == '\r' || ((gchar *) outbuf)[min_size - 1] == '\n'))
+ min_size--;
+
+ camel_imapx_debug (
+ io, priv->prefix, "I/O: '%.*s'\n",
+ (gint) min_size, (gchar *) outbuf);
+ }
+
+ if ((flags & G_CONVERTER_INPUT_AT_END) != 0)
+ result = G_CONVERTER_FINISHED;
+ else if ((flags & G_CONVERTER_FLUSH) != 0)
+ result = G_CONVERTER_FLUSHED;
+ else
+ result = G_CONVERTER_CONVERTED;
+
+ return result;
+}
+
+static void
+imapx_logger_reset (GConverter *converter)
+{
+ /* Nothing to do. */
+}
+
+static void
+camel_imapx_logger_class_init (CamelIMAPXLoggerClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelIMAPXLoggerPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = imapx_logger_set_property;
+ object_class->get_property = imapx_logger_get_property;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PREFIX,
+ g_param_spec_char (
+ "prefix",
+ "Prefix",
+ "Output prefix to distinguish connections",
+ 0x20, 0x7F, '*',
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_imapx_logger_interface_init (GConverterIface *iface)
+{
+ iface->convert = imapx_logger_convert;
+ iface->reset = imapx_logger_reset;
+}
+
+static void
+camel_imapx_logger_init (CamelIMAPXLogger *logger)
+{
+ logger->priv = CAMEL_IMAPX_LOGGER_GET_PRIVATE (logger);
+}
+
+/**
+ * camel_imapx_logger_new:
+ * @prefix: a prefix character
+ *
+ * Creates a new #CamelIMAPXLogger. Each output line generated by the
+ * logger will have a prefix string that includes the @prefix character
+ * to distinguish it from other active loggers.
+ *
+ * Returns: a #CamelIMAPXLogger
+ *
+ * Since: 3.12
+ **/
+GConverter *
+camel_imapx_logger_new (gchar prefix)
+{
+ return g_object_new (
+ CAMEL_TYPE_IMAPX_LOGGER,
+ "prefix", prefix, NULL);
+}
+
+/**
+ * camel_imapx_logger_get_prefix:
+ * @logger: a #CamelIMAPXLogger
+ *
+ * Returns the prefix character passed to camel_imapx_logger_new().
+ *
+ * Returns: the prefix character
+ *
+ * Since: 3.12
+ **/
+gchar
+camel_imapx_logger_get_prefix (CamelIMAPXLogger *logger)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_LOGGER (logger), 0);
+
+ return logger->priv->prefix;
+}
+
diff --git a/src/camel/providers/imapx/camel-imapx-logger.h b/src/camel/providers/imapx/camel-imapx-logger.h
new file mode 100644
index 000000000..cdf9231c7
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-logger.h
@@ -0,0 +1,72 @@
+/*
+ * camel-imapx-logger.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_LOGGER_H
+#define CAMEL_IMAPX_LOGGER_H
+
+#include <gio/gio.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_LOGGER \
+ (camel_imapx_logger_get_type ())
+#define CAMEL_IMAPX_LOGGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_LOGGER, CamelIMAPXLogger))
+#define CAMEL_IMAPX_LOGGER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_LOGGER, CamelIMAPXLoggerClass))
+#define CAMEL_IS_IMAPX_LOGGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_LOGGER))
+#define CAMEL_IS_IMAPX_LOGGER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_LOGGER))
+#define CAMEL_IMAPX_LOGGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_LOGGER, CamelIMAPXLoggerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAPXLogger CamelIMAPXLogger;
+typedef struct _CamelIMAPXLoggerClass CamelIMAPXLoggerClass;
+typedef struct _CamelIMAPXLoggerPrivate CamelIMAPXLoggerPrivate;
+
+/**
+ * CamelIMAPXLogger:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.12
+ **/
+struct _CamelIMAPXLogger {
+ GObject parent;
+ CamelIMAPXLoggerPrivate *priv;
+};
+
+struct _CamelIMAPXLoggerClass {
+ GObjectClass parent_class;
+};
+
+GType camel_imapx_logger_get_type (void) G_GNUC_CONST;
+GConverter * camel_imapx_logger_new (gchar prefix);
+gchar camel_imapx_logger_get_prefix (CamelIMAPXLogger *logger);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_LOGGER_H */
+
diff --git a/src/camel/providers/imapx/camel-imapx-mailbox.c b/src/camel/providers/imapx/camel-imapx-mailbox.c
new file mode 100644
index 000000000..054eecd46
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-mailbox.c
@@ -0,0 +1,1262 @@
+/*
+ * camel-imapx-mailbox.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * SECTION: camel-imapx-mailbox
+ * @include: camel/camel.h
+ * @short_description: Stores the state of an IMAP mailbox
+ *
+ * #CamelIMAPXMailbox models the current state of an IMAP mailbox as
+ * accumulated from untagged IMAP server responses in the current session.
+ *
+ * In particular, a #CamelIMAPXMailbox should <emphasis>not</emphasis> be
+ * populated with locally cached information from the previous session.
+ * This is why instantiation requires a #CamelIMAPXListResponse.
+ **/
+
+#include "camel-imapx-mailbox.h"
+#include "camel-imapx-utils.h"
+
+#define CAMEL_IMAPX_MAILBOX_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_IMAPX_MAILBOX, CamelIMAPXMailboxPrivate))
+
+struct _CamelIMAPXMailboxPrivate {
+ gchar *name;
+ gchar separator;
+ CamelIMAPXNamespace *namespace;
+
+ guint32 messages;
+ guint32 recent;
+ guint32 unseen;
+ guint32 uidnext;
+ guint32 uidvalidity;
+ guint64 highestmodseq;
+ guint32 permanentflags;
+
+ volatile gint change_stamp;
+
+ CamelIMAPXMailboxState state;
+
+ GMutex property_lock;
+ GMutex update_lock;
+ gint update_count;
+
+ /* Protected by the "property_lock". */
+ GHashTable *attributes;
+ GSequence *message_map;
+ gchar **quota_roots;
+};
+
+G_DEFINE_TYPE (
+ CamelIMAPXMailbox,
+ camel_imapx_mailbox,
+ G_TYPE_OBJECT)
+
+static gint
+imapx_mailbox_message_map_compare (gconstpointer a,
+ gconstpointer b,
+ gpointer unused)
+{
+ guint32 uid_a = GPOINTER_TO_UINT (a);
+ guint32 uid_b = GPOINTER_TO_UINT (b);
+
+ return (uid_a == uid_b) ? 0 : (uid_a < uid_b) ? -1 : 1;
+}
+
+static void
+imapx_mailbox_dispose (GObject *object)
+{
+ CamelIMAPXMailboxPrivate *priv;
+
+ priv = CAMEL_IMAPX_MAILBOX_GET_PRIVATE (object);
+
+ g_clear_object (&priv->namespace);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_imapx_mailbox_parent_class)->dispose (object);
+}
+
+static void
+imapx_mailbox_finalize (GObject *object)
+{
+ CamelIMAPXMailboxPrivate *priv;
+
+ priv = CAMEL_IMAPX_MAILBOX_GET_PRIVATE (object);
+
+ g_free (priv->name);
+
+ g_mutex_clear (&priv->property_lock);
+ g_mutex_clear (&priv->update_lock);
+ g_hash_table_destroy (priv->attributes);
+ g_sequence_free (priv->message_map);
+ g_strfreev (priv->quota_roots);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_imapx_mailbox_parent_class)->finalize (object);
+}
+
+static void
+camel_imapx_mailbox_class_init (CamelIMAPXMailboxClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelIMAPXMailboxPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = imapx_mailbox_dispose;
+ object_class->finalize = imapx_mailbox_finalize;
+}
+
+static void
+camel_imapx_mailbox_init (CamelIMAPXMailbox *mailbox)
+{
+ mailbox->priv = CAMEL_IMAPX_MAILBOX_GET_PRIVATE (mailbox);
+
+ g_mutex_init (&mailbox->priv->property_lock);
+ g_mutex_init (&mailbox->priv->update_lock);
+ mailbox->priv->message_map = g_sequence_new (NULL);
+ mailbox->priv->permanentflags = ~0;
+ mailbox->priv->state = CAMEL_IMAPX_MAILBOX_STATE_CREATED;
+ mailbox->priv->update_count = 0;
+ mailbox->priv->change_stamp = 0;
+}
+
+/**
+ * camel_imapx_mailbox_new:
+ * @response: a #CamelIMAPXListResponse
+ * @namespace_: a #CamelIMAPXNamespace
+ *
+ * Creates a new #CamelIMAPXMailbox from @response and @namespace.
+ *
+ * The mailbox's name, path separator character, and attribute set are
+ * initialized from the #CamelIMAPXListResponse.
+ *
+ * Returns: a #CamelIMAPXMailbox
+ *
+ * Since: 3.12
+ **/
+CamelIMAPXMailbox *
+camel_imapx_mailbox_new (CamelIMAPXListResponse *response,
+ CamelIMAPXNamespace *namespace)
+{
+ CamelIMAPXMailbox *mailbox;
+ GHashTable *attributes;
+ const gchar *name;
+ gchar separator;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response), NULL);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_NAMESPACE (namespace), NULL);
+
+ name = camel_imapx_list_response_get_mailbox_name (response);
+ separator = camel_imapx_list_response_get_separator (response);
+ attributes = camel_imapx_list_response_dup_attributes (response);
+
+ /* The INBOX mailbox is case-insensitive. */
+ if (g_ascii_strcasecmp (name, "INBOX") == 0)
+ name = "INBOX";
+
+ mailbox = g_object_new (CAMEL_TYPE_IMAPX_MAILBOX, NULL);
+ mailbox->priv->name = g_strdup (name);
+ mailbox->priv->separator = separator;
+ mailbox->priv->namespace = g_object_ref (namespace);
+ mailbox->priv->attributes = attributes; /* takes ownership */
+
+ return mailbox;
+}
+
+/**
+ * camel_imapx_mailbox_clone:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @new_mailbox_name: new name for the cloned mailbox
+ *
+ * Creates an identical copy of @mailbox, except for the mailbox name.
+ * The copied #CamelIMAPXMailbox is given the name @new_mailbox_name.
+ *
+ * The @new_mailbox_name must be in the same IMAP namespace as @mailbox.
+ *
+ * This is primarily useful for handling mailbox renames. It is safer to
+ * create a new #CamelIMAPXMailbox instance with the new name than to try
+ * and rename an existing #CamelIMAPXMailbox, which could disrupt mailbox
+ * operations in progress as well as data structures that track mailboxes
+ * by name.
+ *
+ * Returns: a copy of @mailbox, named @new_mailbox_name
+ *
+ * Since: 3.12
+ **/
+CamelIMAPXMailbox *
+camel_imapx_mailbox_clone (CamelIMAPXMailbox *mailbox,
+ const gchar *new_mailbox_name)
+{
+ CamelIMAPXMailbox *clone;
+ GHashTableIter iter;
+ gpointer key;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), NULL);
+ g_return_val_if_fail (new_mailbox_name != NULL, NULL);
+
+ /* The INBOX mailbox is case-insensitive. */
+ if (g_ascii_strcasecmp (new_mailbox_name, "INBOX") == 0)
+ new_mailbox_name = "INBOX";
+
+ clone = g_object_new (CAMEL_TYPE_IMAPX_MAILBOX, NULL);
+ clone->priv->name = g_strdup (new_mailbox_name);
+ clone->priv->separator = mailbox->priv->separator;
+ clone->priv->namespace = g_object_ref (mailbox->priv->namespace);
+
+ clone->priv->messages = mailbox->priv->messages;
+ clone->priv->recent = mailbox->priv->recent;
+ clone->priv->unseen = mailbox->priv->unseen;
+ clone->priv->uidnext = mailbox->priv->uidnext;
+ clone->priv->uidvalidity = mailbox->priv->uidvalidity;
+ clone->priv->highestmodseq = mailbox->priv->highestmodseq;
+ clone->priv->state = mailbox->priv->state;
+
+ clone->priv->quota_roots = g_strdupv (mailbox->priv->quota_roots);
+
+ /* Use camel_imapx_list_response_dup_attributes()
+ * as a guide for cloning the mailbox attributes. */
+
+ clone->priv->attributes = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
+
+ g_mutex_lock (&mailbox->priv->property_lock);
+
+ g_hash_table_iter_init (&iter, mailbox->priv->attributes);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ g_hash_table_add (clone->priv->attributes, key);
+
+ g_mutex_unlock (&mailbox->priv->property_lock);
+
+ return clone;
+}
+
+/**
+ * camel_imapx_mailbox_get_state:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Returns current state of the mailbox. This is used for folder
+ * structure updates, to identify newly created, updated, renamed
+ * or removed mailboxes.
+ *
+ * Returns: Current (update) state of the mailbox.
+ *
+ * Since: 3.16
+ **/
+CamelIMAPXMailboxState
+camel_imapx_mailbox_get_state (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), CAMEL_IMAPX_MAILBOX_STATE_UNKNOWN);
+
+ return mailbox->priv->state;
+}
+
+/**
+ * camel_imapx_mailbox_set_state:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @state: a new #CamelIMAPXMailboxState to set
+ *
+ * Sets current (update) state of the mailbox. This is used for folder
+ * structure updates, to identify newly created, updated, renamed
+ * or removed mailboxes.
+ *
+ * Since: 3.16
+ **/
+void
+camel_imapx_mailbox_set_state (CamelIMAPXMailbox *mailbox,
+ CamelIMAPXMailboxState state)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ mailbox->priv->state = state;
+}
+
+/**
+ * camel_imapx_mailbox_exists:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Convenience function returns whether @mailbox exists; that is, whether it
+ * <emphasis>lacks</emphasis> a #CAMEL_IMAPX_LIST_ATTR_NONEXISTENT attribute.
+ *
+ * Non-existent mailboxes should generally be disregarded.
+ *
+ * Returns: whether @mailbox exists
+ *
+ * Since: 3.12
+ **/
+gboolean
+camel_imapx_mailbox_exists (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ return !camel_imapx_mailbox_has_attribute (
+ mailbox, CAMEL_IMAPX_LIST_ATTR_NONEXISTENT);
+}
+
+/**
+ * camel_imapx_mailbox_compare:
+ * @mailbox_a: the first #CamelIMAPXMailbox
+ * @mailbox_b: the second #CamelIMAPXMailbox
+ *
+ * Compares two #CamelIMAPXMailbox instances by their mailbox names.
+ *
+ * Returns: a negative value if @mailbox_a compares before @mailbox_b,
+ * zero if they compare equal, or a positive value if @mailbox_a
+ * compares after @mailbox_b
+ *
+ * Since: 3.12
+ **/
+gint
+camel_imapx_mailbox_compare (CamelIMAPXMailbox *mailbox_a,
+ CamelIMAPXMailbox *mailbox_b)
+{
+ const gchar *mailbox_name_a;
+ const gchar *mailbox_name_b;
+
+ mailbox_name_a = camel_imapx_mailbox_get_name (mailbox_a);
+ mailbox_name_b = camel_imapx_mailbox_get_name (mailbox_b);
+
+ return g_strcmp0 (mailbox_name_a, mailbox_name_b);
+}
+
+/**
+ * camel_imapx_mailbox_matches:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @pattern: mailbox name with possible wildcards
+ *
+ * Returns %TRUE if @mailbox's name matches @pattern. The @pattern may
+ * contain wildcard characters '*' and '%', which are interpreted similar
+ * to the IMAP LIST command.
+ *
+ * Returns: %TRUE if @mailbox's name matches @pattern, %FALSE otherwise
+ *
+ * Since: 3.12
+ **/
+gboolean
+camel_imapx_mailbox_matches (CamelIMAPXMailbox *mailbox,
+ const gchar *pattern)
+{
+ const gchar *name;
+ gchar separator;
+ gchar name_ch;
+ gchar patt_ch;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+ g_return_val_if_fail (pattern != NULL, FALSE);
+
+ name = camel_imapx_mailbox_get_name (mailbox);
+ separator = camel_imapx_mailbox_get_separator (mailbox);
+
+ name_ch = *name++;
+ patt_ch = *pattern++;
+
+ while (name_ch != '\0' && patt_ch != '\0') {
+ if (name_ch == patt_ch) {
+ name_ch = *name++;
+ patt_ch = *pattern++;
+ } else if (patt_ch == '%') {
+ if (name_ch != separator)
+ name_ch = *name++;
+ else
+ patt_ch = *pattern++;
+ } else {
+ return (patt_ch == '*');
+ }
+ }
+
+ return (name_ch == '\0') &&
+ (patt_ch == '%' || patt_ch == '*' || patt_ch == '\0');
+}
+
+/**
+ * camel_imapx_mailbox_get_name:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Returns the mailbox name for @mailbox.
+ *
+ * Returns: the mailbox name
+ *
+ * Since: 3.12
+ **/
+const gchar *
+camel_imapx_mailbox_get_name (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), NULL);
+
+ return mailbox->priv->name;
+}
+
+/**
+ * camel_imapx_mailbox_get_separator:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Returns the path separator character for @mailbox.
+ *
+ * Returns: the mailbox path separator character
+ *
+ * Since: 3.12
+ **/
+gchar
+camel_imapx_mailbox_get_separator (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), '\0');
+
+ return mailbox->priv->separator;
+}
+
+/**
+ * camel_imapx_mailbox_dup_folder_path:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Returns the mailbox name as folder path.
+ *
+ * Returns: the mailbox name as folder path.
+ *
+ * Since: 3.16
+ **/
+gchar *
+camel_imapx_mailbox_dup_folder_path (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), NULL);
+
+ return camel_imapx_mailbox_to_folder_path (
+ camel_imapx_mailbox_get_name (mailbox),
+ camel_imapx_mailbox_get_separator (mailbox));
+}
+
+/**
+ * camel_imapx_mailbox_get_namespace:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Returns the #CamelIMAPXNamespace representing the IMAP server namespace
+ * to which @mailbox belongs.
+ *
+ * Returns: a #CamelIMAPXNamespace
+ *
+ * Since: 3.12
+ **/
+CamelIMAPXNamespace *
+camel_imapx_mailbox_get_namespace (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), NULL);
+
+ return mailbox->priv->namespace;
+}
+
+/**
+ * camel_imapx_mailbox_get_messages:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Returns the last known number of messages in the mailbox.
+ *
+ * This value should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * Returns: the last known "MESSAGES" value
+ *
+ * Since: 3.12
+ **/
+guint32
+camel_imapx_mailbox_get_messages (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), 0);
+
+ return mailbox->priv->messages;
+}
+
+/**
+ * camel_imapx_mailbox_set_messages:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @messages: a newly-reported "MESSAGES" value
+ *
+ * Updates the last known number of messages in the mailbox.
+ *
+ * This value should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_set_messages (CamelIMAPXMailbox *mailbox,
+ guint32 messages)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ if (mailbox->priv->messages == messages)
+ return;
+
+ mailbox->priv->messages = messages;
+
+ g_atomic_int_add (&mailbox->priv->change_stamp, 1);
+}
+
+/**
+ * camel_imapx_mailbox_get_recent:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Returns the last known number of messages with the \Recent flag set.
+ *
+ * This value should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * Returns: the last known "RECENT" value
+ *
+ * Since: 3.12
+ **/
+guint32
+camel_imapx_mailbox_get_recent (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), 0);
+
+ return mailbox->priv->recent;
+}
+
+/**
+ * camel_imapx_mailbox_set_recent:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @recent: a newly-reported "RECENT" value
+ *
+ * Updates the last known number of messages with the \Recent flag set.
+ *
+ * This value should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_set_recent (CamelIMAPXMailbox *mailbox,
+ guint32 recent)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ if (mailbox->priv->recent == recent)
+ return;
+
+ mailbox->priv->recent = recent;
+
+ g_atomic_int_add (&mailbox->priv->change_stamp, 1);
+}
+
+/**
+ * camel_imapx_mailbox_get_unseen:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Returns the last known number of messages which do not have the \Seen
+ * flag set.
+ *
+ * This value should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * Returns: the last known "UNSEEN" value
+ *
+ * Since: 3.12
+ **/
+guint32
+camel_imapx_mailbox_get_unseen (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), 0);
+
+ return mailbox->priv->unseen;
+}
+
+/**
+ * camel_imapx_mailbox_set_unseen:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @unseen: a newly-reported "UNSEEN" value
+ *
+ * Updates the last known number of messages which do not have the \Seen
+ * flag set.
+ *
+ * This value should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_set_unseen (CamelIMAPXMailbox *mailbox,
+ guint32 unseen)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ if (mailbox->priv->unseen == unseen)
+ return;
+
+ mailbox->priv->unseen = unseen;
+
+ g_atomic_int_add (&mailbox->priv->change_stamp, 1);
+}
+
+/**
+ * camel_imapx_mailbox_get_uidnext:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Returns the last known next unique identifier value of the mailbox.
+ *
+ * This value should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * Returns: the last known "UIDNEXT" value
+ *
+ * Since: 3.12
+ **/
+guint32
+camel_imapx_mailbox_get_uidnext (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), 0);
+
+ return mailbox->priv->uidnext;
+}
+
+/**
+ * camel_imapx_mailbox_set_uidnext:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @uidnext: a newly-reported "UIDNEXT" value
+ *
+ * Updates the last known next unique identifier value of the mailbox.
+ *
+ * This value should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_set_uidnext (CamelIMAPXMailbox *mailbox,
+ guint32 uidnext)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ if (mailbox->priv->uidnext == uidnext)
+ return;
+
+ mailbox->priv->uidnext = uidnext;
+
+ g_atomic_int_add (&mailbox->priv->change_stamp, 1);
+}
+
+/**
+ * camel_imapx_mailbox_get_uidvalidity:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Returns the last known unique identifier validity value of the mailbox.
+ *
+ * This valud should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * Returns: the last known "UIDVALIDITY" value
+ *
+ * Since: 3.12
+ **/
+guint32
+camel_imapx_mailbox_get_uidvalidity (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), 0);
+
+ return mailbox->priv->uidvalidity;
+}
+
+/**
+ * camel_imapx_mailbox_set_uidvalidity:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @uidvalidity: a newly-reported "UIDVALIDITY" value
+ *
+ * Updates the last known unique identifier validity value of the mailbox.
+ *
+ * This value should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_set_uidvalidity (CamelIMAPXMailbox *mailbox,
+ guint32 uidvalidity)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ if (mailbox->priv->uidvalidity == uidvalidity)
+ return;
+
+ mailbox->priv->uidvalidity = uidvalidity;
+
+ g_atomic_int_add (&mailbox->priv->change_stamp, 1);
+}
+
+/**
+ * camel_imapx_mailbox_get_highestmodseq:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Returns the last known highest mod-sequence value of all messages in the
+ * mailbox, or zero if the server does not support the persistent storage of
+ * mod-sequences for the mailbox.
+ *
+ * This value should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * Returns: the last known "HIGHESTMODSEQ" value
+ *
+ * Since: 3.12
+ **/
+guint64
+camel_imapx_mailbox_get_highestmodseq (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), 0);
+
+ return mailbox->priv->highestmodseq;
+}
+
+/**
+ * camel_imapx_mailbox_set_highestmodseq:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @highestmodseq: a newly-reported "HIGHESTMODSEQ" value
+ *
+ * Updates the last known highest mod-sequence value of all messages in
+ * the mailbox. If the server does not support the persistent storage of
+ * mod-sequences for the mailbox then the value should remain zero.
+ *
+ * This value should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_set_highestmodseq (CamelIMAPXMailbox *mailbox,
+ guint64 highestmodseq)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ if (mailbox->priv->highestmodseq == highestmodseq)
+ return;
+
+ mailbox->priv->highestmodseq = highestmodseq;
+
+ g_atomic_int_add (&mailbox->priv->change_stamp, 1);
+}
+
+/**
+ * camel_imapx_mailbox_get_permanentflags:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Returns: PERMANENTFLAGS response for the mailbox, or ~0, if the mailbox
+ * was not selected yet.
+ *
+ * Since: 3.16
+ **/
+guint32
+camel_imapx_mailbox_get_permanentflags (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), ~0);
+
+ return mailbox->priv->permanentflags;
+}
+
+/**
+ * camel_imapx_mailbox_set_permanentflags:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @permanentflags: a newly-reported "PERMANENTFLAGS" value
+ *
+ * Updates the last know value for PERMANENTFLAGS for this mailbox.
+ *
+ * Since: 3.16
+ **/
+void
+camel_imapx_mailbox_set_permanentflags (CamelIMAPXMailbox *mailbox,
+ guint32 permanentflags)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ if ((permanentflags & CAMEL_MESSAGE_USER) != 0) {
+ permanentflags |= CAMEL_MESSAGE_JUNK;
+ permanentflags |= CAMEL_MESSAGE_NOTJUNK;
+ }
+
+ mailbox->priv->permanentflags = permanentflags;
+}
+
+/**
+ * camel_imapx_mailbox_dup_quota_roots:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Returns the last known list of quota roots for @mailbox as described
+ * in <ulink url="http://tools.ietf.org/html/rfc2087">RFC 2087</ulink>,
+ * or %NULL if no quota information for @mailbox is available.
+ *
+ * This value should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * The returned newly-allocated, %NULL-terminated string array should
+ * be freed with g_strfreev() when finished with it.
+ *
+ * Returns: the last known "QUOTAROOT" value
+ *
+ * Since: 3.12
+ **/
+gchar **
+camel_imapx_mailbox_dup_quota_roots (CamelIMAPXMailbox *mailbox)
+{
+ gchar **quota_roots;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), NULL);
+
+ g_mutex_lock (&mailbox->priv->property_lock);
+
+ quota_roots = g_strdupv (mailbox->priv->quota_roots);
+
+ g_mutex_unlock (&mailbox->priv->property_lock);
+
+ return quota_roots;
+}
+
+/**
+ * camel_imapx_mailbox_set_quota_roots:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @quota_roots: a newly-reported "QUOTAROOT" value
+ *
+ * Updates the last known list of quota roots for @mailbox as described
+ * in <ulink url="http://tools.ietf.org/html/rfc2087">RFC 2087</ulink>.
+ *
+ * This value should reflect the present state of the IMAP server as
+ * reported through untagged server responses in the current session.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_set_quota_roots (CamelIMAPXMailbox *mailbox,
+ const gchar **quota_roots)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ g_mutex_lock (&mailbox->priv->property_lock);
+
+ g_strfreev (mailbox->priv->quota_roots);
+ mailbox->priv->quota_roots = g_strdupv ((gchar **) quota_roots);
+
+ g_mutex_unlock (&mailbox->priv->property_lock);
+}
+
+/**
+ * camel_imapx_mailbox_copy_message_map:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Creates a copy of @mailbox's message map: a #GSequence of 32-bit integers
+ * which map message sequence numbers (MSNs) to unique identifiers (UIDs).
+ *
+ * Free the returned #GSequence with g_sequeuce_free() when finished with it.
+ *
+ * Returns: a #GSequence mapping MSNs to UIDs
+ *
+ * Since: 3.12
+ **/
+GSequence *
+camel_imapx_mailbox_copy_message_map (CamelIMAPXMailbox *mailbox)
+{
+ GSequence *copy;
+ GSequenceIter *iter;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), NULL);
+
+ copy = g_sequence_new (NULL);
+
+ g_mutex_lock (&mailbox->priv->property_lock);
+
+ iter = g_sequence_get_begin_iter (mailbox->priv->message_map);
+
+ while (!g_sequence_iter_is_end (iter)) {
+ gpointer data;
+
+ data = g_sequence_get (iter);
+ g_sequence_append (copy, data);
+
+ iter = g_sequence_iter_next (iter);
+ }
+
+ g_mutex_unlock (&mailbox->priv->property_lock);
+
+ return copy;
+}
+
+/**
+ * camel_imapx_mailbox_take_message_map:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @message_map: a #GSequence mapping MSNs to UIDs
+ *
+ * Takes ownership of a #GSequence of 32-bit integers which map message
+ * sequence numbers (MSNs) to unique identifiers (UIDs) for @mailbox.
+ *
+ * The @message_map is expected to be assembled from a local cache of
+ * previously fetched UIDs. The @mailbox will update it as untagged
+ * server responses are processed.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_take_message_map (CamelIMAPXMailbox *mailbox,
+ GSequence *message_map)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+ g_return_if_fail (message_map != NULL);
+
+ g_mutex_lock (&mailbox->priv->property_lock);
+
+ /* XXX GSequence is not reference counted. */
+ if (message_map != mailbox->priv->message_map) {
+ g_sequence_free (mailbox->priv->message_map);
+ mailbox->priv->message_map = message_map;
+ }
+
+ g_mutex_unlock (&mailbox->priv->property_lock);
+}
+
+/**
+ * camel_imapx_mailbox_get_msn_for_uid:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @uid: a message's unique identifier
+ * @out_msn: return location for the message's sequence number, or %NULL
+ *
+ * Given a message's unique identifier (@uid), write the message's sequence
+ * number to @out_msn and return %TRUE. If the unique identifier is unknown
+ * (as far as @mailbox has been informed), the function returns %FALSE.
+ *
+ * Returns: whether @out_msn was set
+ *
+ * Since: 3.12
+ **/
+gboolean
+camel_imapx_mailbox_get_msn_for_uid (CamelIMAPXMailbox *mailbox,
+ guint32 uid,
+ guint32 *out_msn)
+{
+ GSequence *message_map;
+ GSequenceIter *iter;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ /* Remember: Message sequence numbers start at 1.
+ * GSequence position numbers start at 0. */
+
+ g_mutex_lock (&mailbox->priv->property_lock);
+
+ message_map = mailbox->priv->message_map;
+ iter = g_sequence_lookup (
+ message_map, GUINT_TO_POINTER (uid),
+ imapx_mailbox_message_map_compare, NULL);
+
+ if (iter != NULL) {
+ if (out_msn != NULL)
+ *out_msn = g_sequence_iter_get_position (iter) + 1;
+ success = TRUE;
+ }
+
+ g_mutex_unlock (&mailbox->priv->property_lock);
+
+ return success;
+}
+
+/**
+ * camel_imapx_mailbox_get_uid_for_msn:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @msn: a message's sequence number (1..n)
+ * @out_uid: return location for the message's unique identifier, or %NULL
+ *
+ * Given a message's sequence number (@msn), write the message's unique
+ * identifier to @out_uid and return %TRUE. If the sequence number is out of
+ * range (as far as @mailbox has been informed), the function returns %FALSE.
+ *
+ * Returns: whether @out_uid was set
+ *
+ * Since: 3.12
+ **/
+gboolean
+camel_imapx_mailbox_get_uid_for_msn (CamelIMAPXMailbox *mailbox,
+ guint32 msn,
+ guint32 *out_uid)
+{
+ GSequence *message_map;
+ GSequenceIter *iter;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ /* Remember: Message sequence numbers start at 1.
+ * GSequence position numbers start at 0. */
+
+ if (msn == 0)
+ return FALSE;
+
+ g_mutex_lock (&mailbox->priv->property_lock);
+
+ message_map = mailbox->priv->message_map;
+ iter = g_sequence_get_iter_at_pos (message_map, msn - 1);
+
+ if (!g_sequence_iter_is_end (iter)) {
+ if (out_uid != NULL) {
+ gpointer data = g_sequence_get (iter);
+ *out_uid = GPOINTER_TO_UINT (data);
+ }
+ success = TRUE;
+ }
+
+ g_mutex_unlock (&mailbox->priv->property_lock);
+
+ return success;
+}
+
+/**
+ * camel_imapx_mailbox_deleted:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Adds the #CAMEL_IMAPX_LIST_ATTR_NONEXISTENT attribute to @mailbox.
+ *
+ * Call this function after successfully completing a DELETE command.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_deleted (CamelIMAPXMailbox *mailbox)
+{
+ const gchar *attribute;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ attribute = CAMEL_IMAPX_LIST_ATTR_NONEXISTENT;
+
+ g_mutex_lock (&mailbox->priv->property_lock);
+
+ g_hash_table_add (
+ mailbox->priv->attributes,
+ (gpointer) g_intern_string (attribute));
+
+ g_mutex_unlock (&mailbox->priv->property_lock);
+}
+
+/**
+ * camel_imapx_mailbox_subscribed:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Add the #CAMEL_IMAPX_LIST_ATTR_SUBSCRIBED attribute to @mailbox.
+ *
+ * Call this function after successfully completing a SUBSCRIBE command.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_subscribed (CamelIMAPXMailbox *mailbox)
+{
+ const gchar *attribute;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ attribute = CAMEL_IMAPX_LIST_ATTR_SUBSCRIBED;
+
+ g_mutex_lock (&mailbox->priv->property_lock);
+
+ g_hash_table_add (
+ mailbox->priv->attributes,
+ (gpointer) g_intern_string (attribute));
+
+ g_mutex_unlock (&mailbox->priv->property_lock);
+}
+
+/**
+ * camel_imapx_mailbox_unsubscribed:
+ * @mailbox: a #CamelIMAPXMailbox
+ *
+ * Removes the #CAMEL_IMAPX_LIST_ATTR_SUBSCRIBED attribute from @mailbox.
+ *
+ * Call this function after successfully completing an UNSUBSCRIBE command.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_unsubscribed (CamelIMAPXMailbox *mailbox)
+{
+ const gchar *attribute;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ attribute = CAMEL_IMAPX_LIST_ATTR_SUBSCRIBED;
+
+ g_mutex_lock (&mailbox->priv->property_lock);
+
+ g_hash_table_remove (mailbox->priv->attributes, attribute);
+
+ g_mutex_unlock (&mailbox->priv->property_lock);
+}
+
+/**
+ * camel_imapx_mailbox_has_attribute:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @attribute: a mailbox attribute
+ *
+ * Returns whether @mailbox includes the given mailbox attribute.
+ * The @attribute should be one of the LIST attribute macros defined
+ * for #CamelIMAPXListResponse.
+ *
+ * Returns: %TRUE if @mailbox has @attribute, or else %FALSE
+ *
+ * Since: 3.12
+ **/
+gboolean
+camel_imapx_mailbox_has_attribute (CamelIMAPXMailbox *mailbox,
+ const gchar *attribute)
+{
+ gboolean has_it;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+ g_return_val_if_fail (attribute != NULL, FALSE);
+
+ g_mutex_lock (&mailbox->priv->property_lock);
+
+ has_it = g_hash_table_contains (mailbox->priv->attributes, attribute);
+
+ g_mutex_unlock (&mailbox->priv->property_lock);
+
+ return has_it;
+}
+
+/**
+ * camel_imapx_mailbox_handle_list_response:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @response: a #CamelIMAPXListResponse
+ *
+ * Updates the internal state of @mailbox from the data in @response.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_handle_list_response (CamelIMAPXMailbox *mailbox,
+ CamelIMAPXListResponse *response)
+{
+ GHashTable *attributes;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+ g_return_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response));
+
+ attributes = camel_imapx_list_response_dup_attributes (response);
+
+ g_mutex_lock (&mailbox->priv->property_lock);
+
+ g_hash_table_destroy (mailbox->priv->attributes);
+ mailbox->priv->attributes = attributes; /* takes ownership */
+
+ g_mutex_unlock (&mailbox->priv->property_lock);
+}
+
+/**
+ * camel_imapx_mailbox_handle_lsub_response:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @response: a #CamelIMAPXListResponse
+ *
+ * Updates the internal state of @mailbox from the data in @response.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_handle_lsub_response (CamelIMAPXMailbox *mailbox,
+ CamelIMAPXListResponse *response)
+{
+ GHashTable *attributes;
+ GHashTableIter iter;
+ gpointer key;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+ g_return_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response));
+
+ /* LIST responses are more authoritative than LSUB responses,
+ * so instead of replacing the old attribute set as we would
+ * for a LIST response, we'll merge the LSUB attributes. */
+
+ attributes = camel_imapx_list_response_dup_attributes (response);
+
+ g_hash_table_iter_init (&iter, attributes);
+
+ g_mutex_lock (&mailbox->priv->property_lock);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ g_hash_table_add (mailbox->priv->attributes, key);
+
+ g_mutex_unlock (&mailbox->priv->property_lock);
+
+ g_hash_table_destroy (attributes);
+}
+
+/**
+ * camel_imapx_mailbox_handle_status_response:
+ * @mailbox: a #CamelIMAPXMailbox
+ * @response: a #CamelIMAPXStatusResponse
+ *
+ * Updates the internal state of @mailbox from the data in @response.
+ *
+ * Since: 3.12
+ **/
+void
+camel_imapx_mailbox_handle_status_response (CamelIMAPXMailbox *mailbox,
+ CamelIMAPXStatusResponse *response)
+{
+ guint32 value32;
+ guint64 value64;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+ g_return_if_fail (CAMEL_IS_IMAPX_STATUS_RESPONSE (response));
+
+ if (camel_imapx_status_response_get_messages (response, &value32))
+ camel_imapx_mailbox_set_messages (mailbox, value32);
+
+ if (camel_imapx_status_response_get_recent (response, &value32))
+ camel_imapx_mailbox_set_recent (mailbox, value32);
+
+ if (camel_imapx_status_response_get_unseen (response, &value32))
+ camel_imapx_mailbox_set_unseen (mailbox, value32);
+
+ if (camel_imapx_status_response_get_uidnext (response, &value32))
+ camel_imapx_mailbox_set_uidnext (mailbox, value32);
+
+ if (camel_imapx_status_response_get_uidvalidity (response, &value32))
+ camel_imapx_mailbox_set_uidvalidity (mailbox, value32);
+
+ if (camel_imapx_status_response_get_highestmodseq (response, &value64))
+ camel_imapx_mailbox_set_highestmodseq (mailbox, value64);
+}
+
+gint
+camel_imapx_mailbox_get_update_count (CamelIMAPXMailbox *mailbox)
+{
+ gint res;
+
+ g_mutex_lock (&mailbox->priv->update_lock);
+ res = mailbox->priv->update_count;
+ g_mutex_unlock (&mailbox->priv->update_lock);
+
+ return res;
+}
+
+void
+camel_imapx_mailbox_inc_update_count (CamelIMAPXMailbox *mailbox,
+ gint inc)
+{
+ g_mutex_lock (&mailbox->priv->update_lock);
+ mailbox->priv->update_count += inc;
+ g_mutex_unlock (&mailbox->priv->update_lock);
+}
+
+gint
+camel_imapx_mailbox_get_change_stamp (CamelIMAPXMailbox *mailbox)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), 0);
+
+ return mailbox->priv->change_stamp;
+}
diff --git a/src/camel/providers/imapx/camel-imapx-mailbox.h b/src/camel/providers/imapx/camel-imapx-mailbox.h
new file mode 100644
index 000000000..9ec86dfb7
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-mailbox.h
@@ -0,0 +1,189 @@
+/*
+ * camel-imapx-mailbox.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_MAILBOX_H
+#define CAMEL_IMAPX_MAILBOX_H
+
+#include "camel-imapx-namespace.h"
+#include "camel-imapx-list-response.h"
+#include "camel-imapx-status-response.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_MAILBOX \
+ (camel_imapx_mailbox_get_type ())
+#define CAMEL_IMAPX_MAILBOX(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_MAILBOX, CamelIMAPXMailbox))
+#define CAMEL_IMAPX_MAILBOX_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_MAILBOX, CamelIMAPXMailboxClass))
+#define CAMEL_IS_IMAPX_MAILBOX(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_MAILBOX))
+#define CAMEL_IS_IMAPX_MAILBOX_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_MAILBOX))
+#define CAMEL_IMAPX_MAILBOX_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_MAILBOX, CamelIMAPXMailboxClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAPXMailbox CamelIMAPXMailbox;
+typedef struct _CamelIMAPXMailboxClass CamelIMAPXMailboxClass;
+typedef struct _CamelIMAPXMailboxPrivate CamelIMAPXMailboxPrivate;
+
+typedef enum {
+ CAMEL_IMAPX_MAILBOX_STATE_UNKNOWN,
+ CAMEL_IMAPX_MAILBOX_STATE_CREATED,
+ CAMEL_IMAPX_MAILBOX_STATE_UPDATED,
+ CAMEL_IMAPX_MAILBOX_STATE_RENAMED
+} CamelIMAPXMailboxState;
+
+/**
+ * CamelIMAPXMailbox:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.12
+ **/
+struct _CamelIMAPXMailbox {
+ GObject parent;
+ CamelIMAPXMailboxPrivate *priv;
+};
+
+struct _CamelIMAPXMailboxClass {
+ GObjectClass parent_class;
+};
+
+GType camel_imapx_mailbox_get_type
+ (void) G_GNUC_CONST;
+CamelIMAPXMailbox *
+ camel_imapx_mailbox_new (CamelIMAPXListResponse *response,
+ CamelIMAPXNamespace *namespace_);
+CamelIMAPXMailbox *
+ camel_imapx_mailbox_clone
+ (CamelIMAPXMailbox *mailbox,
+ const gchar *new_mailbox_name);
+CamelIMAPXMailboxState
+ camel_imapx_mailbox_get_state
+ (CamelIMAPXMailbox *mailbox);
+void camel_imapx_mailbox_set_state
+ (CamelIMAPXMailbox *mailbox,
+ CamelIMAPXMailboxState state);
+gboolean camel_imapx_mailbox_exists
+ (CamelIMAPXMailbox *mailbox);
+gint camel_imapx_mailbox_compare
+ (CamelIMAPXMailbox *mailbox_a,
+ CamelIMAPXMailbox *mailbox_b);
+gboolean camel_imapx_mailbox_matches
+ (CamelIMAPXMailbox *mailbox,
+ const gchar *pattern);
+const gchar * camel_imapx_mailbox_get_name
+ (CamelIMAPXMailbox *mailbox);
+gchar camel_imapx_mailbox_get_separator
+ (CamelIMAPXMailbox *mailbox);
+gchar * camel_imapx_mailbox_dup_folder_path
+ (CamelIMAPXMailbox *mailbox);
+CamelIMAPXNamespace *
+ camel_imapx_mailbox_get_namespace
+ (CamelIMAPXMailbox *mailbox);
+guint32 camel_imapx_mailbox_get_messages
+ (CamelIMAPXMailbox *mailbox);
+void camel_imapx_mailbox_set_messages
+ (CamelIMAPXMailbox *mailbox,
+ guint32 messages);
+guint32 camel_imapx_mailbox_get_recent
+ (CamelIMAPXMailbox *mailbox);
+void camel_imapx_mailbox_set_recent
+ (CamelIMAPXMailbox *mailbox,
+ guint32 recent);
+guint32 camel_imapx_mailbox_get_unseen
+ (CamelIMAPXMailbox *mailbox);
+void camel_imapx_mailbox_set_unseen
+ (CamelIMAPXMailbox *mailbox,
+ guint32 unseen);
+guint32 camel_imapx_mailbox_get_uidnext
+ (CamelIMAPXMailbox *mailbox);
+void camel_imapx_mailbox_set_uidnext
+ (CamelIMAPXMailbox *mailbox,
+ guint32 uidnext);
+guint32 camel_imapx_mailbox_get_uidvalidity
+ (CamelIMAPXMailbox *mailbox);
+void camel_imapx_mailbox_set_uidvalidity
+ (CamelIMAPXMailbox *mailbox,
+ guint32 uidvalidity);
+guint64 camel_imapx_mailbox_get_highestmodseq
+ (CamelIMAPXMailbox *mailbox);
+void camel_imapx_mailbox_set_highestmodseq
+ (CamelIMAPXMailbox *mailbox,
+ guint64 highestmodseq);
+guint32 camel_imapx_mailbox_get_permanentflags
+ (CamelIMAPXMailbox *mailbox);
+void camel_imapx_mailbox_set_permanentflags
+ (CamelIMAPXMailbox *mailbox,
+ guint32 permanentflags);
+gchar ** camel_imapx_mailbox_dup_quota_roots
+ (CamelIMAPXMailbox *mailbox);
+void camel_imapx_mailbox_set_quota_roots
+ (CamelIMAPXMailbox *mailbox,
+ const gchar **quota_roots);
+GSequence * camel_imapx_mailbox_copy_message_map
+ (CamelIMAPXMailbox *mailbox);
+void camel_imapx_mailbox_take_message_map
+ (CamelIMAPXMailbox *mailbox,
+ GSequence *message_map);
+gboolean camel_imapx_mailbox_get_msn_for_uid
+ (CamelIMAPXMailbox *mailbox,
+ guint32 uid,
+ guint32 *out_msn);
+gboolean camel_imapx_mailbox_get_uid_for_msn
+ (CamelIMAPXMailbox *mailbox,
+ guint32 msn,
+ guint32 *out_uid);
+void camel_imapx_mailbox_deleted
+ (CamelIMAPXMailbox *mailbox);
+void camel_imapx_mailbox_subscribed
+ (CamelIMAPXMailbox *mailbox);
+void camel_imapx_mailbox_unsubscribed
+ (CamelIMAPXMailbox *mailbox);
+gboolean camel_imapx_mailbox_has_attribute
+ (CamelIMAPXMailbox *mailbox,
+ const gchar *attribute);
+void camel_imapx_mailbox_handle_list_response
+ (CamelIMAPXMailbox *mailbox,
+ CamelIMAPXListResponse *response);
+void camel_imapx_mailbox_handle_lsub_response
+ (CamelIMAPXMailbox *mailbox,
+ CamelIMAPXListResponse *response);
+void camel_imapx_mailbox_handle_status_response
+ (CamelIMAPXMailbox *mailbox,
+ CamelIMAPXStatusResponse *response);
+
+gint camel_imapx_mailbox_get_update_count
+ (CamelIMAPXMailbox *mailbox);
+void camel_imapx_mailbox_inc_update_count
+ (CamelIMAPXMailbox *mailbox,
+ gint inc);
+gint camel_imapx_mailbox_get_change_stamp
+ (CamelIMAPXMailbox *mailbox);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_MAILBOX_H */
+
diff --git a/src/camel/providers/imapx/camel-imapx-namespace-response.c b/src/camel/providers/imapx/camel-imapx-namespace-response.c
new file mode 100644
index 000000000..219440fff
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-namespace-response.c
@@ -0,0 +1,593 @@
+/*
+ * camel-imapx-namespace-response.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * SECTION: camel-imapx-namespace-response
+ * @include: camel/camel.h
+ * @short_description: Stores an IMAP NAMESPACE response
+ *
+ * #CamelIMAPXNamespaceResponse encapsulates an IMAP NAMESPACE response,
+ * which consists of a set of #CamelIMAPXNamespace objects grouped by
+ * #CamelIMAPXNamespaceCategory.
+ **/
+
+#include "camel-imapx-namespace-response.h"
+
+#include <string.h>
+
+#include "camel-imapx-utils.h"
+
+#define CAMEL_IMAPX_NAMESPACE_RESPONSE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_IMAPX_NAMESPACE_RESPONSE, CamelIMAPXNamespaceResponsePrivate))
+
+struct _CamelIMAPXNamespaceResponsePrivate {
+ GQueue namespaces;
+};
+
+G_DEFINE_TYPE (
+ CamelIMAPXNamespaceResponse,
+ camel_imapx_namespace_response,
+ G_TYPE_OBJECT)
+
+static void
+imapx_namespace_response_add (CamelIMAPXNamespaceResponse *response,
+ CamelIMAPXNamespaceCategory category,
+ const gchar *prefix,
+ gchar separator)
+{
+ CamelIMAPXNamespace *namespace;
+
+ namespace = camel_imapx_namespace_new (category, prefix, separator);
+ g_queue_push_tail (&response->priv->namespaces, namespace);
+}
+
+static void
+imapx_namespace_response_dispose (GObject *object)
+{
+ CamelIMAPXNamespaceResponsePrivate *priv;
+
+ priv = CAMEL_IMAPX_NAMESPACE_RESPONSE_GET_PRIVATE (object);
+
+ while (!g_queue_is_empty (&priv->namespaces))
+ g_object_unref (g_queue_pop_head (&priv->namespaces));
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_imapx_namespace_response_parent_class)->
+ dispose (object);
+}
+
+static void
+camel_imapx_namespace_response_class_init (CamelIMAPXNamespaceResponseClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (
+ class, sizeof (CamelIMAPXNamespaceResponsePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = imapx_namespace_response_dispose;
+}
+
+static void
+camel_imapx_namespace_response_init (CamelIMAPXNamespaceResponse *response)
+{
+ response->priv =
+ CAMEL_IMAPX_NAMESPACE_RESPONSE_GET_PRIVATE (response);
+}
+
+static gboolean
+imapx_namespace_response_parse_namespace (CamelIMAPXInputStream *stream,
+ CamelIMAPXNamespaceResponse *response,
+ CamelIMAPXNamespaceCategory category,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_imapx_token_t tok;
+ guchar *token;
+ guint len;
+ gchar *prefix;
+ gchar separator;
+ gboolean success;
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok == IMAPX_TOK_ERROR)
+ return FALSE;
+ if (tok == IMAPX_TOK_TOKEN) {
+ if (g_ascii_toupper (token[0]) == 'N' &&
+ g_ascii_toupper (token[1]) == 'I' &&
+ g_ascii_toupper (token[2]) == 'L' &&
+ token[3] == 0)
+ return TRUE;
+ }
+ if (tok != '(') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "namespace: expecting NIL or '('");
+ return FALSE;
+ }
+
+repeat:
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok == IMAPX_TOK_ERROR)
+ return FALSE;
+ if (tok != '(') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "namespace: expecting '('");
+ return FALSE;
+ }
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok == IMAPX_TOK_ERROR)
+ return FALSE;
+ if (tok != IMAPX_TOK_STRING) {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "namespace: expecting string");
+ return FALSE;
+ }
+
+ prefix = g_strdup ((gchar *) token);
+
+ success = camel_imapx_input_stream_nstring (
+ stream, &token, cancellable, error);
+
+ if (!success) {
+ g_free (prefix);
+ return FALSE;
+ }
+
+ separator = (token != NULL) ? (gchar) *token : '\0';
+
+ imapx_namespace_response_add (response, category, prefix, separator);
+
+ g_free (prefix);
+
+ /* FIXME Parse any namespace response extensions. */
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok == IMAPX_TOK_ERROR)
+ return FALSE;
+ if (tok != ')') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "namespace: expecting ')'");
+ return FALSE;
+ }
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok == IMAPX_TOK_ERROR)
+ return FALSE;
+ if (tok == '(') {
+ camel_imapx_input_stream_ungettoken (stream, tok, token, len);
+ goto repeat;
+ }
+ if (tok != ')') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "namespace: expecting '(' or ')'");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * camel_imapx_namespace_response_new:
+ * @stream: a #CamelIMAPXInputStream
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Attempts to parse an IMAP NAMESPACE response from @stream and, if
+ * successful, stores the response data in a new #CamelIMAPXNamespaceResponse.
+ * If an error occurs, the function sets @error and returns %NULL.
+ *
+ * Returns: a #CamelIMAPXNamespaceResponse, or %NULL
+ *
+ * Since: 3.12
+ **/
+CamelIMAPXNamespaceResponse *
+camel_imapx_namespace_response_new (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXNamespaceResponse *response;
+ gint ii;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (stream), NULL);
+
+ response = g_object_new (CAMEL_TYPE_IMAPX_NAMESPACE_RESPONSE, NULL);
+
+ for (ii = 0; ii < 3; ii++) {
+ CamelIMAPXNamespaceCategory category;
+ gboolean success;
+
+ /* Don't rely on the enum values being defined
+ * in the same order as the NAMESPACE response. */
+ switch (ii) {
+ case 0:
+ category = CAMEL_IMAPX_NAMESPACE_PERSONAL;
+ break;
+ case 1:
+ category = CAMEL_IMAPX_NAMESPACE_OTHER_USERS;
+ break;
+ case 2:
+ category = CAMEL_IMAPX_NAMESPACE_SHARED;
+ break;
+ }
+
+ success = imapx_namespace_response_parse_namespace (
+ stream, response, category, cancellable, error);
+ if (!success)
+ goto fail;
+ }
+
+ /* Eat the newline. */
+ if (!camel_imapx_input_stream_skip (stream, cancellable, error))
+ goto fail;
+
+ return response;
+
+fail:
+ g_clear_object (&response);
+
+ return NULL;
+}
+
+/**
+ * camel_imapx_namespace_response_faux_new:
+ * @list_response: a #CamelIMAPXListResponse
+ *
+ * Fabricates a new #CamelIMAPXNamespaceResponse from @list_response.
+ * The returned #CamelIMAPXNamespaceResponse will consist of a single
+ * personal #CamelIMAPXNamespace with an empty mailbox prefix string,
+ * and a mailbox separator character taken from @list_response.
+ *
+ * Use this function when the IMAP server does not list the "NAMESPACE"
+ * keyword in its CAPABILITY response.
+ *
+ * Returns: a #CamelIMAPXNamespaceResponse
+ *
+ * Since: 3.12
+ **/
+CamelIMAPXNamespaceResponse *
+camel_imapx_namespace_response_faux_new (CamelIMAPXListResponse *list_response)
+{
+ CamelIMAPXNamespaceResponse *response;
+ CamelIMAPXNamespaceCategory category;
+ gchar separator;
+
+ g_return_val_if_fail (
+ CAMEL_IS_IMAPX_LIST_RESPONSE (list_response), NULL);
+
+ response = g_object_new (CAMEL_TYPE_IMAPX_NAMESPACE_RESPONSE, NULL);
+
+ category = CAMEL_IMAPX_NAMESPACE_PERSONAL;
+ separator = camel_imapx_list_response_get_separator (list_response);
+ imapx_namespace_response_add (response, category, "", separator);
+
+ return response;
+}
+
+/**
+ * camel_imapx_namespace_response_list:
+ * @response: a #CamelIMAPXNamespaceResponse
+ *
+ * Returns a list of IMAP namespaces in the order received from the IMAP
+ * server, which means they are grouped by #CamelIMAPXNamespaceCategory.
+ *
+ * The namespaces returned in the list are referenced for thread-safety.
+ * They must each be unreferenced with g_object_unref() when finished with
+ * them. Free the returned list itself with g_list_free().
+ *
+ * An easy way to free the list properly in one step is as follows:
+ *
+ * |[
+ * g_list_free_full (list, g_object_unref);
+ * ]|
+ *
+ * Returns: a list of #CamelIMAPXNamespace instances
+ *
+ * Since: 3.12
+ **/
+GList *
+camel_imapx_namespace_response_list (CamelIMAPXNamespaceResponse *response)
+{
+ GList *head;
+
+ g_return_val_if_fail (
+ CAMEL_IS_IMAPX_NAMESPACE_RESPONSE (response), NULL);
+
+ head = g_queue_peek_head_link (&response->priv->namespaces);
+
+ return g_list_copy_deep (head, (GCopyFunc) g_object_ref, NULL);
+}
+
+/**
+ * camel_imapx_namespace_response_remove:
+ * @response: a #CamelIMAPXNamespaceResponse
+ * @namespace: a #CamelIMAPXNamespace to add
+ *
+ * Adds a @namespace into the list of namespaces. It adds its own
+ * reference on the @namespace.
+ *
+ * Since: 3.16
+ **/
+void
+camel_imapx_namespace_response_add (CamelIMAPXNamespaceResponse *response,
+ CamelIMAPXNamespace *namespace)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_NAMESPACE_RESPONSE (response));
+ g_return_if_fail (CAMEL_IS_IMAPX_NAMESPACE (namespace));
+
+ g_queue_push_tail (&response->priv->namespaces, g_object_ref (namespace));
+}
+
+/**
+ * camel_imapx_namespace_response_remove:
+ * @response: a #CamelIMAPXNamespaceResponse
+ * @namespace: a #CamelIMAPXNamespace to remove
+ *
+ * Removes @namespace from the list of namespaces in the @response.
+ * If no such namespace exists then does nothing.
+ *
+ * Since: 3.16
+ **/
+void
+camel_imapx_namespace_response_remove (CamelIMAPXNamespaceResponse *response,
+ CamelIMAPXNamespace *namespace)
+{
+ GList *link;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_NAMESPACE_RESPONSE (response));
+ g_return_if_fail (CAMEL_IS_IMAPX_NAMESPACE (namespace));
+
+ for (link = g_queue_peek_head_link (&response->priv->namespaces);
+ link; link = g_list_next (link)) {
+ CamelIMAPXNamespace *ns = link->data;
+
+ if (camel_imapx_namespace_equal (namespace, ns)) {
+ g_queue_remove (&response->priv->namespaces, ns);
+ g_object_unref (ns);
+ break;
+ }
+ }
+}
+
+/**
+ * camel_imapx_namespace_response_lookup:
+ * @response: a #CamelIMAPXNamespaceResponse
+ * @mailbox_name: a mailbox name
+ * @separator: a mailbox path separator character
+ *
+ * Attempts to match @mailbox_name and @separator to a known IMAP namespace
+ * and returns a #CamelIMAPXNamespace, or %NULL if no match was found.
+ *
+ * The returned #CamelIMAPXNamespace is referenced for thread-safety and
+ * should be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelIMAPXNamespace, or %NULL
+ *
+ * Since: 3.12
+ **/
+CamelIMAPXNamespace *
+camel_imapx_namespace_response_lookup (CamelIMAPXNamespaceResponse *response,
+ const gchar *mailbox_name,
+ gchar separator)
+{
+ CamelIMAPXNamespace *match = NULL;
+ GQueue candidates = G_QUEUE_INIT;
+ GList *head, *link;
+ guint ii, length;
+
+ g_return_val_if_fail (
+ CAMEL_IS_IMAPX_NAMESPACE_RESPONSE (response), NULL);
+ g_return_val_if_fail (mailbox_name != NULL, NULL);
+
+ /* Collect all namespaces with matching separators. */
+
+ head = g_queue_peek_head_link (&response->priv->namespaces);
+
+ for (link = head; link != NULL; link = g_list_next (link)) {
+ CamelIMAPXNamespace *namespace;
+ gchar ns_separator;
+
+ namespace = CAMEL_IMAPX_NAMESPACE (link->data);
+ ns_separator = camel_imapx_namespace_get_separator (namespace);
+
+ if (separator == ns_separator)
+ g_queue_push_tail (&candidates, namespace);
+ }
+
+ /* Check namespaces with non-empty prefix strings.
+ * Discard those that don't match the mailbox name. */
+
+ length = g_queue_get_length (&candidates);
+
+ for (ii = 0; ii < length; ii++) {
+ CamelIMAPXNamespace *namespace;
+ const gchar *ns_prefix;
+
+ namespace = g_queue_pop_head (&candidates);
+ ns_prefix = camel_imapx_namespace_get_prefix (namespace);
+ g_return_val_if_fail (ns_prefix != NULL, NULL);
+
+ /* Put namespaces with empty prefix strings
+ * back on the tail of the candidates queue. */
+ if (*ns_prefix == '\0') {
+ g_queue_push_tail (&candidates, namespace);
+ continue;
+ }
+
+ /* Stop processing if we find a match. */
+ if (g_str_has_prefix (mailbox_name, ns_prefix)) {
+ match = namespace;
+ break;
+ }
+ }
+
+ /* Remaining namespaces have empty prefix strings.
+ * Return the first one as the matching namespace. */
+
+ if (match == NULL)
+ match = g_queue_pop_head (&candidates);
+
+ g_queue_clear (&candidates);
+
+ if (match != NULL)
+ g_object_ref (match);
+
+ return match;
+}
+
+/* Helper for camel_imapx_namespace_response_lookup_for_path() */
+static gint
+imapx_namespace_response_rank_candidates (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ CamelIMAPXNamespace *namespace_a;
+ CamelIMAPXNamespace *namespace_b;
+ const gchar *prefix_a;
+ const gchar *prefix_b;
+ gsize prefix_len_a;
+ gsize prefix_len_b;
+
+ namespace_a = CAMEL_IMAPX_NAMESPACE (a);
+ namespace_b = CAMEL_IMAPX_NAMESPACE (b);
+
+ prefix_a = camel_imapx_namespace_get_prefix (namespace_a);
+ prefix_b = camel_imapx_namespace_get_prefix (namespace_b);
+
+ prefix_len_a = strlen (prefix_a);
+ prefix_len_b = strlen (prefix_b);
+
+ /* Rank namespaces by longest prefix string. */
+
+ if (prefix_len_a > prefix_len_b)
+ return -1;
+
+ if (prefix_len_a < prefix_len_b)
+ return 1;
+
+ /* For namespaces with equal length prefixes, compare the prefix
+ * strings themselves. Kind of arbitrary, but we have no better
+ * criteria. Should rarely come up for a sanely configured IMAP
+ * server anyway. */
+ return strcmp (prefix_a, prefix_b);
+}
+
+/**
+ * camel_imapx_namespace_response_lookup_for_path:
+ * @response: a #CamelIMAPXNamespaceResponse
+ * @folder_path: a Camel folder path
+ *
+ * Attempts to match @folder_path to a known IMAP namespace and returns a
+ * #CamelIMAPXNamespace, or %NULL if no match was found.
+ *
+ * If the result is ambiguous, meaning @folder_path could belong to one of
+ * several IMAP namespaces, the namespace with the longest matching prefix
+ * string is preferred. This has the effect of giving a namespace with an
+ * empty prefix the lowest priority.
+ *
+ * The returned #CamelIMAPXNamespace is referenced for thread-safety and
+ * should be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelIMAPXNamespace, or %NULL
+ *
+ * Since: 3.12
+ **/
+CamelIMAPXNamespace *
+camel_imapx_namespace_response_lookup_for_path (CamelIMAPXNamespaceResponse *response,
+ const gchar *folder_path)
+{
+ CamelIMAPXNamespace *match = NULL;
+ GQueue candidates = G_QUEUE_INIT;
+ GList *head, *link;
+ gboolean find_empty_prefix;
+
+ g_return_val_if_fail (
+ CAMEL_IS_IMAPX_NAMESPACE_RESPONSE (response), NULL);
+ g_return_val_if_fail (folder_path != NULL, NULL);
+
+ /* Special cases:
+ * If the folder path is empty or names the INBOX, then
+ * find the first namespace with an empty prefix string. */
+ find_empty_prefix =
+ (*folder_path == '\0') ||
+ (g_ascii_strcasecmp (folder_path, "INBOX") == 0);
+
+ head = g_queue_peek_head_link (&response->priv->namespaces);
+
+ for (link = head; link != NULL; link = g_list_next (link)) {
+ CamelIMAPXNamespace *namespace;
+ const gchar *prefix;
+ gchar *path_prefix;
+ gchar separator;
+
+ namespace = CAMEL_IMAPX_NAMESPACE (link->data);
+ prefix = camel_imapx_namespace_get_prefix (namespace);
+ separator = camel_imapx_namespace_get_separator (namespace);
+
+ /* Special handling when searching for an empty prefix. */
+ if (find_empty_prefix) {
+ if (*prefix == '\0' ||
+ g_ascii_strcasecmp (prefix, "INBOX") == 0 ||
+ (g_ascii_strncasecmp (prefix, "INBOX", 5) == 0 &&
+ prefix[5] == separator && !prefix[6])) {
+ g_queue_push_tail (&candidates, namespace);
+ break;
+ }
+ continue;
+ }
+
+ /* Convert the prefix to a folder path segment. */
+ path_prefix = camel_imapx_mailbox_to_folder_path (
+ prefix, separator);
+
+ if (g_str_has_prefix (folder_path, path_prefix)) {
+ g_queue_insert_sorted (
+ &candidates, namespace,
+ imapx_namespace_response_rank_candidates,
+ NULL);
+ }
+
+ g_free (path_prefix);
+ }
+
+ /* First candidate is the preferred namespace. */
+ match = g_queue_pop_head (&candidates);
+
+ /* Fallback to the first known namespace when none suitable for the given path found */
+ if (!match && head && head->data)
+ match = head->data;
+
+ if (match != NULL)
+ g_object_ref (match);
+
+ /* Discard any unselected candidates. */
+ g_queue_clear (&candidates);
+
+ return match;
+}
+
diff --git a/src/camel/providers/imapx/camel-imapx-namespace-response.h b/src/camel/providers/imapx/camel-imapx-namespace-response.h
new file mode 100644
index 000000000..0321dd31d
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-namespace-response.h
@@ -0,0 +1,97 @@
+/*
+ * camel-imapx-namespace-response.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_NAMESPACE_RESPONSE_H
+#define CAMEL_IMAPX_NAMESPACE_RESPONSE_H
+
+#include "camel-imapx-namespace.h"
+#include "camel-imapx-list-response.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_NAMESPACE_RESPONSE \
+ (camel_imapx_namespace_response_get_type ())
+#define CAMEL_IMAPX_NAMESPACE_RESPONSE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_NAMESPACE_RESPONSE, CamelIMAPXNamespaceResponse))
+#define CAMEL_IMAPX_NAMESPACE_RESPONSE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_NAMESPACE_RESPONSE, CamelIMAPXNamespaceResponseClass))
+#define CAMEL_IS_IMAPX_NAMESPACE_RESPONSE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_NAMESPACE_RESPONSE))
+#define CAMEL_IS_IMAPX_NAMESPACE_RESPONSE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_NAMESPACE_RESPONSE))
+#define CAMEL_IMAPX_NAMESPACE_RESPONSE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_NAMESPACE_RESPONSE, CamelIMAPXNamespaceResponseClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAPXNamespaceResponse CamelIMAPXNamespaceResponse;
+typedef struct _CamelIMAPXNamespaceResponseClass CamelIMAPXNamespaceResponseClass;
+typedef struct _CamelIMAPXNamespaceResponsePrivate CamelIMAPXNamespaceResponsePrivate;
+
+/**
+ * CamelIMAPXNamespaceResponse:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.12
+ **/
+struct _CamelIMAPXNamespaceResponse {
+ GObject parent;
+ CamelIMAPXNamespaceResponsePrivate *priv;
+};
+
+struct _CamelIMAPXNamespaceResponseClass {
+ GObjectClass parent_class;
+};
+
+GType camel_imapx_namespace_response_get_type
+ (void) G_GNUC_CONST;
+CamelIMAPXNamespaceResponse *
+ camel_imapx_namespace_response_new
+ (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+CamelIMAPXNamespaceResponse *
+ camel_imapx_namespace_response_faux_new
+ (CamelIMAPXListResponse *list_response);
+GList * camel_imapx_namespace_response_list
+ (CamelIMAPXNamespaceResponse *response);
+void camel_imapx_namespace_response_add
+ (CamelIMAPXNamespaceResponse *response,
+ CamelIMAPXNamespace *namespace);
+void camel_imapx_namespace_response_remove
+ (CamelIMAPXNamespaceResponse *response,
+ CamelIMAPXNamespace *namespace);
+CamelIMAPXNamespace *
+ camel_imapx_namespace_response_lookup
+ (CamelIMAPXNamespaceResponse *response,
+ const gchar *mailbox_name,
+ gchar separator);
+CamelIMAPXNamespace *
+ camel_imapx_namespace_response_lookup_for_path
+ (CamelIMAPXNamespaceResponse *response,
+ const gchar *folder_path);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_NAMESPACE_RESPONSE_H */
+
diff --git a/src/camel/providers/imapx/camel-imapx-namespace.c b/src/camel/providers/imapx/camel-imapx-namespace.c
new file mode 100644
index 000000000..96cd98fdb
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-namespace.c
@@ -0,0 +1,195 @@
+/*
+ * camel-imapx-namespace.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * SECTION: camel-imapx-namespace
+ * @include: camel/camel.h
+ * @short_description: Stores an IMAP namespace
+ *
+ * #CamelIMAPXNamespace encapsulates an IMAP namespace, which consists of a
+ * namespace category (personal/other users/shared), a mailbox prefix string,
+ * and a mailbox separator character.
+ **/
+
+#include "camel-imapx-namespace.h"
+
+#define CAMEL_IMAPX_NAMESPACE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_IMAPX_NAMESPACE, CamelIMAPXNamespacePrivate))
+
+struct _CamelIMAPXNamespacePrivate {
+ CamelIMAPXNamespaceCategory category;
+ gchar *prefix;
+ gchar separator;
+};
+
+G_DEFINE_TYPE (
+ CamelIMAPXNamespace,
+ camel_imapx_namespace,
+ G_TYPE_OBJECT)
+
+static void
+imapx_namespace_finalize (GObject *object)
+{
+ CamelIMAPXNamespacePrivate *priv;
+
+ priv = CAMEL_IMAPX_NAMESPACE_GET_PRIVATE (object);
+
+ g_free (priv->prefix);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_imapx_namespace_parent_class)->finalize (object);
+}
+
+static void
+camel_imapx_namespace_class_init (CamelIMAPXNamespaceClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelIMAPXNamespacePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = imapx_namespace_finalize;
+}
+
+static void
+camel_imapx_namespace_init (CamelIMAPXNamespace *namespace)
+{
+ namespace->priv = CAMEL_IMAPX_NAMESPACE_GET_PRIVATE (namespace);
+}
+
+/**
+ * camel_imapx_namespace_new:
+ * @category: a #CamelIMAPXNamespaceCategory
+ * @prefix: a mailbox prefix string
+ * @separator: a mailbox path separator character
+ *
+ * Creates a new #CamelIMAPXNamespace from @category, @prefix and @separator.
+ *
+ * Returns: a new #CamelIMAPXNamespace
+ *
+ * Since: 3.12
+ **/
+CamelIMAPXNamespace *
+camel_imapx_namespace_new (CamelIMAPXNamespaceCategory category,
+ const gchar *prefix,
+ gchar separator)
+{
+ CamelIMAPXNamespace *namespace;
+
+ /* Note, mailbox path separator can be NIL. */
+ g_return_val_if_fail (prefix != NULL, NULL);
+
+ /* Not bothering with GObject properties for this class. */
+
+ namespace = g_object_new (CAMEL_TYPE_IMAPX_NAMESPACE, NULL);
+ namespace->priv->category = category;
+ namespace->priv->prefix = g_strdup (prefix);
+ namespace->priv->separator = separator;
+
+ return namespace;
+}
+
+/**
+ * camel_imapx_namespace_equal:
+ * @namespace_a: a #CamelIMAPXNamespace
+ * @namespace_b: another #CamelIMAPXNamespace
+ *
+ * Returns whether @namespace_a and @namespace_b are equivalent, meaning
+ * they share the same category, prefix string, and path separator character.
+ *
+ * Returns: %TRUE if @namespace_a and @namespace_b are equal
+ *
+ * Since: 3.12
+ **/
+gboolean
+camel_imapx_namespace_equal (CamelIMAPXNamespace *namespace_a,
+ CamelIMAPXNamespace *namespace_b)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_NAMESPACE (namespace_a), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_NAMESPACE (namespace_b), FALSE);
+
+ if (namespace_a == namespace_b)
+ return TRUE;
+
+ if (namespace_a->priv->category != namespace_b->priv->category)
+ return FALSE;
+
+ if (namespace_a->priv->separator != namespace_b->priv->separator)
+ return FALSE;
+
+ return g_str_equal (
+ namespace_a->priv->prefix,
+ namespace_b->priv->prefix);
+}
+
+/**
+ * camel_imapx_namespace_get_category:
+ * @namespace_: a #CamelIMAPXNamespace
+ *
+ * Returns the #CamelIMAPXNamespaceCategory for @namespace.
+ *
+ * Returns: a #CamelIMAPXNamespaceCategory
+ *
+ * Since: 3.12
+ **/
+CamelIMAPXNamespaceCategory
+camel_imapx_namespace_get_category (CamelIMAPXNamespace *namespace)
+{
+ g_return_val_if_fail (
+ CAMEL_IS_IMAPX_NAMESPACE (namespace),
+ CAMEL_IMAPX_NAMESPACE_PERSONAL);
+
+ return namespace->priv->category;
+}
+
+/**
+ * camel_imapx_namespace_get_prefix:
+ * @namespace_: a #CamelIMAPXNamespace
+ *
+ * Returns the mailbox prefix string for @namespace.
+ *
+ * Returns: a mailbox prefix string
+ *
+ * Since: 3.12
+ **/
+const gchar *
+camel_imapx_namespace_get_prefix (CamelIMAPXNamespace *namespace)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_NAMESPACE (namespace), NULL);
+
+ return namespace->priv->prefix;
+}
+
+/**
+ * camel_imapx_namespace_get_separator:
+ * @namespace_: a #CamelIMAPXNamespace
+ *
+ * Returns the mailbox path separator charactor for @namespace.
+ *
+ * Returns: the mailbox path separator character
+ *
+ * Since: 3.12
+ **/
+gchar
+camel_imapx_namespace_get_separator (CamelIMAPXNamespace *namespace)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_NAMESPACE (namespace), '\0');
+
+ return namespace->priv->separator;
+}
+
diff --git a/src/camel/providers/imapx/camel-imapx-namespace.h b/src/camel/providers/imapx/camel-imapx-namespace.h
new file mode 100644
index 000000000..d6fdb5f44
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-namespace.h
@@ -0,0 +1,104 @@
+/*
+ * camel-imapx-namespace.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_NAMESPACE_H
+#define CAMEL_IMAPX_NAMESPACE_H
+
+#include <glib-object.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_NAMESPACE \
+ (camel_imapx_namespace_get_type ())
+#define CAMEL_IMAPX_NAMESPACE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_NAMESPACE, CamelIMAPXNamespace))
+#define CAMEL_IMAPX_NAMESPACE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_NAMESPACE, CamelIMAPXNamespaceClass))
+#define CAMEL_IS_IMAPX_NAMESPACE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_NAMESPACE))
+#define CAMEL_IS_IMAPX_NAMESPACE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_NAMESPACE))
+#define CAMEL_IMAPX_NAMESPACE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_NAMESPACE, CamelIMAPXNamespaceClass))
+
+G_BEGIN_DECLS
+
+/**
+ * CamelIMAPXNamespaceCategory:
+ * @CAMEL_IMAPX_NAMESPACE_PERSONAL:
+ * Mailboxes belonging to the authenticated user.
+ * @CAMEL_IMAPX_NAMESPACE_OTHER_USERS:
+ * Personal mailboxes belonging to other users.
+ * @CAMEL_IMAPX_NAMESPACE_SHARED:
+ * Mailboxes intended to be shared amongst users.
+ *
+ * Refer to <ulink url="http://tools.ietf.org/html/rfc2342">RFC 2342</ulink>
+ * for more detailed namespace class descriptions.
+ **/
+typedef enum {
+ CAMEL_IMAPX_NAMESPACE_PERSONAL,
+ CAMEL_IMAPX_NAMESPACE_OTHER_USERS,
+ CAMEL_IMAPX_NAMESPACE_SHARED
+} CamelIMAPXNamespaceCategory;
+
+typedef struct _CamelIMAPXNamespace CamelIMAPXNamespace;
+typedef struct _CamelIMAPXNamespaceClass CamelIMAPXNamespaceClass;
+typedef struct _CamelIMAPXNamespacePrivate CamelIMAPXNamespacePrivate;
+
+/**
+ * CamelIMAPXNamespace:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.12
+ **/
+struct _CamelIMAPXNamespace {
+ GObject parent;
+ CamelIMAPXNamespacePrivate *priv;
+};
+
+struct _CamelIMAPXNamespaceClass {
+ GObjectClass parent_class;
+};
+
+GType camel_imapx_namespace_get_type
+ (void) G_GNUC_CONST;
+CamelIMAPXNamespace *
+ camel_imapx_namespace_new
+ (CamelIMAPXNamespaceCategory category,
+ const gchar *prefix,
+ gchar separator);
+gboolean camel_imapx_namespace_equal
+ (CamelIMAPXNamespace *namespace_a,
+ CamelIMAPXNamespace *namespace_b);
+CamelIMAPXNamespaceCategory
+ camel_imapx_namespace_get_category
+ (CamelIMAPXNamespace *namespace_);
+const gchar * camel_imapx_namespace_get_prefix
+ (CamelIMAPXNamespace *namespace_);
+gchar camel_imapx_namespace_get_separator
+ (CamelIMAPXNamespace *namespace_);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_NAMESPACE_H */
+
diff --git a/src/camel/providers/imapx/camel-imapx-provider.c b/src/camel/providers/imapx/camel-imapx-provider.c
new file mode 100644
index 000000000..9036364aa
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-provider.c
@@ -0,0 +1,180 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-pop3-provider.c: pop3 provider registration code
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors :
+ * Dan Winship <danw@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+#include <camel/camel.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-imapx-store.h"
+
+static guint imapx_url_hash (gconstpointer key);
+static gint imapx_url_equal (gconstpointer a, gconstpointer b);
+
+CamelProviderConfEntry imapx_conf_entries[] = {
+ { CAMEL_PROVIDER_CONF_SECTION_START, "mailcheck", NULL,
+ N_("Checking for New Mail") },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "check-all", NULL,
+ N_("C_heck for new messages in all folders"), "1" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "check-subscribed", NULL,
+ N_("Ch_eck for new messages in subscribed folders"), "0" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "use-qresync", NULL,
+ N_("Use _Quick Resync if the server supports it"), "0" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "use-idle", NULL,
+ N_("_Listen for server change notifications"), "0" },
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+ { CAMEL_PROVIDER_CONF_SECTION_START, "cmdsection", NULL,
+ N_("Connection to Server") },
+ { CAMEL_PROVIDER_CONF_CHECKSPIN, "concurrent-connections", NULL,
+ N_("Numbe_r of concurrent connections to use"), "y:1:3:7" },
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+ { CAMEL_PROVIDER_CONF_SECTION_START, "folders", NULL,
+ N_("Folders") },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "use-subscriptions", NULL,
+ N_("_Show only subscribed folders"), "0" },
+#if 0
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "use-namespace", NULL,
+ N_("O_verride server-supplied folder namespace"), "0" },
+ { CAMEL_PROVIDER_CONF_ENTRY, "namespace", "use-namespace",
+ N_("Namespace:") },
+#endif
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+ { CAMEL_PROVIDER_CONF_SECTION_START, "general", NULL, N_("Options") },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "filter-all", NULL,
+ N_("Apply _filters to new messages in all folders"), "0" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "filter-inbox", "!filter-all",
+ N_("_Apply filters to new messages in Inbox on this server"), "1" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "filter-junk", NULL,
+ N_("Check new messages for _Junk contents"), "0" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "filter-junk-inbox", "filter-junk",
+ N_("Only check for Junk messages in the In_box folder"), "0" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "stay-synchronized", NULL,
+ N_("Synchroni_ze remote mail locally in all folders"), "0" },
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+ { CAMEL_PROVIDER_CONF_END }
+};
+
+CamelProviderPortEntry imapx_port_entries[] = {
+ { 143, N_("Default IMAP port"), FALSE },
+ { 993, N_("IMAP over TLS"), TRUE },
+ { 0, NULL, 0 }
+};
+
+static CamelProvider imapx_provider = {
+ "imapx",
+
+ N_("IMAP+"),
+
+ N_("For reading and storing mail on IMAP servers."),
+
+ "mail",
+
+ CAMEL_PROVIDER_IS_REMOTE | CAMEL_PROVIDER_IS_SOURCE |
+ CAMEL_PROVIDER_IS_STORAGE | CAMEL_PROVIDER_SUPPORTS_SSL|
+ CAMEL_PROVIDER_SUPPORTS_MOBILE_DEVICES |
+ CAMEL_PROVIDER_SUPPORTS_BATCH_FETCH |
+ CAMEL_PROVIDER_SUPPORTS_PURGE_MESSAGE_CACHE,
+
+ CAMEL_URL_NEED_USER | CAMEL_URL_NEED_HOST | CAMEL_URL_ALLOW_AUTH,
+
+ imapx_conf_entries,
+
+ imapx_port_entries,
+
+ /* ... */
+};
+
+extern CamelServiceAuthType camel_imapx_password_authtype;
+
+void camel_imapx_module_init (void);
+
+void
+camel_imapx_module_init (void)
+{
+ imapx_provider.object_types[CAMEL_PROVIDER_STORE] =
+ CAMEL_TYPE_IMAPX_STORE;
+ imapx_provider.url_hash = imapx_url_hash;
+ imapx_provider.url_equal = imapx_url_equal;
+ imapx_provider.authtypes = camel_sasl_authtype_list (FALSE);
+ imapx_provider.authtypes = g_list_prepend (
+ imapx_provider.authtypes, &camel_imapx_password_authtype);
+ imapx_provider.translation_domain = GETTEXT_PACKAGE;
+
+ camel_provider_register (&imapx_provider);
+}
+
+void
+camel_provider_module_init (void)
+{
+ camel_imapx_module_init ();
+}
+
+static void
+imapx_add_hash (guint *hash,
+ gchar *s)
+{
+ if (s)
+ *hash ^= g_str_hash(s);
+}
+
+static guint
+imapx_url_hash (gconstpointer key)
+{
+ const CamelURL *u = (CamelURL *) key;
+ guint hash = 0;
+
+ imapx_add_hash (&hash, u->user);
+ imapx_add_hash (&hash, u->host);
+ hash ^= u->port;
+
+ return hash;
+}
+
+static gint
+imapx_check_equal (gchar *s1,
+ gchar *s2)
+{
+ if (s1 == NULL) {
+ if (s2 == NULL)
+ return TRUE;
+ else
+ return FALSE;
+ }
+
+ if (s2 == NULL)
+ return FALSE;
+
+ return strcmp (s1, s2) == 0;
+}
+
+static gint
+imapx_url_equal (gconstpointer a,
+ gconstpointer b)
+{
+ const CamelURL *u1 = a, *u2 = b;
+
+ return imapx_check_equal (u1->protocol, u2->protocol)
+ && imapx_check_equal (u1->user, u2->user)
+ && imapx_check_equal (u1->host, u2->host)
+ && u1->port == u2->port;
+}
diff --git a/src/camel/providers/imapx/camel-imapx-search.c b/src/camel/providers/imapx/camel-imapx-search.c
new file mode 100644
index 000000000..b4e0a80bb
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-search.c
@@ -0,0 +1,720 @@
+/*
+ * camel-imapx-search.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include "camel-imapx-search.h"
+
+#include <string.h>
+#include <camel/camel.h>
+#include <camel/camel-search-private.h>
+
+#include "camel-imapx-folder.h"
+
+#define CAMEL_IMAPX_SEARCH_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_IMAPX_SEARCH, CamelIMAPXSearchPrivate))
+
+struct _CamelIMAPXSearchPrivate {
+ GWeakRef imapx_store;
+ gint *local_data_search; /* not NULL, if testing whether all used headers are all locally available */
+
+ GCancellable *cancellable; /* not referenced */
+ GError **error; /* not referenced */
+};
+
+enum {
+ PROP_0,
+ PROP_STORE
+};
+
+G_DEFINE_TYPE (
+ CamelIMAPXSearch,
+ camel_imapx_search,
+ CAMEL_TYPE_FOLDER_SEARCH)
+
+static void
+imapx_search_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_STORE:
+ camel_imapx_search_set_store (
+ CAMEL_IMAPX_SEARCH (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_search_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_STORE:
+ g_value_take_object (
+ value,
+ camel_imapx_search_ref_store (
+ CAMEL_IMAPX_SEARCH (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_search_dispose (GObject *object)
+{
+ CamelIMAPXSearchPrivate *priv;
+
+ priv = CAMEL_IMAPX_SEARCH_GET_PRIVATE (object);
+
+ g_weak_ref_set (&priv->imapx_store, NULL);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_imapx_search_parent_class)->dispose (object);
+}
+
+static void
+imapx_search_finalize (GObject *object)
+{
+ CamelIMAPXSearchPrivate *priv;
+
+ priv = CAMEL_IMAPX_SEARCH_GET_PRIVATE (object);
+
+ g_weak_ref_clear (&priv->imapx_store);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_imapx_search_parent_class)->finalize (object);
+}
+
+static CamelSExpResult *
+imapx_search_result_match_all (CamelSExp *sexp,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *result;
+
+ g_return_val_if_fail (search != NULL, NULL);
+
+ if (search->current != NULL) {
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ result->value.boolean = TRUE;
+ } else {
+ gint ii;
+
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ result->value.ptrarray = g_ptr_array_new ();
+
+ for (ii = 0; ii < search->summary->len; ii++)
+ g_ptr_array_add (
+ result->value.ptrarray,
+ (gpointer) search->summary->pdata[ii]);
+ }
+
+ return result;
+}
+
+static CamelSExpResult *
+imapx_search_result_match_none (CamelSExp *sexp,
+ CamelFolderSearch *search)
+{
+ CamelSExpResult *result;
+
+ g_return_val_if_fail (search != NULL, NULL);
+
+ if (search->current != NULL) {
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ result->value.boolean = FALSE;
+ } else {
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ result->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return result;
+}
+
+static CamelSExpResult *
+imapx_search_process_criteria (CamelSExp *sexp,
+ CamelFolderSearch *search,
+ CamelIMAPXStore *imapx_store,
+ const GString *criteria_prefix,
+ const gchar *search_key,
+ const GPtrArray *words,
+ const gchar *from_function)
+{
+ CamelSExpResult *result;
+ CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search);
+ CamelIMAPXMailbox *mailbox;
+ GPtrArray *uids = NULL;
+ GError *local_error = NULL;
+
+ mailbox = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (search->folder), imapx_search->priv->cancellable, &local_error);
+
+ /* Sanity check. */
+ g_return_val_if_fail (
+ ((mailbox != NULL) && (local_error == NULL)) ||
+ ((mailbox == NULL) && (local_error != NULL)), NULL);
+
+ if (mailbox != NULL) {
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+
+ imapx_store = camel_imapx_search_ref_store (imapx_search);
+
+ /* there should always be one, held by one of the callers of this function */
+ g_warn_if_fail (imapx_store != NULL);
+
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+ uids = camel_imapx_conn_manager_uid_search_sync (conn_man, mailbox, criteria_prefix->str, search_key,
+ words ? (const gchar * const *) words->pdata : NULL, imapx_search->priv->cancellable, &local_error);
+
+ g_clear_object (&imapx_store);
+ g_object_unref (mailbox);
+ }
+
+ /* Sanity check. */
+ g_return_val_if_fail (
+ ((uids != NULL) && (local_error == NULL)) ||
+ ((uids == NULL) && (local_error != NULL)), NULL);
+
+ if (local_error != NULL) {
+ g_propagate_error (imapx_search->priv->error, local_error);
+
+ /* Make like we've got an empty result */
+ uids = g_ptr_array_new ();
+ }
+
+ if (search->current != NULL) {
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+ result->value.boolean = (uids && uids->len > 0);
+ } else {
+ result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+ result->value.ptrarray = g_ptr_array_ref (uids);
+ }
+
+ g_ptr_array_unref (uids);
+
+ return result;
+}
+
+static CamelSExpResult *
+imapx_search_match_all (CamelSExp *sexp,
+ gint argc,
+ CamelSExpTerm **argv,
+ CamelFolderSearch *search)
+{
+ CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search);
+ CamelIMAPXStore *imapx_store;
+ CamelSExpResult *result;
+ GPtrArray *summary;
+ gint local_data_search = 0, *prev_local_data_search, ii;
+
+ if (argc != 1)
+ return imapx_search_result_match_none (sexp, search);
+
+ imapx_store = camel_imapx_search_ref_store (CAMEL_IMAPX_SEARCH (search));
+ if (!imapx_store || search->current || !search->summary) {
+ g_clear_object (&imapx_store);
+
+ /* Chain up to parent's method. */
+ return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+ match_all (sexp, argc, argv, search);
+ }
+
+ /* First try to see whether all used headers are available locally - if
+ * they are, then do not use server-side filtering at all. */
+ prev_local_data_search = imapx_search->priv->local_data_search;
+ imapx_search->priv->local_data_search = &local_data_search;
+
+ summary = search->summary_set ? search->summary_set : search->summary;
+
+ if (!CAMEL_IS_VEE_FOLDER (search->folder)) {
+ camel_folder_summary_prepare_fetch_all (search->folder->summary, NULL);
+ }
+
+ for (ii = 0; ii < summary->len; ii++) {
+ search->current = camel_folder_summary_get (search->folder->summary, summary->pdata[ii]);
+ if (search->current) {
+ result = camel_sexp_term_eval (sexp, argv[0]);
+ camel_sexp_result_free (sexp, result);
+ camel_message_info_unref (search->current);
+ search->current = NULL;
+ break;
+ }
+ }
+ imapx_search->priv->local_data_search = prev_local_data_search;
+
+ if (local_data_search >= 0) {
+ g_clear_object (&imapx_store);
+
+ /* Chain up to parent's method. */
+ return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+ match_all (sexp, argc, argv, search);
+ }
+
+ /* let's change the requirements a bit, the parent class expects as a result boolean,
+ * but here is expected GPtrArray of matched UIDs */
+ result = camel_sexp_term_eval (sexp, argv[0]);
+
+ g_object_unref (imapx_store);
+
+ g_return_val_if_fail (result != NULL, result);
+ g_return_val_if_fail (result->type == CAMEL_SEXP_RES_ARRAY_PTR, result);
+
+ return result;
+}
+
+static GPtrArray *
+imapx_search_gather_words (CamelSExpResult **argv,
+ gint from_index,
+ gint argc)
+{
+ GPtrArray *ptrs;
+ GHashTable *words_hash;
+ GHashTableIter iter;
+ gpointer key, value;
+ gint ii, jj;
+
+ g_return_val_if_fail (argv != 0, NULL);
+
+ words_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ for (ii = from_index; ii < argc; ii++) {
+ struct _camel_search_words *words;
+
+ if (argv[ii]->type != CAMEL_SEXP_RES_STRING)
+ continue;
+
+ /* Handle multiple search words within a single term. */
+ words = camel_search_words_split ((const guchar *) argv[ii]->value.string);
+
+ for (jj = 0; jj < words->len; jj++) {
+ const gchar *word = words->words[jj]->word;
+
+ g_hash_table_insert (words_hash, g_strdup (word), NULL);
+ }
+
+ camel_search_words_free (words);
+ }
+
+ ptrs = g_ptr_array_new_full (g_hash_table_size (words_hash), g_free);
+
+ g_hash_table_iter_init (&iter, words_hash);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ g_ptr_array_add (ptrs, g_strdup (key));
+ }
+
+ if (ptrs->len == 0) {
+ g_ptr_array_free (ptrs, TRUE);
+ ptrs = NULL;
+ } else {
+ g_ptr_array_add (ptrs, NULL);
+ }
+
+ g_hash_table_destroy (words_hash);
+
+ return ptrs;
+}
+
+static CamelSExpResult *
+imapx_search_body_contains (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search);
+ CamelIMAPXStore *imapx_store;
+ CamelSExpResult *result;
+ GString *criteria;
+ GPtrArray *words;
+
+ /* Always do body-search server-side */
+ if (imapx_search->priv->local_data_search) {
+ *imapx_search->priv->local_data_search = -1;
+ return imapx_search_result_match_none (sexp, search);
+ }
+
+ /* Match everything if argv = [""] */
+ if (argc == 1 && argv[0]->value.string[0] == '\0')
+ return imapx_search_result_match_all (sexp, search);
+
+ /* Match nothing if empty argv or empty summary. */
+ if (argc == 0 || search->summary->len == 0)
+ return imapx_search_result_match_none (sexp, search);
+
+ imapx_store = camel_imapx_search_ref_store (CAMEL_IMAPX_SEARCH (search));
+
+ /* This will be NULL if we're offline. Search from cache. */
+ if (imapx_store == NULL) {
+ /* Chain up to parent's method. */
+ return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+ body_contains (sexp, argc, argv, search);
+ }
+
+ /* Build the IMAP search criteria. */
+
+ criteria = g_string_sized_new (128);
+
+ if (search->current != NULL) {
+ const gchar *uid;
+
+ /* Limit the search to a single UID. */
+ uid = camel_message_info_get_uid (search->current);
+ g_string_append_printf (criteria, "UID %s", uid);
+ }
+
+ words = imapx_search_gather_words (argv, 0, argc);
+
+ result = imapx_search_process_criteria (sexp, search, imapx_store, criteria, "BODY", words, G_STRFUNC);
+
+ g_string_free (criteria, TRUE);
+ g_ptr_array_free (words, TRUE);
+ g_object_unref (imapx_store);
+
+ return result;
+}
+
+static gboolean
+imapx_search_is_header_from_summary (const gchar *header_name)
+{
+ return g_ascii_strcasecmp (header_name, "From") == 0 ||
+ g_ascii_strcasecmp (header_name, "To") == 0 ||
+ g_ascii_strcasecmp (header_name, "CC") == 0 ||
+ g_ascii_strcasecmp (header_name, "Subject") == 0;
+}
+
+static CamelSExpResult *
+imapx_search_header_contains (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search);
+ CamelIMAPXStore *imapx_store;
+ CamelSExpResult *result;
+ const gchar *headername, *command = NULL;
+ GString *criteria;
+ gchar *search_key = NULL;
+ GPtrArray *words;
+
+ /* Match nothing if empty argv or empty summary. */
+ if (argc <= 1 ||
+ argv[0]->type != CAMEL_SEXP_RES_STRING ||
+ search->summary->len == 0)
+ return imapx_search_result_match_none (sexp, search);
+
+ headername = argv[0]->value.string;
+
+ if (imapx_search_is_header_from_summary (headername)) {
+ if (imapx_search->priv->local_data_search) {
+ if (*imapx_search->priv->local_data_search >= 0)
+ *imapx_search->priv->local_data_search = (*imapx_search->priv->local_data_search) + 1;
+ return imapx_search_result_match_all (sexp, search);
+ }
+
+ /* Chain up to parent's method. */
+ return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+ header_contains (sexp, argc, argv, search);
+ } else if (imapx_search->priv->local_data_search) {
+ *imapx_search->priv->local_data_search = -1;
+ return imapx_search_result_match_none (sexp, search);
+ }
+
+ imapx_store = camel_imapx_search_ref_store (CAMEL_IMAPX_SEARCH (search));
+
+ /* This will be NULL if we're offline. Search from cache. */
+ if (imapx_store == NULL) {
+ /* Chain up to parent's method. */
+ return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+ header_contains (sexp, argc, argv, search);
+ }
+
+ /* Build the IMAP search criteria. */
+
+ criteria = g_string_sized_new (128);
+
+ if (search->current != NULL) {
+ const gchar *uid;
+
+ /* Limit the search to a single UID. */
+ uid = camel_message_info_get_uid (search->current);
+ g_string_append_printf (criteria, "UID %s", uid);
+ }
+
+ if (g_ascii_strcasecmp (headername, "From") == 0)
+ command = "FROM";
+ else if (g_ascii_strcasecmp (headername, "To") == 0)
+ command = "TO";
+ else if (g_ascii_strcasecmp (headername, "CC") == 0)
+ command = "CC";
+ else if (g_ascii_strcasecmp (headername, "Bcc") == 0)
+ command = "BCC";
+ else if (g_ascii_strcasecmp (headername, "Subject") == 0)
+ command = "SUBJECT";
+
+ words = imapx_search_gather_words (argv, 1, argc);
+
+ if (!command)
+ search_key = g_strdup_printf ("HEADER \"%s\"", headername);
+
+ result = imapx_search_process_criteria (sexp, search, imapx_store, criteria, command ? command : search_key, words, G_STRFUNC);
+
+ g_string_free (criteria, TRUE);
+ g_ptr_array_free (words, TRUE);
+ g_object_unref (imapx_store);
+ g_free (search_key);
+
+ return result;
+}
+
+static CamelSExpResult *
+imapx_search_header_exists (CamelSExp *sexp,
+ gint argc,
+ CamelSExpResult **argv,
+ CamelFolderSearch *search)
+{
+ CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search);
+ CamelIMAPXStore *imapx_store;
+ CamelSExpResult *result;
+ GString *criteria;
+ gint ii;
+
+ /* Match nothing if empty argv or empty summary. */
+ if (argc == 0 || search->summary->len == 0)
+ return imapx_search_result_match_none (sexp, search);
+
+ /* Check if asking for locally stored headers only */
+ for (ii = 0; ii < argc; ii++) {
+ if (argv[ii]->type != CAMEL_SEXP_RES_STRING)
+ continue;
+
+ if (!imapx_search_is_header_from_summary (argv[ii]->value.string))
+ break;
+ }
+
+ /* All headers are from summary */
+ if (ii == argc) {
+ if (imapx_search->priv->local_data_search) {
+ if (*imapx_search->priv->local_data_search >= 0)
+ *imapx_search->priv->local_data_search = (*imapx_search->priv->local_data_search) + 1;
+
+ return imapx_search_result_match_all (sexp, search);
+ }
+
+ /* Chain up to parent's method. */
+ return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+ header_exists (sexp, argc, argv, search);
+ } else if (imapx_search->priv->local_data_search) {
+ *imapx_search->priv->local_data_search = -1;
+ return imapx_search_result_match_none (sexp, search);
+ }
+
+ imapx_store = camel_imapx_search_ref_store (CAMEL_IMAPX_SEARCH (search));
+
+ /* This will be NULL if we're offline. Search from cache. */
+ if (imapx_store == NULL) {
+ /* Chain up to parent's method. */
+ return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+ header_exists (sexp, argc, argv, search);
+ }
+
+ /* Build the IMAP search criteria. */
+
+ criteria = g_string_sized_new (128);
+
+ if (search->current != NULL) {
+ const gchar *uid;
+
+ /* Limit the search to a single UID. */
+ uid = camel_message_info_get_uid (search->current);
+ g_string_append_printf (criteria, "UID %s", uid);
+ }
+
+ for (ii = 0; ii < argc; ii++) {
+ const gchar *headername;
+
+ if (argv[ii]->type != CAMEL_SEXP_RES_STRING)
+ continue;
+
+ headername = argv[ii]->value.string;
+
+ if (criteria->len > 0)
+ g_string_append_c (criteria, ' ');
+
+ g_string_append_printf (criteria, "HEADER \"%s\" \"\"", headername);
+ }
+
+ result = imapx_search_process_criteria (sexp, search, imapx_store, criteria, NULL, NULL, G_STRFUNC);
+
+ g_string_free (criteria, TRUE);
+ g_object_unref (imapx_store);
+
+ return result;
+}
+
+static void
+camel_imapx_search_class_init (CamelIMAPXSearchClass *class)
+{
+ GObjectClass *object_class;
+ CamelFolderSearchClass *search_class;
+
+ g_type_class_add_private (class, sizeof (CamelIMAPXSearchPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = imapx_search_set_property;
+ object_class->get_property = imapx_search_get_property;
+ object_class->dispose = imapx_search_dispose;
+ object_class->finalize = imapx_search_finalize;
+
+ search_class = CAMEL_FOLDER_SEARCH_CLASS (class);
+ search_class->match_all = imapx_search_match_all;
+ search_class->body_contains = imapx_search_body_contains;
+ search_class->header_contains = imapx_search_header_contains;
+ search_class->header_exists = imapx_search_header_exists;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_STORE,
+ g_param_spec_object (
+ "store",
+ "IMAPX Store",
+ "IMAPX Store for server-side searches",
+ CAMEL_TYPE_IMAPX_STORE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_imapx_search_init (CamelIMAPXSearch *search)
+{
+ search->priv = CAMEL_IMAPX_SEARCH_GET_PRIVATE (search);
+ search->priv->local_data_search = NULL;
+
+ g_weak_ref_init (&search->priv->imapx_store, NULL);
+}
+
+/**
+ * camel_imapx_search_new:
+ * @imapx_store: a #CamelIMAPXStore to which the search belongs
+ *
+ * Returns a new #CamelIMAPXSearch instance.
+ *
+ * Returns: a new #CamelIMAPXSearch
+ *
+ * Since: 3.8
+ **/
+CamelFolderSearch *
+camel_imapx_search_new (CamelIMAPXStore *imapx_store)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store), NULL);
+
+ return g_object_new (
+ CAMEL_TYPE_IMAPX_SEARCH,
+ "store", imapx_store,
+ NULL);
+}
+
+/**
+ * camel_imapx_search_ref_store:
+ * @search: a #CamelIMAPXSearch
+ *
+ * Returns a #CamelIMAPXStore to use for server-side searches,
+ * or %NULL when the store is offline.
+ *
+ * The returned #CamelIMAPXStore is referenced for thread-safety and
+ * must be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelIMAPXStore, or %NULL
+ *
+ * Since: 3.8
+ **/
+CamelIMAPXStore *
+camel_imapx_search_ref_store (CamelIMAPXSearch *search)
+{
+ CamelIMAPXStore *imapx_store;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SEARCH (search), NULL);
+
+ imapx_store = g_weak_ref_get (&search->priv->imapx_store);
+
+ if (imapx_store && !camel_offline_store_get_online (CAMEL_OFFLINE_STORE (imapx_store)))
+ g_clear_object (&imapx_store);
+
+ return imapx_store;
+}
+
+/**
+ * camel_imapx_search_set_store:
+ * @search: a #CamelIMAPXSearch
+ * @imapx_server: a #CamelIMAPXStore, or %NULL
+ *
+ * Sets a #CamelIMAPXStore to use for server-side searches. Generally
+ * this is set for the duration of a single search when online, and then
+ * reset to %NULL.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_search_set_store (CamelIMAPXSearch *search,
+ CamelIMAPXStore *imapx_store)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SEARCH (search));
+
+ if (imapx_store != NULL)
+ g_return_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store));
+
+ g_weak_ref_set (&search->priv->imapx_store, imapx_store);
+
+ g_object_notify (G_OBJECT (search), "store");
+}
+
+/**
+ * camel_imapx_search_set_cancellable_and_error:
+ * @search: a #CamelIMAPXSearch
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Sets @cancellable and @error to use for server-side searches. This way
+ * the search can return accurate errors and be eventually cancelled by
+ * a user.
+ *
+ * Note: The caller is responsible to keep alive both @cancellable and @error
+ * for the whole run of the search and reset them both to NULL after
+ * the search is finished.
+ *
+ * Since: 3.16
+ **/
+void
+camel_imapx_search_set_cancellable_and_error (CamelIMAPXSearch *search,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SEARCH (search));
+
+ if (cancellable)
+ g_return_if_fail (G_IS_CANCELLABLE (cancellable));
+
+ search->priv->cancellable = cancellable;
+ search->priv->error = error;
+}
diff --git a/src/camel/providers/imapx/camel-imapx-search.h b/src/camel/providers/imapx/camel-imapx-search.h
new file mode 100644
index 000000000..acdba79d9
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-search.h
@@ -0,0 +1,82 @@
+/*
+ * camel-imapx-search.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_SEARCH_H
+#define CAMEL_IMAPX_SEARCH_H
+
+#include <camel/camel.h>
+
+#include "camel-imapx-store.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_SEARCH \
+ (camel_imapx_search_get_type ())
+#define CAMEL_IMAPX_SEARCH(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_SEARCH, CamelIMAPXSearch))
+#define CAMEL_IMAPX_SEARCH_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_SEARCH, CamelIMAPXSearchClass))
+#define CAMEL_IS_IMAPX_SEARCH(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_SEARCH))
+#define CAMEL_IS_IMAPX_SEARCH_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_SEARCH))
+#define CAMEL_IMAPX_SEARCH_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_SEARCH, CamelIMAPXSearchClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAPXSearch CamelIMAPXSearch;
+typedef struct _CamelIMAPXSearchClass CamelIMAPXSearchClass;
+typedef struct _CamelIMAPXSearchPrivate CamelIMAPXSearchPrivate;
+
+/**
+ * CamelIMAPXSearch:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.8
+ **/
+struct _CamelIMAPXSearch {
+ CamelFolderSearch parent;
+ CamelIMAPXSearchPrivate *priv;
+};
+
+struct _CamelIMAPXSearchClass {
+ CamelFolderSearchClass parent_class;
+};
+
+GType camel_imapx_search_get_type (void) G_GNUC_CONST;
+CamelFolderSearch *
+ camel_imapx_search_new (CamelIMAPXStore *imapx_store);
+CamelIMAPXStore *
+ camel_imapx_search_ref_store (CamelIMAPXSearch *search);
+void camel_imapx_search_set_store (CamelIMAPXSearch *search,
+ CamelIMAPXStore *imapx_store);
+void camel_imapx_search_set_cancellable_and_error
+ (CamelIMAPXSearch *search,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_SEARCH_H */
+
diff --git a/src/camel/providers/imapx/camel-imapx-server.c b/src/camel/providers/imapx/camel-imapx-server.c
new file mode 100644
index 000000000..518dbe3ef
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-server.c
@@ -0,0 +1,6614 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gnetworking.h>
+
+#include <libical/ical.h>
+
+#ifndef G_OS_WIN32
+#include <glib-unix.h>
+#endif /* G_OS_WIN32 */
+
+#include <camel/camel.h>
+
+#include "camel-imapx-server.h"
+
+#include "camel-imapx-folder.h"
+#include "camel-imapx-input-stream.h"
+#include "camel-imapx-job.h"
+#include "camel-imapx-logger.h"
+#include "camel-imapx-settings.h"
+#include "camel-imapx-store.h"
+#include "camel-imapx-summary.h"
+#include "camel-imapx-utils.h"
+
+#define CAMEL_IMAPX_SERVER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_IMAPX_SERVER, CamelIMAPXServerPrivate))
+
+#define c(...) camel_imapx_debug(command, __VA_ARGS__)
+#define e(...) camel_imapx_debug(extra, __VA_ARGS__)
+
+#define COMMAND_LOCK(x) g_rec_mutex_lock (&(x)->priv->command_lock)
+#define COMMAND_UNLOCK(x) g_rec_mutex_unlock (&(x)->priv->command_lock)
+
+/* Try pipelining fetch requests, 'in bits' */
+#define MULTI_SIZE (32768 * 8)
+
+#define MAX_COMMAND_LEN 1000
+
+/* Ping the server after a period of inactivity to avoid being logged off.
+ * Using a 29 minute inactivity timeout as recommended in RFC 2177 (IDLE). */
+#define INACTIVITY_TIMEOUT_SECONDS (29 * 60)
+
+/* Number of seconds to remain in PENDING state waiting for other commands
+ to be queued, before actually sending IDLE */
+#define IMAPX_IDLE_WAIT_SECONDS 2
+
+#ifdef G_OS_WIN32
+#ifdef gmtime_r
+#undef gmtime_r
+#endif
+
+/* The gmtime() in Microsoft's C library is MT-safe */
+#define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
+#endif
+
+G_DEFINE_QUARK (camel-imapx-server-error-quark, camel_imapx_server_error)
+
+/* untagged response handling */
+
+/* May need to turn this into separate,
+ * subclassable GObject with proper getter/setter
+ * functions so derived implementations can
+ * supply their own context information.
+ * The context supplied here, however, should
+ * not be exposed outside CamelIMAPXServer.
+ * An instance is created in imapx_untagged()
+ * with a lifetime of one run of this function.
+ * In order to supply a derived context instance,
+ * we would need to register a derived _new()
+ * function for it which will be called inside
+ * imapx_untagged().
+ *
+ * TODO: rethink this construct.
+ */
+typedef struct _CamelIMAPXServerUntaggedContext CamelIMAPXServerUntaggedContext;
+
+struct _CamelIMAPXServerUntaggedContext {
+ CamelSortType fetch_order;
+ gulong id;
+ guint len;
+ guchar *token;
+ gint tok;
+ gboolean lsub;
+ struct _status_info *sinfo;
+};
+
+/* internal untagged handler prototypes */
+static gboolean imapx_untagged_bye (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_capability (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_exists (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_expunge (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_fetch (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_flags (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_list (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_lsub (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_namespace (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_ok_no_bad (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_preauth (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_quota (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_quotaroot (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_recent (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_search (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_status (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean imapx_untagged_vanished (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+
+enum {
+ IMAPX_UNTAGGED_ID_BAD = 0,
+ IMAPX_UNTAGGED_ID_BYE,
+ IMAPX_UNTAGGED_ID_CAPABILITY,
+ IMAPX_UNTAGGED_ID_EXISTS,
+ IMAPX_UNTAGGED_ID_EXPUNGE,
+ IMAPX_UNTAGGED_ID_FETCH,
+ IMAPX_UNTAGGED_ID_FLAGS,
+ IMAPX_UNTAGGED_ID_LIST,
+ IMAPX_UNTAGGED_ID_LSUB,
+ IMAPX_UNTAGGED_ID_NAMESPACE,
+ IMAPX_UNTAGGED_ID_NO,
+ IMAPX_UNTAGGED_ID_OK,
+ IMAPX_UNTAGGED_ID_PREAUTH,
+ IMAPX_UNTAGGED_ID_QUOTA,
+ IMAPX_UNTAGGED_ID_QUOTAROOT,
+ IMAPX_UNTAGGED_ID_RECENT,
+ IMAPX_UNTAGGED_ID_SEARCH,
+ IMAPX_UNTAGGED_ID_STATUS,
+ IMAPX_UNTAGGED_ID_VANISHED,
+ IMAPX_UNTAGGED_LAST_ID
+};
+
+static const CamelIMAPXUntaggedRespHandlerDesc _untagged_descr[] = {
+ {CAMEL_IMAPX_UNTAGGED_BAD, imapx_untagged_ok_no_bad, NULL, FALSE},
+ {CAMEL_IMAPX_UNTAGGED_BYE, imapx_untagged_bye, NULL, FALSE},
+ {CAMEL_IMAPX_UNTAGGED_CAPABILITY, imapx_untagged_capability, NULL, FALSE},
+ {CAMEL_IMAPX_UNTAGGED_EXISTS, imapx_untagged_exists, NULL, TRUE},
+ {CAMEL_IMAPX_UNTAGGED_EXPUNGE, imapx_untagged_expunge, NULL, TRUE},
+ {CAMEL_IMAPX_UNTAGGED_FETCH, imapx_untagged_fetch, NULL, TRUE},
+ {CAMEL_IMAPX_UNTAGGED_FLAGS, imapx_untagged_flags, NULL, TRUE},
+ {CAMEL_IMAPX_UNTAGGED_LIST, imapx_untagged_list, NULL, TRUE},
+ {CAMEL_IMAPX_UNTAGGED_LSUB, imapx_untagged_lsub, NULL, TRUE},
+ {CAMEL_IMAPX_UNTAGGED_NAMESPACE, imapx_untagged_namespace, NULL, FALSE},
+ {CAMEL_IMAPX_UNTAGGED_NO, imapx_untagged_ok_no_bad, NULL, FALSE},
+ {CAMEL_IMAPX_UNTAGGED_OK, imapx_untagged_ok_no_bad, NULL, FALSE},
+ {CAMEL_IMAPX_UNTAGGED_PREAUTH, imapx_untagged_preauth, CAMEL_IMAPX_UNTAGGED_OK, TRUE /*overridden */ },
+ {CAMEL_IMAPX_UNTAGGED_QUOTA, imapx_untagged_quota, NULL, FALSE},
+ {CAMEL_IMAPX_UNTAGGED_QUOTAROOT, imapx_untagged_quotaroot, NULL, FALSE},
+ {CAMEL_IMAPX_UNTAGGED_RECENT, imapx_untagged_recent, NULL, TRUE},
+ {CAMEL_IMAPX_UNTAGGED_SEARCH, imapx_untagged_search, NULL, FALSE},
+ {CAMEL_IMAPX_UNTAGGED_STATUS, imapx_untagged_status, NULL, TRUE},
+ {CAMEL_IMAPX_UNTAGGED_VANISHED, imapx_untagged_vanished, NULL, TRUE},
+};
+
+typedef enum {
+ IMAPX_IDLE_STATE_OFF, /* no IDLE running at all */
+ IMAPX_IDLE_STATE_SCHEDULED, /* IDLE scheduled, but still waiting */
+ IMAPX_IDLE_STATE_PREPARING, /* IDLE command going to be processed */
+ IMAPX_IDLE_STATE_RUNNING, /* IDLE command had been processed, server responded */
+ IMAPX_IDLE_STATE_STOPPING /* DONE had been issued, waiting for completion */
+} IMAPXIdleState;
+
+struct _CamelIMAPXServerPrivate {
+ GWeakRef store;
+ GCancellable *cancellable; /* the main connection cancellable, it's cancelled on disconnect */
+
+ CamelIMAPXServerUntaggedContext *context;
+ GHashTable *untagged_handlers;
+
+ /* The 'stream_lock' also guards the GSubprocess. */
+ GInputStream *input_stream;
+ GOutputStream *output_stream;
+ GIOStream *connection;
+ GSubprocess *subprocess;
+ GMutex stream_lock;
+
+ GSource *inactivity_timeout;
+ GMutex inactivity_timeout_lock;
+
+ /* Info on currently selected folder. */
+ GMutex select_lock;
+ GWeakRef select_mailbox;
+ GWeakRef select_pending;
+ gint last_selected_mailbox_change_stamp;
+
+ GMutex changes_lock;
+ CamelFolderChangeInfo *changes;
+
+ /* Data items to request in STATUS commands:
+ * STATUS $mailbox_name ($status_data_items) */
+ gchar *status_data_items;
+
+ /* Return options for extended LIST commands:
+ * LIST "" $pattern RETURN ($list_return_opts) */
+ gchar *list_return_opts;
+
+ /* Untagged SEARCH data gets deposited here.
+ * The search command should claim the results
+ * when finished and reset the pointer to NULL. */
+ GArray *search_results;
+ GMutex search_results_lock;
+
+ GHashTable *known_alerts;
+ GMutex known_alerts_lock;
+
+ /* INBOX separator character, so we can correctly normalize
+ * INBOX and descendants of INBOX in IMAP responses that do
+ * not include a separator character with the mailbox name,
+ * such as STATUS. Used for camel_imapx_parse_mailbox(). */
+ gchar inbox_separator;
+
+ /* IDLE support */
+ GMutex idle_lock;
+ GCond idle_cond;
+ IMAPXIdleState idle_state;
+ GSource *idle_pending;
+ CamelIMAPXMailbox *idle_mailbox;
+ GCancellable *idle_cancellable;
+ guint idle_stamp;
+
+ gboolean is_cyrus;
+
+ /* Info about the current connection; guarded by priv->stream_lock */
+ struct _capability_info *cinfo;
+
+ GRecMutex command_lock;
+
+ gchar tagprefix;
+ guint32 state;
+
+ gboolean use_qresync;
+
+ CamelIMAPXCommand *current_command;
+ CamelIMAPXCommand *continuation_command;
+
+ /* operation data */
+ GIOStream *get_message_stream;
+
+ CamelIMAPXMailbox *fetch_changes_mailbox; /* not referenced */
+ CamelFolder *fetch_changes_folder; /* not referenced */
+ GHashTable *fetch_changes_infos; /* gchar *uid ~> FetchChangesInfo-s */
+ gint64 fetch_changes_last_progress; /* when was called last progress */
+
+ struct _status_info *copyuid_status;
+};
+
+enum {
+ PROP_0,
+ PROP_STORE
+};
+
+enum {
+ REFRESH_MAILBOX,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static gboolean imapx_continuation (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GOutputStream *output_stream,
+ gboolean litplus,
+ GCancellable *cancellable,
+ GError **error);
+static void imapx_disconnect (CamelIMAPXServer *is);
+
+/* states for the connection? */
+enum {
+ IMAPX_DISCONNECTED,
+ IMAPX_SHUTDOWN,
+ IMAPX_CONNECTED,
+ IMAPX_AUTHENTICATED,
+ IMAPX_INITIALISED,
+ IMAPX_SELECTED
+};
+
+struct _imapx_flag_change {
+ GPtrArray *infos;
+ gchar *name;
+};
+
+static gint imapx_refresh_info_uid_cmp (gconstpointer ap,
+ gconstpointer bp,
+ gboolean ascending);
+static gint imapx_uids_array_cmp (gconstpointer ap,
+ gconstpointer bp);
+static void imapx_sync_free_user (GArray *user_set);
+
+G_DEFINE_TYPE (CamelIMAPXServer, camel_imapx_server, G_TYPE_OBJECT)
+
+typedef struct _FetchChangesInfo {
+ guint32 server_flags;
+ CamelFlag *server_user_flags;
+} FetchChangesInfo;
+
+static void
+fetch_changes_info_free (gpointer ptr)
+{
+ FetchChangesInfo *nfo = ptr;
+
+ if (nfo) {
+ camel_flag_list_free (&nfo->server_user_flags);
+ g_free (nfo);
+ }
+}
+
+static GWeakRef *
+imapx_weak_ref_new (gpointer object)
+{
+ GWeakRef *weak_ref;
+
+ /* XXX Might want to expose this in Camel's public API if it
+ * proves useful elsewhere. Based on e_weak_ref_new(). */
+
+ weak_ref = g_slice_new0 (GWeakRef);
+ g_weak_ref_init (weak_ref, object);
+
+ return weak_ref;
+}
+
+static void
+imapx_weak_ref_free (GWeakRef *weak_ref)
+{
+ g_return_if_fail (weak_ref != NULL);
+
+ /* XXX Might want to expose this in Camel's public API if it
+ * proves useful elsewhere. Based on e_weak_ref_free(). */
+
+ g_weak_ref_clear (weak_ref);
+ g_slice_free (GWeakRef, weak_ref);
+}
+
+static const CamelIMAPXUntaggedRespHandlerDesc *
+replace_untagged_descriptor (GHashTable *untagged_handlers,
+ const gchar *key,
+ const CamelIMAPXUntaggedRespHandlerDesc *descr)
+{
+ const CamelIMAPXUntaggedRespHandlerDesc *prev = NULL;
+
+ g_return_val_if_fail (untagged_handlers != NULL, NULL);
+ g_return_val_if_fail (key != NULL, NULL);
+ /* descr may be NULL (to delete a handler) */
+
+ prev = g_hash_table_lookup (untagged_handlers, key);
+ g_hash_table_replace (
+ untagged_handlers,
+ g_strdup (key),
+ (gpointer) descr);
+ return prev;
+}
+
+static void
+add_initial_untagged_descriptor (GHashTable *untagged_handlers,
+ guint untagged_id)
+{
+ const CamelIMAPXUntaggedRespHandlerDesc *prev = NULL;
+ const CamelIMAPXUntaggedRespHandlerDesc *cur = NULL;
+
+ g_return_if_fail (untagged_handlers != NULL);
+ g_return_if_fail (untagged_id < IMAPX_UNTAGGED_LAST_ID);
+
+ cur = &(_untagged_descr[untagged_id]);
+ prev = replace_untagged_descriptor (
+ untagged_handlers,
+ cur->untagged_response,
+ cur);
+ /* there must not be any previous handler here */
+ g_return_if_fail (prev == NULL);
+}
+
+static GHashTable *
+create_initial_untagged_handler_table (void)
+{
+ GHashTable *uh = g_hash_table_new_full (
+ camel_strcase_hash,
+ camel_strcase_equal,
+ g_free,
+ NULL);
+ guint32 ii = 0;
+
+ /* CamelIMAPXServer predefined handlers*/
+ for (ii = 0; ii < IMAPX_UNTAGGED_LAST_ID; ii++)
+ add_initial_untagged_descriptor (uh, ii);
+
+ g_return_val_if_fail (g_hash_table_size (uh) == IMAPX_UNTAGGED_LAST_ID, NULL);
+
+ return uh;
+}
+
+struct _uidset_state {
+ gint entries, uids;
+ gint total, limit;
+ guint32 start;
+ guint32 last;
+};
+
+/*
+ this creates a uid (or sequence number) set directly into a command,
+ if total is set, then we break it up into total uids. (i.e. command time)
+ if limit is set, then we break it up into limit entries (i.e. command length)
+*/
+static void
+imapx_uidset_init (struct _uidset_state *ss,
+ gint total,
+ gint limit)
+{
+ ss->uids = 0;
+ ss->entries = 0;
+ ss->start = 0;
+ ss->last = 0;
+ ss->total = total;
+ ss->limit = limit;
+}
+
+static gboolean
+imapx_uidset_done (struct _uidset_state *ss,
+ CamelIMAPXCommand *ic)
+{
+ gint ret = FALSE;
+
+ if (ss->last != 0) {
+ if (ss->entries > 0)
+ camel_imapx_command_add (ic, ",");
+ if (ss->last == ss->start)
+ camel_imapx_command_add (ic, "%d", ss->last);
+ else
+ camel_imapx_command_add (ic, "%d:%d", ss->start, ss->last);
+ }
+
+ ret = ss->last != 0;
+
+ ss->start = 0;
+ ss->last = 0;
+ ss->uids = 0;
+ ss->entries = 0;
+
+ return ret;
+}
+
+static gint
+imapx_uidset_add (struct _uidset_state *ss,
+ CamelIMAPXCommand *ic,
+ const gchar *uid)
+{
+ guint32 uidn;
+
+ uidn = strtoul (uid, NULL, 10);
+ if (uidn == 0)
+ return -1;
+
+ ss->uids++;
+
+ e (ic->is->priv->tagprefix, "uidset add '%s'\n", uid);
+
+ if (ss->last == 0) {
+ e (ic->is->priv->tagprefix, " start\n");
+ ss->start = uidn;
+ ss->last = uidn;
+ } else {
+ if (ss->start - 1 == uidn) {
+ ss->start = uidn;
+ } else {
+ if (ss->last != uidn - 1) {
+ if (ss->last == ss->start) {
+ e (ic->is->priv->tagprefix, " ,next\n");
+ if (ss->entries > 0)
+ camel_imapx_command_add (ic, ",");
+ camel_imapx_command_add (ic, "%d", ss->start);
+ ss->entries++;
+ } else {
+ e (ic->is->priv->tagprefix, " :range\n");
+ if (ss->entries > 0)
+ camel_imapx_command_add (ic, ",");
+ camel_imapx_command_add (ic, "%d:%d", ss->start, ss->last);
+ ss->entries += 2;
+ }
+ ss->start = uidn;
+ }
+
+ ss->last = uidn;
+ }
+ }
+
+ if ((ss->limit && ss->entries >= ss->limit)
+ || (ss->limit && ss->uids >= ss->limit)
+ || (ss->total && ss->uids >= ss->total)) {
+ e (ic->is->priv->tagprefix, " done, %d entries, %d uids\n", ss->entries, ss->uids);
+ if (!imapx_uidset_done (ss, ic))
+ return -1;
+ return 1;
+ }
+
+ return 0;
+}
+
+static CamelFolder *
+imapx_server_ref_folder (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox)
+{
+ CamelFolder *folder;
+ CamelIMAPXStore *store;
+ gchar *folder_path;
+ GError *local_error = NULL;
+
+ store = camel_imapx_server_ref_store (is);
+
+ folder_path = camel_imapx_mailbox_dup_folder_path (mailbox);
+
+ folder = camel_store_get_folder_sync (
+ CAMEL_STORE (store), folder_path, 0, NULL, &local_error);
+
+ g_free (folder_path);
+
+ g_object_unref (store);
+
+ /* Sanity check. */
+ g_warn_if_fail (
+ ((folder != NULL) && (local_error == NULL)) ||
+ ((folder == NULL) && (local_error != NULL)));
+
+ if (local_error != NULL) {
+ g_warning (
+ "%s: Failed to get folder for '%s': %s",
+ G_STRFUNC, camel_imapx_mailbox_get_name (mailbox), local_error->message);
+ g_error_free (local_error);
+ }
+
+ return folder;
+}
+
+static void
+imapx_server_stash_command_arguments (CamelIMAPXServer *is)
+{
+ GString *buffer;
+
+ /* Stash some reusable capability-based command arguments. */
+
+ buffer = g_string_new ("MESSAGES UNSEEN UIDVALIDITY UIDNEXT");
+ if (CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, CONDSTORE))
+ g_string_append (buffer, " HIGHESTMODSEQ");
+ g_free (is->priv->status_data_items);
+ is->priv->status_data_items = g_string_free (buffer, FALSE);
+
+ g_free (is->priv->list_return_opts);
+ if (!is->priv->is_cyrus && CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, LIST_EXTENDED)) {
+ buffer = g_string_new ("CHILDREN SUBSCRIBED");
+ if (CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, LIST_STATUS))
+ g_string_append_printf (
+ buffer, " STATUS (%s)",
+ is->priv->status_data_items);
+ if (CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, SPECIAL_USE))
+ g_string_append_printf (buffer, " SPECIAL-USE");
+ is->priv->list_return_opts = g_string_free (buffer, FALSE);
+ } else {
+ is->priv->list_return_opts = NULL;
+ }
+}
+
+static gpointer
+imapx_server_inactivity_thread (gpointer user_data)
+{
+ CamelIMAPXServer *is = user_data;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
+
+ if (camel_imapx_server_is_in_idle (is)) {
+ /* Stop and restart the IDLE command. */
+ if (!camel_imapx_server_schedule_idle_sync (is, NULL, is->priv->cancellable, &local_error) &&
+ !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ camel_imapx_debug (io, camel_imapx_server_get_tagprefix (is),
+ "%s: Failed to restart IDLE: %s\n", G_STRFUNC, local_error ? local_error->message : "Unknown error");
+ } else {
+ if (!camel_imapx_server_noop_sync (is, NULL, is->priv->cancellable, &local_error) &&
+ !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ camel_imapx_debug (io, camel_imapx_server_get_tagprefix (is),
+ "%s: Failed to issue NOOP: %s\n", G_STRFUNC, local_error ? local_error->message : "Unknown error");
+ }
+
+ g_clear_error (&local_error);
+ g_object_unref (is);
+
+ return NULL;
+}
+
+static gboolean
+imapx_server_inactivity_timeout_cb (gpointer data)
+{
+ CamelIMAPXServer *is;
+ GThread *thread;
+ GError *local_error = NULL;
+
+ is = g_weak_ref_get (data);
+
+ if (is == NULL)
+ return G_SOURCE_REMOVE;
+
+ thread = g_thread_try_new (NULL, imapx_server_inactivity_thread, g_object_ref (is), &local_error);
+ if (!thread) {
+ g_warning ("%s: Failed to start inactivity thread: %s", G_STRFUNC, local_error ? local_error->message : "Unknown error");
+ g_object_unref (is);
+ } else {
+ g_thread_unref (thread);
+ }
+
+ g_clear_error (&local_error);
+ g_object_unref (is);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+imapx_server_reset_inactivity_timer (CamelIMAPXServer *is)
+{
+ g_mutex_lock (&is->priv->inactivity_timeout_lock);
+
+ if (is->priv->inactivity_timeout != NULL) {
+ g_source_destroy (is->priv->inactivity_timeout);
+ g_source_unref (is->priv->inactivity_timeout);
+ }
+
+ is->priv->inactivity_timeout =
+ g_timeout_source_new_seconds (INACTIVITY_TIMEOUT_SECONDS);
+ g_source_set_callback (
+ is->priv->inactivity_timeout,
+ imapx_server_inactivity_timeout_cb,
+ imapx_weak_ref_new (is),
+ (GDestroyNotify) imapx_weak_ref_free);
+ g_source_attach (is->priv->inactivity_timeout, NULL);
+
+ g_mutex_unlock (&is->priv->inactivity_timeout_lock);
+}
+
+static gint
+imapx_server_set_connection_timeout (GIOStream *connection,
+ gint timeout_seconds)
+{
+ GSocket *socket;
+ gint previous_timeout = -1;
+
+ if (G_IS_TLS_CONNECTION (connection)) {
+ GIOStream *base_io_stream = NULL;
+
+ g_object_get (G_OBJECT (connection), "base-io-stream", &base_io_stream, NULL);
+
+ connection = base_io_stream;
+ } else if (connection) {
+ /* Connection can be NULL, when a custom command (GSubProcess) is used instead */
+ g_object_ref (connection);
+ }
+
+ if (!G_IS_SOCKET_CONNECTION (connection)) {
+ g_clear_object (&connection);
+ return previous_timeout;
+ }
+
+ socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (connection));
+ if (socket) {
+ previous_timeout = g_socket_get_timeout (socket);
+ g_socket_set_timeout (socket, timeout_seconds);
+ }
+
+ g_clear_object (&connection);
+
+ return previous_timeout;
+}
+
+static void
+imapx_expunge_uid_from_summary (CamelIMAPXServer *is,
+ const gchar *uid,
+ gboolean unsolicited)
+{
+ CamelFolder *folder;
+ CamelIMAPXMailbox *mailbox;
+ guint32 messages;
+
+ mailbox = camel_imapx_server_ref_pending_or_selected (is);
+
+ g_return_if_fail (mailbox != NULL);
+
+ folder = imapx_server_ref_folder (is, mailbox);
+ g_return_if_fail (folder != NULL);
+
+ messages = camel_imapx_mailbox_get_messages (mailbox);
+
+ if (unsolicited && messages > 0)
+ camel_imapx_mailbox_set_messages (mailbox, messages - 1);
+
+ g_return_if_fail (is->priv->changes != NULL);
+
+ camel_folder_summary_remove_uid (folder->summary, uid);
+
+ g_mutex_lock (&is->priv->changes_lock);
+
+ camel_folder_change_info_remove_uid (is->priv->changes, uid);
+
+ if (camel_imapx_server_is_in_idle (is)) {
+ CamelFolderChangeInfo *changes;
+
+ changes = is->priv->changes;
+ is->priv->changes = camel_folder_change_info_new ();
+
+ g_mutex_unlock (&is->priv->changes_lock);
+
+ camel_folder_summary_save_to_db (folder->summary, NULL);
+ imapx_update_store_summary (folder);
+ camel_folder_changed (folder, changes);
+
+ camel_folder_change_info_free (changes);
+ } else {
+ g_mutex_unlock (&is->priv->changes_lock);
+ }
+
+ g_object_unref (folder);
+ g_object_unref (mailbox);
+}
+
+/* untagged response handler functions */
+
+static gboolean
+imapx_untagged_capability (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct _capability_info *cinfo;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ if (is->priv->cinfo != NULL) {
+ imapx_free_capability (is->priv->cinfo);
+ is->priv->cinfo = NULL;
+ }
+
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ cinfo = imapx_parse_capability (CAMEL_IMAPX_INPUT_STREAM (input_stream), cancellable, error);
+
+ if (!cinfo)
+ return FALSE;
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ if (is->priv->cinfo != NULL)
+ imapx_free_capability (is->priv->cinfo);
+ is->priv->cinfo = cinfo;
+
+ c (is->priv->tagprefix, "got capability flags %08x\n", is->priv->cinfo->capa);
+
+ imapx_server_stash_command_arguments (is);
+
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_untagged_expunge (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXMailbox *mailbox;
+ gulong expunge = 0;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ expunge = is->priv->context->id;
+
+ COMMAND_LOCK (is);
+
+ /* Ignore EXPUNGE responses when not running a COPY(MOVE)_MESSAGE job */
+ if (!is->priv->current_command || (is->priv->current_command->job_kind != CAMEL_IMAPX_JOB_COPY_MESSAGE &&
+ is->priv->current_command->job_kind != CAMEL_IMAPX_JOB_MOVE_MESSAGE)) {
+ COMMAND_UNLOCK (is);
+
+ c (is->priv->tagprefix, "ignoring untagged expunge: %lu\n", expunge);
+ return TRUE;
+ }
+
+ COMMAND_UNLOCK (is);
+
+ c (is->priv->tagprefix, "expunged: %lu\n", expunge);
+
+ mailbox = camel_imapx_server_ref_pending_or_selected (is);
+
+ if (mailbox != NULL) {
+ CamelFolder *folder;
+ gchar *uid;
+
+ folder = imapx_server_ref_folder (is, mailbox);
+ g_return_val_if_fail (folder != NULL, FALSE);
+
+ uid = camel_imapx_dup_uid_from_summary_index (folder, expunge - 1);
+
+ if (uid != NULL)
+ imapx_expunge_uid_from_summary (is, uid, TRUE);
+
+ g_object_unref (folder);
+ g_free (uid);
+ }
+
+ g_clear_object (&mailbox);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_untagged_vanished (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelIMAPXMailbox *mailbox;
+ GArray *uids;
+ GList *uid_list = NULL;
+ gboolean unsolicited = TRUE;
+ guint ii = 0;
+ guint len = 0;
+ guchar *token = NULL;
+ gint tok = 0;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ tok = camel_imapx_input_stream_token (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ &token, &len, cancellable, error);
+ if (tok < 0)
+ return FALSE;
+ if (tok == '(') {
+ unsolicited = FALSE;
+ while (tok != ')') {
+ /* We expect this to be 'EARLIER' */
+ tok = camel_imapx_input_stream_token (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ &token, &len, cancellable, error);
+ if (tok < 0)
+ return FALSE;
+ }
+ } else {
+ camel_imapx_input_stream_ungettoken (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ tok, token, len);
+ }
+
+ uids = imapx_parse_uids (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream), cancellable, error);
+ if (uids == NULL)
+ return FALSE;
+
+ mailbox = camel_imapx_server_ref_pending_or_selected (is);
+
+ g_return_val_if_fail (mailbox != NULL, FALSE);
+
+ folder = imapx_server_ref_folder (is, mailbox);
+ g_return_val_if_fail (folder != NULL, FALSE);
+
+ if (unsolicited) {
+ guint32 messages;
+
+ messages = camel_imapx_mailbox_get_messages (mailbox);
+
+ if (messages < uids->len) {
+ c (
+ is->priv->tagprefix,
+ "Error: mailbox messages (%u) is "
+ "fewer than vanished %u\n",
+ messages, uids->len);
+ messages = 0;
+ } else {
+ messages -= uids->len;
+ }
+
+ camel_imapx_mailbox_set_messages (mailbox, messages);
+ }
+
+ g_return_val_if_fail (is->priv->changes != NULL, FALSE);
+
+ g_mutex_lock (&is->priv->changes_lock);
+
+ for (ii = 0; ii < uids->len; ii++) {
+ guint32 uid;
+ gchar *str;
+
+ uid = g_array_index (uids, guint32, ii);
+
+ e (is->priv->tagprefix, "vanished: %u\n", uid);
+
+ str = g_strdup_printf ("%u", uid);
+ uid_list = g_list_prepend (uid_list, str);
+ camel_folder_change_info_remove_uid (is->priv->changes, str);
+ }
+
+ g_mutex_unlock (&is->priv->changes_lock);
+
+ uid_list = g_list_reverse (uid_list);
+ camel_folder_summary_remove_uids (folder->summary, uid_list);
+
+ /* If the response is truly unsolicited (e.g. via NOTIFY)
+ * then go ahead and emit the change notification now. */
+ COMMAND_LOCK (is);
+ if (!is->priv->current_command) {
+ COMMAND_UNLOCK (is);
+
+ g_mutex_lock (&is->priv->changes_lock);
+ if (is->priv->changes->uid_removed &&
+ is->priv->changes->uid_removed->len >= 100) {
+ CamelFolderChangeInfo *changes;
+
+ changes = is->priv->changes;
+ is->priv->changes = camel_folder_change_info_new ();
+
+ g_mutex_unlock (&is->priv->changes_lock);
+
+ camel_folder_summary_save_to_db (folder->summary, NULL);
+ imapx_update_store_summary (folder);
+
+ camel_folder_changed (folder, changes);
+ camel_folder_change_info_free (changes);
+ } else {
+ g_mutex_unlock (&is->priv->changes_lock);
+ }
+ } else {
+ COMMAND_UNLOCK (is);
+ }
+
+ g_list_free_full (uid_list, (GDestroyNotify) g_free);
+ g_array_free (uids, TRUE);
+
+ g_object_unref (folder);
+ g_object_unref (mailbox);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_untagged_namespace (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXNamespaceResponse *response;
+ CamelIMAPXStore *imapx_store;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ response = camel_imapx_namespace_response_new (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream), cancellable, error);
+
+ if (response == NULL)
+ return FALSE;
+
+ imapx_store = camel_imapx_server_ref_store (is);
+ camel_imapx_store_set_namespaces (imapx_store, response);
+
+ g_clear_object (&imapx_store);
+ g_object_unref (response);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_untagged_exists (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelIMAPXMailbox *mailbox;
+ guint32 exists;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ mailbox = camel_imapx_server_ref_pending_or_selected (is);
+
+ if (mailbox == NULL) {
+ g_warning ("%s: No mailbox available", G_STRFUNC);
+ return TRUE;
+ }
+
+ exists = (guint32) is->priv->context->id;
+
+ c (is->priv->tagprefix, "%s: updating mailbox '%s' messages: %d ~> %d\n", G_STRFUNC,
+ camel_imapx_mailbox_get_name (mailbox),
+ camel_imapx_mailbox_get_messages (mailbox),
+ exists);
+
+ camel_imapx_mailbox_set_messages (mailbox, exists);
+
+ folder = imapx_server_ref_folder (is, mailbox);
+ g_return_val_if_fail (folder != NULL, FALSE);
+
+ if (camel_imapx_server_is_in_idle (is)) {
+ guint count;
+
+ count = camel_folder_summary_count (folder->summary);
+ if (count < exists)
+ g_signal_emit (is, signals[REFRESH_MAILBOX], 0, mailbox);
+ }
+
+ g_object_unref (folder);
+ g_object_unref (mailbox);
+
+ return success;
+}
+
+static gboolean
+imapx_untagged_flags (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint32 flags = 0;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ success = imapx_parse_flags (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ &flags, NULL, cancellable, error);
+
+ c (is->priv->tagprefix, "flags: %08x\n", flags);
+
+ return success;
+}
+
+static gboolean
+imapx_untagged_fetch (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct _fetch_info *finfo;
+ gboolean got_body_header;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ finfo = imapx_parse_fetch (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream), cancellable, error);
+ if (finfo == NULL) {
+ imapx_free_fetch (finfo);
+ return FALSE;
+ }
+
+ /* Some IMAP servers respond with BODY[HEADER] when
+ * asked for RFC822.HEADER. Treat them equivalently. */
+ got_body_header =
+ ((finfo->got & FETCH_HEADER) == 0) &&
+ (finfo->header == NULL) &&
+ ((finfo->got & FETCH_BODY) != 0) &&
+ (g_strcmp0 (finfo->section, "HEADER") == 0);
+
+ if (got_body_header) {
+ finfo->got |= FETCH_HEADER;
+ finfo->got &= ~FETCH_BODY;
+ finfo->header = finfo->body;
+ finfo->body = NULL;
+ }
+
+ if ((finfo->got & (FETCH_BODY | FETCH_UID)) == (FETCH_BODY | FETCH_UID)) {
+ GOutputStream *output_stream;
+ gconstpointer body_data;
+ gsize body_size;
+
+ g_return_val_if_fail (is->priv->get_message_stream != NULL, FALSE);
+
+ /* Fill out the body stream, in the right spot. */
+
+ g_seekable_seek (
+ G_SEEKABLE (is->priv->get_message_stream),
+ finfo->offset, G_SEEK_SET,
+ NULL, NULL);
+
+ output_stream = g_io_stream_get_output_stream (is->priv->get_message_stream);
+
+ body_data = g_bytes_get_data (finfo->body, &body_size);
+
+ /* Sometimes the server, like Microsoft Exchange, reports larger message
+ size than it actually is, which results in no data being read from
+ the server for that particular offset. */
+ if (body_size) {
+ g_mutex_lock (&is->priv->stream_lock);
+ if (!g_output_stream_write_all (
+ output_stream, body_data, body_size,
+ NULL, cancellable, error)) {
+ g_mutex_unlock (&is->priv->stream_lock);
+ g_prefix_error (
+ error, "%s: ",
+ _("Error writing to cache stream"));
+ imapx_free_fetch (finfo);
+ return FALSE;
+ }
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+ }
+
+ if ((finfo->got & FETCH_FLAGS) && !(finfo->got & FETCH_HEADER)) {
+ CamelIMAPXMailbox *select_mailbox;
+ CamelIMAPXMailbox *select_pending;
+
+ if (is->priv->fetch_changes_mailbox) {
+ g_return_val_if_fail (is->priv->fetch_changes_mailbox != NULL, FALSE);
+ g_return_val_if_fail (is->priv->fetch_changes_folder != NULL, FALSE);
+ g_return_val_if_fail (is->priv->fetch_changes_infos != NULL, FALSE);
+ }
+
+ g_mutex_lock (&is->priv->select_lock);
+ select_mailbox = g_weak_ref_get (&is->priv->select_mailbox);
+ select_pending = g_weak_ref_get (&is->priv->select_pending);
+ g_mutex_unlock (&is->priv->select_lock);
+
+ /* This is either a refresh_info job, check to see if it is
+ * and update if so, otherwise it must've been an unsolicited
+ * response, so update the summary to match. */
+ if ((finfo->got & FETCH_UID) != 0 && is->priv->fetch_changes_folder && is->priv->fetch_changes_infos) {
+ FetchChangesInfo *nfo;
+ gint64 monotonic_time;
+ gint n_messages;
+
+ nfo = g_hash_table_lookup (is->priv->fetch_changes_infos, finfo->uid);
+ if (!nfo) {
+ nfo = g_new0 (FetchChangesInfo, 1);
+
+ g_hash_table_insert (is->priv->fetch_changes_infos, (gpointer) camel_pstring_strdup (finfo->uid), nfo);
+ }
+
+ nfo->server_flags = finfo->flags;
+ nfo->server_user_flags = finfo->user_flags;
+ finfo->user_flags = NULL;
+
+ monotonic_time = g_get_monotonic_time ();
+ n_messages = camel_imapx_mailbox_get_messages (is->priv->fetch_changes_mailbox);
+
+ if (n_messages > 0 && is->priv->fetch_changes_last_progress + G_USEC_PER_SEC / 2 < monotonic_time &&
+ is->priv->context && is->priv->context->id <= n_messages) {
+ COMMAND_LOCK (is);
+
+ if (is->priv->current_command) {
+ COMMAND_UNLOCK (is);
+
+ is->priv->fetch_changes_last_progress = monotonic_time;
+
+ camel_operation_progress (cancellable, 100 * is->priv->context->id
+ / camel_imapx_mailbox_get_messages (is->priv->fetch_changes_mailbox));
+ } else {
+ COMMAND_UNLOCK (is);
+ }
+ }
+ } else if (select_mailbox != NULL) {
+ CamelFolder *select_folder;
+ CamelMessageInfo *mi = NULL;
+ gboolean changed = FALSE;
+ gchar *uid = NULL;
+
+ c (is->priv->tagprefix, "flag changed: %lu\n", is->priv->context->id);
+
+ select_folder = imapx_server_ref_folder (is, select_mailbox);
+ g_return_val_if_fail (select_folder != NULL, FALSE);
+
+ if (finfo->got & FETCH_UID) {
+ uid = finfo->uid;
+ finfo->uid = NULL;
+ } else {
+ uid = camel_imapx_dup_uid_from_summary_index (
+ select_folder,
+ is->priv->context->id - 1);
+ }
+
+ if (uid) {
+ mi = camel_folder_summary_get (select_folder->summary, uid);
+ if (mi) {
+ /* It's unsolicited _unless_ select_pending (i.e. during
+ * a QRESYNC SELECT */
+ changed = imapx_update_message_info_flags (
+ mi, finfo->flags,
+ finfo->user_flags,
+ camel_imapx_mailbox_get_permanentflags (select_mailbox),
+ select_folder,
+ (select_pending == NULL));
+ } else {
+ /* This (UID + FLAGS for previously unknown message) might
+ * happen during a SELECT (QRESYNC). We should use it. */
+ c (is->priv->tagprefix, "flags changed for unknown uid %s\n.", uid);
+ }
+ finfo->user_flags = NULL;
+ }
+
+ if (changed) {
+ g_return_val_if_fail (is->priv->changes != NULL, FALSE);
+
+ g_mutex_lock (&is->priv->changes_lock);
+ camel_folder_change_info_change_uid (is->priv->changes, uid);
+ g_mutex_unlock (&is->priv->changes_lock);
+ }
+ g_free (uid);
+
+ if (changed && camel_imapx_server_is_in_idle (is)) {
+ camel_folder_summary_save_to_db (select_folder->summary, NULL);
+ imapx_update_store_summary (select_folder);
+
+ g_mutex_lock (&is->priv->changes_lock);
+
+ camel_folder_changed (select_folder, is->priv->changes);
+ camel_folder_change_info_clear (is->priv->changes);
+
+ g_mutex_unlock (&is->priv->changes_lock);
+ }
+
+ if (mi)
+ camel_message_info_unref (mi);
+
+ g_object_unref (select_folder);
+ }
+
+ g_clear_object (&select_mailbox);
+ g_clear_object (&select_pending);
+ }
+
+ if ((finfo->got & (FETCH_HEADER | FETCH_UID)) == (FETCH_HEADER | FETCH_UID)) {
+ CamelIMAPXMailbox *mailbox;
+ CamelFolder *folder;
+ CamelMimeParser *mp;
+ CamelMessageInfo *mi;
+ guint32 messages;
+ guint32 unseen;
+ guint32 uidnext;
+
+ /* This must be a refresh info job as well, but it has
+ * asked for new messages to be added to the index. */
+
+ if (is->priv->fetch_changes_mailbox) {
+ g_return_val_if_fail (is->priv->fetch_changes_mailbox != NULL, FALSE);
+ g_return_val_if_fail (is->priv->fetch_changes_folder != NULL, FALSE);
+ g_return_val_if_fail (is->priv->fetch_changes_infos != NULL, FALSE);
+
+ folder = g_object_ref (is->priv->fetch_changes_folder);
+ mailbox = g_object_ref (is->priv->fetch_changes_mailbox);
+ } else {
+ mailbox = camel_imapx_server_ref_selected (is);
+ folder = mailbox ? imapx_server_ref_folder (is, mailbox) : NULL;
+ }
+
+ if (!mailbox || !folder || (!(finfo->got & FETCH_FLAGS) && !is->priv->fetch_changes_infos)) {
+ g_clear_object (&mailbox);
+ g_clear_object (&folder);
+ imapx_free_fetch (finfo);
+
+ return TRUE;
+ }
+
+ messages = camel_imapx_mailbox_get_messages (mailbox);
+ unseen = camel_imapx_mailbox_get_unseen (mailbox);
+ uidnext = camel_imapx_mailbox_get_uidnext (mailbox);
+
+ /* Do we want to save these headers for later too? Do we care? */
+
+ mp = camel_mime_parser_new ();
+ camel_mime_parser_init_with_bytes (mp, finfo->header);
+ mi = camel_folder_summary_info_new_from_parser (folder->summary, mp);
+ g_object_unref (mp);
+
+ if (mi != NULL) {
+ guint32 server_flags;
+ CamelFlag *server_user_flags;
+ CamelMessageInfoBase *binfo;
+ gboolean free_user_flags = FALSE;
+
+ mi->uid = camel_pstring_strdup (finfo->uid);
+
+ if (!(finfo->got & FETCH_FLAGS) && is->priv->fetch_changes_infos) {
+ FetchChangesInfo *nfo;
+
+ nfo = g_hash_table_lookup (is->priv->fetch_changes_infos, finfo->uid);
+ g_return_val_if_fail (nfo != NULL, FALSE);
+
+ server_flags = nfo->server_flags;
+ server_user_flags = nfo->server_user_flags;
+ } else {
+ server_flags = finfo->flags;
+ server_user_flags = finfo->user_flags;
+ /* free user_flags ? */
+ finfo->user_flags = NULL;
+ free_user_flags = TRUE;
+ }
+
+ /* If the message is a really new one -- equal or higher than what
+ * we know as UIDNEXT for the folder, then it came in since we last
+ * fetched UIDNEXT and UNREAD count. We'll update UIDNEXT in the
+ * command completion, but update UNREAD count now according to the
+ * message SEEN flag */
+ if (!(server_flags & CAMEL_MESSAGE_SEEN)) {
+ guint64 uidl;
+
+ uidl = strtoull (mi->uid, NULL, 10);
+
+ if (uidl >= uidnext) {
+ c (is->priv->tagprefix, "Updating unseen count for new message %s\n", mi->uid);
+ camel_imapx_mailbox_set_unseen (mailbox, unseen + 1);
+ } else {
+ c (is->priv->tagprefix, "Not updating unseen count for new message %s\n", mi->uid);
+ }
+ }
+
+ binfo = (CamelMessageInfoBase *) mi;
+ binfo->size = finfo->size;
+
+ camel_folder_summary_lock (folder->summary);
+
+ if (!camel_folder_summary_check_uid (folder->summary, mi->uid)) {
+ imapx_set_message_info_flags_for_new_message (mi, server_flags, server_user_flags, FALSE, NULL, camel_imapx_mailbox_get_permanentflags (mailbox));
+ camel_folder_summary_add (folder->summary, mi);
+
+ g_mutex_lock (&is->priv->changes_lock);
+
+ camel_folder_change_info_add_uid (is->priv->changes, mi->uid);
+ camel_folder_change_info_recent_uid (is->priv->changes, mi->uid);
+
+ g_mutex_unlock (&is->priv->changes_lock);
+
+ if (messages > 0) {
+ gint cnt = (camel_folder_summary_count (folder->summary) * 100) / messages;
+
+ camel_operation_progress (cancellable, cnt ? cnt : 1);
+ }
+ } else {
+ camel_message_info_unref (mi);
+ }
+
+ camel_folder_summary_unlock (folder->summary);
+
+ if (free_user_flags && server_user_flags)
+ camel_flag_list_free (&server_user_flags);
+ }
+
+ g_clear_object (&mailbox);
+ g_clear_object (&folder);
+ }
+
+ imapx_free_fetch (finfo);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_untagged_lsub (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXListResponse *response;
+ CamelIMAPXStore *imapx_store;
+ const gchar *mailbox_name;
+ gchar separator;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ /* LSUB response is syntactically compatible with LIST response. */
+ response = camel_imapx_list_response_new (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream), cancellable, error);
+ if (response == NULL)
+ return FALSE;
+
+ camel_imapx_list_response_add_attribute (
+ response, CAMEL_IMAPX_LIST_ATTR_SUBSCRIBED);
+
+ mailbox_name = camel_imapx_list_response_get_mailbox_name (response);
+ separator = camel_imapx_list_response_get_separator (response);
+
+ /* Record the INBOX separator character once we know it. */
+ if (camel_imapx_mailbox_is_inbox (mailbox_name))
+ is->priv->inbox_separator = separator;
+
+ imapx_store = camel_imapx_server_ref_store (is);
+ camel_imapx_store_handle_lsub_response (imapx_store, is, response);
+
+ g_clear_object (&imapx_store);
+ g_clear_object (&response);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_untagged_list (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXListResponse *response;
+ CamelIMAPXStore *imapx_store;
+ const gchar *mailbox_name;
+ gchar separator;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ response = camel_imapx_list_response_new (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream), cancellable, error);
+ if (response == NULL)
+ return FALSE;
+
+ mailbox_name = camel_imapx_list_response_get_mailbox_name (response);
+ separator = camel_imapx_list_response_get_separator (response);
+
+ /* Record the INBOX separator character once we know it. */
+ if (camel_imapx_mailbox_is_inbox (mailbox_name))
+ is->priv->inbox_separator = separator;
+
+ imapx_store = camel_imapx_server_ref_store (is);
+ camel_imapx_store_handle_list_response (imapx_store, is, response);
+
+ g_clear_object (&imapx_store);
+ g_clear_object (&response);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_untagged_quota (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar *quota_root_name = NULL;
+ CamelFolderQuotaInfo *quota_info = NULL;
+ gboolean success;
+
+ success = camel_imapx_parse_quota (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ cancellable, &quota_root_name, &quota_info, error);
+
+ /* Sanity check */
+ g_return_val_if_fail (
+ (success && (quota_root_name != NULL)) ||
+ (!success && (quota_root_name == NULL)), FALSE);
+
+ if (success) {
+ CamelIMAPXStore *store;
+
+ store = camel_imapx_server_ref_store (is);
+ camel_imapx_store_set_quota_info (
+ store, quota_root_name, quota_info);
+ g_object_unref (store);
+
+ g_free (quota_root_name);
+ camel_folder_quota_info_free (quota_info);
+ }
+
+ return success;
+}
+
+static gboolean
+imapx_untagged_quotaroot (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXMailbox *mailbox;
+ gchar *mailbox_name = NULL;
+ gchar **quota_roots = NULL;
+ gboolean success;
+
+ success = camel_imapx_parse_quotaroot (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ cancellable, &mailbox_name, &quota_roots, error);
+
+ /* Sanity check */
+ g_return_val_if_fail (
+ (success && (mailbox_name != NULL)) ||
+ (!success && (mailbox_name == NULL)), FALSE);
+
+ if (!success)
+ return FALSE;
+
+ imapx_store = camel_imapx_server_ref_store (is);
+ mailbox = camel_imapx_store_ref_mailbox (imapx_store, mailbox_name);
+ g_clear_object (&imapx_store);
+
+ if (mailbox != NULL) {
+ camel_imapx_mailbox_set_quota_roots (
+ mailbox, (const gchar **) quota_roots);
+ g_object_unref (mailbox);
+ } else {
+ g_warning (
+ "%s: Unknown mailbox '%s'",
+ G_STRFUNC, mailbox_name);
+ }
+
+ g_free (mailbox_name);
+ g_strfreev (quota_roots);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_untagged_recent (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXMailbox *mailbox;
+ guint32 recent;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ mailbox = camel_imapx_server_ref_pending_or_selected (is);
+
+ if (mailbox == NULL) {
+ g_warning ("%s: No mailbox available", G_STRFUNC);
+ return TRUE;
+ }
+
+ recent = (guint32) is->priv->context->id;
+
+ c (is->priv->tagprefix, "%s: updating mailbox '%s' recent: %d ~> %d\n", G_STRFUNC,
+ camel_imapx_mailbox_get_name (mailbox),
+ camel_imapx_mailbox_get_recent (mailbox),
+ recent);
+
+ camel_imapx_mailbox_set_recent (mailbox, recent);
+
+ g_object_unref (mailbox);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_untagged_search (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GArray *search_results;
+ gint tok;
+ guint len;
+ guchar *token;
+ guint64 number;
+ gboolean success = FALSE;
+
+ search_results = g_array_new (FALSE, FALSE, sizeof (guint64));
+
+ while (TRUE) {
+ gboolean success;
+
+ /* Peek at the next token, and break
+ * out of the loop if we get a newline. */
+ tok = camel_imapx_input_stream_token (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ &token, &len, cancellable, error);
+ if (tok == '\n')
+ break;
+ if (tok == IMAPX_TOK_ERROR)
+ goto exit;
+ camel_imapx_input_stream_ungettoken (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ tok, token, len);
+
+ success = camel_imapx_input_stream_number (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ &number, cancellable, error);
+
+ if (!success)
+ goto exit;
+
+ g_array_append_val (search_results, number);
+ }
+
+ g_mutex_lock (&is->priv->search_results_lock);
+
+ if (is->priv->search_results == NULL)
+ is->priv->search_results = g_array_ref (search_results);
+ else
+ g_warning ("%s: Conflicting search results", G_STRFUNC);
+
+ g_mutex_unlock (&is->priv->search_results_lock);
+
+ success = TRUE;
+
+exit:
+ g_array_unref (search_results);
+
+ return success;
+}
+
+static gboolean
+imapx_untagged_status (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXStatusResponse *response;
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXMailbox *mailbox;
+ const gchar *mailbox_name;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ response = camel_imapx_status_response_new (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ is->priv->inbox_separator, cancellable, error);
+ if (response == NULL)
+ return FALSE;
+
+ mailbox_name = camel_imapx_status_response_get_mailbox_name (response);
+
+ imapx_store = camel_imapx_server_ref_store (is);
+ mailbox = camel_imapx_store_ref_mailbox (imapx_store, mailbox_name);
+
+ if (mailbox != NULL) {
+ camel_imapx_mailbox_handle_status_response (mailbox, response);
+ camel_imapx_store_emit_mailbox_updated (imapx_store, mailbox);
+ g_object_unref (mailbox);
+ }
+
+ g_clear_object (&imapx_store);
+ g_object_unref (response);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_untagged_bye (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guchar *token = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ success = camel_imapx_input_stream_text (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ &token, cancellable, error);
+
+ /* XXX It's weird to be setting an error on success,
+ * but it's to indicate the server hung up on us. */
+ if (success) {
+ g_strstrip ((gchar *) token);
+
+ c (is->priv->tagprefix, "BYE: %s\n", token);
+ g_set_error (
+ error, CAMEL_IMAPX_SERVER_ERROR, CAMEL_IMAPX_SERVER_ERROR_TRY_RECONNECT,
+ "IMAP server said BYE: %s", token);
+ }
+
+ g_free (token);
+
+ is->priv->state = IMAPX_SHUTDOWN;
+
+ return FALSE;
+}
+
+static gboolean
+imapx_untagged_preauth (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ c (is->priv->tagprefix, "preauthenticated\n");
+ if (is->priv->state < IMAPX_AUTHENTICATED)
+ is->priv->state = IMAPX_AUTHENTICATED;
+
+ return TRUE;
+}
+
+static gboolean
+imapx_untagged_ok_no_bad (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXMailbox *mailbox;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ /* TODO: validate which ones of these can happen as unsolicited responses */
+ /* TODO: handle bye/preauth differently */
+ camel_imapx_input_stream_ungettoken (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ is->priv->context->tok,
+ is->priv->context->token,
+ is->priv->context->len);
+
+ /* These untagged responses can belong to ongoing SELECT command, thus
+ to the pending select mailbox, not to the currently selected or closing
+ mailbox, thus prefer the select pending mailbox, from the other two.
+ This makes sure that for example UIDVALIDITY is not incorrectly
+ overwritten with a value from a different mailbox, thus the offline
+ cache will persist, instead of being vanished.
+ */
+ mailbox = camel_imapx_server_ref_pending_or_selected (is);
+
+ is->priv->context->sinfo = imapx_parse_status (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ mailbox, cancellable, error);
+
+ g_clear_object (&mailbox);
+
+ if (is->priv->context->sinfo == NULL)
+ return FALSE;
+
+ switch (is->priv->context->sinfo->condition) {
+ case IMAPX_CLOSED:
+ c (
+ is->priv->tagprefix,
+ "previously selected mailbox is now closed\n");
+ {
+ CamelIMAPXMailbox *select_mailbox;
+ CamelIMAPXMailbox *select_pending;
+
+ g_mutex_lock (&is->priv->select_lock);
+
+ select_mailbox = g_weak_ref_get (&is->priv->select_mailbox);
+ select_pending = g_weak_ref_get (&is->priv->select_pending);
+
+ if (select_mailbox == NULL) {
+ g_weak_ref_set (&is->priv->select_mailbox, select_pending);
+
+ if (select_pending)
+ is->priv->last_selected_mailbox_change_stamp = camel_imapx_mailbox_get_change_stamp (select_pending);
+ else
+ is->priv->last_selected_mailbox_change_stamp = 0;
+ }
+
+ g_mutex_unlock (&is->priv->select_lock);
+
+ g_clear_object (&select_mailbox);
+ g_clear_object (&select_pending);
+ }
+ break;
+ case IMAPX_ALERT:
+ c (is->priv->tagprefix, "ALERT!: %s\n", is->priv->context->sinfo->text);
+ {
+ const gchar *alert_message;
+ gboolean emit_alert = FALSE;
+
+ g_mutex_lock (&is->priv->known_alerts_lock);
+
+ alert_message = is->priv->context->sinfo->text;
+
+ if (alert_message != NULL) {
+ emit_alert = !g_hash_table_contains (
+ is->priv->known_alerts,
+ alert_message);
+ }
+
+ if (emit_alert) {
+ CamelIMAPXStore *store;
+ CamelService *service;
+ CamelSession *session;
+
+ store = camel_imapx_server_ref_store (is);
+
+ g_hash_table_add (
+ is->priv->known_alerts,
+ g_strdup (alert_message));
+
+ service = CAMEL_SERVICE (store);
+ session = camel_service_ref_session (service);
+
+ if (session) {
+ camel_session_user_alert (
+ session, service,
+ CAMEL_SESSION_ALERT_WARNING,
+ alert_message);
+
+ g_object_unref (session);
+ }
+
+ g_object_unref (store);
+ }
+
+ g_mutex_unlock (&is->priv->known_alerts_lock);
+ }
+ break;
+ case IMAPX_PARSE:
+ c (is->priv->tagprefix, "PARSE: %s\n", is->priv->context->sinfo->text);
+ break;
+ case IMAPX_CAPABILITY:
+ if (is->priv->context->sinfo->u.cinfo) {
+ struct _capability_info *cinfo;
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ cinfo = is->priv->cinfo;
+ is->priv->cinfo = is->priv->context->sinfo->u.cinfo;
+ is->priv->context->sinfo->u.cinfo = NULL;
+ if (cinfo)
+ imapx_free_capability (cinfo);
+ c (is->priv->tagprefix, "got capability flags %08x\n", is->priv->cinfo ? is->priv->cinfo->capa : 0xFFFFFFFF);
+
+ if (is->priv->context->sinfo->text) {
+ guint32 list_extended = imapx_lookup_capability ("LIST-EXTENDED");
+
+ is->priv->is_cyrus = is->priv->is_cyrus || camel_strstrcase (is->priv->context->sinfo->text, "cyrus");
+ if (is->priv->is_cyrus && is->priv->cinfo && (is->priv->cinfo->capa & list_extended) != 0) {
+ /* Disable LIST-EXTENDED for cyrus servers */
+ c (is->priv->tagprefix, "Disabling LIST-EXTENDED extension for a Cyrus server\n");
+ is->priv->cinfo->capa &= ~list_extended;
+ }
+ }
+
+ imapx_server_stash_command_arguments (is);
+
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+ break;
+ case IMAPX_COPYUID:
+ imapx_free_status (is->priv->copyuid_status);
+ is->priv->copyuid_status = is->priv->context->sinfo;
+ is->priv->context->sinfo = NULL;
+ break;
+ default:
+ break;
+ }
+
+ imapx_free_status (is->priv->context->sinfo);
+ is->priv->context->sinfo = NULL;
+
+ return TRUE;
+}
+
+/* handle any untagged responses */
+static gboolean
+imapx_untagged (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXSettings *settings;
+ CamelSortType fetch_order;
+ guchar *p = NULL, c;
+ const gchar *token = NULL;
+ gboolean success = FALSE;
+
+ /* If is->priv->context is not NULL here, it basically means
+ * that imapx_untagged() got called concurrently for the same
+ * CamelIMAPXServer instance. Should this ever happen, then
+ * we will need to protect this data structure with locks
+ */
+ g_return_val_if_fail (is->priv->context == NULL, FALSE);
+ is->priv->context = g_new0 (CamelIMAPXServerUntaggedContext, 1);
+
+ settings = camel_imapx_server_ref_settings (is);
+ fetch_order = camel_imapx_settings_get_fetch_order (settings);
+ g_object_unref (settings);
+
+ is->priv->context->lsub = FALSE;
+ is->priv->context->fetch_order = fetch_order;
+
+ e (is->priv->tagprefix, "got untagged response\n");
+ is->priv->context->id = 0;
+ is->priv->context->tok = camel_imapx_input_stream_token (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ &(is->priv->context->token),
+ &(is->priv->context->len),
+ cancellable, error);
+ if (is->priv->context->tok < 0)
+ goto exit;
+
+ if (is->priv->context->tok == IMAPX_TOK_INT) {
+ is->priv->context->id = strtoul (
+ (gchar *) is->priv->context->token, NULL, 10);
+ is->priv->context->tok = camel_imapx_input_stream_token (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ &(is->priv->context->token),
+ &(is->priv->context->len),
+ cancellable, error);
+ if (is->priv->context->tok < 0)
+ goto exit;
+ }
+
+ if (is->priv->context->tok == '\n') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "truncated server response");
+ goto exit;
+ }
+
+ e (is->priv->tagprefix, "Have token '%s' id %lu\n", is->priv->context->token, is->priv->context->id);
+ p = is->priv->context->token;
+ while ((c = *p))
+ *p++ = g_ascii_toupper ((gchar) c);
+
+ token = (const gchar *) is->priv->context->token; /* FIXME need 'guchar *token' here */
+ while (token != NULL) {
+ CamelIMAPXUntaggedRespHandlerDesc *desc = NULL;
+
+ desc = g_hash_table_lookup (is->priv->untagged_handlers, token);
+ if (desc == NULL) {
+ /* unknown response, just ignore it */
+ c (is->priv->tagprefix, "unknown token: %s\n", is->priv->context->token);
+ break;
+ }
+ if (desc->handler == NULL) {
+ /* no handler function, ignore token */
+ c (is->priv->tagprefix, "no handler for token: %s\n", is->priv->context->token);
+ break;
+ }
+
+ /* call the handler function */
+ success = desc->handler (is, input_stream, cancellable, error);
+ if (!success)
+ goto exit;
+
+ /* is there another handler next-in-line? */
+ token = desc->next_response;
+ if (token != NULL) {
+ /* TODO do we need to update 'priv->context->token'
+ * to the value of 'token' here, before
+ * calling the handler next-in-line for this
+ * specific run of imapx_untagged()?
+ * It has not been done in the original code
+ * in the "fall through" situation in the
+ * token switch statement, which is what
+ * we're mimicking here
+ */
+ continue;
+ }
+
+ if (!desc->skip_stream_when_done)
+ goto exit;
+ }
+
+ success = camel_imapx_input_stream_skip (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream), cancellable, error);
+
+exit:
+ g_free (is->priv->context);
+ is->priv->context = NULL;
+
+ return success;
+}
+
+static gssize
+imapx_server_write_file_with_progress (GOutputStream *output_stream,
+ GInputStream *input_stream,
+ goffset file_size,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gssize n_read;
+ gsize bytes_copied, n_written;
+ gchar buffer[8192];
+ goffset file_offset;
+ gboolean res;
+
+ g_return_val_if_fail (G_IS_OUTPUT_STREAM (output_stream), -1);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input_stream), -1);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ file_offset = 0;
+ bytes_copied = 0;
+ res = TRUE;
+ do {
+ n_read = g_input_stream_read (input_stream, buffer, sizeof (buffer), cancellable, error);
+ if (n_read == -1) {
+ res = FALSE;
+ break;
+ }
+
+ if (n_read == 0)
+ break;
+
+ if (!g_output_stream_write_all (output_stream, buffer, n_read, &n_written, cancellable, error) || n_written == -1) {
+ res = FALSE;
+ break;
+ }
+
+ file_offset += n_read;
+
+ if (file_size > 0) {
+ gdouble divd = (gdouble) file_offset / (gdouble) file_size;
+ camel_operation_progress (cancellable, (gint) (100 * divd));
+ }
+
+ bytes_copied += n_written;
+ if (bytes_copied > G_MAXSSIZE)
+ bytes_copied = G_MAXSSIZE;
+ } while (res);
+
+ if (res)
+ return bytes_copied;
+
+ return -1;
+}
+
+/* handle any continuation requests
+ * either data continuations, or auth continuation */
+static gboolean
+imapx_continuation (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GOutputStream *output_stream,
+ gboolean litplus,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommand *ic, *newic = NULL;
+ CamelIMAPXCommandPart *cp;
+ GList *link;
+ gssize n_bytes_written;
+ gboolean success;
+
+ /* The 'literal' pointer is like a write-lock, nothing else
+ * can write while we have it ... so we dont need any
+ * ohter lock here. All other writes go through
+ * queue-lock */
+ if (camel_imapx_server_is_in_idle (is)) {
+ success = camel_imapx_input_stream_skip (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ c (is->priv->tagprefix, "Got continuation response for IDLE \n");
+
+ g_mutex_lock (&is->priv->idle_lock);
+ is->priv->idle_state = IMAPX_IDLE_STATE_RUNNING;
+ g_cond_broadcast (&is->priv->idle_cond);
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ return TRUE;
+ }
+
+ ic = is->priv->continuation_command;
+ if (!litplus) {
+ if (ic == NULL) {
+ c (is->priv->tagprefix, "got continuation response with no outstanding continuation requests?\n");
+ return camel_imapx_input_stream_skip (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ cancellable, error);
+ }
+ c (is->priv->tagprefix, "got continuation response for data\n");
+ } else {
+ c (is->priv->tagprefix, "sending LITERAL+ continuation\n");
+ g_return_val_if_fail (ic != NULL, FALSE);
+ }
+
+ /* coverity[deadcode] */
+ link = ic ? ic->current_part : NULL;
+ g_return_val_if_fail (link != NULL, FALSE);
+ cp = (CamelIMAPXCommandPart *) link->data;
+
+ switch (cp->type & CAMEL_IMAPX_COMMAND_MASK) {
+ case CAMEL_IMAPX_COMMAND_DATAWRAPPER:
+ c (is->priv->tagprefix, "writing data wrapper to literal\n");
+ n_bytes_written =
+ camel_data_wrapper_write_to_output_stream_sync (
+ CAMEL_DATA_WRAPPER (cp->ob),
+ output_stream, cancellable, error);
+ if (n_bytes_written < 0)
+ return FALSE;
+ break;
+ case CAMEL_IMAPX_COMMAND_AUTH: {
+ gchar *resp;
+ guchar *token;
+
+ success = camel_imapx_input_stream_text (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ &token, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ resp = camel_sasl_challenge_base64_sync (
+ (CamelSasl *) cp->ob, (const gchar *) token,
+ cancellable, error);
+ g_free (token);
+ if (resp == NULL)
+ return FALSE;
+ c (is->priv->tagprefix, "got auth continuation, feeding token '%s' back to auth mech\n", resp);
+
+ g_mutex_lock (&is->priv->stream_lock);
+ n_bytes_written = g_output_stream_write_all (
+ output_stream, resp, strlen (resp),
+ NULL, cancellable, error);
+ g_mutex_unlock (&is->priv->stream_lock);
+ g_free (resp);
+
+ if (n_bytes_written < 0)
+ return FALSE;
+
+ /* we want to keep getting called until we get a status reponse from the server
+ * ignore what sasl tells us */
+ newic = ic;
+ /* We already ate the end of the input stream line */
+ goto noskip;
+ break; }
+ case CAMEL_IMAPX_COMMAND_FILE: {
+ GFile *file;
+ GFileInfo *file_info;
+ GFileInputStream *file_input_stream;
+ goffset file_size = 0;
+
+ c (is->priv->tagprefix, "writing file '%s' to literal\n", (gchar *) cp->ob);
+
+ file = g_file_new_for_path (cp->ob);
+ file_input_stream = g_file_read (file, cancellable, error);
+ g_object_unref (file);
+
+ if (file_input_stream == NULL)
+ return FALSE;
+
+ file_info = g_file_input_stream_query_info (file_input_stream,
+ G_FILE_ATTRIBUTE_STANDARD_SIZE, cancellable, NULL);
+ if (file_info) {
+ file_size = g_file_info_get_size (file_info);
+ g_object_unref (file_info);
+ }
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ n_bytes_written = imapx_server_write_file_with_progress (
+ output_stream, G_INPUT_STREAM (file_input_stream),
+ file_size, cancellable, error);
+
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ g_input_stream_close (G_INPUT_STREAM (file_input_stream), cancellable, NULL);
+ g_object_unref (file_input_stream);
+
+ if (n_bytes_written < 0)
+ return FALSE;
+
+ break; }
+ case CAMEL_IMAPX_COMMAND_STRING:
+ g_mutex_lock (&is->priv->stream_lock);
+ n_bytes_written = g_output_stream_write_all (
+ output_stream, cp->ob, cp->ob_size,
+ NULL, cancellable, error);
+ g_mutex_unlock (&is->priv->stream_lock);
+ if (n_bytes_written < 0)
+ return FALSE;
+ break;
+ default:
+ /* should we just ignore? */
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "continuation response for non-continuation request");
+ return FALSE;
+ }
+
+ if (!litplus) {
+ success = camel_imapx_input_stream_skip (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ cancellable, error);
+
+ if (!success)
+ return FALSE;
+ }
+
+noskip:
+ link = g_list_next (link);
+ if (link != NULL) {
+ ic->current_part = link;
+ cp = (CamelIMAPXCommandPart *) link->data;
+
+ c (is->priv->tagprefix, "next part of command \"%c%05u: %s\"\n", is->priv->tagprefix, ic->tag, cp->data);
+
+ g_mutex_lock (&is->priv->stream_lock);
+ n_bytes_written = g_output_stream_write_all (
+ output_stream, cp->data, strlen (cp->data),
+ NULL, cancellable, error);
+ g_mutex_unlock (&is->priv->stream_lock);
+ if (n_bytes_written < 0)
+ return FALSE;
+
+ if (cp->type & (CAMEL_IMAPX_COMMAND_CONTINUATION | CAMEL_IMAPX_COMMAND_LITERAL_PLUS)) {
+ newic = ic;
+ } else {
+ g_warn_if_fail (g_list_next (link) == NULL);
+ }
+ } else {
+ c (is->priv->tagprefix, "%p: queueing continuation\n", ic);
+ }
+
+ g_mutex_lock (&is->priv->stream_lock);
+ n_bytes_written = g_output_stream_write_all (
+ output_stream, "\r\n", 2, NULL, cancellable, error);
+ g_mutex_unlock (&is->priv->stream_lock);
+ if (n_bytes_written < 0)
+ return FALSE;
+
+ is->priv->continuation_command = newic;
+
+ return TRUE;
+}
+
+/* handle a completion line */
+static gboolean
+imapx_completion (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ guchar *token,
+ gint len,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommand *ic;
+ CamelIMAPXMailbox *mailbox;
+ gboolean success = FALSE;
+ guint tag;
+
+ /* Given "A0001 ...", 'A' = tag prefix, '0001' = tag. */
+
+ if (token[0] != is->priv->tagprefix) {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "Server sent unexpected response: %s", token);
+ return FALSE;
+ }
+
+ tag = strtoul ((gchar *) token + 1, NULL, 10);
+
+ COMMAND_LOCK (is);
+
+ if (is->priv->current_command != NULL && is->priv->current_command->tag == tag)
+ ic = camel_imapx_command_ref (is->priv->current_command);
+ else
+ ic = NULL;
+
+ COMMAND_UNLOCK (is);
+
+ if (ic == NULL) {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "got response tag unexpectedly: %s", token);
+ return FALSE;
+ }
+
+ c (is->priv->tagprefix, "Got completion response for command %05u '%s'\n", ic->tag, camel_imapx_job_get_kind_name (ic->job_kind));
+
+ g_mutex_lock (&is->priv->changes_lock);
+
+ if (camel_folder_change_info_changed (is->priv->changes)) {
+ CamelFolder *folder = NULL;
+ CamelIMAPXMailbox *mailbox;
+ CamelFolderChangeInfo *changes;
+
+ changes = is->priv->changes;
+ is->priv->changes = camel_folder_change_info_new ();
+
+ g_mutex_unlock (&is->priv->changes_lock);
+
+ mailbox = camel_imapx_server_ref_selected (is);
+
+ g_warn_if_fail (mailbox != NULL);
+
+ if (mailbox) {
+ folder = imapx_server_ref_folder (is, mailbox);
+ g_return_val_if_fail (folder != NULL, FALSE);
+
+ camel_folder_summary_save_to_db (folder->summary, NULL);
+
+ imapx_update_store_summary (folder);
+ camel_folder_changed (folder, changes);
+ }
+
+ camel_folder_change_info_free (changes);
+
+ g_clear_object (&folder);
+ g_clear_object (&mailbox);
+ } else {
+ g_mutex_unlock (&is->priv->changes_lock);
+ }
+
+ if (g_list_next (ic->current_part) != NULL) {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "command still has unsent parts? %s", camel_imapx_job_get_kind_name (ic->job_kind));
+ goto exit;
+ }
+
+ mailbox = camel_imapx_server_ref_selected (is);
+
+ ic->status = imapx_parse_status (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ mailbox, cancellable, error);
+
+ g_clear_object (&mailbox);
+
+ if (ic->status == NULL)
+ goto exit;
+
+ if (ic->status->condition == IMAPX_CAPABILITY) {
+ guint32 list_extended = imapx_lookup_capability ("LIST-EXTENDED");
+
+ is->priv->is_cyrus = is->priv->is_cyrus || (ic->status->text && camel_strstrcase (ic->status->text, "cyrus"));
+ if (is->priv->is_cyrus && ic->status->u.cinfo && (ic->status->u.cinfo->capa & list_extended) != 0) {
+ /* Disable LIST-EXTENDED for cyrus servers */
+ c (is->priv->tagprefix, "Disabling LIST-EXTENDED extension for a Cyrus server\n");
+ ic->status->u.cinfo->capa &= ~list_extended;
+ }
+ }
+
+ success = TRUE;
+
+exit:
+
+ ic->completed = TRUE;
+ camel_imapx_command_unref (ic);
+
+ return success;
+}
+
+static gboolean
+imapx_step (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint len;
+ guchar *token;
+ gint tok;
+ gboolean success = FALSE;
+
+ tok = camel_imapx_input_stream_token (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ &token, &len, cancellable, error);
+
+ switch (tok) {
+ case IMAPX_TOK_ERROR:
+ /* GError is already set. */
+ break;
+ case '*':
+ success = imapx_untagged (
+ is, input_stream, cancellable, error);
+ break;
+ case IMAPX_TOK_TOKEN:
+ success = imapx_completion (
+ is, input_stream,
+ token, len, cancellable, error);
+ break;
+ case '+':
+ success = imapx_continuation (
+ is, input_stream, output_stream,
+ FALSE, cancellable, error);
+ break;
+ default:
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "unexpected server response:");
+ break;
+ }
+
+ return success;
+}
+
+static void
+imapx_server_set_streams (CamelIMAPXServer *is,
+ GInputStream *input_stream,
+ GOutputStream *output_stream)
+{
+ GConverter *logger;
+
+ if (input_stream != NULL) {
+ GInputStream *temp_stream;
+
+ /* The logger produces debugging output. */
+ logger = camel_imapx_logger_new (is->priv->tagprefix);
+ input_stream = g_converter_input_stream_new (
+ input_stream, logger);
+ g_clear_object (&logger);
+
+ /* Buffer the input stream for parsing. */
+ temp_stream = camel_imapx_input_stream_new (input_stream);
+ camel_binding_bind_property (
+ temp_stream, "close-base-stream",
+ input_stream, "close-base-stream",
+ G_BINDING_SYNC_CREATE);
+ g_object_unref (input_stream);
+ input_stream = temp_stream;
+ }
+
+ if (output_stream != NULL) {
+ /* The logger produces debugging output. */
+ logger = camel_imapx_logger_new (is->priv->tagprefix);
+ output_stream = g_converter_output_stream_new (
+ output_stream, logger);
+ g_clear_object (&logger);
+ }
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ /* Don't close the base streams so STARTTLS works correctly. */
+
+ if (G_IS_FILTER_INPUT_STREAM (is->priv->input_stream)) {
+ g_filter_input_stream_set_close_base_stream (
+ G_FILTER_INPUT_STREAM (is->priv->input_stream),
+ FALSE);
+ }
+
+ if (G_IS_FILTER_OUTPUT_STREAM (is->priv->output_stream)) {
+ g_filter_output_stream_set_close_base_stream (
+ G_FILTER_OUTPUT_STREAM (is->priv->output_stream),
+ FALSE);
+ }
+
+ g_clear_object (&is->priv->input_stream);
+ is->priv->input_stream = input_stream;
+
+ g_clear_object (&is->priv->output_stream);
+ is->priv->output_stream = output_stream;
+
+ g_mutex_unlock (&is->priv->stream_lock);
+}
+
+#ifdef G_OS_UNIX
+static void
+imapx_server_child_process_setup (gpointer user_data)
+{
+#ifdef TIOCNOTTY
+ gint fd;
+#endif
+
+ setsid ();
+
+#ifdef TIOCNOTTY
+ /* Detach from the controlling tty if we have one. Otherwise,
+ * SSH might do something stupid like trying to use it instead
+ * of running $SSH_ASKPASS. */
+ if ((fd = open ("/dev/tty", O_RDONLY)) != -1) {
+ ioctl (fd, TIOCNOTTY, NULL);
+ close (fd);
+ }
+#endif /* TIOCNOTTY */
+}
+#endif /* G_OS_UNIX */
+
+static gboolean
+connect_to_server_process (CamelIMAPXServer *is,
+ const gchar *cmd,
+ GError **error)
+{
+ GSubprocessLauncher *launcher;
+ GSubprocess *subprocess = NULL;
+ CamelNetworkSettings *network_settings;
+ CamelProvider *provider;
+ CamelSettings *settings;
+ CamelIMAPXStore *store;
+ CamelURL url;
+ gchar **argv = NULL;
+ gchar *buf;
+ gchar *cmd_copy;
+ gchar *full_cmd;
+ const gchar *password;
+ gchar *host;
+ gchar *user;
+ guint16 port;
+
+ memset (&url, 0, sizeof (CamelURL));
+
+ launcher = g_subprocess_launcher_new (
+ G_SUBPROCESS_FLAGS_STDIN_PIPE |
+ G_SUBPROCESS_FLAGS_STDOUT_PIPE |
+ G_SUBPROCESS_FLAGS_STDERR_SILENCE);
+
+#ifdef G_OS_UNIX
+ g_subprocess_launcher_set_child_setup (
+ launcher, imapx_server_child_process_setup,
+ NULL, (GDestroyNotify) NULL);
+#endif
+
+ store = camel_imapx_server_ref_store (is);
+
+ password = camel_service_get_password (CAMEL_SERVICE (store));
+ provider = camel_service_get_provider (CAMEL_SERVICE (store));
+ settings = camel_service_ref_settings (CAMEL_SERVICE (store));
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ port = camel_network_settings_get_port (network_settings);
+ user = camel_network_settings_dup_user (network_settings);
+
+ /* Put full details in the environment, in case the connection
+ * program needs them */
+ camel_url_set_protocol (&url, provider->protocol);
+ camel_url_set_host (&url, host);
+ camel_url_set_port (&url, port);
+ camel_url_set_user (&url, user);
+ buf = camel_url_to_string (&url, 0);
+
+ g_subprocess_launcher_setenv (launcher, "URL", buf, TRUE);
+ g_subprocess_launcher_setenv (launcher, "URLHOST", host, TRUE);
+
+ if (port > 0) {
+ gchar *port_string;
+
+ port_string = g_strdup_printf ("%u", port);
+ g_subprocess_launcher_setenv (
+ launcher, "URLPORT", port_string, TRUE);
+ g_free (port_string);
+ }
+
+ if (user != NULL) {
+ g_subprocess_launcher_setenv (
+ launcher, "URLPORT", user, TRUE);
+ }
+
+ if (password != NULL) {
+ g_subprocess_launcher_setenv (
+ launcher, "URLPASSWD", password, TRUE);
+ }
+
+ g_free (buf);
+
+ g_object_unref (settings);
+ g_object_unref (store);
+
+ /* Now do %h, %u, etc. substitution in cmd */
+ buf = cmd_copy = g_strdup (cmd);
+
+ full_cmd = g_strdup ("");
+
+ for (;;) {
+ gchar *pc;
+ gchar *tmp;
+ const gchar *var;
+ gint 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 = host;
+ break;
+ case 'u':
+ var = 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);
+
+ g_free (host);
+ g_free (user);
+
+ if (g_shell_parse_argv (full_cmd, NULL, &argv, error)) {
+ subprocess = g_subprocess_launcher_spawnv (
+ launcher, (const gchar * const *) argv, error);
+ g_strfreev (argv);
+ }
+
+ g_free (full_cmd);
+ g_object_unref (launcher);
+
+ if (subprocess != NULL) {
+ GInputStream *input_stream;
+ GOutputStream *output_stream;
+
+ g_mutex_lock (&is->priv->stream_lock);
+ g_warn_if_fail (is->priv->subprocess == NULL);
+ is->priv->subprocess = g_object_ref (subprocess);
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ input_stream = g_subprocess_get_stdout_pipe (subprocess);
+ output_stream = g_subprocess_get_stdin_pipe (subprocess);
+
+ imapx_server_set_streams (is, input_stream, output_stream);
+
+ g_object_unref (subprocess);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+imapx_connect_to_server (CamelIMAPXServer *is,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNetworkSettings *network_settings;
+ CamelNetworkSecurityMethod method;
+ CamelIMAPXStore *store;
+ CamelSettings *settings;
+ GIOStream *connection = NULL;
+ GIOStream *tls_stream;
+ GSocket *socket;
+ guint len;
+ guchar *token;
+ gint tok;
+ CamelIMAPXCommand *ic;
+ gchar *shell_command = NULL;
+ gboolean use_shell_command;
+ gboolean success = TRUE;
+ gchar *host;
+
+ store = camel_imapx_server_ref_store (is);
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (store));
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ method = camel_network_settings_get_security_method (network_settings);
+
+ use_shell_command = camel_imapx_settings_get_use_shell_command (
+ CAMEL_IMAPX_SETTINGS (settings));
+
+ if (use_shell_command)
+ shell_command = camel_imapx_settings_dup_shell_command (
+ CAMEL_IMAPX_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ if (shell_command != NULL) {
+ success = connect_to_server_process (is, shell_command, error);
+
+ g_free (shell_command);
+
+ if (success)
+ goto connected;
+ else
+ goto exit;
+ }
+
+ connection = camel_network_service_connect_sync (
+ CAMEL_NETWORK_SERVICE (store), cancellable, error);
+
+ if (connection != NULL) {
+ GInputStream *input_stream;
+ GOutputStream *output_stream;
+ GError *local_error = NULL;
+
+ /* Disable the Nagle algorithm with TCP_NODELAY, since IMAP
+ * commands should be issued immediately even we've not yet
+ * received a response to a previous command. */
+ socket = g_socket_connection_get_socket (
+ G_SOCKET_CONNECTION (connection));
+ g_socket_set_option (
+ socket, IPPROTO_TCP, TCP_NODELAY, 1, &local_error);
+ if (local_error != NULL) {
+ /* Failure to set the socket option is non-fatal. */
+ g_warning ("%s: %s", G_STRFUNC, local_error->message);
+ g_clear_error (&local_error);
+ }
+
+ g_mutex_lock (&is->priv->stream_lock);
+ g_warn_if_fail (is->priv->connection == NULL);
+ is->priv->connection = g_object_ref (connection);
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ input_stream = g_io_stream_get_input_stream (connection);
+ output_stream = g_io_stream_get_output_stream (connection);
+
+ imapx_server_set_streams (is, input_stream, output_stream);
+
+ /* Hang on to the connection reference in case we need to
+ * issue STARTTLS below. */
+ } else {
+ success = FALSE;
+ goto exit;
+ }
+
+connected:
+ while (1) {
+ GInputStream *input_stream;
+
+ input_stream = camel_imapx_server_ref_input_stream (is);
+
+ token = NULL;
+ tok = camel_imapx_input_stream_token (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ &token, &len, cancellable, error);
+
+ if (tok < 0) {
+ success = FALSE;
+
+ } else if (tok == '*') {
+ success = imapx_untagged (
+ is, input_stream, cancellable, error);
+
+ if (success) {
+ g_object_unref (input_stream);
+ break;
+ }
+
+ } else {
+ camel_imapx_input_stream_ungettoken (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ tok, token, len);
+
+ success = camel_imapx_input_stream_text (
+ CAMEL_IMAPX_INPUT_STREAM (input_stream),
+ &token, cancellable, error);
+
+ g_free (token);
+ }
+
+ g_object_unref (input_stream);
+
+ if (!success)
+ goto exit;
+ }
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ if (!is->priv->cinfo) {
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_CAPABILITY, "CAPABILITY");
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Failed to get capabilities"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+
+ if (!success)
+ goto exit;
+ } else {
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+
+ if (method == CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT) {
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ if (CAMEL_IMAPX_LACK_CAPABILITY (is->priv->cinfo, STARTTLS)) {
+ g_mutex_unlock (&is->priv->stream_lock);
+ g_set_error (
+ error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC,
+ _("Failed to connect to IMAP server %s in secure mode: %s"),
+ host, _("STARTTLS not supported"));
+ success = FALSE;
+ goto exit;
+ } else {
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_STARTTLS, "STARTTLS");
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Failed to issue STARTTLS"), cancellable, error);
+
+ if (success) {
+ g_mutex_lock (&is->priv->stream_lock);
+
+ /* See if we got new capabilities
+ * in the STARTTLS response. */
+ imapx_free_capability (is->priv->cinfo);
+ is->priv->cinfo = NULL;
+ if (ic->status->condition == IMAPX_CAPABILITY) {
+ is->priv->cinfo = ic->status->u.cinfo;
+ ic->status->u.cinfo = NULL;
+ c (is->priv->tagprefix, "got capability flags %08x\n", is->priv->cinfo ? is->priv->cinfo->capa : 0xFFFFFFFF);
+ imapx_server_stash_command_arguments (is);
+ }
+
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+
+ camel_imapx_command_unref (ic);
+
+ if (!success)
+ goto exit;
+
+ tls_stream = camel_network_service_starttls (
+ CAMEL_NETWORK_SERVICE (store), connection, error);
+
+ if (tls_stream != NULL) {
+ GInputStream *input_stream;
+ GOutputStream *output_stream;
+
+ g_mutex_lock (&is->priv->stream_lock);
+ g_object_unref (is->priv->connection);
+ is->priv->connection = g_object_ref (tls_stream);
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ input_stream =
+ g_io_stream_get_input_stream (tls_stream);
+ output_stream =
+ g_io_stream_get_output_stream (tls_stream);
+
+ imapx_server_set_streams (
+ is, input_stream, output_stream);
+
+ g_object_unref (tls_stream);
+ } else {
+ g_prefix_error (
+ error,
+ _("Failed to connect to IMAP server %s in secure mode: "),
+ host);
+ success = FALSE;
+ goto exit;
+ }
+
+ /* Get new capabilities if they weren't already given */
+ g_mutex_lock (&is->priv->stream_lock);
+ if (is->priv->cinfo == NULL) {
+ g_mutex_unlock (&is->priv->stream_lock);
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_CAPABILITY, "CAPABILITY");
+ success = camel_imapx_server_process_command_sync (is, ic, _("Failed to get capabilities"), cancellable, error);
+ camel_imapx_command_unref (ic);
+
+ if (!success)
+ goto exit;
+ } else {
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+ }
+
+exit:
+ if (!success) {
+ g_mutex_lock (&is->priv->stream_lock);
+
+ g_clear_object (&is->priv->input_stream);
+ g_clear_object (&is->priv->output_stream);
+ g_clear_object (&is->priv->connection);
+ g_clear_object (&is->priv->subprocess);
+
+ if (is->priv->cinfo != NULL) {
+ imapx_free_capability (is->priv->cinfo);
+ is->priv->cinfo = NULL;
+ }
+
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+
+ g_free (host);
+
+ g_clear_object (&connection);
+ g_clear_object (&store);
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_is_connected (CamelIMAPXServer *imapx_server)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (imapx_server), FALSE);
+
+ return imapx_server->priv->state >= IMAPX_CONNECTED;
+}
+
+CamelAuthenticationResult
+camel_imapx_server_authenticate_sync (CamelIMAPXServer *is,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNetworkSettings *network_settings;
+ CamelIMAPXStore *store;
+ CamelService *service;
+ CamelSettings *settings;
+ CamelAuthenticationResult result;
+ CamelIMAPXCommand *ic;
+ CamelSasl *sasl = NULL;
+ gchar *host;
+ gchar *user;
+
+ g_return_val_if_fail (
+ CAMEL_IS_IMAPX_SERVER (is),
+ CAMEL_AUTHENTICATION_ERROR);
+
+ store = camel_imapx_server_ref_store (is);
+
+ service = CAMEL_SERVICE (store);
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ user = camel_network_settings_dup_user (network_settings);
+
+ g_object_unref (settings);
+
+ if (mechanism != NULL) {
+ g_mutex_lock (&is->priv->stream_lock);
+
+ if (is->priv->cinfo && !g_hash_table_lookup (is->priv->cinfo->auth_types, mechanism) && (
+ !g_str_equal (mechanism, "Google") || !g_hash_table_lookup (is->priv->cinfo->auth_types, "XOAUTH2"))) {
+ g_mutex_unlock (&is->priv->stream_lock);
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("IMAP server %s does not support %s "
+ "authentication"), host, mechanism);
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ } else {
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+
+ sasl = camel_sasl_new ("imap", mechanism, service);
+ if (sasl == NULL) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("No support for %s authentication"),
+ mechanism);
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+ }
+
+ if (sasl != NULL) {
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_AUTHENTICATE, "AUTHENTICATE %A", sasl);
+ } else {
+ const gchar *password;
+
+ password = camel_service_get_password (service);
+
+ if (user == NULL) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Cannot authenticate without a username"));
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+
+ if (password == NULL) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Authentication password not available"));
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_LOGIN, "LOGIN %s %s", user, password);
+ }
+
+ if (!camel_imapx_server_process_command_sync (is, ic, _("Failed to authenticate"), cancellable, error) && (
+ !ic->status || ic->status->result != IMAPX_NO))
+ result = CAMEL_AUTHENTICATION_ERROR;
+ else if (ic->status->result == IMAPX_OK)
+ result = CAMEL_AUTHENTICATION_ACCEPTED;
+ else if (ic->status->result == IMAPX_NO) {
+ g_clear_error (error);
+
+ if (camel_imapx_store_is_connecting_concurrent_connection (store)) {
+ /* At least one connection succeeded, probably max connection limit
+ set on the server had been reached, thus use special error code
+ for it, to instruct the connection manager to decrease the limit
+ and use already created connection. */
+ g_set_error_literal (
+ error, CAMEL_IMAPX_SERVER_ERROR,
+ CAMEL_IMAPX_SERVER_ERROR_CONCURRENT_CONNECT_FAILED,
+ ic->status->text ? ic->status->text : _("Unknown error"));
+ result = CAMEL_AUTHENTICATION_ERROR;
+ } else if (sasl) {
+ CamelSaslClass *sasl_class;
+
+ sasl_class = CAMEL_SASL_GET_CLASS (sasl);
+ if (sasl_class && sasl_class->auth_type && !sasl_class->auth_type->need_password) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ ic->status->text ? ic->status->text : _("Unknown error"));
+ result = CAMEL_AUTHENTICATION_ERROR;
+ } else {
+ result = CAMEL_AUTHENTICATION_REJECTED;
+ }
+ } else {
+ result = CAMEL_AUTHENTICATION_REJECTED;
+ }
+ } else {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ ic->status->text ? ic->status->text : _("Unknown error"));
+ result = CAMEL_AUTHENTICATION_ERROR;
+ }
+
+ /* Forget old capabilities after login. */
+ if (result == CAMEL_AUTHENTICATION_ACCEPTED) {
+ g_mutex_lock (&is->priv->stream_lock);
+
+ if (is->priv->cinfo) {
+ imapx_free_capability (is->priv->cinfo);
+ is->priv->cinfo = NULL;
+ }
+
+ if (ic->status->condition == IMAPX_CAPABILITY) {
+ is->priv->cinfo = ic->status->u.cinfo;
+ ic->status->u.cinfo = NULL;
+ c (is->priv->tagprefix, "got capability flags %08x\n", is->priv->cinfo ? is->priv->cinfo->capa : 0xFFFFFFFF);
+ imapx_server_stash_command_arguments (is);
+ }
+
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+
+ camel_imapx_command_unref (ic);
+
+ if (sasl != NULL)
+ g_object_unref (sasl);
+
+exit:
+ g_free (host);
+ g_free (user);
+
+ g_object_unref (store);
+
+ return result;
+}
+
+static gboolean
+imapx_reconnect (CamelIMAPXServer *is,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommand *ic;
+ CamelService *service;
+ CamelSession *session;
+ CamelIMAPXStore *store;
+ CamelSettings *settings;
+ gchar *mechanism;
+ gboolean use_qresync;
+ gboolean use_idle;
+ gboolean success = FALSE;
+
+ store = camel_imapx_server_ref_store (is);
+
+ service = CAMEL_SERVICE (store);
+ session = camel_service_ref_session (service);
+ if (!session) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ g_object_unref (store);
+ return FALSE;
+ }
+
+ settings = camel_service_ref_settings (service);
+
+ mechanism = camel_network_settings_dup_auth_mechanism (
+ CAMEL_NETWORK_SETTINGS (settings));
+
+ use_qresync = camel_imapx_settings_get_use_qresync (CAMEL_IMAPX_SETTINGS (settings));
+ use_idle = camel_imapx_settings_get_use_idle (CAMEL_IMAPX_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ if (!imapx_connect_to_server (is, cancellable, error))
+ goto exception;
+
+ if (is->priv->state == IMAPX_AUTHENTICATED)
+ goto preauthed;
+
+ if (!camel_session_authenticate_sync (
+ session, service, mechanism, cancellable, error))
+ goto exception;
+
+ /* After login we re-capa unless the server already told us. */
+ g_mutex_lock (&is->priv->stream_lock);
+ if (is->priv->cinfo == NULL) {
+ GError *local_error = NULL;
+
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_CAPABILITY, "CAPABILITY");
+ camel_imapx_server_process_command_sync (is, ic, _("Failed to get capabilities"), cancellable, &local_error);
+ camel_imapx_command_unref (ic);
+
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ goto exception;
+ }
+ } else {
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+
+ is->priv->state = IMAPX_AUTHENTICATED;
+
+preauthed:
+ /* Fetch namespaces (if supported). */
+ g_mutex_lock (&is->priv->stream_lock);
+ if (CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, NAMESPACE)) {
+ GError *local_error = NULL;
+
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_NAMESPACE, "NAMESPACE");
+ camel_imapx_server_process_command_sync (is, ic, _("Failed to issue NAMESPACE"), cancellable, &local_error);
+ camel_imapx_command_unref (ic);
+
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ goto exception;
+ }
+
+ g_mutex_lock (&is->priv->stream_lock);
+ }
+
+ /* Enable quick mailbox resynchronization (if supported). */
+ if (use_qresync && CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, QRESYNC)) {
+ GError *local_error = NULL;
+
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_ENABLE, "ENABLE CONDSTORE QRESYNC");
+ camel_imapx_server_process_command_sync (is, ic, _("Failed to enable QResync"), cancellable, &local_error);
+ camel_imapx_command_unref (ic);
+
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ goto exception;
+ }
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ is->priv->use_qresync = TRUE;
+ } else {
+ is->priv->use_qresync = FALSE;
+ }
+
+ /* Set NOTIFY options after enabling QRESYNC (if supported). */
+ if (use_idle && CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, NOTIFY)) {
+ GError *local_error = NULL;
+
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ /* XXX The list of FETCH attributes is negotiable. */
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_NOTIFY, "NOTIFY SET "
+ "(selected "
+ "(MessageNew (UID RFC822.SIZE RFC822.HEADER FLAGS)"
+ " MessageExpunge"
+ " FlagChange)) "
+ "(personal "
+ "(MessageNew"
+ " MessageExpunge"
+ " MailboxName"
+ " SubscriptionChange))");
+ camel_imapx_server_process_command_sync (is, ic, _("Failed to issue NOTIFY"), cancellable, &local_error);
+ camel_imapx_command_unref (ic);
+
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ goto exception;
+ }
+
+ g_mutex_lock (&is->priv->stream_lock);
+ }
+
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ is->priv->state = IMAPX_INITIALISED;
+
+ success = TRUE;
+
+ goto exit;
+
+exception:
+ imapx_disconnect (is);
+
+exit:
+ g_free (mechanism);
+
+ g_object_unref (session);
+ g_object_unref (store);
+
+ return success;
+}
+
+/* ********************************************************************** */
+
+/* FIXME: this is basically a copy of the same in camel-imapx-utils.c */
+static struct {
+ const gchar *name;
+ guint32 flag;
+} flags_table[] = {
+ { "\\ANSWERED", CAMEL_MESSAGE_ANSWERED },
+ { "\\DELETED", CAMEL_MESSAGE_DELETED },
+ { "\\DRAFT", CAMEL_MESSAGE_DRAFT },
+ { "\\FLAGGED", CAMEL_MESSAGE_FLAGGED },
+ { "\\SEEN", CAMEL_MESSAGE_SEEN },
+ { "\\RECENT", CAMEL_IMAPX_MESSAGE_RECENT },
+ { "JUNK", CAMEL_MESSAGE_JUNK },
+ { "NOTJUNK", CAMEL_MESSAGE_NOTJUNK }
+};
+
+static void
+imapx_server_set_store (CamelIMAPXServer *server,
+ CamelIMAPXStore *store)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_STORE (store));
+
+ g_weak_ref_set (&server->priv->store, store);
+}
+
+static void
+imapx_server_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_STORE:
+ imapx_server_set_store (
+ CAMEL_IMAPX_SERVER (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_server_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_STORE:
+ g_value_take_object (
+ value,
+ camel_imapx_server_ref_store (
+ CAMEL_IMAPX_SERVER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_server_dispose (GObject *object)
+{
+ CamelIMAPXServer *server = CAMEL_IMAPX_SERVER (object);
+
+ g_cancellable_cancel (server->priv->cancellable);
+
+ imapx_disconnect (server);
+
+ g_weak_ref_set (&server->priv->store, NULL);
+
+ g_clear_object (&server->priv->subprocess);
+
+ g_mutex_lock (&server->priv->idle_lock);
+ g_clear_object (&server->priv->idle_cancellable);
+ g_clear_object (&server->priv->idle_mailbox);
+ if (server->priv->idle_pending) {
+ g_source_destroy (server->priv->idle_pending);
+ g_source_unref (server->priv->idle_pending);
+ server->priv->idle_pending = NULL;
+ }
+ g_mutex_unlock (&server->priv->idle_lock);
+
+ g_clear_object (&server->priv->subprocess);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_imapx_server_parent_class)->dispose (object);
+}
+
+static void
+imapx_server_finalize (GObject *object)
+{
+ CamelIMAPXServer *is = CAMEL_IMAPX_SERVER (object);
+
+ g_mutex_clear (&is->priv->stream_lock);
+ g_mutex_clear (&is->priv->select_lock);
+ g_mutex_clear (&is->priv->changes_lock);
+
+ camel_folder_change_info_free (is->priv->changes);
+ imapx_free_status (is->priv->copyuid_status);
+
+ g_free (is->priv->context);
+ g_hash_table_destroy (is->priv->untagged_handlers);
+
+ if (is->priv->inactivity_timeout != NULL)
+ g_source_unref (is->priv->inactivity_timeout);
+ g_mutex_clear (&is->priv->inactivity_timeout_lock);
+
+ g_free (is->priv->status_data_items);
+ g_free (is->priv->list_return_opts);
+
+ if (is->priv->search_results != NULL)
+ g_array_unref (is->priv->search_results);
+ g_mutex_clear (&is->priv->search_results_lock);
+
+ g_hash_table_destroy (is->priv->known_alerts);
+ g_mutex_clear (&is->priv->known_alerts_lock);
+
+ g_mutex_clear (&is->priv->idle_lock);
+ g_cond_clear (&is->priv->idle_cond);
+
+ g_rec_mutex_clear (&is->priv->command_lock);
+
+ g_weak_ref_clear (&is->priv->store);
+ g_weak_ref_clear (&is->priv->select_mailbox);
+ g_weak_ref_clear (&is->priv->select_pending);
+ g_clear_object (&is->priv->cancellable);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_imapx_server_parent_class)->finalize (object);
+}
+
+static void
+imapx_server_constructed (GObject *object)
+{
+ CamelIMAPXServer *server;
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_imapx_server_parent_class)->constructed (object);
+
+ server = CAMEL_IMAPX_SERVER (object);
+ server->priv->tagprefix = 'Z';
+}
+
+static void
+camel_imapx_server_class_init (CamelIMAPXServerClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelIMAPXServerPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = imapx_server_set_property;
+ object_class->get_property = imapx_server_get_property;
+ object_class->finalize = imapx_server_finalize;
+ object_class->dispose = imapx_server_dispose;
+ object_class->constructed = imapx_server_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_STORE,
+ g_param_spec_object (
+ "store",
+ "Store",
+ "IMAPX store for this server",
+ CAMEL_TYPE_IMAPX_STORE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ signals[REFRESH_MAILBOX] = g_signal_new (
+ "refresh-mailbox",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CamelIMAPXServerClass, refresh_mailbox),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ CAMEL_TYPE_IMAPX_MAILBOX);
+}
+
+static void
+camel_imapx_server_init (CamelIMAPXServer *is)
+{
+ is->priv = CAMEL_IMAPX_SERVER_GET_PRIVATE (is);
+
+ is->priv->untagged_handlers = create_initial_untagged_handler_table ();
+
+ g_mutex_init (&is->priv->stream_lock);
+ g_mutex_init (&is->priv->inactivity_timeout_lock);
+ g_mutex_init (&is->priv->select_lock);
+ g_mutex_init (&is->priv->changes_lock);
+ g_mutex_init (&is->priv->search_results_lock);
+ g_mutex_init (&is->priv->known_alerts_lock);
+
+ g_weak_ref_init (&is->priv->store, NULL);
+ g_weak_ref_init (&is->priv->select_mailbox, NULL);
+ g_weak_ref_init (&is->priv->select_pending, NULL);
+
+ is->priv->cancellable = g_cancellable_new ();
+
+ is->priv->state = IMAPX_DISCONNECTED;
+ is->priv->is_cyrus = FALSE;
+ is->priv->copyuid_status = NULL;
+
+ is->priv->changes = camel_folder_change_info_new ();
+
+ is->priv->known_alerts = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) NULL);
+
+ /* Initialize IDLE members. */
+ g_mutex_init (&is->priv->idle_lock);
+ g_cond_init (&is->priv->idle_cond);
+ is->priv->idle_state = IMAPX_IDLE_STATE_OFF;
+ is->priv->idle_stamp = 0;
+
+ g_rec_mutex_init (&is->priv->command_lock);
+}
+
+CamelIMAPXServer *
+camel_imapx_server_new (CamelIMAPXStore *store)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (store), NULL);
+
+ return g_object_new (
+ CAMEL_TYPE_IMAPX_SERVER,
+ "store", store, NULL);
+}
+
+CamelIMAPXStore *
+camel_imapx_server_ref_store (CamelIMAPXServer *server)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), NULL);
+
+ return g_weak_ref_get (&server->priv->store);
+}
+
+CamelIMAPXSettings *
+camel_imapx_server_ref_settings (CamelIMAPXServer *server)
+{
+ CamelIMAPXStore *store;
+ CamelSettings *settings;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), NULL);
+
+ store = camel_imapx_server_ref_store (server);
+ settings = camel_service_ref_settings (CAMEL_SERVICE (store));
+ g_object_unref (store);
+
+ return CAMEL_IMAPX_SETTINGS (settings);
+}
+
+/**
+ * camel_imapx_server_ref_input_stream:
+ * @is: a #CamelIMAPXServer
+ *
+ * Returns the #GInputStream for @is, which is owned by either a
+ * #GTcpConnection or a #GSubprocess. If the #CamelIMAPXServer is not
+ * yet connected or has lost its connection, the function returns %NULL.
+ *
+ * The returned #GInputStream is referenced for thread-safety and must
+ * be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #GInputStream, or %NULL
+ *
+ * Since: 3.12
+ **/
+GInputStream *
+camel_imapx_server_ref_input_stream (CamelIMAPXServer *is)
+{
+ GInputStream *input_stream = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ if (is->priv->input_stream != NULL)
+ input_stream = g_object_ref (is->priv->input_stream);
+
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ return input_stream;
+}
+
+/**
+ * camel_imapx_server_ref_output_stream:
+ * @is: a #CamelIMAPXServer
+ *
+ * Returns the #GOutputStream for @is, which is owned by either a
+ * #GTcpConnection or a #GSubprocess. If the #CamelIMAPXServer is not
+ * yet connected or has lost its connection, the function returns %NULL.
+ *
+ * The returned #GOutputStream is referenced for thread-safety and must
+ * be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #GOutputStream, or %NULL
+ *
+ * Since: 3.12
+ **/
+GOutputStream *
+camel_imapx_server_ref_output_stream (CamelIMAPXServer *is)
+{
+ GOutputStream *output_stream = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ if (is->priv->output_stream != NULL)
+ output_stream = g_object_ref (is->priv->output_stream);
+
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ return output_stream;
+}
+
+/**
+ * camel_imapx_server_ref_selected:
+ * @is: a #CamelIMAPXServer
+ *
+ * Returns the #CamelIMAPXMailbox representing the currently selected
+ * mailbox (or mailbox <emphasis>being</emphasis> selected if a SELECT
+ * command is in progress) on the IMAP server, or %NULL if no mailbox
+ * is currently selected or being selected on the server.
+ *
+ * The returned #CamelIMAPXMailbox is reference for thread-safety and
+ * should be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelIMAPXMailbox, or %NULL
+ *
+ * Since: 3.12
+ **/
+CamelIMAPXMailbox *
+camel_imapx_server_ref_selected (CamelIMAPXServer *is)
+{
+ CamelIMAPXMailbox *mailbox;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
+
+ g_mutex_lock (&is->priv->select_lock);
+
+ mailbox = g_weak_ref_get (&is->priv->select_mailbox);
+ if (mailbox == NULL)
+ mailbox = g_weak_ref_get (&is->priv->select_pending);
+
+ g_mutex_unlock (&is->priv->select_lock);
+
+ return mailbox;
+}
+
+/* Some untagged responses updated pending SELECT mailbox, not the currently
+ selected or closing one, thus use this function instead. */
+CamelIMAPXMailbox *
+camel_imapx_server_ref_pending_or_selected (CamelIMAPXServer *is)
+{
+ CamelIMAPXMailbox *mailbox;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
+
+ g_mutex_lock (&is->priv->select_lock);
+
+ mailbox = g_weak_ref_get (&is->priv->select_pending);
+ if (mailbox == NULL)
+ mailbox = g_weak_ref_get (&is->priv->select_mailbox);
+
+ g_mutex_unlock (&is->priv->select_lock);
+
+ return mailbox;
+}
+
+gboolean
+camel_imapx_server_mailbox_selected (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox)
+{
+ CamelIMAPXMailbox *selected_mailbox;
+ gboolean res;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ g_mutex_lock (&is->priv->select_lock);
+ selected_mailbox = g_weak_ref_get (&is->priv->select_mailbox);
+ res = selected_mailbox == mailbox;
+ g_clear_object (&selected_mailbox);
+ g_mutex_unlock (&is->priv->select_lock);
+
+ return res;
+}
+
+gboolean
+camel_imapx_server_ensure_selected_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommand *ic;
+ CamelIMAPXMailbox *selected_mailbox;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ g_mutex_lock (&is->priv->select_lock);
+ selected_mailbox = g_weak_ref_get (&is->priv->select_mailbox);
+ if (selected_mailbox == mailbox) {
+ gboolean request_noop;
+ gint change_stamp;
+
+ change_stamp = selected_mailbox ? camel_imapx_mailbox_get_change_stamp (selected_mailbox) : 0;
+ request_noop = selected_mailbox && is->priv->last_selected_mailbox_change_stamp != change_stamp;
+
+ if (request_noop)
+ is->priv->last_selected_mailbox_change_stamp = change_stamp;
+
+ g_mutex_unlock (&is->priv->select_lock);
+ g_clear_object (&selected_mailbox);
+
+ if (request_noop) {
+ c (is->priv->tagprefix, "%s: Selected mailbox '%s' changed, do NOOP instead\n", G_STRFUNC, camel_imapx_mailbox_get_name (mailbox));
+
+ return camel_imapx_server_noop_sync (is, mailbox, cancellable, error);
+ }
+
+ return TRUE;
+ }
+
+ g_clear_object (&selected_mailbox);
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_SELECT, "SELECT %M", mailbox);
+
+ if (is->priv->use_qresync) {
+ CamelFolder *folder;
+
+ folder = imapx_server_ref_folder (is, mailbox);
+ camel_imapx_command_add_qresync_parameter (ic, folder);
+ g_clear_object (&folder);
+ }
+
+ g_weak_ref_set (&is->priv->select_pending, mailbox);
+ g_mutex_unlock (&is->priv->select_lock);
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Failed to select mailbox"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+
+ g_mutex_lock (&is->priv->select_lock);
+
+ g_weak_ref_set (&is->priv->select_pending, NULL);
+
+ if (success) {
+ is->priv->state = IMAPX_SELECTED;
+ is->priv->last_selected_mailbox_change_stamp = camel_imapx_mailbox_get_change_stamp (mailbox);
+ g_weak_ref_set (&is->priv->select_mailbox, mailbox);
+ } else {
+ is->priv->state = IMAPX_INITIALISED;
+ is->priv->last_selected_mailbox_change_stamp = 0;
+ g_weak_ref_set (&is->priv->select_mailbox, NULL);
+ }
+
+ g_mutex_unlock (&is->priv->select_lock);
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_process_command_sync (CamelIMAPXServer *is,
+ CamelIMAPXCommand *ic,
+ const gchar *error_prefix,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommandPart *cp;
+ GInputStream *input_stream = NULL;
+ GOutputStream *output_stream = NULL;
+ gboolean cp_literal_plus;
+ GList *head;
+ gchar *string;
+ gboolean success = FALSE;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_COMMAND (ic), FALSE);
+
+ camel_imapx_command_close (ic);
+ if (ic->status) {
+ imapx_free_status (ic->status);
+ ic->status = NULL;
+ }
+ ic->completed = FALSE;
+
+ head = g_queue_peek_head_link (&ic->parts);
+ g_return_val_if_fail (head != NULL, FALSE);
+ cp = (CamelIMAPXCommandPart *) head->data;
+ ic->current_part = head;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, &local_error)) {
+ if (error_prefix && local_error)
+ g_prefix_error (&local_error, "%s: ", error_prefix);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return FALSE;
+ }
+
+ cp_literal_plus = ((cp->type & CAMEL_IMAPX_COMMAND_LITERAL_PLUS) != 0);
+
+ COMMAND_LOCK (is);
+
+ if (is->priv->current_command != NULL) {
+ g_warning ("%s: [%c] %p: Starting command %p (%s) while still processing %p (%s)", G_STRFUNC,
+ is->priv->tagprefix, is, ic, camel_imapx_job_get_kind_name (ic->job_kind),
+ is->priv->current_command, camel_imapx_job_get_kind_name (is->priv->current_command->job_kind));
+ }
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, &local_error)) {
+ c (is->priv->tagprefix, "%s: command %p (%s) cancelled\n", G_STRFUNC, ic, camel_imapx_job_get_kind_name (ic->job_kind));
+
+ COMMAND_UNLOCK (is);
+
+ if (error_prefix && local_error)
+ g_prefix_error (&local_error, "%s: ", error_prefix);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return FALSE;
+ }
+
+ c (is->priv->tagprefix, "%s: %p (%s) ~> %p (%s)\n", G_STRFUNC, is->priv->current_command,
+ is->priv->current_command ? camel_imapx_job_get_kind_name (is->priv->current_command->job_kind) : "",
+ ic, camel_imapx_job_get_kind_name (ic->job_kind));
+
+ is->priv->current_command = ic;
+ is->priv->continuation_command = ic;
+
+ COMMAND_UNLOCK (is);
+
+ input_stream = camel_imapx_server_ref_input_stream (is);
+ output_stream = camel_imapx_server_ref_output_stream (is);
+
+ if (output_stream == NULL) {
+ local_error = g_error_new_literal (
+ CAMEL_IMAPX_SERVER_ERROR, CAMEL_IMAPX_SERVER_ERROR_TRY_RECONNECT,
+ _("Cannot issue command, no stream available"));
+ goto exit;
+ }
+
+ c (
+ is->priv->tagprefix,
+ "Starting command (%s) %c%05u %s\r\n",
+ is->priv->current_command ? " literal" : "",
+ is->priv->tagprefix,
+ ic->tag,
+ cp->data && g_str_has_prefix (cp->data, "LOGIN") ?
+ "LOGIN..." : cp->data);
+
+ if (ic->job_kind == CAMEL_IMAPX_JOB_DONE)
+ string = g_strdup_printf ("%s\r\n", cp->data);
+ else
+ string = g_strdup_printf ("%c%05u %s\r\n", is->priv->tagprefix, ic->tag, cp->data);
+ g_mutex_lock (&is->priv->stream_lock);
+ success = g_output_stream_write_all (
+ output_stream, string, strlen (string),
+ NULL, cancellable, &local_error);
+ g_mutex_unlock (&is->priv->stream_lock);
+ g_free (string);
+
+ if (local_error != NULL || !success)
+ goto exit;
+
+ while (is->priv->continuation_command == ic && cp_literal_plus) {
+ /* Sent LITERAL+ continuation immediately */
+ imapx_continuation (
+ is, input_stream, output_stream,
+ TRUE, cancellable, &local_error);
+ if (local_error != NULL)
+ goto exit;
+ }
+
+ while (success && !ic->completed)
+ success = imapx_step (is, input_stream, output_stream, cancellable, &local_error);
+
+ imapx_server_reset_inactivity_timer (is);
+
+ exit:
+
+ COMMAND_LOCK (is);
+
+ if (is->priv->current_command == ic) {
+ c (is->priv->tagprefix, "%s: %p ~> %p; success:%d local-error:%s result:%s status-text:'%s'\n", G_STRFUNC,
+ is->priv->current_command, NULL, success, local_error ? local_error->message : "[null]",
+ ic->status ? (
+ ic->status->result == IMAPX_OK ? "OK" :
+ ic->status->result == IMAPX_NO ? "NO" :
+ ic->status->result == IMAPX_BAD ? "BAD" :
+ ic->status->result == IMAPX_PREAUTH ? "PREAUTH" :
+ ic->status->result == IMAPX_BYE ? "BYE" : "???") : "[null]",
+ ic->status ? ic->status->text : "[null]");
+
+ is->priv->current_command = NULL;
+ is->priv->continuation_command = NULL;
+ } else {
+ c (is->priv->tagprefix, "%s: current command:%p doesn't match passed-in command:%p success:%d local-error:%s result:%s status-text:'%s'\n", G_STRFUNC,
+ is->priv->current_command, ic, success, local_error ? local_error->message : "[null]",
+ ic->status ? (
+ ic->status->result == IMAPX_OK ? "OK" :
+ ic->status->result == IMAPX_NO ? "NO" :
+ ic->status->result == IMAPX_BAD ? "BAD" :
+ ic->status->result == IMAPX_PREAUTH ? "PREAUTH" :
+ ic->status->result == IMAPX_BYE ? "BYE" : "???") : "[null]",
+ ic->status ? ic->status->text : "[null]");
+ }
+
+ COMMAND_UNLOCK (is);
+
+ /* Server reported error. */
+ if (success && ic->status && ic->status->result != IMAPX_OK) {
+ g_set_error (
+ &local_error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC,
+ "%s", ic->status->text);
+ }
+
+ if (local_error) {
+ /* Sadly, G_IO_ERROR_FAILED is also used for 'Connection reset by peer' error;
+ since GLib 2.44 is used G_IO_ERROR_CONNECTION_CLOSED, which is the same as G_IO_ERROR_BROKEN_PIPE */
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_FAILED) ||
+ g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE) ||
+ g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) {
+ local_error->domain = CAMEL_IMAPX_SERVER_ERROR;
+ local_error->code = CAMEL_IMAPX_SERVER_ERROR_TRY_RECONNECT;
+ }
+
+ if (error_prefix && local_error)
+ g_prefix_error (&local_error, "%s: ", error_prefix);
+
+ g_propagate_error (error, local_error);
+
+ success = FALSE;
+ }
+
+ g_clear_object (&input_stream);
+ g_clear_object (&output_stream);
+
+ return success;
+}
+
+static void
+imapx_disconnect (CamelIMAPXServer *is)
+{
+ g_cancellable_cancel (is->priv->cancellable);
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ if (is->priv->connection) {
+ /* No need to wait for close for too long */
+ imapx_server_set_connection_timeout (is->priv->connection, 3);
+ }
+
+ g_clear_object (&is->priv->input_stream);
+ g_clear_object (&is->priv->output_stream);
+ g_clear_object (&is->priv->connection);
+ g_clear_object (&is->priv->subprocess);
+
+ if (is->priv->cinfo) {
+ imapx_free_capability (is->priv->cinfo);
+ is->priv->cinfo = NULL;
+ }
+
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ g_mutex_lock (&is->priv->select_lock);
+ is->priv->last_selected_mailbox_change_stamp = 0;
+ g_weak_ref_set (&is->priv->select_mailbox, NULL);
+ g_weak_ref_set (&is->priv->select_pending, NULL);
+ g_mutex_unlock (&is->priv->select_lock);
+
+ is->priv->is_cyrus = FALSE;
+ is->priv->state = IMAPX_DISCONNECTED;
+
+ g_mutex_lock (&is->priv->idle_lock);
+ is->priv->idle_state = IMAPX_IDLE_STATE_OFF;
+ g_cond_broadcast (&is->priv->idle_cond);
+ g_mutex_unlock (&is->priv->idle_lock);
+}
+
+/* Client commands */
+gboolean
+camel_imapx_server_connect_sync (CamelIMAPXServer *is,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ if (is->priv->state == IMAPX_SHUTDOWN) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ "Shutting down");
+ return FALSE;
+ }
+
+ if (is->priv->state >= IMAPX_INITIALISED)
+ return TRUE;
+
+ is->priv->is_cyrus = FALSE;
+
+ if (!imapx_reconnect (is, cancellable, error))
+ return FALSE;
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ if (CAMEL_IMAPX_LACK_CAPABILITY (is->priv->cinfo, NAMESPACE)) {
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ /* This also creates a needed faux NAMESPACE */
+ if (!camel_imapx_server_list_sync (is, "INBOX", 0, cancellable, error))
+ return FALSE;
+ } else {
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+
+ return TRUE;
+}
+
+gboolean
+camel_imapx_server_disconnect_sync (CamelIMAPXServer *is,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GCancellable *idle_cancellable;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ g_mutex_lock (&is->priv->idle_lock);
+ idle_cancellable = is->priv->idle_cancellable;
+ if (idle_cancellable)
+ g_object_ref (idle_cancellable);
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ if (idle_cancellable)
+ g_cancellable_cancel (idle_cancellable);
+ g_clear_object (&idle_cancellable);
+
+ g_mutex_lock (&is->priv->stream_lock);
+ if (is->priv->connection) {
+ /* No need to wait for close for too long */
+ imapx_server_set_connection_timeout (is->priv->connection, 3);
+ }
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ /* Ignore errors here. */
+ camel_imapx_server_stop_idle_sync (is, cancellable, NULL);
+
+ g_mutex_lock (&is->priv->stream_lock);
+ if (is->priv->connection)
+ success = g_io_stream_close (is->priv->connection, cancellable, error);
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ imapx_disconnect (is);
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_query_auth_types_sync (CamelIMAPXServer *is,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ return imapx_connect_to_server (is, cancellable, error);
+}
+
+CamelStream *
+camel_imapx_server_get_message_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolderSummary *summary,
+ CamelDataCache *message_cache,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMessageInfo *mi;
+ CamelStream *result_stream = NULL;
+ CamelIMAPXSettings *settings;
+ GIOStream *cache_stream;
+ gsize data_size;
+ gboolean use_multi_fetch;
+ gboolean success, retrying = FALSE;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), NULL);
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
+ g_return_val_if_fail (CAMEL_IS_DATA_CACHE (message_cache), NULL);
+ g_return_val_if_fail (message_uid != NULL, NULL);
+
+ if (!camel_imapx_server_ensure_selected_sync (is, mailbox, cancellable, error))
+ return NULL;
+
+ mi = camel_folder_summary_get (summary, message_uid);
+ if (mi == NULL) {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID_UID,
+ _("Cannot get message with message ID %s: %s"),
+ message_uid, _("No such message available."));
+ return NULL;
+ }
+
+ /* This makes sure that if any file is left on the disk, it is not reused.
+ That can happen when the previous message download had been cancelled
+ or finished with an error. */
+ camel_data_cache_remove (message_cache, "tmp", message_uid, NULL);
+
+ cache_stream = camel_data_cache_add (message_cache, "tmp", message_uid, error);
+ if (cache_stream == NULL) {
+ camel_message_info_unref (mi);
+ return NULL;
+ }
+
+ settings = camel_imapx_server_ref_settings (is);
+ data_size = ((CamelMessageInfoBase *) mi)->size;
+ use_multi_fetch = data_size > MULTI_SIZE && camel_imapx_settings_get_use_multi_fetch (settings);
+ g_object_unref (settings);
+
+ g_warn_if_fail (is->priv->get_message_stream == NULL);
+
+ is->priv->get_message_stream = cache_stream;
+
+ try_again:
+ if (use_multi_fetch) {
+ CamelIMAPXCommand *ic;
+ gsize fetch_offset = 0;
+
+ do {
+ camel_operation_progress (cancellable, fetch_offset * 100 / data_size);
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_GET_MESSAGE, "UID FETCH %t (BODY.PEEK[]", message_uid);
+ camel_imapx_command_add (ic, "<%u.%u>", fetch_offset, MULTI_SIZE);
+ camel_imapx_command_add (ic, ")");
+ fetch_offset += MULTI_SIZE;
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error fetching message"), cancellable, &local_error);
+
+ camel_imapx_command_unref (ic);
+ ic = NULL;
+
+ if (success) {
+ gsize really_fetched = g_seekable_tell (G_SEEKABLE (is->priv->get_message_stream));
+
+ /* Don't automatically stop when we reach the reported message
+ * size -- some crappy servers (like Microsoft Exchange) have
+ * a tendency to lie about it. Keep going (one request at a
+ * time) until the data actually stop coming. */
+ if (fetch_offset < data_size ||
+ fetch_offset == really_fetched) {
+ /* just continue */
+ } else {
+ break;
+ }
+ }
+ } while (success);
+ } else {
+ CamelIMAPXCommand *ic;
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_GET_MESSAGE, "UID FETCH %t (BODY.PEEK[])", message_uid);
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error fetching message"), cancellable, &local_error);
+
+ camel_imapx_command_unref (ic);
+ }
+
+ if (success && !retrying && !g_seekable_tell (G_SEEKABLE (is->priv->get_message_stream))) {
+ /* Nothing had been read from the server. Maybe this connection
+ doesn't know about the message on the server side yet, thus
+ invoke NOOP and retry. */
+ CamelIMAPXCommand *ic;
+
+ retrying = TRUE;
+
+ c (is->priv->tagprefix, "%s: Returned no message data, retrying after NOOP\n", G_STRFUNC);
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_NOOP, "NOOP");
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error performing NOOP"), cancellable, &local_error);
+
+ camel_imapx_command_unref (ic);
+
+ if (success)
+ goto try_again;
+ }
+
+ is->priv->get_message_stream = NULL;
+
+ if (success) {
+ if (local_error == NULL) {
+ g_io_stream_close (cache_stream, cancellable, &local_error);
+ g_prefix_error (
+ &local_error, "%s: ",
+ _("Failed to close the tmp stream"));
+ }
+
+ if (local_error == NULL &&
+ g_cancellable_set_error_if_cancelled (cancellable, &local_error)) {
+ g_prefix_error (
+ &local_error, "%s: ",
+ _("Error fetching message"));
+ }
+
+ if (local_error == NULL) {
+ gchar *cur_filename;
+ gchar *tmp_filename;
+ gchar *dirname;
+
+ cur_filename = camel_data_cache_get_filename (message_cache, "cur", message_uid);
+ tmp_filename = camel_data_cache_get_filename (message_cache, "tmp", message_uid);
+
+ dirname = g_path_get_dirname (cur_filename);
+ g_mkdir_with_parents (dirname, 0700);
+ g_free (dirname);
+
+ if (g_rename (tmp_filename, cur_filename) == 0) {
+ /* Exchange the "tmp" stream for the "cur" stream. */
+ g_clear_object (&cache_stream);
+ cache_stream = camel_data_cache_get (message_cache, "cur", message_uid, &local_error);
+ } else {
+ g_set_error (
+ &local_error, G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ "%s: %s",
+ _("Failed to copy the tmp file"),
+ g_strerror (errno));
+ }
+
+ g_free (cur_filename);
+ g_free (tmp_filename);
+ }
+
+ /* Delete the 'tmp' file only if the operation succeeded. It's because
+ cancelled operations end before they are properly finished (IMAP-protocol speaking),
+ thus if any other GET_MESSAGE operation was waiting for this job, then it
+ realized that the message was not downloaded and opened its own "tmp" file, but
+ of the same name, thus this remove would drop file which could be used
+ by a different GET_MESSAGE job. */
+ if (!local_error && !g_cancellable_is_cancelled (cancellable))
+ camel_data_cache_remove (message_cache, "tmp", message_uid, NULL);
+ }
+
+ if (!local_error) {
+ result_stream = camel_stream_new (cache_stream);
+ } else {
+ g_propagate_error (error, local_error);
+ }
+
+ g_clear_object (&cache_stream);
+
+ return result_stream;
+}
+
+gboolean
+camel_imapx_server_sync_message_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolderSummary *summary,
+ CamelDataCache *message_cache,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar *cache_file = NULL;
+ gboolean is_cached;
+ struct stat st;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+ g_return_val_if_fail (CAMEL_IS_DATA_CACHE (message_cache), FALSE);
+ g_return_val_if_fail (message_uid != NULL, FALSE);
+
+ /* Check if the cache file already exists and is non-empty. */
+ cache_file = camel_data_cache_get_filename (message_cache, "cur", message_uid);
+ is_cached = (g_stat (cache_file, &st) == 0 && st.st_size > 0);
+ g_free (cache_file);
+
+ if (!is_cached) {
+ CamelStream *stream;
+
+ stream = camel_imapx_server_get_message_sync (
+ is, mailbox, summary,
+ message_cache, message_uid,
+ cancellable, error);
+
+ success = (stream != NULL);
+
+ g_clear_object (&stream);
+ }
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_copy_message_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ CamelIMAPXMailbox *destination,
+ GPtrArray *uids,
+ gboolean delete_originals,
+ gboolean remove_deleted_flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GPtrArray *data_uids;
+ gint ii;
+ gboolean use_move_command = FALSE;
+ CamelIMAPXCommand *ic;
+ CamelFolder *folder;
+ GHashTable *source_infos;
+ gboolean remove_junk_flags;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (destination), FALSE);
+ g_return_val_if_fail (uids != NULL, FALSE);
+
+ /* To get permanent flags. That's okay if the "SELECT" fails here, as it can be
+ due to the folder being write-only; just ignore the error and continue. */
+ camel_imapx_server_ensure_selected_sync (is, destination, cancellable, NULL);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ if (!camel_imapx_server_ensure_selected_sync (is, mailbox, cancellable, error))
+ return FALSE;
+
+ folder = imapx_server_ref_folder (is, mailbox);
+ g_return_val_if_fail (folder != NULL, FALSE);
+
+ remove_deleted_flags = remove_deleted_flags || (folder->folder_flags & CAMEL_FOLDER_IS_TRASH) != 0;
+ remove_junk_flags = (folder->folder_flags & CAMEL_FOLDER_IS_JUNK) != 0;
+
+ /* If we're moving messages, prefer "UID MOVE" if supported. */
+ if (delete_originals) {
+ g_mutex_lock (&is->priv->stream_lock);
+
+ if (CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, MOVE)) {
+ delete_originals = FALSE;
+ use_move_command = TRUE;
+ }
+
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+
+ source_infos = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, camel_message_info_unref);
+ data_uids = g_ptr_array_new ();
+
+ for (ii = 0; ii < uids->len; ii++) {
+ gchar *uid = (gchar *) camel_pstring_strdup (uids->pdata[ii]);
+
+ g_ptr_array_add (data_uids, uid);
+ g_hash_table_insert (source_infos, uid, camel_folder_summary_get (folder->summary, uid));
+ }
+
+ g_ptr_array_sort (data_uids, (GCompareFunc) imapx_uids_array_cmp);
+
+ ii = 0;
+ while (ii < data_uids->len && success) {
+ struct _uidset_state uidset;
+ gint last_index = ii;
+
+ imapx_uidset_init (&uidset, 0, MAX_COMMAND_LEN);
+
+ if (use_move_command)
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_MOVE_MESSAGE, "UID MOVE ");
+ else
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_COPY_MESSAGE, "UID COPY ");
+
+ while (ii < data_uids->len) {
+ const gchar *uid = (gchar *) g_ptr_array_index (data_uids, ii);
+
+ ii++;
+
+ if (imapx_uidset_add (&uidset, ic, uid) == 1)
+ break;
+ }
+
+ imapx_uidset_done (&uidset, ic);
+
+ camel_imapx_command_add (ic, " %M", destination);
+
+ imapx_free_status (is->priv->copyuid_status);
+ is->priv->copyuid_status = NULL;
+
+ success = camel_imapx_server_process_command_sync (is, ic,
+ use_move_command ? _("Error moving messages") : _("Error copying messages"),
+ cancellable, error);
+
+ if (success) {
+ struct _status_info *copyuid_status = is->priv->copyuid_status;
+
+ if (ic->status && ic->status->condition == IMAPX_COPYUID)
+ copyuid_status = ic->status;
+
+ if (copyuid_status && copyuid_status->u.copyuid.uids &&
+ copyuid_status->u.copyuid.copied_uids &&
+ copyuid_status->u.copyuid.uids->len == copyuid_status->u.copyuid.copied_uids->len) {
+ CamelFolder *destination_folder;
+
+ destination_folder = imapx_server_ref_folder (is, destination);
+ if (destination_folder) {
+ CamelMessageInfo *source_info, *destination_info;
+ CamelFolderChangeInfo *changes;
+ gint ii;
+
+ changes = camel_folder_change_info_new ();
+
+ for (ii = 0; ii < copyuid_status->u.copyuid.uids->len; ii++) {
+ gchar *uid;
+ gboolean is_new = FALSE;
+
+ uid = g_strdup_printf ("%d", g_array_index (copyuid_status->u.copyuid.uids, guint32, ii));
+ source_info = g_hash_table_lookup (source_infos, uid);
+ g_free (uid);
+
+ if (!source_info)
+ continue;
+
+ uid = g_strdup_printf ("%d", g_array_index (copyuid_status->u.copyuid.copied_uids, guint32, ii));
+ destination_info = camel_folder_summary_get (folder->summary, uid);
+
+ if (!destination_info) {
+ is_new = TRUE;
+ destination_info = camel_message_info_clone (source_info);
+ destination_info->summary = destination_folder->summary;
+ camel_pstring_free (destination_info->uid);
+ destination_info->uid = camel_pstring_strdup (uid);
+ }
+
+ g_free (uid);
+
+ imapx_set_message_info_flags_for_new_message (
+ destination_info,
+ ((CamelMessageInfoBase *) source_info)->flags,
+ ((CamelMessageInfoBase *) source_info)->user_flags,
+ TRUE,
+ ((CamelMessageInfoBase *) source_info)->user_tags,
+ camel_imapx_mailbox_get_permanentflags (destination));
+ if (remove_deleted_flags)
+ camel_message_info_set_flags (destination_info, CAMEL_MESSAGE_DELETED, 0);
+ if (remove_junk_flags)
+ camel_message_info_set_flags (destination_info, CAMEL_MESSAGE_JUNK, 0);
+ if (is_new)
+ camel_folder_summary_add (destination_folder->summary, destination_info);
+ camel_folder_change_info_add_uid (changes, destination_info->uid);
+
+ if (!is_new)
+ camel_message_info_unref (destination_info);
+ }
+
+ if (camel_folder_change_info_changed (changes)) {
+ camel_folder_summary_touch (destination_folder->summary);
+ camel_folder_summary_save_to_db (destination_folder->summary, NULL);
+ camel_folder_changed (destination_folder, changes);
+ }
+
+ camel_folder_change_info_free (changes);
+ g_object_unref (destination_folder);
+ }
+ }
+
+ if (delete_originals || use_move_command) {
+ CamelFolderChangeInfo *changes = NULL;
+ gint jj;
+
+ camel_folder_freeze (folder);
+
+ for (jj = last_index; jj < ii; jj++) {
+ const gchar *uid = uids->pdata[jj];
+
+ if (delete_originals) {
+ camel_folder_delete_message (folder, uid);
+ } else {
+ if (camel_folder_summary_remove_uid (folder->summary, uid)) {
+ if (!changes)
+ changes = camel_folder_change_info_new ();
+
+ camel_folder_change_info_remove_uid (changes, uid);
+ }
+ }
+ }
+
+ if (changes && camel_folder_change_info_changed (changes)) {
+ camel_folder_summary_touch (folder->summary);
+ camel_folder_summary_save_to_db (folder->summary, NULL);
+ camel_folder_changed (folder, changes);
+ }
+
+ camel_folder_thaw (folder);
+
+ if (changes)
+ camel_folder_change_info_free (changes);
+ }
+ }
+
+ imapx_free_status (is->priv->copyuid_status);
+ is->priv->copyuid_status = NULL;
+
+ camel_imapx_command_unref (ic);
+ }
+
+ g_hash_table_destroy (source_infos);
+ g_ptr_array_foreach (data_uids, (GFunc) camel_pstring_free, NULL);
+ g_ptr_array_free (data_uids, TRUE);
+ g_object_unref (folder);
+
+ return success;
+}
+
+static const gchar *
+get_month_str (gint month)
+{
+ static const gchar tm_months[][4] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+
+ if (month < 1 || month > 12)
+ return NULL;
+
+ return tm_months[month - 1];
+}
+
+gboolean
+camel_imapx_server_append_message_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolderSummary *summary,
+ CamelDataCache *message_cache,
+ CamelMimeMessage *message,
+ const CamelMessageInfo *mi,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar *uid = NULL, *path = NULL;
+ CamelMimeFilter *filter;
+ CamelIMAPXCommand *ic;
+ CamelMessageInfo *info;
+ GIOStream *base_stream;
+ GOutputStream *output_stream;
+ GOutputStream *filter_stream;
+ gint res;
+ time_t date_time;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), FALSE);
+ g_return_val_if_fail (CAMEL_IS_DATA_CACHE (message_cache), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
+ /* CamelMessageInfo can be NULL. */
+
+ /* That's okay if the "SELECT" fails here, as it can be due to
+ the folder being write-only; just ignore the error and continue. */
+ camel_imapx_server_ensure_selected_sync (is, mailbox, cancellable, NULL);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ /* Append just assumes we have no/a dodgy connection. We dump
+ * stuff into the 'new' directory, and let the summary know it's
+ * there. Then we fire off a no-reply job which will asynchronously
+ * upload the message at some point in the future, and fix up the
+ * summary to match */
+
+ /* chen cleanup this later */
+ uid = imapx_get_temp_uid ();
+ base_stream = camel_data_cache_add (message_cache, "new", uid, error);
+ if (base_stream == NULL) {
+ g_prefix_error (error, _("Cannot create spool file: "));
+ g_free (uid);
+ return FALSE;
+ }
+
+ output_stream = g_io_stream_get_output_stream (base_stream);
+ filter = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_CRLF);
+ filter_stream = camel_filter_output_stream_new (output_stream, filter);
+
+ g_filter_output_stream_set_close_base_stream (
+ G_FILTER_OUTPUT_STREAM (filter_stream), FALSE);
+
+ res = camel_data_wrapper_write_to_output_stream_sync (
+ CAMEL_DATA_WRAPPER (message),
+ filter_stream, cancellable, error);
+
+ g_object_unref (base_stream);
+ g_object_unref (filter_stream);
+ g_object_unref (filter);
+
+ if (res == -1) {
+ g_prefix_error (error, _("Cannot create spool file: "));
+ camel_data_cache_remove (message_cache, "new", uid, NULL);
+ g_free (uid);
+ return FALSE;
+ }
+
+ date_time = camel_mime_message_get_date (message, NULL);
+ path = camel_data_cache_get_filename (message_cache, "new", uid);
+ info = camel_folder_summary_info_new_from_message (summary, message, NULL);
+ info->uid = camel_pstring_strdup (uid);
+
+ if (mi != NULL) {
+ struct icaltimetype icaltime;
+ CamelMessageInfoBase *base_info = (CamelMessageInfoBase *) info;
+ const CamelFlag *flag;
+ const CamelTag *tag;
+
+ base_info->flags = camel_message_info_get_flags (mi);
+ base_info->size = camel_message_info_get_size (mi);
+
+ flag = camel_message_info_get_user_flags (mi);
+ while (flag != NULL) {
+ if (*flag->name != '\0')
+ camel_flag_set (
+ &base_info->user_flags,
+ flag->name, TRUE);
+ flag = flag->next;
+ }
+
+ tag = camel_message_info_get_user_tags (mi);
+ while (tag != NULL) {
+ if (*tag->name != '\0')
+ camel_tag_set (
+ &base_info->user_tags,
+ tag->name, tag->value);
+ tag = tag->next;
+ }
+
+ if (date_time > 0) {
+ icaltime = icaltime_from_timet (date_time, FALSE);
+ if (!icaltime_is_valid_time (icaltime))
+ date_time = -1;
+ }
+
+ if (date_time <= 0)
+ date_time = camel_message_info_get_date_received (mi);
+
+ if (date_time > 0) {
+ icaltime = icaltime_from_timet (date_time, FALSE);
+ if (!icaltime_is_valid_time (icaltime))
+ date_time = -1;
+ }
+ }
+
+ if (!camel_message_info_get_size (info)) {
+ CamelStreamNull *sn = (CamelStreamNull *) camel_stream_null_new ();
+
+ camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (message),
+ CAMEL_STREAM (sn), NULL, NULL);
+ ((CamelMessageInfoBase *) info)->size = sn->written;
+ g_object_unref (sn);
+ }
+
+ g_free (uid);
+
+ if (camel_mime_message_has_attachment (message))
+ ((CamelMessageInfoBase *) info)->flags |= CAMEL_MESSAGE_ATTACHMENTS;
+
+ if (date_time > 0) {
+ gchar *date_time_str;
+ struct tm stm;
+
+ gmtime_r (&date_time, &stm);
+
+ /* Store always in UTC */
+ date_time_str = g_strdup_printf (
+ "\"%02d-%s-%04d %02d:%02d:%02d +0000\"",
+ stm.tm_mday,
+ get_month_str (stm.tm_mon + 1),
+ stm.tm_year + 1900,
+ stm.tm_hour,
+ stm.tm_min,
+ stm.tm_sec);
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_APPEND_MESSAGE, "APPEND %M %F %t %P",
+ mailbox,
+ ((CamelMessageInfoBase *) info)->flags,
+ ((CamelMessageInfoBase *) info)->user_flags,
+ date_time_str,
+ path);
+
+ g_free (date_time_str);
+ } else {
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_APPEND_MESSAGE, "APPEND %M %F %P",
+ mailbox,
+ ((CamelMessageInfoBase *) info)->flags,
+ ((CamelMessageInfoBase *) info)->user_flags,
+ path);
+ }
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error appending message"), cancellable, error);
+
+ if (success) {
+ CamelIMAPXFolder *imapx_folder;
+ CamelFolder *folder;
+ CamelMessageInfo *mi;
+ gchar *cur, *old_uid;
+ guint32 uidvalidity;
+
+ folder = imapx_server_ref_folder (is, mailbox);
+ g_return_val_if_fail (folder != NULL, FALSE);
+
+ uidvalidity = camel_imapx_mailbox_get_uidvalidity (mailbox);
+
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+
+ /* Append done. If we the server supports UIDPLUS we will get
+ * an APPENDUID response with the new uid. This lets us move the
+ * message we have directly to the cache and also create a correctly
+ * numbered MessageInfo, without losing any information. Otherwise
+ * we have to wait for the server to let us know it was appended. */
+
+ mi = camel_message_info_clone (info);
+ old_uid = g_strdup (info->uid);
+
+ if (ic->status && ic->status->condition == IMAPX_APPENDUID) {
+ c (is->priv->tagprefix, "Got appenduid %d %d\n", (gint) ic->status->u.appenduid.uidvalidity, (gint) ic->status->u.appenduid.uid);
+ if (ic->status->u.appenduid.uidvalidity == uidvalidity) {
+ if (appended_uid)
+ *appended_uid = g_strdup_printf ("%u", (guint) ic->status->u.appenduid.uid);
+ mi->uid = camel_pstring_add (g_strdup_printf ("%u", (guint) ic->status->u.appenduid.uid), TRUE);
+
+ cur = camel_data_cache_get_filename (imapx_folder->cache, "cur", mi->uid);
+ if (g_rename (path, cur) == -1 && errno != ENOENT) {
+ g_warning ("%s: Failed to rename '%s' to '%s': %s", G_STRFUNC, path, cur, g_strerror (errno));
+ }
+
+ imapx_set_message_info_flags_for_new_message (
+ mi,
+ ((CamelMessageInfoBase *) info)->flags,
+ ((CamelMessageInfoBase *) info)->user_flags,
+ TRUE,
+ ((CamelMessageInfoBase *) info)->user_tags,
+ camel_imapx_mailbox_get_permanentflags (mailbox));
+
+ camel_folder_summary_add (folder->summary, mi);
+
+ g_mutex_lock (&is->priv->changes_lock);
+ camel_folder_change_info_add_uid (is->priv->changes, mi->uid);
+ g_mutex_unlock (&is->priv->changes_lock);
+
+ mi = NULL;
+
+ g_free (cur);
+ } else {
+ c (is->priv->tagprefix, "but uidvalidity changed \n");
+ }
+ }
+
+ camel_data_cache_remove (imapx_folder->cache, "new", old_uid, NULL);
+ g_free (old_uid);
+
+ camel_imapx_command_unref (ic);
+ if (mi)
+ camel_message_info_unref (mi);
+ g_object_unref (folder);
+ }
+
+ g_free (path);
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_noop_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ /* Mailbox may be NULL. */
+
+ if (mailbox)
+ success = camel_imapx_server_ensure_selected_sync (is, mailbox, cancellable, error);
+
+ if (success) {
+ CamelIMAPXCommand *ic;
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_NOOP, "NOOP");
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error performing NOOP"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+ }
+
+ return success;
+}
+
+/* ********************************************************************** */
+
+static gint
+imapx_refresh_info_uid_cmp (gconstpointer ap,
+ gconstpointer bp,
+ gboolean ascending)
+{
+ guint av, bv;
+
+ av = g_ascii_strtoull ((const gchar *) ap, NULL, 10);
+ bv = g_ascii_strtoull ((const gchar *) bp, NULL, 10);
+
+ if (av < bv)
+ return ascending ? -1 : 1;
+ else if (av > bv)
+ return ascending ? 1 : -1;
+ else
+ return 0;
+}
+
+static gint
+imapx_uids_array_cmp (gconstpointer ap,
+ gconstpointer bp)
+{
+ const gchar **a = (const gchar **) ap;
+ const gchar **b = (const gchar **) bp;
+
+ return imapx_refresh_info_uid_cmp (*a, *b, TRUE);
+}
+
+static gint
+imapx_uids_desc_cmp (gconstpointer ap,
+ gconstpointer bp)
+{
+ const gchar *a = (const gchar *) ap;
+ const gchar *b = (const gchar *) bp;
+
+ return imapx_refresh_info_uid_cmp (a, b, FALSE);
+}
+
+static void
+imapx_server_process_fetch_changes_infos (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolder *folder,
+ GHashTable *infos,
+ GHashTable *known_uids,
+ GSList **out_fetch_summary_uids,
+ guint64 from_uidl,
+ guint64 to_uidl)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+ CamelFolderSummary *summary;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_SERVER (is));
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (infos != NULL);
+
+ if (out_fetch_summary_uids)
+ g_return_if_fail (*out_fetch_summary_uids == NULL);
+
+ summary = folder->summary;
+
+ g_hash_table_iter_init (&iter, infos);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ const gchar *uid = key;
+ FetchChangesInfo *nfo = value;
+ CamelMessageInfo *minfo;
+
+ if (!uid || !nfo)
+ continue;
+
+ if (known_uids)
+ g_hash_table_insert (known_uids, (gpointer) camel_pstring_strdup (uid), GINT_TO_POINTER (1));
+
+ if (!camel_folder_summary_check_uid (summary, uid) ||
+ !(minfo = camel_folder_summary_get (summary, uid))) {
+ if (out_fetch_summary_uids) {
+ *out_fetch_summary_uids = g_slist_prepend (*out_fetch_summary_uids,
+ (gpointer) camel_pstring_strdup (uid));
+ }
+
+ continue;
+ }
+
+ if (imapx_update_message_info_flags (
+ minfo,
+ nfo->server_flags,
+ nfo->server_user_flags,
+ camel_imapx_mailbox_get_permanentflags (mailbox),
+ folder, FALSE)) {
+ g_mutex_lock (&is->priv->changes_lock);
+ camel_folder_change_info_change_uid (is->priv->changes, camel_message_info_get_uid (minfo));
+ g_mutex_unlock (&is->priv->changes_lock);
+ }
+
+ camel_message_info_unref (minfo);
+ }
+}
+
+static gboolean
+imapx_server_fetch_changes (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolder *folder,
+ GHashTable *known_uids,
+ guint64 from_uidl,
+ guint64 to_uidl,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GSList *fetch_summary_uids = NULL;
+ GHashTable *infos; /* uid ~> FetchChangesInfo */
+ CamelIMAPXCommand *ic;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ if (!from_uidl)
+ from_uidl = 1;
+
+ if (to_uidl > 0) {
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_REFRESH_INFO, "UID FETCH %lld:%lld (UID FLAGS)", from_uidl, to_uidl);
+ } else {
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_REFRESH_INFO, "UID FETCH %lld:* (UID FLAGS)", from_uidl);
+ }
+
+ g_return_val_if_fail (is->priv->fetch_changes_mailbox == NULL, FALSE);
+ g_return_val_if_fail (is->priv->fetch_changes_folder == NULL, FALSE);
+ g_return_val_if_fail (is->priv->fetch_changes_infos == NULL, FALSE);
+
+ infos = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) camel_pstring_free, fetch_changes_info_free);
+
+ is->priv->fetch_changes_mailbox = mailbox;
+ is->priv->fetch_changes_folder = folder;
+ is->priv->fetch_changes_infos = infos;
+ is->priv->fetch_changes_last_progress = 0;
+
+ camel_operation_push_message (cancellable,
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ _("Scanning for changed messages in '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
+ camel_folder_get_full_name (folder));
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error scanning changes"), cancellable, error);
+
+ camel_operation_pop_message (cancellable);
+ camel_imapx_command_unref (ic);
+
+ /* It can partly succeed. */
+ imapx_server_process_fetch_changes_infos (is, mailbox, folder, infos, known_uids, &fetch_summary_uids, from_uidl, to_uidl);
+
+ g_hash_table_remove_all (infos);
+
+ if (success && fetch_summary_uids) {
+ struct _uidset_state uidset;
+ GSList *link;
+
+ ic = NULL;
+ imapx_uidset_init (&uidset, 0, 100);
+
+ camel_operation_push_message (cancellable,
+ /* Translators: The first '%s' is replaced with an account name and the second '%s'
+ is replaced with a full path name. The spaces around ':' are intentional, as
+ the whole '%s : %s' is meant as an absolute identification of the folder. */
+ _("Fetching summary information for new messages in '%s : %s'"),
+ camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))),
+ camel_folder_get_full_name (folder));
+
+ fetch_summary_uids = g_slist_sort (fetch_summary_uids, imapx_uids_desc_cmp);
+
+ for (link = fetch_summary_uids; link; link = g_slist_next (link)) {
+ const gchar *uid = link->data;
+
+ if (!uid)
+ continue;
+
+ if (!ic)
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_REFRESH_INFO, "UID FETCH ");
+
+ if (imapx_uidset_add (&uidset, ic, uid) == 1 || (!link->next && ic && imapx_uidset_done (&uidset, ic))) {
+ camel_imapx_command_add (ic, " (RFC822.SIZE RFC822.HEADER FLAGS)");
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error fetching message info"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+ ic = NULL;
+
+ if (!success)
+ break;
+
+ imapx_server_process_fetch_changes_infos (is, mailbox, folder, infos, NULL, NULL, 0, 0);
+ g_hash_table_remove_all (infos);
+ }
+ }
+
+ camel_operation_pop_message (cancellable);
+
+ imapx_server_process_fetch_changes_infos (is, mailbox, folder, infos, NULL, NULL, 0, 0);
+ }
+
+ g_return_val_if_fail (is->priv->fetch_changes_mailbox == mailbox, FALSE);
+ g_return_val_if_fail (is->priv->fetch_changes_folder == folder, FALSE);
+ g_return_val_if_fail (is->priv->fetch_changes_infos == infos, FALSE);
+
+ is->priv->fetch_changes_mailbox = NULL;
+ is->priv->fetch_changes_folder = NULL;
+ is->priv->fetch_changes_infos = NULL;
+
+ g_slist_free_full (fetch_summary_uids, (GDestroyNotify) camel_pstring_free);
+ g_hash_table_destroy (infos);
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_refresh_info_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommand *ic;
+ CamelIMAPXMailbox *selected_mailbox;
+ CamelIMAPXSummary *imapx_summary;
+ CamelFolder *folder;
+ GHashTable *known_uids;
+ guint32 messages;
+ guint32 unseen;
+ guint32 uidnext;
+ guint32 uidvalidity;
+ guint64 highestmodseq;
+ guint32 total;
+ guint64 uidl;
+ gboolean need_rescan;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ selected_mailbox = camel_imapx_server_ref_pending_or_selected (is);
+ if (selected_mailbox == mailbox) {
+ success = camel_imapx_server_noop_sync (is, mailbox, cancellable, error);
+ } else {
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_STATUS, "STATUS %M (%t)", mailbox, is->priv->status_data_items);
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error running STATUS"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+ }
+ g_clear_object (&selected_mailbox);
+
+ if (!success)
+ return FALSE;
+
+ folder = imapx_server_ref_folder (is, mailbox);
+ g_return_val_if_fail (folder != NULL, FALSE);
+
+ imapx_summary = CAMEL_IMAPX_SUMMARY (folder->summary);
+
+ messages = camel_imapx_mailbox_get_messages (mailbox);
+ unseen = camel_imapx_mailbox_get_unseen (mailbox);
+ uidnext = camel_imapx_mailbox_get_uidnext (mailbox);
+ uidvalidity = camel_imapx_mailbox_get_uidvalidity (mailbox);
+ highestmodseq = camel_imapx_mailbox_get_highestmodseq (mailbox);
+ total = camel_folder_summary_count (folder->summary);
+
+ need_rescan =
+ (uidvalidity > 0 && uidvalidity != imapx_summary->validity) ||
+ total != messages ||
+ imapx_summary->uidnext != uidnext ||
+ camel_folder_summary_get_unread_count (folder->summary) != unseen ||
+ imapx_summary->modseq != highestmodseq;
+
+ if (!need_rescan) {
+ g_object_unref (folder);
+ return TRUE;
+ }
+
+ if (!camel_imapx_server_ensure_selected_sync (is, mailbox, cancellable, error)) {
+ g_object_unref (folder);
+ return FALSE;
+ }
+
+ if (is->priv->use_qresync && imapx_summary->modseq > 0 && uidvalidity > 0) {
+ imapx_summary->modseq = highestmodseq;
+ if (total != messages ||
+ camel_folder_summary_get_unread_count (folder->summary) != unseen ||
+ imapx_summary->modseq != highestmodseq) {
+ c (
+ is->priv->tagprefix,
+ "Eep, after QRESYNC we're out of sync. "
+ "total %u / %u, unread %u / %u, modseq %"
+ G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT "\n",
+ total, messages,
+ camel_folder_summary_get_unread_count (folder->summary),
+ unseen,
+ imapx_summary->modseq,
+ highestmodseq);
+ } else {
+ c (
+ is->priv->tagprefix,
+ "OK, after QRESYNC we're still in sync. "
+ "total %u / %u, unread %u / %u, modseq %"
+ G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT "\n",
+ total, messages,
+ camel_folder_summary_get_unread_count (folder->summary),
+ unseen,
+ imapx_summary->modseq,
+ highestmodseq);
+ g_object_unref (folder);
+ return TRUE;
+ }
+ }
+
+ if (total > 0) {
+ gchar *uid = camel_imapx_dup_uid_from_summary_index (folder, total - 1);
+ if (uid) {
+ uidl = g_ascii_strtoull (uid, NULL, 10);
+ g_free (uid);
+ uidl++;
+ } else {
+ uidl = 1;
+ }
+ } else {
+ uidl = 1;
+ }
+
+ camel_folder_summary_prepare_fetch_all (folder->summary, NULL);
+
+ known_uids = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) camel_pstring_free, NULL);
+
+ success = imapx_server_fetch_changes (is, mailbox, folder, known_uids, uidl, 0, cancellable, error);
+ if (success && uidl != 1)
+ success = imapx_server_fetch_changes (is, mailbox, folder, known_uids, 0, uidl, cancellable, error);
+
+ if (success) {
+ CamelFolderChangeInfo *changes;
+ GList *removed = NULL;
+ GPtrArray *array;
+ gint ii;
+
+ camel_folder_summary_lock (folder->summary);
+
+ changes = camel_folder_change_info_new ();
+
+ array = camel_folder_summary_get_array (folder->summary);
+ for (ii = 0; array && ii < array->len; ii++) {
+ const gchar *uid = array->pdata[ii];
+
+ if (!uid)
+ continue;
+
+ if (!g_hash_table_contains (known_uids, uid)) {
+ removed = g_list_prepend (removed, (gpointer) uid);
+ camel_folder_change_info_remove_uid (changes, uid);
+ }
+ }
+
+ camel_folder_summary_unlock (folder->summary);
+
+ if (removed != NULL) {
+ camel_folder_summary_remove_uids (folder->summary, removed);
+ camel_folder_summary_touch (folder->summary);
+
+ /* Shares UIDs with the 'array'. */
+ g_list_free (removed);
+ }
+
+ if (camel_folder_change_info_changed (changes)) {
+ camel_folder_summary_save_to_db (folder->summary, NULL);
+ imapx_update_store_summary (folder);
+ camel_folder_changed (folder, changes);
+ }
+
+ camel_folder_change_info_free (changes);
+ camel_folder_summary_free_array (array);
+ }
+
+ g_hash_table_destroy (known_uids);
+ g_object_unref (folder);
+
+ return success;
+}
+
+static void
+imapx_sync_free_user (GArray *user_set)
+{
+ gint i;
+
+ if (user_set == NULL)
+ return;
+
+ for (i = 0; i < user_set->len; i++) {
+ struct _imapx_flag_change *flag_change = &g_array_index (user_set, struct _imapx_flag_change, i);
+ GPtrArray *infos = flag_change->infos;
+ gint j;
+
+ for (j = 0; j < infos->len; j++) {
+ CamelMessageInfo *info = g_ptr_array_index (infos, j);
+ camel_message_info_unref (info);
+ }
+
+ g_ptr_array_free (infos, TRUE);
+ g_free (flag_change->name);
+ }
+ g_array_free (user_set, TRUE);
+}
+
+static void
+imapx_unset_folder_flagged_flag (CamelFolderSummary *summary,
+ GPtrArray *changed_uids,
+ gboolean except_deleted_messages)
+{
+ CamelMessageInfo *info;
+ gboolean changed = FALSE;
+ gint ii;
+
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary));
+ g_return_if_fail (changed_uids != NULL);
+
+ for (ii = 0; ii < changed_uids->len; ii++) {
+ info = camel_folder_summary_get (summary, changed_uids->pdata[ii]);
+
+ if (info) {
+ CamelMessageInfoBase *mi = (CamelMessageInfoBase *) info;
+
+ /* some infos could be only 'dirty' (needed to save into summary) */
+ if ((mi->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0 &&
+ (!except_deleted_messages || (mi->flags & CAMEL_MESSAGE_DELETED) == 0)) {
+ mi->flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
+ mi->dirty = TRUE;
+ changed = TRUE;
+ }
+
+ camel_message_info_unref (info);
+ }
+ }
+
+ if (changed) {
+ camel_folder_summary_touch (summary);
+ camel_folder_summary_save_to_db (summary, NULL);
+ }
+}
+
+static void
+imapx_server_info_changed_cb (CamelIMAPXSummary *summary,
+ CamelMessageInfo *info,
+ gpointer user_data)
+{
+ GHashTable *changed_meanwhile = user_data;
+
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (changed_meanwhile != NULL);
+
+ /* The UID can be NULL in case of a newly fetched message, for example when creating
+ the message info in imapx_untagged_fetch() by camel_folder_summary_info_new_from_parser() */
+ if (camel_message_info_get_uid (info)) {
+ g_hash_table_insert (changed_meanwhile,
+ (gpointer) camel_pstring_strdup (camel_message_info_get_uid (info)),
+ GINT_TO_POINTER (1));
+ }
+}
+
+gboolean
+camel_imapx_server_sync_changes_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ gboolean can_influence_flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint i, jj, on, on_orset, off_orset;
+ GPtrArray *changed_uids;
+ GArray *on_user = NULL, *off_user = NULL;
+ CamelFolder *folder;
+ CamelIMAPXMessageInfo *info;
+ GHashTable *changed_meanwhile;
+ gulong changed_meanwhile_handler_id;
+ guint32 permanentflags;
+ struct _uidset_state uidset;
+ gint unread_change = 0;
+ gboolean use_real_junk_path = FALSE;
+ gboolean use_real_trash_path = FALSE;
+ gboolean remove_deleted_flags = FALSE;
+ gboolean nothing_to_do;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ folder = imapx_server_ref_folder (is, mailbox);
+ g_return_val_if_fail (folder != NULL, FALSE);
+
+ /* We calculate two masks, a mask of all flags which have been
+ * turned off and a mask of all flags which have been turned
+ * on. If either of these aren't 0, then we have work to do,
+ * and we fire off a job to do it.
+ *
+ * User flags are a bit more tricky, we rely on the user
+ * flags being sorted, and then we create a bunch of lists;
+ * one for each flag being turned off, including each
+ * info being turned off, and one for each flag being turned on.
+ */
+ changed_uids = camel_folder_summary_get_changed (folder->summary);
+
+ if (changed_uids->len == 0) {
+ camel_folder_free_uids (folder, changed_uids);
+ g_object_unref (folder);
+ return TRUE;
+ }
+
+ changed_meanwhile = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) camel_pstring_free, NULL);
+ changed_meanwhile_handler_id = g_signal_connect (folder->summary, "info-changed",
+ G_CALLBACK (imapx_server_info_changed_cb), changed_meanwhile);
+
+ if (can_influence_flags) {
+ CamelIMAPXSettings *settings;
+
+ settings = camel_imapx_server_ref_settings (is);
+ use_real_junk_path = camel_imapx_settings_get_use_real_junk_path (settings);
+ use_real_trash_path = camel_imapx_settings_get_use_real_trash_path (settings);
+ if (use_real_trash_path) {
+ CamelFolder *trash_folder = NULL;
+ gchar *real_trash_path;
+
+ real_trash_path = camel_imapx_settings_dup_real_trash_path (settings);
+ if (real_trash_path)
+ trash_folder = camel_store_get_folder_sync (
+ camel_folder_get_parent_store (folder),
+ real_trash_path, 0, cancellable, NULL);
+
+ /* Remove deleted flags in all but the trash folder itself */
+ remove_deleted_flags = !trash_folder || trash_folder != folder;
+
+ use_real_trash_path = trash_folder != NULL;
+
+ g_clear_object (&trash_folder);
+ g_free (real_trash_path);
+ }
+ g_object_unref (settings);
+ }
+
+ if (changed_uids->len > 20)
+ camel_folder_summary_prepare_fetch_all (folder->summary, NULL);
+
+ off_orset = on_orset = 0;
+ for (i = 0; i < changed_uids->len; i++) {
+ guint32 flags, sflags;
+ CamelFlag *uflags, *suflags;
+ const gchar *uid;
+ guint j = 0;
+
+ uid = g_ptr_array_index (changed_uids, i);
+
+ info = (CamelIMAPXMessageInfo *)
+ camel_folder_summary_get (folder->summary, uid);
+
+ if (info == NULL)
+ continue;
+
+ if (!(info->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED)) {
+ camel_message_info_unref (info);
+ continue;
+ }
+
+ flags = info->info.flags & CAMEL_IMAPX_SERVER_FLAGS;
+ sflags = info->server_flags & CAMEL_IMAPX_SERVER_FLAGS;
+
+ if (can_influence_flags) {
+ gboolean move_to_real_junk;
+ gboolean move_to_real_trash;
+
+ move_to_real_junk =
+ use_real_junk_path &&
+ (flags & CAMEL_MESSAGE_JUNK);
+
+ move_to_real_trash =
+ use_real_trash_path && remove_deleted_flags &&
+ (flags & CAMEL_MESSAGE_DELETED);
+
+ if (move_to_real_junk)
+ camel_imapx_folder_add_move_to_real_junk (
+ CAMEL_IMAPX_FOLDER (folder), uid);
+
+ if (move_to_real_trash)
+ camel_imapx_folder_add_move_to_real_trash (
+ CAMEL_IMAPX_FOLDER (folder), uid);
+ }
+
+ if (flags != sflags) {
+ off_orset |= (flags ^ sflags) & ~flags;
+ on_orset |= (flags ^ sflags) & flags;
+ }
+
+ uflags = info->info.user_flags;
+ suflags = info->server_user_flags;
+ while (uflags || suflags) {
+ gint res;
+
+ if (uflags) {
+ if (suflags)
+ res = strcmp (uflags->name, suflags->name);
+ else if (*uflags->name)
+ res = -1;
+ else {
+ uflags = uflags->next;
+ continue;
+ }
+ } else {
+ res = 1;
+ }
+
+ if (res == 0) {
+ uflags = uflags->next;
+ suflags = suflags->next;
+ } else {
+ GArray *user_set;
+ CamelFlag *user_flag;
+ struct _imapx_flag_change *change = NULL, add = { 0 };
+
+ if (res < 0) {
+ if (on_user == NULL)
+ on_user = g_array_new (FALSE, FALSE, sizeof (struct _imapx_flag_change));
+ user_set = on_user;
+ user_flag = uflags;
+ uflags = uflags->next;
+ } else {
+ if (off_user == NULL)
+ off_user = g_array_new (FALSE, FALSE, sizeof (struct _imapx_flag_change));
+ user_set = off_user;
+ user_flag = suflags;
+ suflags = suflags->next;
+ }
+
+ /* Could sort this and binary search */
+ for (j = 0; j < user_set->len; j++) {
+ change = &g_array_index (user_set, struct _imapx_flag_change, j);
+ if (strcmp (change->name, user_flag->name) == 0)
+ goto found;
+ }
+ add.name = g_strdup (user_flag->name);
+ add.infos = g_ptr_array_new ();
+ g_array_append_val (user_set, add);
+ change = &add;
+ found:
+ camel_message_info_ref (info);
+ g_ptr_array_add (change->infos, info);
+ }
+ }
+
+ camel_message_info_unref (info);
+ }
+
+ nothing_to_do =
+ (on_orset == 0) &&
+ (off_orset == 0) &&
+ (on_user == NULL) &&
+ (off_user == NULL);
+
+ if (nothing_to_do) {
+ g_signal_handler_disconnect (folder->summary, changed_meanwhile_handler_id);
+
+ imapx_sync_free_user (on_user);
+ imapx_sync_free_user (off_user);
+ imapx_unset_folder_flagged_flag (folder->summary, changed_uids, remove_deleted_flags);
+ camel_folder_free_uids (folder, changed_uids);
+ g_hash_table_destroy (changed_meanwhile);
+ g_object_unref (folder);
+ return TRUE;
+ }
+
+ if (!camel_imapx_server_ensure_selected_sync (is, mailbox, cancellable, error)) {
+ g_signal_handler_disconnect (folder->summary, changed_meanwhile_handler_id);
+
+ imapx_sync_free_user (on_user);
+ imapx_sync_free_user (off_user);
+ camel_folder_free_uids (folder, changed_uids);
+ g_hash_table_destroy (changed_meanwhile);
+ g_object_unref (folder);
+ return FALSE;
+ }
+
+ permanentflags = camel_imapx_mailbox_get_permanentflags (mailbox);
+
+ success = TRUE;
+ for (on = 0; on < 2 && success; on++) {
+ guint32 orset = on ? on_orset : off_orset;
+ GArray *user_set = on ? on_user : off_user;
+
+ for (jj = 0; jj < G_N_ELEMENTS (flags_table) && success; jj++) {
+ guint32 flag = flags_table[jj].flag;
+ CamelIMAPXCommand *ic = NULL;
+
+ if ((orset & flag) == 0)
+ continue;
+
+ c (is->priv->tagprefix, "checking/storing %s flags '%s'\n", on ? "on" : "off", flags_table[jj].name);
+ imapx_uidset_init (&uidset, 0, 100);
+ for (i = 0; i < changed_uids->len && success; i++) {
+ CamelIMAPXMessageInfo *info;
+ gboolean remove_deleted_flag;
+ guint32 flags;
+ guint32 sflags;
+ gint send;
+
+ info = (CamelIMAPXMessageInfo *)
+ camel_folder_summary_get (
+ folder->summary,
+ changed_uids->pdata[i]);
+
+ if (info == NULL)
+ continue;
+
+ flags = (info->info.flags & CAMEL_IMAPX_SERVER_FLAGS) & permanentflags;
+ sflags = (info->server_flags & CAMEL_IMAPX_SERVER_FLAGS) & permanentflags;
+ send = 0;
+
+ remove_deleted_flag =
+ remove_deleted_flags &&
+ (flags & CAMEL_MESSAGE_DELETED);
+
+ if (remove_deleted_flag) {
+ /* Remove the DELETED flag so the
+ * message appears normally in the
+ * real Trash folder when copied. */
+ flags &= ~CAMEL_MESSAGE_DELETED;
+ }
+
+ if ( (on && (((flags ^ sflags) & flags) & flag))
+ || (!on && (((flags ^ sflags) & ~flags) & flag))) {
+ if (ic == NULL) {
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_SYNC_CHANGES, "UID STORE ");
+ }
+ send = imapx_uidset_add (&uidset, ic, camel_message_info_get_uid (info));
+ }
+ if (send == 1 || (i == changed_uids->len - 1 && ic && imapx_uidset_done (&uidset, ic))) {
+ camel_imapx_command_add (ic, " %tFLAGS.SILENT (%t)", on ? "+" : "-", flags_table[jj].name);
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error syncing changes"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+ ic = NULL;
+
+ if (!success)
+ break;
+ }
+ if (flag == CAMEL_MESSAGE_SEEN) {
+ /* Remember how the server's unread count will change if this
+ * command succeeds */
+ if (on)
+ unread_change--;
+ else
+ unread_change++;
+ }
+
+ /* The second round and the server doesn't support saving user flags,
+ thus store them at least locally */
+ if (on && (permanentflags & CAMEL_MESSAGE_USER) == 0) {
+ camel_flag_list_copy (&info->server_user_flags, &info->info.user_flags);
+ }
+
+ camel_message_info_unref (info);
+ }
+
+ g_warn_if_fail (ic == NULL);
+ }
+
+ if (user_set && (permanentflags & CAMEL_MESSAGE_USER) != 0 && success) {
+ CamelIMAPXCommand *ic = NULL;
+
+ for (jj = 0; jj < user_set->len && success; jj++) {
+ struct _imapx_flag_change *c = &g_array_index (user_set, struct _imapx_flag_change, jj);
+
+ imapx_uidset_init (&uidset, 0, 100);
+ for (i = 0; i < c->infos->len; i++) {
+ CamelIMAPXMessageInfo *info = c->infos->pdata[i];
+
+ if (ic == NULL)
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_SYNC_CHANGES, "UID STORE ");
+
+ if (imapx_uidset_add (&uidset, ic, camel_message_info_get_uid (info)) == 1
+ || (i == c->infos->len - 1 && imapx_uidset_done (&uidset, ic))) {
+ gchar *utf7;
+
+ utf7 = camel_utf8_utf7 (c->name);
+
+ camel_imapx_command_add (ic, " %tFLAGS.SILENT (%t)", on ? "+" : "-", utf7 ? utf7 : c->name);
+
+ g_free (utf7);
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error syncing changes"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+ ic = NULL;
+
+ if (!success)
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ g_signal_handler_disconnect (folder->summary, changed_meanwhile_handler_id);
+
+ if (success) {
+ CamelStore *parent_store;
+ guint32 unseen;
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ camel_folder_summary_lock (folder->summary);
+
+ for (i = 0; i < changed_uids->len; i++) {
+ CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) camel_folder_summary_get (folder->summary,
+ changed_uids->pdata[i]);
+
+ if (!xinfo)
+ continue;
+
+ xinfo->server_flags = xinfo->info.flags & CAMEL_IMAPX_SERVER_FLAGS;
+ if (!remove_deleted_flags ||
+ !(xinfo->info.flags & CAMEL_MESSAGE_DELETED)) {
+ xinfo->info.flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
+ } else {
+ /* to stare back the \Deleted flag */
+ xinfo->server_flags &= ~CAMEL_MESSAGE_DELETED;
+ xinfo->info.flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
+ }
+ xinfo->info.dirty = TRUE;
+ if ((permanentflags & CAMEL_MESSAGE_USER) != 0 ||
+ camel_flag_list_size (&xinfo->server_user_flags) == 0)
+ camel_flag_list_copy (&xinfo->server_user_flags, &xinfo->info.user_flags);
+
+ if (g_hash_table_lookup (changed_meanwhile, changed_uids->pdata[i]))
+ xinfo->info.flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
+
+ camel_folder_summary_touch (folder->summary);
+ camel_message_info_unref (xinfo);
+ }
+
+ camel_folder_summary_unlock (folder->summary);
+
+ /* Apply the changes to server-side unread count; it won't tell
+ * us of these changes, of course. */
+ unseen = camel_imapx_mailbox_get_unseen (mailbox);
+ unseen += unread_change;
+ camel_imapx_mailbox_set_unseen (mailbox, unseen);
+
+ if (folder->summary && (folder->summary->flags & CAMEL_FOLDER_SUMMARY_DIRTY) != 0) {
+ CamelStoreInfo *si;
+
+ /* ... and store's summary when folder's summary is dirty */
+ si = camel_store_summary_path (CAMEL_IMAPX_STORE (parent_store)->summary, camel_folder_get_full_name (folder));
+ if (si) {
+ if (si->total != camel_folder_summary_get_saved_count (folder->summary) ||
+ si->unread != camel_folder_summary_get_unread_count (folder->summary)) {
+ si->total = camel_folder_summary_get_saved_count (folder->summary);
+ si->unread = camel_folder_summary_get_unread_count (folder->summary);
+ camel_store_summary_touch (CAMEL_IMAPX_STORE (parent_store)->summary);
+ }
+
+ camel_store_summary_info_unref (CAMEL_IMAPX_STORE (parent_store)->summary, si);
+ }
+ }
+
+ camel_folder_summary_save_to_db (folder->summary, NULL);
+ camel_store_summary_save (CAMEL_IMAPX_STORE (parent_store)->summary);
+ }
+
+ imapx_sync_free_user (on_user);
+ imapx_sync_free_user (off_user);
+ camel_folder_free_uids (folder, changed_uids);
+ g_hash_table_destroy (changed_meanwhile);
+ g_object_unref (folder);
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_expunge_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ folder = imapx_server_ref_folder (is, mailbox);
+ g_return_val_if_fail (folder != NULL, FALSE);
+
+ success = camel_imapx_server_ensure_selected_sync (is, mailbox, cancellable, error);
+
+ if (success) {
+ CamelIMAPXCommand *ic;
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_EXPUNGE, "EXPUNGE");
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error expunging message"), cancellable, error);
+ if (success) {
+ GPtrArray *uids;
+ CamelStore *parent_store;
+ const gchar *full_name;
+
+ full_name = camel_folder_get_full_name (folder);
+ parent_store = camel_folder_get_parent_store (folder);
+
+ camel_folder_summary_lock (folder->summary);
+
+ camel_folder_summary_save_to_db (folder->summary, NULL);
+ uids = camel_db_get_folder_deleted_uids (parent_store->cdb_r, full_name, NULL);
+
+ if (uids && uids->len) {
+ CamelFolderChangeInfo *changes;
+ GList *removed = NULL;
+ gint i;
+
+ changes = camel_folder_change_info_new ();
+ for (i = 0; i < uids->len; i++) {
+ camel_folder_change_info_remove_uid (changes, uids->pdata[i]);
+ removed = g_list_prepend (removed, (gpointer) uids->pdata[i]);
+ }
+
+ camel_folder_summary_remove_uids (folder->summary, removed);
+ camel_folder_summary_save_to_db (folder->summary, NULL);
+
+ camel_folder_changed (folder, changes);
+ camel_folder_change_info_free (changes);
+
+ g_list_free (removed);
+ g_ptr_array_foreach (uids, (GFunc) camel_pstring_free, NULL);
+ }
+
+ if (uids)
+ g_ptr_array_free (uids, TRUE);
+
+ camel_folder_summary_unlock (folder->summary);
+ }
+
+ camel_imapx_command_unref (ic);
+ }
+
+ g_clear_object (&folder);
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_list_sync (CamelIMAPXServer *is,
+ const gchar *pattern,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommand *ic;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (pattern != NULL, FALSE);
+
+ if (is->priv->list_return_opts != NULL) {
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_LIST, "LIST \"\" %s RETURN (%t)",
+ pattern, is->priv->list_return_opts);
+ } else {
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_LIST, "LIST \"\" %s",
+ pattern);
+ }
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error fetching folders"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+
+ if (!success)
+ return FALSE;
+
+ if (!is->priv->list_return_opts) {
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_LSUB, "LSUB \"\" %s",
+ pattern);
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error fetching subscribed folders"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+ }
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_create_mailbox_sync (CamelIMAPXServer *is,
+ const gchar *mailbox_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommand *ic;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (mailbox_name != NULL, FALSE);
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_CREATE_MAILBOX, "CREATE %m", mailbox_name);
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error creating folder"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+
+ if (success) {
+ gchar *utf7_pattern;
+
+ utf7_pattern = camel_utf8_utf7 (mailbox_name);
+
+ /* List the new mailbox so we trigger our untagged
+ * LIST handler. This simulates being notified of
+ * a newly-created mailbox, so we can just let the
+ * callback functions handle the bookkeeping. */
+ success = camel_imapx_server_list_sync (is, utf7_pattern, 0, cancellable, error);
+
+ g_free (utf7_pattern);
+ }
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_delete_mailbox_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommand *ic;
+ CamelIMAPXMailbox *inbox;
+ CamelIMAPXStore *imapx_store;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ /* Avoid camel_imapx_job_set_mailbox() here. We
+ * don't want to select the mailbox to be deleted. */
+
+ imapx_store = camel_imapx_server_ref_store (is);
+ /* Keep going, even if this returns NULL. */
+ inbox = camel_imapx_store_ref_mailbox (imapx_store, "INBOX");
+
+ /* Make sure the to-be-deleted folder is not
+ * selected by selecting INBOX for this operation. */
+ success = camel_imapx_server_ensure_selected_sync (is, inbox, cancellable, error);
+ if (!success) {
+ g_clear_object (&inbox);
+ g_clear_object (&imapx_store);
+ return FALSE;
+ }
+
+ /* Just to make sure it'll not disappeare before the end of this function */
+ g_object_ref (mailbox);
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_DELETE_MAILBOX, "DELETE %M", mailbox);
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error deleting folder"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+
+ if (success) {
+ camel_imapx_mailbox_deleted (mailbox);
+ camel_imapx_store_emit_mailbox_updated (imapx_store, mailbox);
+ }
+
+ g_clear_object (&inbox);
+ g_clear_object (&imapx_store);
+ g_clear_object (&mailbox);
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_rename_mailbox_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ const gchar *new_mailbox_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommand *ic;
+ CamelIMAPXMailbox *inbox;
+ CamelIMAPXStore *imapx_store;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+ g_return_val_if_fail (new_mailbox_name != NULL, FALSE);
+
+ imapx_store = camel_imapx_server_ref_store (is);
+ inbox = camel_imapx_store_ref_mailbox (imapx_store, "INBOX");
+ g_return_val_if_fail (inbox != NULL, FALSE);
+
+ /* We don't want to select the mailbox to be renamed. */
+ success = camel_imapx_server_ensure_selected_sync (is, inbox, cancellable, error);
+ if (!success) {
+ g_clear_object (&inbox);
+ g_clear_object (&imapx_store);
+ return FALSE;
+ }
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_RENAME_MAILBOX, "RENAME %M %m", mailbox, new_mailbox_name);
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error renaming folder"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+
+ if (success) {
+ /* Perform the same processing as imapx_untagged_list()
+ * would if the server notified us of a renamed mailbox. */
+
+ camel_imapx_store_handle_mailbox_rename (imapx_store, mailbox, new_mailbox_name);
+ }
+
+ g_clear_object (&inbox);
+ g_clear_object (&imapx_store);
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_subscribe_mailbox_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommand *ic;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ /* We don't want to select the mailbox to be subscribed. */
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_SUBSCRIBE_MAILBOX, "SUBSCRIBE %M", mailbox);
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error subscribing to folder"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+
+ if (success) {
+ CamelIMAPXStore *imapx_store;
+
+ /* Perform the same processing as imapx_untagged_list()
+ * would if the server notified us of a subscription. */
+
+ imapx_store = camel_imapx_server_ref_store (is);
+
+ camel_imapx_mailbox_subscribed (mailbox);
+ camel_imapx_store_emit_mailbox_updated (imapx_store, mailbox);
+
+ g_clear_object (&imapx_store);
+ }
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_unsubscribe_mailbox_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommand *ic;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ /* We don't want to select the mailbox to be unsubscribed. */
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_UNSUBSCRIBE_MAILBOX, "UNSUBSCRIBE %M", mailbox);
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error unsubscribing from folder"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+
+ if (success) {
+ CamelIMAPXStore *imapx_store;
+
+ /* Perform the same processing as imapx_untagged_list()
+ * would if the server notified us of an unsubscription. */
+
+ imapx_store = camel_imapx_server_ref_store (is);
+
+ camel_imapx_mailbox_unsubscribed (mailbox);
+ camel_imapx_store_emit_mailbox_updated (imapx_store, mailbox);
+
+ g_clear_object (&imapx_store);
+ }
+
+ return success;
+}
+
+gboolean
+camel_imapx_server_update_quota_info_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommand *ic;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ if (CAMEL_IMAPX_LACK_CAPABILITY (is->priv->cinfo, QUOTA)) {
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ g_set_error_literal (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("IMAP server does not support quotas"));
+ return FALSE;
+ } else {
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+
+ success = camel_imapx_server_ensure_selected_sync (is, mailbox, cancellable, error);
+ if (!success)
+ return FALSE;
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_UPDATE_QUOTA_INFO, "GETQUOTAROOT %M", mailbox);
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error retrieving quota information"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+
+ return success;
+}
+
+GPtrArray *
+camel_imapx_server_uid_search_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ const gchar *criteria_prefix,
+ const gchar *search_key,
+ const gchar * const *words,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXCommand *ic;
+ GArray *uid_search_results;
+ GPtrArray *results = NULL;
+ gint ii;
+ gboolean need_charset = FALSE;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), NULL);
+ g_return_val_if_fail (criteria_prefix != NULL, NULL);
+
+ success = camel_imapx_server_ensure_selected_sync (is, mailbox, cancellable, error);
+ if (!success)
+ return FALSE;
+
+ for (ii = 0; !need_charset && words && words[ii]; ii++) {
+ need_charset = !imapx_util_all_is_ascii (words[ii]);
+ }
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_UID_SEARCH, "UID SEARCH");
+ if (need_charset)
+ camel_imapx_command_add (ic, " CHARSET UTF-8");
+ if (criteria_prefix && *criteria_prefix)
+ camel_imapx_command_add (ic, " %t", criteria_prefix);
+
+ if (search_key && words) {
+ for (ii = 0; words[ii]; ii++) {
+ camel_imapx_command_add (ic, " %t %s", search_key, words[ii]);
+ }
+ }
+
+ success = camel_imapx_server_process_command_sync (is, ic, _("Search failed"), cancellable, error);
+
+ camel_imapx_command_unref (ic);
+
+ g_mutex_lock (&is->priv->search_results_lock);
+ uid_search_results = is->priv->search_results;
+ is->priv->search_results = NULL;
+ g_mutex_unlock (&is->priv->search_results_lock);
+
+ if (success) {
+ guint ii;
+
+ /* Convert the numeric UIDs to strings. */
+
+ g_return_val_if_fail (uid_search_results != NULL, NULL);
+
+ results = g_ptr_array_new_full (uid_search_results->len, (GDestroyNotify) camel_pstring_free);
+
+ for (ii = 0; ii < uid_search_results->len; ii++) {
+ const gchar *pooled_uid;
+ guint64 numeric_uid;
+ gchar *alloced_uid;
+
+ numeric_uid = g_array_index (uid_search_results, guint64, ii);
+ alloced_uid = g_strdup_printf ("%" G_GUINT64_FORMAT, numeric_uid);
+ pooled_uid = camel_pstring_add (alloced_uid, TRUE);
+ g_ptr_array_add (results, (gpointer) pooled_uid);
+ }
+ }
+
+ if (uid_search_results)
+ g_array_unref (uid_search_results);
+
+ return results;
+}
+
+typedef struct _IdleThreadData {
+ CamelIMAPXServer *is;
+ GCancellable *idle_cancellable;
+ gint idle_stamp;
+} IdleThreadData;
+
+static gpointer
+imapx_server_idle_thread (gpointer user_data)
+{
+ IdleThreadData *itd = user_data;
+ CamelIMAPXServer *is;
+ CamelIMAPXMailbox *mailbox;
+ CamelIMAPXCommand *ic;
+ CamelIMAPXCommandPart *cp;
+ GCancellable *idle_cancellable;
+ GError *local_error = NULL;
+ gint previous_timeout = -1;
+ gboolean success = FALSE;
+ gboolean rather_disconnect = FALSE;
+
+ g_return_val_if_fail (itd != NULL, NULL);
+
+ is = itd->is;
+ idle_cancellable = itd->idle_cancellable;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
+ g_return_val_if_fail (G_IS_CANCELLABLE (idle_cancellable), NULL);
+
+ g_mutex_lock (&is->priv->idle_lock);
+
+ if (g_cancellable_is_cancelled (idle_cancellable) ||
+ is->priv->idle_stamp != itd->idle_stamp ||
+ is->priv->idle_state != IMAPX_IDLE_STATE_SCHEDULED) {
+ g_cond_broadcast (&is->priv->idle_cond);
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ g_clear_object (&itd->is);
+ g_clear_object (&itd->idle_cancellable);
+ g_free (itd);
+
+ return NULL;
+ }
+
+ is->priv->idle_state = IMAPX_IDLE_STATE_PREPARING;
+ g_cond_broadcast (&is->priv->idle_cond);
+
+ mailbox = is->priv->idle_mailbox;
+ if (mailbox)
+ g_object_ref (mailbox);
+
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ if (!mailbox)
+ mailbox = camel_imapx_server_ref_selected (is);
+
+ if (!mailbox)
+ goto exit;
+
+ success = camel_imapx_server_ensure_selected_sync (is, mailbox, idle_cancellable, &local_error);
+ if (!success) {
+ rather_disconnect = TRUE;
+ goto exit;
+ }
+
+ ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_IDLE, "IDLE");
+ camel_imapx_command_close (ic);
+
+ cp = g_queue_peek_head (&ic->parts);
+ cp->type |= CAMEL_IMAPX_COMMAND_CONTINUATION;
+
+ g_mutex_lock (&is->priv->stream_lock);
+ /* Set the connection timeout to one minute more than the inactivity timeout */
+ if (is->priv->connection)
+ previous_timeout = imapx_server_set_connection_timeout (is->priv->connection, INACTIVITY_TIMEOUT_SECONDS + 60);
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ g_mutex_lock (&is->priv->idle_lock);
+ if (is->priv->idle_stamp == itd->idle_stamp &&
+ is->priv->idle_state == IMAPX_IDLE_STATE_PREPARING) {
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ /* Blocks, until the DONE is issued or on inactivity timeout, error, ... */
+ success = camel_imapx_server_process_command_sync (is, ic, _("Error running IDLE"), idle_cancellable, &local_error);
+
+ rather_disconnect = rather_disconnect || !success || g_cancellable_is_cancelled (idle_cancellable);
+ } else {
+ g_mutex_unlock (&is->priv->idle_lock);
+ }
+
+ if (previous_timeout >= 0) {
+ g_mutex_lock (&is->priv->stream_lock);
+ if (is->priv->connection)
+ imapx_server_set_connection_timeout (is->priv->connection, previous_timeout);
+ g_mutex_unlock (&is->priv->stream_lock);
+ }
+
+ camel_imapx_command_unref (ic);
+
+ exit:
+ g_mutex_lock (&is->priv->idle_lock);
+ g_clear_object (&is->priv->idle_cancellable);
+ is->priv->idle_state = IMAPX_IDLE_STATE_OFF;
+ g_cond_broadcast (&is->priv->idle_cond);
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ if (success)
+ c (camel_imapx_server_get_tagprefix (is), "IDLE finished successfully\n");
+ else if (local_error)
+ c (camel_imapx_server_get_tagprefix (is), "IDLE finished with error: %s%s\n", local_error->message, rather_disconnect ? "; rather disconnect" : "");
+ else
+ c (camel_imapx_server_get_tagprefix (is), "IDLE finished without error%s\n", rather_disconnect ? "; rather disconnect" : "");
+
+ if (rather_disconnect) {
+ imapx_disconnect (is);
+ }
+
+ g_clear_object (&mailbox);
+ g_clear_error (&local_error);
+
+ g_clear_object (&itd->is);
+ g_clear_object (&itd->idle_cancellable);
+ g_free (itd);
+
+ return NULL;
+}
+
+static gboolean
+imapx_server_run_idle_thread_cb (gpointer user_data)
+{
+ GWeakRef *is_weakref = user_data;
+ CamelIMAPXServer *is;
+
+ g_return_val_if_fail (is_weakref != NULL, FALSE);
+
+ is = g_weak_ref_get (is_weakref);
+ if (!is)
+ return FALSE;
+
+ g_mutex_lock (&is->priv->idle_lock);
+
+ if (g_main_current_source () == is->priv->idle_pending) {
+ if (!g_source_is_destroyed (g_main_current_source ()) &&
+ is->priv->idle_state == IMAPX_IDLE_STATE_SCHEDULED) {
+ IdleThreadData *itd;
+ GThread *thread;
+ GError *local_error = NULL;
+
+ itd = g_new0 (IdleThreadData, 1);
+ itd->is = g_object_ref (is);
+ itd->idle_cancellable = g_object_ref (is->priv->idle_cancellable);
+ itd->idle_stamp = is->priv->idle_stamp;
+
+ thread = g_thread_try_new (NULL, imapx_server_idle_thread, itd, &local_error);
+ if (thread) {
+ g_thread_unref (thread);
+ } else {
+ g_warning ("%s: Failed to create IDLE thread: %s", G_STRFUNC, local_error ? local_error->message : "Unknown error");
+
+ g_clear_object (&itd->is);
+ g_clear_object (&itd->idle_cancellable);
+ g_free (itd);
+ }
+
+ g_clear_error (&local_error);
+ }
+
+ g_source_unref (is->priv->idle_pending);
+ is->priv->idle_pending = NULL;
+ }
+
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ return FALSE;
+}
+
+gboolean
+camel_imapx_server_can_use_idle (CamelIMAPXServer *is)
+{
+ gboolean use_idle = FALSE;
+
+ g_mutex_lock (&is->priv->stream_lock);
+
+ /* No need for IDLE if the server supports NOTIFY. */
+ if (CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, NOTIFY)) {
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ return FALSE;
+ }
+
+ if (CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, IDLE)) {
+ CamelIMAPXSettings *settings;
+
+ settings = camel_imapx_server_ref_settings (is);
+ use_idle = camel_imapx_settings_get_use_idle (settings);
+ g_object_unref (settings);
+ }
+
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ return use_idle;
+}
+
+gboolean
+camel_imapx_server_is_in_idle (CamelIMAPXServer *is)
+{
+ gboolean in_idle;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ g_mutex_lock (&is->priv->idle_lock);
+ in_idle = is->priv->idle_state != IMAPX_IDLE_STATE_OFF;
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ return in_idle;
+}
+
+CamelIMAPXMailbox *
+camel_imapx_server_ref_idle_mailbox (CamelIMAPXServer *is)
+{
+ CamelIMAPXMailbox *mailbox = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
+
+ g_mutex_lock (&is->priv->idle_lock);
+
+ if (is->priv->idle_state != IMAPX_IDLE_STATE_OFF) {
+ if (is->priv->idle_mailbox)
+ mailbox = g_object_ref (is->priv->idle_mailbox);
+ else
+ mailbox = camel_imapx_server_ref_selected (is);
+ }
+
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ return mailbox;
+}
+
+gboolean
+camel_imapx_server_schedule_idle_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+ if (mailbox)
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
+
+ if (!camel_imapx_server_stop_idle_sync (is, cancellable, error))
+ return FALSE;
+
+ if (!camel_imapx_server_can_use_idle (is))
+ return TRUE;
+
+ g_mutex_lock (&is->priv->idle_lock);
+
+ if (is->priv->idle_state != IMAPX_IDLE_STATE_OFF) {
+ g_warn_if_fail (is->priv->idle_state == IMAPX_IDLE_STATE_OFF);
+
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ return FALSE;
+ }
+
+ g_warn_if_fail (is->priv->idle_cancellable == NULL);
+
+ is->priv->idle_cancellable = g_cancellable_new ();
+ is->priv->idle_stamp++;
+
+ if (is->priv->idle_pending) {
+ g_source_destroy (is->priv->idle_pending);
+ g_source_unref (is->priv->idle_pending);
+ }
+
+ g_clear_object (&is->priv->idle_mailbox);
+ if (mailbox)
+ is->priv->idle_mailbox = g_object_ref (mailbox);
+
+ is->priv->idle_state = IMAPX_IDLE_STATE_SCHEDULED;
+ is->priv->idle_pending = g_timeout_source_new_seconds (IMAPX_IDLE_WAIT_SECONDS);
+ g_source_set_callback (
+ is->priv->idle_pending, imapx_server_run_idle_thread_cb,
+ imapx_weak_ref_new (is), (GDestroyNotify) imapx_weak_ref_free);
+ g_source_attach (is->priv->idle_pending, NULL);
+
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ return TRUE;
+}
+
+static void
+imapx_server_wait_idle_stop_cancelled_cb (GCancellable *cancellable,
+ gpointer user_data)
+{
+ CamelIMAPXServer *is = user_data;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_SERVER (is));
+
+ g_mutex_lock (&is->priv->idle_lock);
+ g_cond_broadcast (&is->priv->idle_cond);
+ g_mutex_unlock (&is->priv->idle_lock);
+}
+
+gboolean
+camel_imapx_server_stop_idle_sync (CamelIMAPXServer *is,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GCancellable *idle_cancellable;
+ gulong handler_id = 0;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ g_mutex_lock (&is->priv->idle_lock);
+
+ if (is->priv->idle_state == IMAPX_IDLE_STATE_OFF) {
+ g_mutex_unlock (&is->priv->idle_lock);
+ return TRUE;
+ } else if (is->priv->idle_state == IMAPX_IDLE_STATE_SCHEDULED) {
+ if (is->priv->idle_pending) {
+ g_source_destroy (is->priv->idle_pending);
+ g_source_unref (is->priv->idle_pending);
+ is->priv->idle_pending = NULL;
+ }
+
+ is->priv->idle_state = IMAPX_IDLE_STATE_OFF;
+ g_cond_broadcast (&is->priv->idle_cond);
+ }
+
+ idle_cancellable = is->priv->idle_cancellable ? g_object_ref (is->priv->idle_cancellable) : NULL;
+
+ g_clear_object (&is->priv->idle_cancellable);
+ g_clear_object (&is->priv->idle_mailbox);
+ is->priv->idle_stamp++;
+
+ if (cancellable) {
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ /* Do not hold the idle_lock here, because the callback can be called
+ immediately, which leads to a deadlock inside it. */
+ handler_id = g_cancellable_connect (cancellable, G_CALLBACK (imapx_server_wait_idle_stop_cancelled_cb), is, NULL);
+
+ g_mutex_lock (&is->priv->idle_lock);
+ }
+
+ while (is->priv->idle_state == IMAPX_IDLE_STATE_PREPARING &&
+ !g_cancellable_is_cancelled (cancellable)) {
+ g_cond_wait (&is->priv->idle_cond, &is->priv->idle_lock);
+ }
+
+ if (is->priv->idle_state == IMAPX_IDLE_STATE_RUNNING &&
+ !g_cancellable_is_cancelled (cancellable)) {
+ is->priv->idle_state = IMAPX_IDLE_STATE_STOPPING;
+ g_cond_broadcast (&is->priv->idle_cond);
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ g_mutex_lock (&is->priv->stream_lock);
+ if (is->priv->output_stream) {
+ gint previous_timeout = -1;
+
+ /* Set the connection timeout to some short time, no need to wait for it for too long */
+ if (is->priv->connection)
+ previous_timeout = imapx_server_set_connection_timeout (is->priv->connection, 5);
+
+ success = g_output_stream_flush (is->priv->output_stream, cancellable, error);
+ success = success && g_output_stream_write_all (is->priv->output_stream, "DONE\r\n", 6, NULL, cancellable, error);
+ success = success && g_output_stream_flush (is->priv->output_stream, cancellable, error);
+
+ if (previous_timeout >= 0 && is->priv->connection)
+ imapx_server_set_connection_timeout (is->priv->connection, previous_timeout);
+ } else {
+ success = FALSE;
+
+ /* This message won't get into UI. */
+ g_set_error_literal (error, CAMEL_IMAPX_SERVER_ERROR, CAMEL_IMAPX_SERVER_ERROR_TRY_RECONNECT,
+ "Reconnect after couldn't issue DONE command");
+ }
+ g_mutex_unlock (&is->priv->stream_lock);
+ g_mutex_lock (&is->priv->idle_lock);
+ }
+
+ while (success && is->priv->idle_state != IMAPX_IDLE_STATE_OFF &&
+ !g_cancellable_is_cancelled (cancellable)) {
+ g_cond_wait (&is->priv->idle_cond, &is->priv->idle_lock);
+ }
+
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ if (cancellable && handler_id)
+ g_cancellable_disconnect (cancellable, handler_id);
+
+ if (success && g_cancellable_is_cancelled (cancellable)) {
+ g_clear_error (error);
+
+ success = FALSE;
+
+ /* This message won't get into UI. */
+ g_set_error_literal (error, CAMEL_IMAPX_SERVER_ERROR, CAMEL_IMAPX_SERVER_ERROR_TRY_RECONNECT,
+ "Reconnect after cancelled IDLE stop command");
+ }
+
+ if (!success) {
+ if (idle_cancellable)
+ g_cancellable_cancel (idle_cancellable);
+
+ g_mutex_lock (&is->priv->idle_lock);
+ is->priv->idle_state = IMAPX_IDLE_STATE_OFF;
+ g_mutex_unlock (&is->priv->idle_lock);
+
+ imapx_disconnect (is);
+ }
+
+ g_clear_object (&idle_cancellable);
+
+ return success;
+}
+
+/**
+ * camel_imapx_server_register_untagged_handler:
+ * @is: a #CamelIMAPXServer instance
+ * @untagged_response: a string representation of the IMAP
+ * untagged response code. Must be
+ * all-uppercase with underscores allowed
+ * (see RFC 3501)
+ * @desc: a #CamelIMAPXUntaggedRespHandlerDesc handler description
+ * structure. The descriptor structure is expected to
+ * remain stable over the lifetime of the #CamelIMAPXServer
+ * instance it was registered with. It is the responsibility
+ * of the caller to ensure this
+ *
+ * Register a new handler function for IMAP untagged responses.
+ * Pass in a NULL descriptor to delete an existing handler (the
+ * untagged response will remain known, but will no longer be acted
+ * upon if the handler is deleted). The return value is intended
+ * to be used in cases where e.g. an extension to existing handler
+ * code is implemented with just some new code to be run before
+ * or after the original handler code
+ *
+ * Returns: the #CamelIMAPXUntaggedRespHandlerDesc previously
+ * registered for this untagged response, if any,
+ * NULL otherwise.
+ *
+ * Since: 3.6
+ */
+const CamelIMAPXUntaggedRespHandlerDesc *
+camel_imapx_server_register_untagged_handler (CamelIMAPXServer *is,
+ const gchar *untagged_response,
+ const CamelIMAPXUntaggedRespHandlerDesc *desc)
+{
+ const CamelIMAPXUntaggedRespHandlerDesc *previous = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
+ g_return_val_if_fail (untagged_response != NULL, NULL);
+ /* desc may be NULL */
+
+ previous = replace_untagged_descriptor (
+ is->priv->untagged_handlers,
+ untagged_response, desc);
+
+ return previous;
+}
+
+/* This function is not thread-safe. */
+const struct _capability_info *
+camel_imapx_server_get_capability_info (CamelIMAPXServer *is)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
+
+ return is->priv->cinfo;
+}
+
+gboolean
+camel_imapx_server_have_capability (CamelIMAPXServer *is,
+ guint32 capability)
+{
+ gboolean have;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ g_mutex_lock (&is->priv->stream_lock);
+ have = is->priv->cinfo != NULL && (is->priv->cinfo->capa & capability) != 0;
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ return have;
+}
+
+gboolean
+camel_imapx_server_lack_capability (CamelIMAPXServer *is,
+ guint32 capability)
+{
+ gboolean lack;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+ g_mutex_lock (&is->priv->stream_lock);
+ lack = is->priv->cinfo != NULL && (is->priv->cinfo->capa & capability) == 0;
+ g_mutex_unlock (&is->priv->stream_lock);
+
+ return lack;
+}
+
+gchar
+camel_imapx_server_get_tagprefix (CamelIMAPXServer *is)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), 0);
+
+ return is->priv->tagprefix;
+}
+
+void
+camel_imapx_server_set_tagprefix (CamelIMAPXServer *is,
+ gchar tagprefix)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SERVER (is));
+ g_return_if_fail ((tagprefix >= 'A' && tagprefix <= 'Z') || (tagprefix >= 'a' && tagprefix <= 'z'));
+
+ is->priv->tagprefix = tagprefix;
+}
+
+CamelIMAPXCommand *
+camel_imapx_server_ref_current_command (CamelIMAPXServer *is)
+{
+ CamelIMAPXCommand *command;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
+
+ COMMAND_LOCK (is);
+
+ command = is->priv->current_command;
+ if (command)
+ camel_imapx_command_ref (command);
+
+ COMMAND_UNLOCK (is);
+
+ return command;
+}
diff --git a/src/camel/providers/imapx/camel-imapx-server.h b/src/camel/providers/imapx/camel-imapx-server.h
new file mode 100644
index 000000000..16d5090c6
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-server.h
@@ -0,0 +1,304 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_SERVER_H
+#define CAMEL_IMAPX_SERVER_H
+
+#include <camel/camel.h>
+
+#include "camel-imapx-command.h"
+#include "camel-imapx-mailbox.h"
+#include "camel-imapx-namespace-response.h"
+#include "camel-imapx-store-summary.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_SERVER \
+ (camel_imapx_server_get_type ())
+#define CAMEL_IMAPX_SERVER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_SERVER, CamelIMAPXServer))
+#define CAMEL_IMAPX_SERVER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_SERVER, CamelIMAPXServerClass))
+#define CAMEL_IS_IMAPX_SERVER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_SERVER))
+#define CAMEL_IS_IMAPX_SERVER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_SERVER))
+#define CAMEL_IMAPX_SERVER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_SERVER, CamelIMAPXServerClass))
+
+#define CAMEL_IMAPX_SERVER_ERROR (camel_imapx_server_error_quark ())
+
+G_BEGIN_DECLS
+
+typedef enum {
+ CAMEL_IMAPX_SERVER_ERROR_CONCURRENT_CONNECT_FAILED,
+ CAMEL_IMAPX_SERVER_ERROR_TRY_RECONNECT
+} CamelIMAPXServerError;
+
+GQuark camel_imapx_server_error_quark (void) G_GNUC_CONST;
+
+/* Avoid a circular reference. */
+struct _CamelIMAPXStore;
+struct _CamelIMAPXSettings;
+struct _CamelIMAPXJob;
+
+typedef struct _CamelIMAPXServer CamelIMAPXServer;
+typedef struct _CamelIMAPXServerClass CamelIMAPXServerClass;
+typedef struct _CamelIMAPXServerPrivate CamelIMAPXServerPrivate;
+
+/* untagged response handling */
+typedef gboolean
+ (*CamelIMAPXUntaggedRespHandler)
+ (CamelIMAPXServer *server,
+ GInputStream *input_stream,
+ GCancellable *cancellable,
+ GError **error);
+
+/**
+ * CamelIMAPXUntaggedRespHandlerDesc:
+ * @untagged_response: a string representation of the IMAP
+ * untagged response code. Must be
+ * all-uppercase with underscores allowed
+ * (see RFC 3501)
+ * @handler: an untagged response handler function for #CamelIMAPXServer
+ * @next_response: the IMAP untagged code to call a registered
+ * handler for directly after successfully
+ * running @handler. If not NULL, @skip_stream_when_done
+ * for the current handler has no effect
+ * @skip_stream_when_done: whether or not to skip the current IMAP
+ * untagged response in the #GInputStream.
+ * Set to TRUE if your handler does not eat
+ * the stream up to the next response token
+ *
+ * IMAP untagged response handler function descriptor. Use in conjunction
+ * with camel_imapx_server_register_untagged_handler() to register a new
+ * handler function for a given untagged response code
+ *
+ * Since: 3.6
+ */
+typedef struct _CamelIMAPXUntaggedRespHandlerDesc CamelIMAPXUntaggedRespHandlerDesc;
+struct _CamelIMAPXUntaggedRespHandlerDesc {
+ const gchar *untagged_response;
+ const CamelIMAPXUntaggedRespHandler handler;
+ const gchar *next_response;
+ gboolean skip_stream_when_done;
+};
+
+struct _CamelIMAPXServer {
+ GObject parent;
+ CamelIMAPXServerPrivate *priv;
+};
+
+struct _CamelIMAPXServerClass {
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (*refresh_mailbox) (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox);
+};
+
+GType camel_imapx_server_get_type (void);
+CamelIMAPXServer *
+ camel_imapx_server_new (struct _CamelIMAPXStore *store);
+struct _CamelIMAPXStore *
+ camel_imapx_server_ref_store (CamelIMAPXServer *is);
+struct _CamelIMAPXSettings *
+ camel_imapx_server_ref_settings (CamelIMAPXServer *is);
+GInputStream * camel_imapx_server_ref_input_stream
+ (CamelIMAPXServer *is);
+GOutputStream * camel_imapx_server_ref_output_stream
+ (CamelIMAPXServer *is);
+CamelIMAPXMailbox *
+ camel_imapx_server_ref_selected (CamelIMAPXServer *is);
+CamelIMAPXMailbox *
+ camel_imapx_server_ref_pending_or_selected
+ (CamelIMAPXServer *is);
+const struct _capability_info *
+ camel_imapx_server_get_capability_info
+ (CamelIMAPXServer *is);
+gboolean camel_imapx_server_have_capability
+ (CamelIMAPXServer *is,
+ guint32 capability);
+gboolean camel_imapx_server_lack_capability
+ (CamelIMAPXServer *is,
+ guint32 capability);
+gchar camel_imapx_server_get_tagprefix
+ (CamelIMAPXServer *is);
+void camel_imapx_server_set_tagprefix
+ (CamelIMAPXServer *is,
+ gchar tagprefix);
+CamelIMAPXCommand *
+ camel_imapx_server_ref_current_command
+ (CamelIMAPXServer *is);
+gboolean camel_imapx_server_connect_sync (CamelIMAPXServer *is,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_disconnect_sync
+ (CamelIMAPXServer *is,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_is_connected (CamelIMAPXServer *imapx_server);
+CamelAuthenticationResult
+ camel_imapx_server_authenticate_sync
+ (CamelIMAPXServer *is,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_query_auth_types_sync
+ (CamelIMAPXServer *is,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_mailbox_selected
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox);
+gboolean camel_imapx_server_ensure_selected_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_process_command_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXCommand *ic,
+ const gchar *error_prefix,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_list_sync (CamelIMAPXServer *is,
+ const gchar *pattern,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_refresh_info_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_sync_changes_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ gboolean can_influence_flags,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_expunge_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_noop_sync (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+CamelStream * camel_imapx_server_get_message_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolderSummary *summary,
+ CamelDataCache *message_cache,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_copy_message_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ CamelIMAPXMailbox *destination,
+ GPtrArray *uids,
+ gboolean delete_originals,
+ gboolean remove_deleted_flags,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_append_message_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolderSummary *summary,
+ CamelDataCache *message_cache,
+ CamelMimeMessage *message,
+ const CamelMessageInfo *mi,
+ gchar **append_uid,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_sync_message_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ CamelFolderSummary *summary,
+ CamelDataCache *message_cache,
+ const gchar *message_uid,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_create_mailbox_sync
+ (CamelIMAPXServer *is,
+ const gchar *mailbox_name,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_delete_mailbox_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_rename_mailbox_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ const gchar *new_mailbox_name,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_subscribe_mailbox_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_unsubscribe_mailbox_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_update_quota_info_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+GPtrArray * camel_imapx_server_uid_search_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ const gchar *criteria_prefix,
+ const gchar *search_key,
+ const gchar * const *words,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_can_use_idle (CamelIMAPXServer *is);
+gboolean camel_imapx_server_is_in_idle (CamelIMAPXServer *is);
+CamelIMAPXMailbox *
+ camel_imapx_server_ref_idle_mailbox
+ (CamelIMAPXServer *is);
+gboolean camel_imapx_server_schedule_idle_sync
+ (CamelIMAPXServer *is,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_imapx_server_stop_idle_sync
+ (CamelIMAPXServer *is,
+ GCancellable *cancellable,
+ GError **error);
+
+const CamelIMAPXUntaggedRespHandlerDesc *
+ camel_imapx_server_register_untagged_handler
+ (CamelIMAPXServer *is,
+ const gchar *untagged_response,
+ const CamelIMAPXUntaggedRespHandlerDesc *desc);
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_SERVER_H */
diff --git a/src/camel/providers/imapx/camel-imapx-settings.c b/src/camel/providers/imapx/camel-imapx-settings.c
new file mode 100644
index 000000000..b6665c950
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-settings.c
@@ -0,0 +1,1865 @@
+/*
+ * camel-imapx-settings.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-imapx-settings.h"
+
+#define MIN_CONCURRENT_CONNECTIONS 1
+#define MAX_CONCURRENT_CONNECTIONS 7
+
+#define CAMEL_IMAPX_SETTINGS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_IMAPX_SETTINGS, CamelIMAPXSettingsPrivate))
+
+struct _CamelIMAPXSettingsPrivate {
+ GMutex property_lock;
+ gchar *namespace;
+ gchar *real_junk_path;
+ gchar *real_trash_path;
+ gchar *shell_command;
+
+ guint concurrent_connections;
+
+ gboolean use_multi_fetch;
+ gboolean check_all;
+ gboolean check_subscribed;
+ gboolean filter_all;
+ gboolean filter_junk;
+ gboolean filter_junk_inbox;
+ gboolean use_idle;
+ gboolean use_namespace;
+ gboolean use_qresync;
+ gboolean use_real_junk_path;
+ gboolean use_real_trash_path;
+ gboolean use_shell_command;
+ gboolean use_subscriptions;
+ gboolean ignore_other_users_namespace;
+ gboolean ignore_shared_folders_namespace;
+
+ CamelSortType fetch_order;
+};
+
+enum {
+ PROP_0,
+ PROP_AUTH_MECHANISM,
+ PROP_USE_MULTI_FETCH,
+ PROP_CHECK_ALL,
+ PROP_CHECK_SUBSCRIBED,
+ PROP_CONCURRENT_CONNECTIONS,
+ PROP_FETCH_ORDER,
+ PROP_FILTER_ALL,
+ PROP_FILTER_JUNK,
+ PROP_FILTER_JUNK_INBOX,
+ PROP_HOST,
+ PROP_NAMESPACE,
+ PROP_PORT,
+ PROP_REAL_JUNK_PATH,
+ PROP_REAL_TRASH_PATH,
+ PROP_SECURITY_METHOD,
+ PROP_SHELL_COMMAND,
+ PROP_USER,
+ PROP_USE_IDLE,
+ PROP_USE_NAMESPACE,
+ PROP_USE_QRESYNC,
+ PROP_USE_REAL_JUNK_PATH,
+ PROP_USE_REAL_TRASH_PATH,
+ PROP_USE_SHELL_COMMAND,
+ PROP_USE_SUBSCRIPTIONS,
+ PROP_IGNORE_OTHER_USERS_NAMESPACE,
+ PROP_IGNORE_SHARED_FOLDERS_NAMESPACE
+};
+
+G_DEFINE_TYPE_WITH_CODE (
+ CamelIMAPXSettings,
+ camel_imapx_settings,
+ CAMEL_TYPE_OFFLINE_SETTINGS,
+ G_IMPLEMENT_INTERFACE (
+ CAMEL_TYPE_NETWORK_SETTINGS, NULL))
+
+static void
+imapx_settings_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTH_MECHANISM:
+ camel_network_settings_set_auth_mechanism (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_USE_MULTI_FETCH:
+ camel_imapx_settings_set_use_multi_fetch (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_CHECK_ALL:
+ camel_imapx_settings_set_check_all (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_CHECK_SUBSCRIBED:
+ camel_imapx_settings_set_check_subscribed (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_CONCURRENT_CONNECTIONS:
+ camel_imapx_settings_set_concurrent_connections (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_uint (value));
+ return;
+
+ case PROP_FETCH_ORDER:
+ camel_imapx_settings_set_fetch_order (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_FILTER_ALL:
+ camel_imapx_settings_set_filter_all (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_FILTER_JUNK:
+ camel_imapx_settings_set_filter_junk (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_FILTER_JUNK_INBOX:
+ camel_imapx_settings_set_filter_junk_inbox (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_HOST:
+ camel_network_settings_set_host (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_NAMESPACE:
+ camel_imapx_settings_set_namespace (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_PORT:
+ camel_network_settings_set_port (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_uint (value));
+ return;
+
+ case PROP_REAL_JUNK_PATH:
+ camel_imapx_settings_set_real_junk_path (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_REAL_TRASH_PATH:
+ camel_imapx_settings_set_real_trash_path (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_SECURITY_METHOD:
+ camel_network_settings_set_security_method (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_SHELL_COMMAND:
+ camel_imapx_settings_set_shell_command (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_USER:
+ camel_network_settings_set_user (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_USE_IDLE:
+ camel_imapx_settings_set_use_idle (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_USE_NAMESPACE:
+ camel_imapx_settings_set_use_namespace (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_USE_QRESYNC:
+ camel_imapx_settings_set_use_qresync (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_USE_REAL_JUNK_PATH:
+ camel_imapx_settings_set_use_real_junk_path (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_USE_REAL_TRASH_PATH:
+ camel_imapx_settings_set_use_real_trash_path (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_USE_SHELL_COMMAND:
+ camel_imapx_settings_set_use_shell_command (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_USE_SUBSCRIPTIONS:
+ camel_imapx_settings_set_use_subscriptions (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_IGNORE_OTHER_USERS_NAMESPACE:
+ camel_imapx_settings_set_ignore_other_users_namespace (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_IGNORE_SHARED_FOLDERS_NAMESPACE:
+ camel_imapx_settings_set_ignore_shared_folders_namespace (
+ CAMEL_IMAPX_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_settings_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTH_MECHANISM:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_auth_mechanism (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_USE_MULTI_FETCH:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_use_multi_fetch (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_CHECK_ALL:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_check_all (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_CHECK_SUBSCRIBED:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_check_subscribed (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_CONCURRENT_CONNECTIONS:
+ g_value_set_uint (
+ value,
+ camel_imapx_settings_get_concurrent_connections (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_FETCH_ORDER:
+ g_value_set_enum (
+ value,
+ camel_imapx_settings_get_fetch_order (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_FILTER_ALL:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_filter_all (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_FILTER_JUNK:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_filter_junk (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_FILTER_JUNK_INBOX:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_filter_junk_inbox (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_HOST:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_host (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_NAMESPACE:
+ g_value_take_string (
+ value,
+ camel_imapx_settings_dup_namespace (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_PORT:
+ g_value_set_uint (
+ value,
+ camel_network_settings_get_port (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_REAL_JUNK_PATH:
+ g_value_take_string (
+ value,
+ camel_imapx_settings_dup_real_junk_path (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_REAL_TRASH_PATH:
+ g_value_take_string (
+ value,
+ camel_imapx_settings_dup_real_trash_path (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_SECURITY_METHOD:
+ g_value_set_enum (
+ value,
+ camel_network_settings_get_security_method (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_SHELL_COMMAND:
+ g_value_take_string (
+ value,
+ camel_imapx_settings_dup_shell_command (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_USER:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_user (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_USE_IDLE:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_use_idle (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_USE_NAMESPACE:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_use_namespace (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_USE_QRESYNC:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_use_qresync (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_USE_REAL_JUNK_PATH:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_use_real_junk_path (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_USE_REAL_TRASH_PATH:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_use_real_trash_path (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_USE_SHELL_COMMAND:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_use_shell_command (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_USE_SUBSCRIPTIONS:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_use_subscriptions (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_IGNORE_OTHER_USERS_NAMESPACE:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_ignore_other_users_namespace (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+
+ case PROP_IGNORE_SHARED_FOLDERS_NAMESPACE:
+ g_value_set_boolean (
+ value,
+ camel_imapx_settings_get_ignore_shared_folders_namespace (
+ CAMEL_IMAPX_SETTINGS (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_settings_finalize (GObject *object)
+{
+ CamelIMAPXSettingsPrivate *priv;
+
+ priv = CAMEL_IMAPX_SETTINGS_GET_PRIVATE (object);
+
+ g_mutex_clear (&priv->property_lock);
+
+ g_free (priv->namespace);
+ g_free (priv->shell_command);
+ g_free (priv->real_trash_path);
+ g_free (priv->real_junk_path);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_imapx_settings_parent_class)->finalize (object);
+}
+
+static void
+camel_imapx_settings_class_init (CamelIMAPXSettingsClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelIMAPXSettingsPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = imapx_settings_set_property;
+ object_class->get_property = imapx_settings_get_property;
+ object_class->finalize = imapx_settings_finalize;
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_AUTH_MECHANISM,
+ "auth-mechanism");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_MULTI_FETCH,
+ g_param_spec_boolean (
+ "use-multi-fetch",
+ "Use Multi Fetch",
+ "Whether allow downloading of large messages in chunks",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CHECK_ALL,
+ g_param_spec_boolean (
+ "check-all",
+ "Check All",
+ "Check all folders for new messages",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CHECK_SUBSCRIBED,
+ g_param_spec_boolean (
+ "check-subscribed",
+ "Check Subscribed",
+ "Check only subscribed folders for new messages",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CONCURRENT_CONNECTIONS,
+ g_param_spec_uint (
+ "concurrent-connections",
+ "Concurrent Connections",
+ "Number of concurrent IMAP connections to use",
+ MIN_CONCURRENT_CONNECTIONS,
+ MAX_CONCURRENT_CONNECTIONS,
+ 3,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FETCH_ORDER,
+ g_param_spec_enum (
+ "fetch-order",
+ "Fetch Order",
+ "Order in which new messages should be fetched",
+ CAMEL_TYPE_SORT_TYPE,
+ CAMEL_SORT_ASCENDING,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FILTER_ALL,
+ g_param_spec_boolean (
+ "filter-all",
+ "Filter All",
+ "Whether to apply filters in all folders",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FILTER_JUNK,
+ g_param_spec_boolean (
+ "filter-junk",
+ "Filter Junk",
+ "Whether to filter junk from all folders",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FILTER_JUNK_INBOX,
+ g_param_spec_boolean (
+ "filter-junk-inbox",
+ "Filter Junk Inbox",
+ "Whether to filter junk from Inbox only",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_HOST,
+ "host");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_NAMESPACE,
+ g_param_spec_string (
+ "namespace",
+ "Namespace",
+ "Custom IMAP namespace",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_PORT,
+ "port");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_REAL_JUNK_PATH,
+ g_param_spec_string (
+ "real-junk-path",
+ "Real Junk Path",
+ "Path for a non-virtual Junk folder",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_REAL_TRASH_PATH,
+ g_param_spec_string (
+ "real-trash-path",
+ "Real Trash Path",
+ "Path for a non-virtual Trash folder",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_SECURITY_METHOD,
+ "security-method");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SHELL_COMMAND,
+ g_param_spec_string (
+ "shell-command",
+ "Shell Command",
+ "Shell command for connecting to the server",
+ "ssh -C -l %u %h exec /usr/sbin/imapd",
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_USER,
+ "user");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_IDLE,
+ g_param_spec_boolean (
+ "use-idle",
+ "Use IDLE",
+ "Whether to use the IDLE IMAP extension",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_NAMESPACE,
+ g_param_spec_boolean (
+ "use-namespace",
+ "Use Namespace",
+ "Whether to use a custom IMAP namespace",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_QRESYNC,
+ g_param_spec_boolean (
+ "use-qresync",
+ "Use QRESYNC",
+ "Whether to use the QRESYNC IMAP extension",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_REAL_JUNK_PATH,
+ g_param_spec_boolean (
+ "use-real-junk-path",
+ "Use Real Junk Path",
+ "Whether to use a non-virtual Junk folder",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_REAL_TRASH_PATH,
+ g_param_spec_boolean (
+ "use-real-trash-path",
+ "Use Real Trash Path",
+ "Whether to use a non-virtual Trash folder",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_SHELL_COMMAND,
+ g_param_spec_boolean (
+ "use-shell-command",
+ "Use Shell Command",
+ "Whether to use a custom shell "
+ "command to connect to the server",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_SUBSCRIPTIONS,
+ g_param_spec_boolean (
+ "use-subscriptions",
+ "Use Subscriptions",
+ "Whether to honor folder subscriptions",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_IGNORE_OTHER_USERS_NAMESPACE,
+ g_param_spec_boolean (
+ "ignore-other-users-namespace",
+ "Ignore Other Users Namespace",
+ "Whether to ignore other users namespace",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_IGNORE_SHARED_FOLDERS_NAMESPACE,
+ g_param_spec_boolean (
+ "ignore-shared-folders-namespace",
+ "Ignore Shared Folders Namespace",
+ "Whether to ignore shared folders namespace",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_imapx_settings_init (CamelIMAPXSettings *settings)
+{
+ settings->priv = CAMEL_IMAPX_SETTINGS_GET_PRIVATE (settings);
+ g_mutex_init (&settings->priv->property_lock);
+}
+
+/**
+ * camel_imapx_settings_get_use_multi_fetch:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether large messages can be downloaded in chunks.
+ * The default is %TRUE, but some server can be slower when
+ * the messages are downloaded in parts, rather than in one call.
+ *
+ * Returns: whether large messages can be downloaded in chunks
+ *
+ * Since: 3.20
+ **/
+guint
+camel_imapx_settings_get_use_multi_fetch (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), 0);
+
+ return settings->priv->use_multi_fetch;
+}
+
+/**
+ * camel_imapx_settings_set_use_multi_fetch:
+ * @settings: a #CamelIMAPXSettings
+ * @use_multi_fetch: whether can download large messages in chunks
+ *
+ * Sets whether can download large messages in chunks.
+ *
+ * Since: 3.20
+ **/
+void
+camel_imapx_settings_set_use_multi_fetch (CamelIMAPXSettings *settings,
+ guint use_multi_fetch)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->use_multi_fetch == use_multi_fetch)
+ return;
+
+ settings->priv->use_multi_fetch = use_multi_fetch;
+
+ g_object_notify (G_OBJECT (settings), "use-multi-fetch");
+}
+
+/**
+ * camel_imapx_settings_get_check_all:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to check all folders for new messages.
+ *
+ * Returns: whether to check all folders for new messages
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_imapx_settings_get_check_all (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->check_all;
+}
+
+/**
+ * camel_imapx_settings_set_check_all:
+ * @settings: a #CamelIMAPXSettings
+ * @check_all: whether to check all folders for new messages
+ *
+ * Sets whether to check all folders for new messages.
+ *
+ * Since: 3.2
+ **/
+void
+camel_imapx_settings_set_check_all (CamelIMAPXSettings *settings,
+ gboolean check_all)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->check_all == check_all)
+ return;
+
+ settings->priv->check_all = check_all;
+
+ g_object_notify (G_OBJECT (settings), "check-all");
+}
+
+/**
+ * camel_imapx_settings_get_check_subscribed:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to check only subscribed folders for new messages.
+ * Note that #CamelIMAPXSettings:check-all, if %TRUE, overrides this setting.
+ *
+ * Returns: whether to check only subscribed folders for new messages
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_imapx_settings_get_check_subscribed (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->check_subscribed;
+}
+
+/**
+ * camel_imapx_settings_set_check_subscribed:
+ * @settings: a #CamelIMAPXSettings
+ * @check_subscribed: whether to check only subscribed folders for new messages
+ *
+ * Sets whether to check only subscribed folders for new messages. Note
+ * that #CamelIMAPXSettings:check-all, if %TRUE, overrides this setting.
+ *
+ * Since: 3.2
+ **/
+void
+camel_imapx_settings_set_check_subscribed (CamelIMAPXSettings *settings,
+ gboolean check_subscribed)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->check_subscribed == check_subscribed)
+ return;
+
+ settings->priv->check_subscribed = check_subscribed;
+
+ g_object_notify (G_OBJECT (settings), "check-subscribed");
+}
+
+/**
+ * camel_imapx_settings_get_concurrent_connections:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns the number of concurrent network connections to the IMAP server
+ * to use for faster command/response processing.
+ *
+ * Returns: the number of concurrent connections to use
+ *
+ * Since: 3.16
+ **/
+guint
+camel_imapx_settings_get_concurrent_connections (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), 1);
+
+ return settings->priv->concurrent_connections;
+}
+
+/**
+ * camel_imapx_settings_set_concurrent_connections:
+ * @settings: a #CamelIMAPXSettings
+ * @concurrent_connections: the number of concurrent connections to use
+ *
+ * Sets the number of concurrent network connections to the IMAP server to
+ * use for faster command/response processing.
+ *
+ * The minimum number of connections is 1, the maximum is 7. The
+ * @concurrent_connections value will be clamped to these limits if
+ * necessary.
+ *
+ * Since: 3.16
+ **/
+void
+camel_imapx_settings_set_concurrent_connections (CamelIMAPXSettings *settings,
+ guint concurrent_connections)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ concurrent_connections = CLAMP (
+ concurrent_connections,
+ MIN_CONCURRENT_CONNECTIONS,
+ MAX_CONCURRENT_CONNECTIONS);
+
+ if (settings->priv->concurrent_connections == concurrent_connections)
+ return;
+
+ settings->priv->concurrent_connections = concurrent_connections;
+
+ g_object_notify (G_OBJECT (settings), "concurrent-connections");
+}
+
+/**
+ * camel_imapx_settings_get_fetch_order:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns the order in which new messages should be fetched.
+ *
+ * Returns: the order in which new messages should be fetched
+ *
+ * Since: 3.2
+ **/
+CamelSortType
+camel_imapx_settings_get_fetch_order (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (
+ CAMEL_IS_IMAPX_SETTINGS (settings),
+ CAMEL_SORT_ASCENDING);
+
+ return settings->priv->fetch_order;
+}
+
+/**
+ * camel_imapx_settings_set_fetch_order:
+ * @settings: a #CamelIMAPXSettings
+ * @fetch_order: the order in which new messages should be fetched
+ *
+ * Sets the order in which new messages should be fetched.
+ *
+ * Since: 3.2
+ **/
+void
+camel_imapx_settings_set_fetch_order (CamelIMAPXSettings *settings,
+ CamelSortType fetch_order)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->fetch_order == fetch_order)
+ return;
+
+ settings->priv->fetch_order = fetch_order;
+
+ g_object_notify (G_OBJECT (settings), "fetch-order");
+}
+
+/**
+ * camel_imapx_settings_get_filter_all:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether apply filters in all folders.
+ *
+ * Returns: whether to apply filters in all folders
+ *
+ * Since: 3.4
+ **/
+gboolean
+camel_imapx_settings_get_filter_all (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->filter_all;
+}
+
+/**
+ * camel_imapx_settings_set_filter_all:
+ * @settings: a #CamelIMAPXSettings
+ * @filter_all: whether to apply filters in all folders
+ *
+ * Sets whether to apply filters in all folders.
+ *
+ * Since: 3.4
+ **/
+void
+camel_imapx_settings_set_filter_all (CamelIMAPXSettings *settings,
+ gboolean filter_all)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->filter_all == filter_all)
+ return;
+
+ settings->priv->filter_all = filter_all;
+
+ g_object_notify (G_OBJECT (settings), "filter-all");
+}
+
+/**
+ * camel_imapx_settings_get_filter_junk:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to automatically find and tag junk messages amongst new
+ * messages in all folders.
+ *
+ * Returns: whether to filter junk in all folders
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_imapx_settings_get_filter_junk (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->filter_junk;
+}
+
+/**
+ * camel_imapx_settings_set_filter_junk:
+ * @settings: a #CamelIMAPXSettings
+ * @filter_junk: whether to filter junk in all folders
+ *
+ * Sets whether to automatically find and tag junk messages amongst new
+ * messages in all folders.
+ *
+ * Since: 3.2
+ **/
+void
+camel_imapx_settings_set_filter_junk (CamelIMAPXSettings *settings,
+ gboolean filter_junk)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->filter_junk == filter_junk)
+ return;
+
+ settings->priv->filter_junk = filter_junk;
+
+ g_object_notify (G_OBJECT (settings), "filter-junk");
+}
+
+/**
+ * camel_imapx_settings_get_filter_junk_inbox:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to automatically find and tag junk messages amongst new
+ * messages in the Inbox folder only.
+ *
+ * Returns: whether to filter junk in Inbox only
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_imapx_settings_get_filter_junk_inbox (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->filter_junk_inbox;
+}
+
+/**
+ * camel_imapx_settings_set_filter_junk_inbox:
+ * @settings: a #CamelIMAPXSettings
+ * @filter_junk_inbox: whether to filter junk in Inbox only
+ *
+ * Sets whether to automatically find and tag junk messages amongst new
+ * messages in the Inbox folder only.
+ *
+ * Since: 3.2
+ **/
+void
+camel_imapx_settings_set_filter_junk_inbox (CamelIMAPXSettings *settings,
+ gboolean filter_junk_inbox)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->filter_junk_inbox == filter_junk_inbox)
+ return;
+
+ settings->priv->filter_junk_inbox = filter_junk_inbox;
+
+ g_object_notify (G_OBJECT (settings), "filter-junk-inbox");
+}
+
+/**
+ * camel_imapx_settings_get_namespace:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns the custom IMAP namespace in which to find folders.
+ *
+ * Returns: the custom IMAP namespace, or %NULL
+ *
+ * Since: 3.2
+ **/
+const gchar *
+camel_imapx_settings_get_namespace (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), NULL);
+
+ return settings->priv->namespace;
+}
+
+/**
+ * camel_imapx_settings_dup_namespace:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Thread-safe variation of camel_imapx_settings_get_namespace().
+ * Use this function when accessing @settings from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelIMAPXSettings:namespace
+ *
+ * Since: 3.4
+ **/
+gchar *
+camel_imapx_settings_dup_namespace (CamelIMAPXSettings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), NULL);
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ protected = camel_imapx_settings_get_namespace (settings);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_imapx_settings_set_namespace:
+ * @settings: a #CamelIMAPXSettings
+ * @namespace_: an IMAP namespace, or %NULL
+ *
+ * Sets the custom IMAP namespace in which to find folders. If @namespace_
+ * is %NULL, the default namespace is used.
+ *
+ * Since: 3.2
+ **/
+void
+camel_imapx_settings_set_namespace (CamelIMAPXSettings *settings,
+ const gchar *namespace_)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ /* The default namespace is an empty string. */
+ if (namespace_ == NULL)
+ namespace_ = "";
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ if (g_strcmp0 (settings->priv->namespace, namespace_) == 0) {
+ g_mutex_unlock (&settings->priv->property_lock);
+ return;
+ }
+
+ g_free (settings->priv->namespace);
+ settings->priv->namespace = g_strdup (namespace_);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ g_object_notify (G_OBJECT (settings), "namespace");
+}
+
+/**
+ * camel_imapx_settings_get_real_junk_path:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns the path to a real, non-virtual Junk folder to be used instead
+ * of Camel's standard virtual Junk folder.
+ *
+ * Returns: path to a real junk folder
+ *
+ * Since: 3.8
+ **/
+const gchar *
+camel_imapx_settings_get_real_junk_path (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), NULL);
+
+ return settings->priv->real_junk_path;
+}
+
+/**
+ * camel_imapx_settings_dup_real_junk_path:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Thread-safe variation of camel_imapx_settings_get_real_junk_path().
+ * Use this function when accessing @settings from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelIMAPXSettings:real-junk-path
+ *
+ * Since: 3.8
+ **/
+gchar *
+camel_imapx_settings_dup_real_junk_path (CamelIMAPXSettings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), NULL);
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ protected = camel_imapx_settings_get_real_junk_path (settings);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_imapx_settings_set_real_junk_path:
+ * @settings: a #CamelIMAPXSettings
+ * @real_junk_path: path to a real Junk folder, or %NULL
+ *
+ * Sets the path to a real, non-virtual Junk folder to be used instead of
+ * Camel's standard virtual Junk folder.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_settings_set_real_junk_path (CamelIMAPXSettings *settings,
+ const gchar *real_junk_path)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ /* An empty string is equivalent to NULL. */
+ if (real_junk_path != NULL && *real_junk_path == '\0')
+ real_junk_path = NULL;
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ g_free (settings->priv->real_junk_path);
+ settings->priv->real_junk_path = g_strdup (real_junk_path);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ g_object_notify (G_OBJECT (settings), "real-junk-path");
+}
+
+/**
+ * camel_imapx_settings_get_real_trash_path:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns the path to a real, non-virtual Trash folder to be used instead
+ * of Camel's standard virtual Trash folder.
+ *
+ * Returns: path to a real Trash folder
+ *
+ * Since: 3.8
+ **/
+const gchar *
+camel_imapx_settings_get_real_trash_path (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), NULL);
+
+ return settings->priv->real_trash_path;
+}
+
+/**
+ * camel_imapx_settings_dup_real_trash_path:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Thread-safe variation of camel_imapx_settings_get_real_trash_path().
+ * Use this function when accessing @settings from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelIMAPXsettings:real-trash-path
+ *
+ * Since: 3.8
+ **/
+gchar *
+camel_imapx_settings_dup_real_trash_path (CamelIMAPXSettings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), NULL);
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ protected = camel_imapx_settings_get_real_trash_path (settings);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_imapx_settings_set_real_trash_path:
+ * @settings: a #CamelIMAPXSettings
+ * @real_trash_path: path to a real Trash folder, or %NULL
+ *
+ * Sets the path to a real, non-virtual Trash folder to be used instead of
+ * Camel's standard virtual Trash folder.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_settings_set_real_trash_path (CamelIMAPXSettings *settings,
+ const gchar *real_trash_path)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ /* An empty string is equivalent to NULL. */
+ if (real_trash_path != NULL && *real_trash_path == '\0')
+ real_trash_path = NULL;
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ g_free (settings->priv->real_trash_path);
+ settings->priv->real_trash_path = g_strdup (real_trash_path);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ g_object_notify (G_OBJECT (settings), "real-trash-path");
+}
+
+/**
+ * camel_imapx_settings_get_shell_command:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns an optional shell command used to establish an input/output
+ * stream with an IMAP server. Normally the input/output stream is
+ * established through a network socket.
+ *
+ * This option is useful only to a select few advanced users who likely
+ * administer their own IMAP server. Most users will not understand what
+ * this option menas or how to use it. Probably not worth exposing in a
+ * graphical interface.
+ *
+ * Returns: shell command for connecting to the server, or %NULL
+ *
+ * Since: 3.2
+ **/
+const gchar *
+camel_imapx_settings_get_shell_command (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), NULL);
+
+ return settings->priv->shell_command;
+}
+
+/**
+ * camel_imapx_settings_dup_shell_command:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Thread-safe variation of camel_imapx_settings_get_shell_command().
+ * Use this function when accessing @settings from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelIMAPXSettings:shell-command
+ *
+ * Since: 3.4
+ **/
+gchar *
+camel_imapx_settings_dup_shell_command (CamelIMAPXSettings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), NULL);
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ protected = camel_imapx_settings_get_shell_command (settings);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_imapx_settings_set_shell_command:
+ * @settings: a #CamelIMAPXSettings
+ * @shell_command: shell command for connecting to the server, or %NULL
+ *
+ * Sets an optional shell command used to establish an input/output stream
+ * with an IMAP server. Normally the input/output stream is established
+ * through a network socket.
+ *
+ * This option is useful only to a select few advanced users who likely
+ * administer their own IMAP server. Most users will not understand what
+ * this option means or how to use it. Probably not worth exposing in a
+ * graphical interface.
+ *
+ * Since: 3.2
+ **/
+void
+camel_imapx_settings_set_shell_command (CamelIMAPXSettings *settings,
+ const gchar *shell_command)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ /* An empty string is equivalent to NULL. */
+ if (shell_command != NULL && *shell_command == '\0')
+ shell_command = NULL;
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ if (g_strcmp0 (settings->priv->shell_command, shell_command) == 0) {
+ g_mutex_unlock (&settings->priv->property_lock);
+ return;
+ }
+
+ g_free (settings->priv->shell_command);
+ settings->priv->shell_command = g_strdup (shell_command);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ g_object_notify (G_OBJECT (settings), "shell-command");
+}
+
+/**
+ * camel_imapx_settings_get_use_idle:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to use the IMAP IDLE extension if the server supports
+ * it. See RFC 2177 for more details.
+ *
+ * Returns: whether to use the IDLE extension
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_imapx_settings_get_use_idle (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->use_idle;
+}
+
+/**
+ * camel_imapx_settings_set_use_idle:
+ * @settings: a #CamelIMAPXSettings
+ * @use_idle: whether to use the IDLE extension
+ *
+ * Sets whether to use the IMAP IDLE extension if the server supports it.
+ * See RFC 2177 for more details.
+ *
+ * Since: 3.2
+ **/
+void
+camel_imapx_settings_set_use_idle (CamelIMAPXSettings *settings,
+ gboolean use_idle)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->use_idle == use_idle)
+ return;
+
+ settings->priv->use_idle = use_idle;
+
+ g_object_notify (G_OBJECT (settings), "use-idle");
+}
+
+/**
+ * camel_imapx_settings_get_use_namespace:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to use a custom IMAP namespace to find folders. The
+ * namespace itself is given by the #CamelIMAPStore:namespace property.
+ *
+ * Returns: whether to use a custom IMAP namespace
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_imapx_settings_get_use_namespace (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->use_namespace;
+}
+
+/**
+ * camel_imapx_settings_set_use_namespace:
+ * @settings: a #CamelIMAPXSettings
+ * @use_namespace: whether to use a custom IMAP namespace
+ *
+ * Sets whether to use a custom IMAP namespace to find folders. The
+ * namespace itself is given by the #CamelIMAPXSettings:namespace property.
+ *
+ * Since: 3.2
+ **/
+void
+camel_imapx_settings_set_use_namespace (CamelIMAPXSettings *settings,
+ gboolean use_namespace)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->use_namespace == use_namespace)
+ return;
+
+ settings->priv->use_namespace = use_namespace;
+
+ g_object_notify (G_OBJECT (settings), "use-namespace");
+}
+
+/**
+ * camel_imapx_settings_get_ignore_other_users_namespace:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to ignore namespace for other users.
+ *
+ * Returns: whether to ignore namespace for other users
+ *
+ * Since: 3.16
+ **/
+gboolean
+camel_imapx_settings_get_ignore_other_users_namespace (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->ignore_other_users_namespace;
+}
+
+/**
+ * camel_imapx_settings_set_ignore_other_users_namespace:
+ * @settings: a #CamelIMAPXSettings
+ * @ignore: whether to ignore the namespace
+ *
+ * Sets whether to ignore other users namespace.
+ *
+ * Since: 3.16
+ **/
+void
+camel_imapx_settings_set_ignore_other_users_namespace (CamelIMAPXSettings *settings,
+ gboolean ignore)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->ignore_other_users_namespace == ignore)
+ return;
+
+ settings->priv->ignore_other_users_namespace = ignore;
+
+ g_object_notify (G_OBJECT (settings), "ignore-other-users-namespace");
+}
+
+/**
+ * camel_imapx_settings_get_ignore_shared_folders_namespace:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to ignore namespace for shared folders.
+ *
+ * Returns: whether to ignore namespace for shared folders
+ *
+ * Since: 3.16
+ **/
+gboolean
+camel_imapx_settings_get_ignore_shared_folders_namespace (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->ignore_shared_folders_namespace;
+}
+
+/**
+ * camel_imapx_settings_set_ignore_shared_folders_namespace:
+ * @settings: a #CamelIMAPXSettings
+ * @ignore: whether to ignore the namespace
+ *
+ * Sets whether to ignore shared folders namespace.
+ *
+ * Since: 3.16
+ **/
+void
+camel_imapx_settings_set_ignore_shared_folders_namespace (CamelIMAPXSettings *settings,
+ gboolean ignore)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->ignore_shared_folders_namespace == ignore)
+ return;
+
+ settings->priv->ignore_shared_folders_namespace = ignore;
+
+ g_object_notify (G_OBJECT (settings), "ignore-shared-folders-namespace");
+}
+
+/**
+ * camel_imapx_settings_get_use_qresync:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to use the Quick Mailbox Resynchronization (QRESYNC)
+ * IMAP extension if the server supports it. See RFC 5162 for more
+ * details.
+ *
+ * Returns: whether to use the QRESYNC extension
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_imapx_settings_get_use_qresync (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->use_qresync;
+}
+
+/**
+ * camel_imapx_settings_set_use_qresync:
+ * @settings: a #CamelIMAPXSettings
+ * @use_qresync: whether to use the QRESYNC extension
+ *
+ * Sets whether to use the Quick Mailbox Resynchronization (QRESYNC)
+ * IMAP extension if the server supports it. See RFC 5162 for more
+ * details.
+ *
+ * Since: 3.2
+ **/
+void
+camel_imapx_settings_set_use_qresync (CamelIMAPXSettings *settings,
+ gboolean use_qresync)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->use_qresync == use_qresync)
+ return;
+
+ settings->priv->use_qresync = use_qresync;
+
+ g_object_notify (G_OBJECT (settings), "use-qresync");
+}
+
+/**
+ * camel_imapx_settings_get_use_real_junk_path:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to use a real, non-virtual Junk folder instead of Camel's
+ * standard virtual Junk folder.
+ *
+ * Returns: whether to use a real Junk folder
+ *
+ * Since: 3.8
+ **/
+gboolean
+camel_imapx_settings_get_use_real_junk_path (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->use_real_junk_path;
+}
+
+/**
+ * camel_imapx_settings_set_use_real_junk_path:
+ * @settings: a #CamelIMAPXSettings
+ * @use_real_junk_path: whether to use a real Junk folder
+ *
+ * Sets whether to use a real, non-virtual Junk folder instead of Camel's
+ * standard virtual Junk folder.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_settings_set_use_real_junk_path (CamelIMAPXSettings *settings,
+ gboolean use_real_junk_path)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->use_real_junk_path == use_real_junk_path)
+ return;
+
+ settings->priv->use_real_junk_path = use_real_junk_path;
+
+ g_object_notify (G_OBJECT (settings), "use-real-junk-path");
+}
+
+/**
+ * camel_imapx_settings_get_use_real_trash_path:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to use a real, non-virtual Trash folder instead of Camel's
+ * standard virtual Trash folder.
+ *
+ * Returns: whether to use a real Trash folder
+ *
+ * Since: 3.8
+ **/
+gboolean
+camel_imapx_settings_get_use_real_trash_path (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->use_real_trash_path;
+}
+
+/**
+ * camel_imapx_settings_set_use_real_trash_path:
+ * @settings: a #CamelIMAPXSettings
+ * @use_real_trash_path: whether to use a real Trash folder
+ *
+ * Sets whether to use a real, non-virtual Trash folder instead of Camel's
+ * standard virtual Trash folder.
+ *
+ * Since: 3.8
+ **/
+void
+camel_imapx_settings_set_use_real_trash_path (CamelIMAPXSettings *settings,
+ gboolean use_real_trash_path)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->use_real_trash_path == use_real_trash_path)
+ return;
+
+ settings->priv->use_real_trash_path = use_real_trash_path;
+
+ g_object_notify (G_OBJECT (settings), "use-real-trash-path");
+}
+
+/**
+ * camel_imapx_settings_get_use_shell_command:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to use a custom shell command to establish an input/output
+ * stream with an IMAP server, instead of the more common method of opening a
+ * network socket. The shell command itself is given by the
+ * #CamelIMAPXSettings:shell-command property.
+ *
+ * This option is useful only to a select few advanced users who likely
+ * administer their own IMAP server. Most users will not understand what
+ * this option means or how to use it. Probably not worth exposing in a
+ * graphical interface.
+ *
+ * Returns: whether to use a custom shell command to connect to the server
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_imapx_settings_get_use_shell_command (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->use_shell_command;
+}
+
+/**
+ * camel_imapx_settings_set_use_shell_command:
+ * @settings: a #CamelIMAPXSettings
+ * @use_shell_command: whether to use a custom shell command to connect
+ * to the server
+ *
+ * Sets whether to use a custom shell command to establish an input/output
+ * stream with an IMAP server, instead of the more common method of opening
+ * a network socket. The shell command itself is given by the
+ * #CamelIMAPXSettings:shell-command property.
+ *
+ * This option is useful only to a select few advanced users who likely
+ * administer their own IMAP server. Most users will not understand what
+ * this option means or how to use it. Probably not worth exposing in a
+ * graphical interface.
+ *
+ * Since: 3.2
+ **/
+void
+camel_imapx_settings_set_use_shell_command (CamelIMAPXSettings *settings,
+ gboolean use_shell_command)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->use_shell_command == use_shell_command)
+ return;
+
+ settings->priv->use_shell_command = use_shell_command;
+
+ g_object_notify (G_OBJECT (settings), "use-shell-command");
+}
+
+/**
+ * camel_imapx_settings_get_use_subscriptions:
+ * @settings: a #CamelIMAPXSettings
+ *
+ * Returns whether to list and operate only on subscribed folders, or to
+ * list and operate on all available folders regardless of subscriptions.
+ *
+ * Returns: whether to honor folder subscriptions
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_imapx_settings_get_use_subscriptions (CamelIMAPXSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings), FALSE);
+
+ return settings->priv->use_subscriptions;
+}
+
+/**
+ * camel_imapx_settings_set_use_subscriptions:
+ * @settings: a #CamelIMAPXSettings
+ * @use_subscriptions: whether to honor folder subscriptions
+ *
+ * Sets whether to list and operate only on subscribed folders, or to
+ * list and operate on all available folders regardless of subscriptions.
+ *
+ * Since: 3.2
+ **/
+void
+camel_imapx_settings_set_use_subscriptions (CamelIMAPXSettings *settings,
+ gboolean use_subscriptions)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_SETTINGS (settings));
+
+ if (settings->priv->use_subscriptions == use_subscriptions)
+ return;
+
+ settings->priv->use_subscriptions = use_subscriptions;
+
+ g_object_notify (G_OBJECT (settings), "use-subscriptions");
+}
+
diff --git a/src/camel/providers/imapx/camel-imapx-settings.h b/src/camel/providers/imapx/camel-imapx-settings.h
new file mode 100644
index 000000000..f1e402e6f
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-settings.h
@@ -0,0 +1,174 @@
+/*
+ * camel-imapx-settings.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_SETTINGS_H
+#define CAMEL_IMAPX_SETTINGS_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_SETTINGS \
+ (camel_imapx_settings_get_type ())
+#define CAMEL_IMAPX_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_SETTINGS, CamelIMAPXSettings))
+#define CAMEL_IMAPX_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_SETTINGS, CamelIMAPXSettingsClass))
+#define CAMEL_IS_IMAPX_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_SETTINGS))
+#define CAMEL_IS_IMAPX_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_SETTINGS))
+#define CAMEL_IMAPX_SETTINGS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_SETTINGS, CamelIMAPXSettingsClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAPXSettings CamelIMAPXSettings;
+typedef struct _CamelIMAPXSettingsClass CamelIMAPXSettingsClass;
+typedef struct _CamelIMAPXSettingsPrivate CamelIMAPXSettingsPrivate;
+
+struct _CamelIMAPXSettings {
+ CamelOfflineSettings parent;
+ CamelIMAPXSettingsPrivate *priv;
+};
+
+struct _CamelIMAPXSettingsClass {
+ CamelOfflineSettingsClass parent_class;
+};
+
+GType camel_imapx_settings_get_type (void) G_GNUC_CONST;
+guint camel_imapx_settings_get_use_multi_fetch
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_use_multi_fetch
+ (CamelIMAPXSettings *settings,
+ guint use_multi_fetch);
+gboolean camel_imapx_settings_get_check_all
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_check_all
+ (CamelIMAPXSettings *settings,
+ gboolean check_all);
+gboolean camel_imapx_settings_get_check_subscribed
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_check_subscribed
+ (CamelIMAPXSettings *settings,
+ gboolean check_subscribed);
+guint camel_imapx_settings_get_concurrent_connections
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_concurrent_connections
+ (CamelIMAPXSettings *settings,
+ guint concurrent_connections);
+CamelSortType camel_imapx_settings_get_fetch_order
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_fetch_order
+ (CamelIMAPXSettings *settings,
+ CamelSortType fetch_order);
+gboolean camel_imapx_settings_get_filter_all
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_filter_all
+ (CamelIMAPXSettings *settings,
+ gboolean filter_all);
+gboolean camel_imapx_settings_get_filter_junk
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_filter_junk
+ (CamelIMAPXSettings *settings,
+ gboolean filter_junk);
+gboolean camel_imapx_settings_get_filter_junk_inbox
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_filter_junk_inbox
+ (CamelIMAPXSettings *settings,
+ gboolean filter_junk_inbox);
+const gchar * camel_imapx_settings_get_namespace
+ (CamelIMAPXSettings *settings);
+gchar * camel_imapx_settings_dup_namespace
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_namespace
+ (CamelIMAPXSettings *settings,
+ const gchar *namespace_);
+const gchar * camel_imapx_settings_get_real_junk_path
+ (CamelIMAPXSettings *settings);
+gchar * camel_imapx_settings_dup_real_junk_path
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_real_junk_path
+ (CamelIMAPXSettings *settings,
+ const gchar *real_junk_path);
+const gchar * camel_imapx_settings_get_real_trash_path
+ (CamelIMAPXSettings *settings);
+gchar * camel_imapx_settings_dup_real_trash_path
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_real_trash_path
+ (CamelIMAPXSettings *settings,
+ const gchar *real_trash_path);
+const gchar * camel_imapx_settings_get_shell_command
+ (CamelIMAPXSettings *settings);
+gchar * camel_imapx_settings_dup_shell_command
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_shell_command
+ (CamelIMAPXSettings *settings,
+ const gchar *shell_command);
+gboolean camel_imapx_settings_get_use_idle
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_use_idle
+ (CamelIMAPXSettings *settings,
+ gboolean use_idle);
+gboolean camel_imapx_settings_get_use_namespace
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_use_namespace
+ (CamelIMAPXSettings *settings,
+ gboolean use_namespace);
+gboolean camel_imapx_settings_get_ignore_other_users_namespace
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_ignore_other_users_namespace
+ (CamelIMAPXSettings *settings,
+ gboolean ignore);
+gboolean camel_imapx_settings_get_ignore_shared_folders_namespace
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_ignore_shared_folders_namespace
+ (CamelIMAPXSettings *settings,
+ gboolean ignore);
+gboolean camel_imapx_settings_get_use_qresync
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_use_qresync
+ (CamelIMAPXSettings *settings,
+ gboolean use_qresync);
+gboolean camel_imapx_settings_get_use_real_junk_path
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_use_real_junk_path
+ (CamelIMAPXSettings *settings,
+ gboolean use_real_junk_path);
+gboolean camel_imapx_settings_get_use_real_trash_path
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_use_real_trash_path
+ (CamelIMAPXSettings *settings,
+ gboolean use_real_trash_path);
+gboolean camel_imapx_settings_get_use_shell_command
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_use_shell_command
+ (CamelIMAPXSettings *settings,
+ gboolean use_shell_command);
+gboolean camel_imapx_settings_get_use_subscriptions
+ (CamelIMAPXSettings *settings);
+void camel_imapx_settings_set_use_subscriptions
+ (CamelIMAPXSettings *settings,
+ gboolean use_subscriptions);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_SETTINGS_H */
diff --git a/src/camel/providers/imapx/camel-imapx-status-response.c b/src/camel/providers/imapx/camel-imapx-status-response.c
new file mode 100644
index 000000000..3e1168b21
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-status-response.c
@@ -0,0 +1,443 @@
+/*
+ * camel-imapx-status-response.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * SECTION: camel-imapx-status-response
+ * @include: camel/camel.h
+ * @short_description: Stores an IMAP STATUS respose
+ *
+ * #CamelIMAPXStatusResponse encapsulates an IMAP STATUS response, which
+ * describes the current status of a mailbox in terms of various message
+ * counts and change tracking indicators.
+ **/
+
+#include "camel-imapx-status-response.h"
+
+#include "camel-imapx-utils.h"
+
+#define CAMEL_IMAPX_STATUS_RESPONSE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_IMAPX_STATUS_RESPONSE, CamelIMAPXStatusResponsePrivate))
+
+struct _CamelIMAPXStatusResponsePrivate {
+ gchar *mailbox_name;
+
+ guint32 messages;
+ guint32 recent;
+ guint32 unseen;
+ guint32 uidnext;
+ guint32 uidvalidity;
+ guint64 highestmodseq;
+
+ gboolean have_messages;
+ gboolean have_recent;
+ gboolean have_unseen;
+ gboolean have_uidnext;
+ gboolean have_uidvalidity;
+ gboolean have_highestmodseq;
+};
+
+G_DEFINE_TYPE (
+ CamelIMAPXStatusResponse,
+ camel_imapx_status_response,
+ G_TYPE_OBJECT)
+
+static void
+imapx_status_response_finalize (GObject *object)
+{
+ CamelIMAPXStatusResponsePrivate *priv;
+
+ priv = CAMEL_IMAPX_STATUS_RESPONSE_GET_PRIVATE (object);
+
+ g_free (priv->mailbox_name);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_imapx_status_response_parent_class)->
+ finalize (object);
+}
+
+static void
+camel_imapx_status_response_class_init (CamelIMAPXStatusResponseClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (
+ class, sizeof (CamelIMAPXStatusResponsePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = imapx_status_response_finalize;
+}
+
+static void
+camel_imapx_status_response_init (CamelIMAPXStatusResponse *response)
+{
+ response->priv = CAMEL_IMAPX_STATUS_RESPONSE_GET_PRIVATE (response);
+}
+
+/**
+ * camel_imapx_status_response_new:
+ * @stream: a #CamelIMAPXInputStream
+ * @inbox_separator: the separator character for INBOX
+ * @cancellable: a #GCancellable
+ * @error: return location for a #GError, or %NULL
+ *
+ * Attempts to parse an IMAP STATUS response from @stream and, if successful,
+ * stores the response data in a new #CamelIMAPXStatusResponse. If an error
+ * occurs, the function sets @error and returns %NULL.
+ *
+ * Returns: a #CamelIMAPXStatusResponse, or %NULL
+ *
+ * Since: 3.10
+ **/
+CamelIMAPXStatusResponse *
+camel_imapx_status_response_new (CamelIMAPXInputStream *stream,
+ gchar inbox_separator,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXStatusResponse *response;
+ camel_imapx_token_t tok;
+ guchar *token;
+ guint len;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (stream), NULL);
+
+ response = g_object_new (CAMEL_TYPE_IMAPX_STATUS_RESPONSE, NULL);
+
+ /* Parse mailbox name. */
+
+ response->priv->mailbox_name = camel_imapx_parse_mailbox (
+ stream, inbox_separator, cancellable, error);
+ if (response->priv->mailbox_name == NULL)
+ goto fail;
+
+ /* Parse status attributes. */
+
+ tok = camel_imapx_input_stream_token (
+ CAMEL_IMAPX_INPUT_STREAM (stream),
+ &token, &len, cancellable, error);
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+ if (tok != '(') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "status: expecting '('");
+ goto fail;
+ }
+
+ tok = camel_imapx_input_stream_token (
+ CAMEL_IMAPX_INPUT_STREAM (stream),
+ &token, &len, cancellable, error);
+
+ while (tok == IMAPX_TOK_TOKEN) {
+ guint64 number;
+ gboolean success;
+
+ switch (imapx_tokenise ((gchar *) token, len)) {
+ case IMAPX_MESSAGES:
+ success = camel_imapx_input_stream_number (
+ CAMEL_IMAPX_INPUT_STREAM (stream),
+ &number, cancellable, error);
+ response->priv->messages = (guint32) number;
+ response->priv->have_messages = TRUE;
+ break;
+
+ case IMAPX_RECENT:
+ success = camel_imapx_input_stream_number (
+ CAMEL_IMAPX_INPUT_STREAM (stream),
+ &number, cancellable, error);
+ response->priv->recent = (guint32) number;
+ response->priv->have_recent = TRUE;
+ break;
+
+ case IMAPX_UNSEEN:
+ success = camel_imapx_input_stream_number (
+ CAMEL_IMAPX_INPUT_STREAM (stream),
+ &number, cancellable, error);
+ response->priv->unseen = (guint32) number;
+ response->priv->have_unseen = TRUE;
+ break;
+
+ case IMAPX_UIDNEXT:
+ success = camel_imapx_input_stream_number (
+ CAMEL_IMAPX_INPUT_STREAM (stream),
+ &number, cancellable, error);
+ response->priv->uidnext = (guint32) number;
+ response->priv->have_uidnext = TRUE;
+ break;
+
+ case IMAPX_UIDVALIDITY:
+ success = camel_imapx_input_stream_number (
+ CAMEL_IMAPX_INPUT_STREAM (stream),
+ &number, cancellable, error);
+ response->priv->uidvalidity = (guint32) number;
+ response->priv->have_uidvalidity = TRUE;
+ break;
+
+ /* See RFC 4551 section 3.6 */
+ case IMAPX_HIGHESTMODSEQ:
+ success = camel_imapx_input_stream_number (
+ CAMEL_IMAPX_INPUT_STREAM (stream),
+ &number, cancellable, error);
+ response->priv->highestmodseq = number;
+ response->priv->have_highestmodseq = TRUE;
+ break;
+
+ default:
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "unknown status attribute");
+ success = FALSE;
+ break;
+ }
+
+ if (!success)
+ goto fail;
+
+ tok = camel_imapx_input_stream_token (
+ CAMEL_IMAPX_INPUT_STREAM (stream),
+ &token, &len, cancellable, error);
+ }
+
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+
+ if (tok != ')') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "status: expecting ')' or attribute");
+ goto fail;
+ }
+
+ return response;
+
+fail:
+ g_clear_object (&response);
+
+ return NULL;
+}
+
+/**
+ * camel_imapx_status_response_get_mailbox_name:
+ * @response: a #CamelIMAPXStatusResponse
+ *
+ * Returns the mailbox name for @response.
+ *
+ * Returns: the mailbox name
+ *
+ * Since: 3.10
+ **/
+const gchar *
+camel_imapx_status_response_get_mailbox_name (CamelIMAPXStatusResponse *response)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STATUS_RESPONSE (response), NULL);
+
+ return response->priv->mailbox_name;
+}
+
+/**
+ * camel_imapx_status_response_get_messages:
+ * @response: a #CamelIMAPXStatusResponse
+ * @out_messages: return location for the status value, or %NULL
+ *
+ * If @response includes an updated "MESSAGES" value, write the value to
+ * @out_messages and return %TRUE. Otherwise leave @out_messages unset
+ * and return %FALSE.
+ *
+ * The "MESSAGES" value refers to the number of messages in the mailbox.
+ *
+ * The @out_messages argument can be %NULL, in which case the function
+ * simply returns whether an updated "MESSAGES" value is present.
+ *
+ * Returns: whether @out_messages was set
+ *
+ * Since: 3.10
+ **/
+gboolean
+camel_imapx_status_response_get_messages (CamelIMAPXStatusResponse *response,
+ guint32 *out_messages)
+{
+ g_return_val_if_fail (
+ CAMEL_IS_IMAPX_STATUS_RESPONSE (response), FALSE);
+
+ if (out_messages != NULL && response->priv->have_messages)
+ *out_messages = response->priv->messages;
+
+ return response->priv->have_messages;
+}
+
+/**
+ * camel_imapx_status_response_get_recent:
+ * @response: a #CamelIMAPXStatusResponse
+ * @out_recent: return location for the status value, or %NULL
+ *
+ * If @response includes an updated "RECENT" value, write the value to
+ * @out_recent and return %TRUE. Otherwise leave @out_recent unset and
+ * return %FALSE.
+ *
+ * The "RECENT" value refers to the number of messages with the \Recent
+ * flag set.
+ *
+ * The @out_recent argument can be %NULL, in which case the function
+ * simply returns whether an updated "RECENT" value is present.
+ *
+ * Returns: whether @out_recent was set
+ *
+ * Since: 3.10
+ **/
+gboolean
+camel_imapx_status_response_get_recent (CamelIMAPXStatusResponse *response,
+ guint32 *out_recent)
+{
+ g_return_val_if_fail (
+ CAMEL_IS_IMAPX_STATUS_RESPONSE (response), FALSE);
+
+ if (out_recent != NULL && response->priv->have_recent)
+ *out_recent = response->priv->recent;
+
+ return response->priv->have_recent;
+}
+
+/**
+ * camel_imapx_status_response_get_unseen:
+ * @response: a #CamelIMAPXStatusResponse
+ * @out_unseen: return location for the status value, or %NULL
+ *
+ * If @response includes an updated "UNSEEN" value, write the value to
+ * @out_unseen and return %TRUE. Otherwise leave @out_unseen unset and
+ * return %FALSE.
+ *
+ * The "UNSEEN" value refers to the number of messages which do not have
+ * the \Seen flag set.
+ *
+ * The @out_unseen argument can be %NULL, in which case the function
+ * simply returns whether an updated "UNSEEN" value is present.
+ *
+ * Returns: whether @out_unseen was set
+ *
+ * Since: 3.10
+ **/
+gboolean
+camel_imapx_status_response_get_unseen (CamelIMAPXStatusResponse *response,
+ guint32 *out_unseen)
+{
+ g_return_val_if_fail (
+ CAMEL_IS_IMAPX_STATUS_RESPONSE (response), FALSE);
+
+ if (out_unseen != NULL && response->priv->have_unseen)
+ *out_unseen = response->priv->unseen;
+
+ return response->priv->have_unseen;
+}
+
+/**
+ * camel_imapx_status_response_get_uidnext:
+ * @response: a #CamelIMAPXStatusResponse
+ * @out_uidnext: return location for the status value, or %NULL
+ *
+ * If @response includes an updated "UIDNEXT" value, write the value to
+ * @out_uidnext and return %TRUE. Otherwise leave @out_uidnext unset and
+ * return %FALSE.
+ *
+ * The "UIDNEXT" value refers to the next unique identifier value of the
+ * mailbox.
+ *
+ * The @out_uidnext argument can be %NULL, in which case the function
+ * simply returns whether an updated "UIDNEXT" value is present.
+ *
+ * Returns: whether @out_uidnext was set
+ *
+ * Since: 3.10
+ **/
+gboolean
+camel_imapx_status_response_get_uidnext (CamelIMAPXStatusResponse *response,
+ guint32 *out_uidnext)
+{
+ g_return_val_if_fail (
+ CAMEL_IS_IMAPX_STATUS_RESPONSE (response), FALSE);
+
+ if (out_uidnext != NULL && response->priv->have_uidnext)
+ *out_uidnext = response->priv->uidnext;
+
+ return response->priv->have_uidnext;
+}
+
+/**
+ * camel_imapx_status_response_get_uidvalidity:
+ * @response: a #CamelIMAPXStatusResponse
+ * @out_uidvalidity: return location for the status value, or %NULL
+ *
+ * If @response includes an updated "UIDVALIDITY" value, write the value to
+ * @out_uidvalidity and return %TRUE. Otherwise leave @out_uidvalidity unset
+ * and return %FALSE.
+ *
+ * The "UIDVALIDITY" value refers to the unique identifier validity of the
+ * mailbox.
+ *
+ * The @out_uidvalidity argument can be %NULL, in which case the function
+ * simply returns whether an updated "UIDVALIDITY" value is present.
+ *
+ * Returns: whether @out_uidvalidity was set
+ *
+ * Since: 3.10
+ **/
+gboolean
+camel_imapx_status_response_get_uidvalidity (CamelIMAPXStatusResponse *response,
+ guint32 *out_uidvalidity)
+{
+ g_return_val_if_fail (
+ CAMEL_IS_IMAPX_STATUS_RESPONSE (response), FALSE);
+
+ if (out_uidvalidity != NULL && response->priv->have_uidvalidity)
+ *out_uidvalidity = response->priv->uidvalidity;
+
+ return response->priv->have_uidvalidity;
+}
+
+/**
+ * camel_imapx_status_response_get_highestmodseq:
+ * @response: a #CamelIMAPXStatusResponse
+ * @out_highestmodseq: return location for the status value, or %NULL
+ *
+ * If @response includes an updated "HIGHESTMODSEQ" value, write the value to
+ * @out_highestmodseq and return %TRUE. Otherwise leave @out_highestmodseq
+ * unset and return %FALSE.
+ *
+ * The "HIGHESTMODSEQ" value refers to the the highest mod-sequence value of
+ * all messages in the mailbox, assuming the server supports the persistent
+ * storage of mod-sequences.
+ *
+ * The @out_highestmodseq argument can be %NULL, in which case the function
+ * simply returns whether an updated "HIGHESTMODSEQ" value is present.
+ *
+ * Returns: whether @out_highestmodseq was set
+ *
+ * Since: 3.10
+ **/
+gboolean
+camel_imapx_status_response_get_highestmodseq (CamelIMAPXStatusResponse *response,
+ guint64 *out_highestmodseq)
+{
+ g_return_val_if_fail (
+ CAMEL_IS_IMAPX_STATUS_RESPONSE (response), FALSE);
+
+ if (out_highestmodseq != NULL && response->priv->have_highestmodseq)
+ *out_highestmodseq = response->priv->highestmodseq;
+
+ return response->priv->have_highestmodseq;
+}
+
diff --git a/src/camel/providers/imapx/camel-imapx-status-response.h b/src/camel/providers/imapx/camel-imapx-status-response.h
new file mode 100644
index 000000000..49af19a79
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-status-response.h
@@ -0,0 +1,99 @@
+/*
+ * camel-imapx-status-response.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_STATUS_RESPONSE_H
+#define CAMEL_IMAPX_STATUS_RESPONSE_H
+
+#include <gio/gio.h>
+
+#include "camel-imapx-input-stream.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_STATUS_RESPONSE \
+ (camel_imapx_status_response_get_type ())
+#define CAMEL_IMAPX_STATUS_RESPONSE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_STATUS_RESPONSE, CamelIMAPXStatusResponse))
+#define CAMEL_IMAPX_STATUS_RESPONSE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_STATUS_RESPONSE, CamelIMAPXStatusResponseClass))
+#define CAMEL_IS_IMAPX_STATUS_RESPONSE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_STATUS_RESPONSE))
+#define CAMEL_IS_IMAPX_STATUS_RESPONSE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_STATUS_RESPONSE))
+#define CAMEL_IMAPX_STATUS_RESPONSE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_STATUS_RESPONSE, CamelIMAPXStatusResponseClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAPXStatusResponse CamelIMAPXStatusResponse;
+typedef struct _CamelIMAPXStatusResponseClass CamelIMAPXStatusResponseClass;
+typedef struct _CamelIMAPXStatusResponsePrivate CamelIMAPXStatusResponsePrivate;
+
+/**
+ * CamelIMAPXStatusResponse:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.10
+ **/
+struct _CamelIMAPXStatusResponse {
+ GObject parent;
+ CamelIMAPXStatusResponsePrivate *priv;
+};
+
+struct _CamelIMAPXStatusResponseClass {
+ GObjectClass parent_class;
+};
+
+GType camel_imapx_status_response_get_type
+ (void) G_GNUC_CONST;
+CamelIMAPXStatusResponse *
+ camel_imapx_status_response_new
+ (CamelIMAPXInputStream *stream,
+ gchar inbox_separator,
+ GCancellable *cancellable,
+ GError **error);
+const gchar * camel_imapx_status_response_get_mailbox_name
+ (CamelIMAPXStatusResponse *response);
+gboolean camel_imapx_status_response_get_messages
+ (CamelIMAPXStatusResponse *response,
+ guint32 *out_messages);
+gboolean camel_imapx_status_response_get_recent
+ (CamelIMAPXStatusResponse *response,
+ guint32 *out_recent);
+gboolean camel_imapx_status_response_get_unseen
+ (CamelIMAPXStatusResponse *response,
+ guint32 *out_unseen);
+gboolean camel_imapx_status_response_get_uidnext
+ (CamelIMAPXStatusResponse *response,
+ guint32 *out_uidnext);
+gboolean camel_imapx_status_response_get_uidvalidity
+ (CamelIMAPXStatusResponse *response,
+ guint32 *out_uidvalidity);
+gboolean camel_imapx_status_response_get_highestmodseq
+ (CamelIMAPXStatusResponse *response,
+ guint64 *out_highestmodseq);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_STATUS_RESPONSE_H */
+
diff --git a/src/camel/providers/imapx/camel-imapx-store-summary.c b/src/camel/providers/imapx/camel-imapx-store-summary.c
new file mode 100644
index 000000000..6b0158e93
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-store-summary.c
@@ -0,0 +1,368 @@
+/*
+ * camel-imapx-store-summary.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <camel/camel.h>
+
+#include "camel-imapx-utils.h"
+#include "camel-imapx-store-summary.h"
+
+#define d(...) camel_imapx_debug(debug, '?', __VA_ARGS__)
+
+/* Version 0: Original IMAPX file format. */
+#define CAMEL_IMAPX_STORE_SUMMARY_VERSION_0 (0)
+
+/* Version 1: (3.10) Store the hierarchy separator. */
+#define CAMEL_IMAPX_STORE_SUMMARY_VERSION_1 (1);
+
+#define CAMEL_IMAPX_STORE_SUMMARY_VERSION (1)
+
+G_DEFINE_TYPE (
+ CamelIMAPXStoreSummary,
+ camel_imapx_store_summary,
+ CAMEL_TYPE_STORE_SUMMARY)
+
+static gboolean
+namespace_load (FILE *in)
+{
+ gchar *unused = NULL;
+ gboolean success = FALSE;
+ guint32 j;
+
+ /* XXX This eats through the old namespace data for backward
+ * compatibility. Next time we bump the summary version,
+ * delete all this cruft. */
+
+ for (j = 0; j < 3; j++) {
+ gint32 i, n = 0;
+
+ if (camel_file_util_decode_fixed_int32 (in, &n) == -1)
+ goto exit;
+
+ for (i = 0; i < n; i++) {
+ guint32 sep;
+
+ if (camel_file_util_decode_string (in, &unused) == -1)
+ goto exit;
+
+ g_free (unused);
+ unused = NULL;
+
+ if (camel_file_util_decode_string (in, &unused) == -1)
+ goto exit;
+
+ g_free (unused);
+ unused = NULL;
+
+ if (camel_file_util_decode_uint32 (in, &sep) == -1)
+ goto exit;
+ }
+ }
+
+ success = TRUE;
+
+exit:
+ g_free (unused);
+
+ return success;
+}
+
+static gint
+imapx_store_summary_summary_header_load (CamelStoreSummary *summary,
+ FILE *in)
+{
+ CamelStoreSummaryClass *store_summary_class;
+ gint32 version, unused;
+
+ store_summary_class =
+ CAMEL_STORE_SUMMARY_CLASS (
+ camel_imapx_store_summary_parent_class);
+
+ /* Chain up to parent's summary_header_load() method. */
+ if (store_summary_class->summary_header_load (summary, in) == -1)
+ return -1;
+
+ if (camel_file_util_decode_fixed_int32 (in, &version) == -1)
+ return -1;
+
+ if (version < CAMEL_IMAPX_STORE_SUMMARY_VERSION) {
+ g_warning ("IMAPx: Unable to load store summary: Expected version (%d), got (%d)",
+ CAMEL_IMAPX_STORE_SUMMARY_VERSION, version);
+ return -1;
+ }
+
+ if (camel_file_util_decode_fixed_int32 (in, &unused) == -1)
+ return -1;
+
+ /* XXX This just eats old data that we no longer use. */
+ if (!namespace_load (in))
+ return -1;
+
+ return 0;
+}
+
+static gint
+imapx_store_summary_summary_header_save (CamelStoreSummary *summary,
+ FILE *out)
+{
+ CamelStoreSummaryClass *store_summary_class;
+
+ store_summary_class =
+ CAMEL_STORE_SUMMARY_CLASS (
+ camel_imapx_store_summary_parent_class);
+
+ /* Chain up to parent's summary_header_save() method. */
+ if (store_summary_class->summary_header_save (summary, out) == -1)
+ return -1;
+
+ /* always write as latest version */
+ if (camel_file_util_encode_fixed_int32 (
+ out, CAMEL_IMAPX_STORE_SUMMARY_VERSION) == -1)
+ return -1;
+
+ if (camel_file_util_encode_fixed_int32 (out, 0) == -1)
+ return -1;
+
+ /* XXX This just saves zero-count namespace placeholders for
+ * backward compatibility. Next time we bump the summary
+ * version, delete all this cruft. */
+
+ if (camel_file_util_encode_fixed_int32 (out, 0) == -1)
+ return -1;
+
+ if (camel_file_util_encode_fixed_int32 (out, 0) == -1)
+ return -1;
+
+ if (camel_file_util_encode_fixed_int32 (out, 0) == -1)
+ return -1;
+
+ return 0;
+}
+
+static CamelStoreInfo *
+imapx_store_summary_store_info_load (CamelStoreSummary *summary,
+ FILE *in)
+{
+ CamelStoreSummaryClass *store_summary_class;
+ CamelStoreInfo *si;
+ gchar *mailbox_name = NULL;
+ gchar *separator = NULL;
+
+ store_summary_class =
+ CAMEL_STORE_SUMMARY_CLASS (
+ camel_imapx_store_summary_parent_class);
+
+ /* Chain up to parent's store_info_load() method. */
+ si = store_summary_class->store_info_load (summary, in);
+ if (si == NULL)
+ return NULL;
+
+ if (camel_file_util_decode_string (in, &separator) == -1) {
+ camel_store_summary_info_unref (summary, si);
+ return NULL;
+ }
+
+ if (camel_file_util_decode_string (in, &mailbox_name) == -1) {
+ camel_store_summary_info_unref (summary, si);
+ g_free (separator);
+ return NULL;
+ }
+
+ camel_imapx_normalize_mailbox (mailbox_name, *separator);
+
+ /* NB: this is done again for compatability */
+ if (camel_imapx_mailbox_is_inbox (mailbox_name))
+ si->flags |=
+ CAMEL_FOLDER_SYSTEM |
+ CAMEL_FOLDER_TYPE_INBOX;
+
+ ((CamelIMAPXStoreInfo *) si)->mailbox_name = mailbox_name;
+ ((CamelIMAPXStoreInfo *) si)->separator = *separator;
+
+ g_free (separator);
+
+ return si;
+}
+
+static gint
+imapx_store_summary_store_info_save (CamelStoreSummary *summary,
+ FILE *out,
+ CamelStoreInfo *si)
+{
+ CamelStoreSummaryClass *store_summary_class;
+ gchar separator[] = { '\0', '\0' };
+ const gchar *mailbox_name;
+
+ store_summary_class =
+ CAMEL_STORE_SUMMARY_CLASS (
+ camel_imapx_store_summary_parent_class);
+
+ mailbox_name = ((CamelIMAPXStoreInfo *) si)->mailbox_name;
+ separator[0] = ((CamelIMAPXStoreInfo *) si)->separator;
+
+ /* Chain up to parent's store_info_save() method. */
+ if (store_summary_class->store_info_save (summary, out, si) == -1)
+ return -1;
+
+ if (camel_file_util_encode_string (out, separator) == -1)
+ return -1;
+
+ if (camel_file_util_encode_string (out, mailbox_name) == -1)
+ return -1;
+
+ return 0;
+}
+
+static void
+imapx_store_summary_store_info_free (CamelStoreSummary *summary,
+ CamelStoreInfo *si)
+{
+ CamelStoreSummaryClass *store_summary_class;
+
+ store_summary_class =
+ CAMEL_STORE_SUMMARY_CLASS (
+ camel_imapx_store_summary_parent_class);
+
+ g_free (((CamelIMAPXStoreInfo *) si)->mailbox_name);
+
+ /* Chain up to parent's store_info_free() method. */
+ store_summary_class->store_info_free (summary, si);
+}
+
+static void
+camel_imapx_store_summary_class_init (CamelIMAPXStoreSummaryClass *class)
+{
+ CamelStoreSummaryClass *store_summary_class;
+
+ store_summary_class = CAMEL_STORE_SUMMARY_CLASS (class);
+ store_summary_class->store_info_size = sizeof (CamelIMAPXStoreInfo);
+ store_summary_class->summary_header_load =imapx_store_summary_summary_header_load;
+ store_summary_class->summary_header_save = imapx_store_summary_summary_header_save;
+ store_summary_class->store_info_load = imapx_store_summary_store_info_load;
+ store_summary_class->store_info_save = imapx_store_summary_store_info_save;
+ store_summary_class->store_info_free = imapx_store_summary_store_info_free;
+}
+
+static void
+camel_imapx_store_summary_init (CamelIMAPXStoreSummary *summary)
+{
+}
+
+/**
+ * camel_imapx_store_summary_mailbox:
+ * @summary: a #CamelStoreSummary
+ * @mailbox_name: a mailbox name
+ *
+ * Retrieve a summary item by mailbox name.
+ *
+ * The returned #CamelIMAPXStoreInfo is referenced for thread-safety
+ * and should be unreferenced with camel_store_summary_info_unref()
+ * when finished with it.
+ *
+ * Returns: a #CamelIMAPXStoreInfo, or %NULL
+ **/
+CamelIMAPXStoreInfo *
+camel_imapx_store_summary_mailbox (CamelStoreSummary *summary,
+ const gchar *mailbox_name)
+{
+ CamelStoreInfo *match = NULL;
+ GPtrArray *array;
+ gboolean find_inbox;
+ guint ii;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STORE_SUMMARY (summary), NULL);
+ g_return_val_if_fail (mailbox_name != NULL, NULL);
+
+ find_inbox = camel_imapx_mailbox_is_inbox (mailbox_name);
+
+ array = camel_store_summary_array (summary);
+
+ for (ii = 0; ii < array->len; ii++) {
+ CamelIMAPXStoreInfo *info;
+ gboolean is_inbox;
+
+ info = g_ptr_array_index (array, ii);
+ is_inbox = camel_imapx_mailbox_is_inbox (info->mailbox_name);
+
+ if (find_inbox && is_inbox) {
+ match = camel_store_summary_info_ref (
+ summary, (CamelStoreInfo *) info);
+ break;
+ }
+
+ if (g_str_equal (info->mailbox_name, mailbox_name)) {
+ match = camel_store_summary_info_ref (
+ summary, (CamelStoreInfo *) info);
+ break;
+ }
+ }
+
+ camel_store_summary_array_free (summary, array);
+
+ return (CamelIMAPXStoreInfo *) match;
+}
+
+CamelIMAPXStoreInfo *
+camel_imapx_store_summary_add_from_mailbox (CamelStoreSummary *summary,
+ CamelIMAPXMailbox *mailbox)
+{
+ CamelIMAPXStoreInfo *info;
+ const gchar *mailbox_name;
+ gchar *folder_path;
+ gchar separator;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STORE_SUMMARY (summary), NULL);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), NULL);
+
+ mailbox_name = camel_imapx_mailbox_get_name (mailbox);
+ separator = camel_imapx_mailbox_get_separator (mailbox);
+
+ info = camel_imapx_store_summary_mailbox (summary, mailbox_name);
+ if (info != NULL) {
+ camel_store_summary_info_unref (
+ summary, (CamelStoreInfo *) info);
+ return info;
+ }
+
+ folder_path = camel_imapx_mailbox_to_folder_path (
+ mailbox_name, separator);
+
+ info = (CamelIMAPXStoreInfo *)
+ camel_store_summary_add_from_path (summary, folder_path);
+
+ g_free (folder_path);
+
+ g_return_val_if_fail (info != NULL, NULL);
+
+ info->mailbox_name = g_strdup (mailbox_name);
+ info->separator = separator;
+
+ if (camel_imapx_mailbox_is_inbox (mailbox_name))
+ info->info.flags |=
+ CAMEL_FOLDER_SYSTEM |
+ CAMEL_FOLDER_TYPE_INBOX;
+
+ return info;
+}
+
diff --git a/src/camel/providers/imapx/camel-imapx-store-summary.h b/src/camel/providers/imapx/camel-imapx-store-summary.h
new file mode 100644
index 000000000..2d919d10d
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-store-summary.h
@@ -0,0 +1,78 @@
+/*
+ * camel-imapx-store-summary.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_STORE_SUMMARY_H
+#define CAMEL_IMAPX_STORE_SUMMARY_H
+
+#include <camel/camel.h>
+
+#include "camel-imapx-mailbox.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_STORE_SUMMARY \
+ (camel_imapx_store_summary_get_type ())
+#define CAMEL_IMAPX_STORE_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_STORE_SUMMARY, CamelIMAPXStoreSummary))
+#define CAMEL_IMAPX_STORE_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_STORE_SUMMARY, CamelIMAPXStoreSummaryClass))
+#define CAMEL_IS_IMAPX_STORE_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_STORE_SUMMARY))
+#define CAMEL_IS_IMAPX_STORE_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_STORE_SUMMARY))
+#define CAMEL_IMAPX_STORE_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_STORE_SUMMARY, CamelIMAPXStoreSummaryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAPXStoreSummary CamelIMAPXStoreSummary;
+typedef struct _CamelIMAPXStoreSummaryClass CamelIMAPXStoreSummaryClass;
+
+typedef struct _CamelIMAPXStoreInfo CamelIMAPXStoreInfo;
+
+struct _CamelIMAPXStoreInfo {
+ CamelStoreInfo info;
+ gchar *mailbox_name;
+ gchar separator;
+};
+
+struct _CamelIMAPXStoreSummary {
+ CamelStoreSummary parent;
+};
+
+struct _CamelIMAPXStoreSummaryClass {
+ CamelStoreSummaryClass parent_class;
+};
+
+GType camel_imapx_store_summary_get_type
+ (void) G_GNUC_CONST;
+CamelIMAPXStoreInfo *
+ camel_imapx_store_summary_mailbox
+ (CamelStoreSummary *summary,
+ const gchar *mailbox_name);
+CamelIMAPXStoreInfo *
+ camel_imapx_store_summary_add_from_mailbox
+ (CamelStoreSummary *summary,
+ CamelIMAPXMailbox *mailbox);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAP_STORE_SUMMARY_H */
diff --git a/src/camel/providers/imapx/camel-imapx-store.c b/src/camel/providers/imapx/camel-imapx-store.c
new file mode 100644
index 000000000..96bbb8417
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-store.c
@@ -0,0 +1,3701 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-imap-store.c : class for a imap store
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <sys/types.h>
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-imapx-conn-manager.h"
+#include "camel-imapx-folder.h"
+#include "camel-imapx-job.h"
+#include "camel-imapx-server.h"
+#include "camel-imapx-settings.h"
+#include "camel-imapx-store.h"
+#include "camel-imapx-summary.h"
+#include "camel-imapx-utils.h"
+
+/* Specified in RFC 2060 section 2.1 */
+#define IMAP_PORT 143
+#define IMAPS_PORT 993
+
+#define FINFO_REFRESH_INTERVAL 60
+
+#define CAMEL_IMAPX_STORE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_IMAPX_STORE, CamelIMAPXStorePrivate))
+
+#define e(...) camel_imapx_debug(extra, __VA_ARGS__)
+
+struct _CamelIMAPXStorePrivate {
+ CamelIMAPXConnManager *conn_man;
+
+ CamelIMAPXServer *connecting_server;
+ gboolean is_concurrent_connection;
+
+ GMutex server_lock;
+
+ GHashTable *quota_info;
+ GMutex quota_info_lock;
+
+ GMutex settings_lock;
+ CamelSettings *settings;
+ gulong settings_notify_handler_id;
+
+ /* Used for synchronizing get_folder_info_sync(). */
+ GMutex get_finfo_lock;
+ time_t last_refresh_time;
+ volatile gint syncing_folders;
+
+ CamelIMAPXNamespaceResponse *namespaces;
+ GMutex namespaces_lock;
+
+ GHashTable *mailboxes;
+ GMutex mailboxes_lock;
+};
+
+enum {
+ PROP_0,
+ PROP_CONNECTABLE,
+ PROP_HOST_REACHABLE,
+ PROP_CONN_MANAGER
+};
+
+enum {
+ MAILBOX_CREATED,
+ MAILBOX_RENAMED,
+ MAILBOX_UPDATED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static GInitableIface *parent_initable_interface;
+
+/* Forward Declarations */
+static void camel_imapx_store_initable_init (GInitableIface *iface);
+static void camel_network_service_init (CamelNetworkServiceInterface *iface);
+static void camel_subscribable_init (CamelSubscribableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ CamelIMAPXStore,
+ camel_imapx_store,
+ CAMEL_TYPE_OFFLINE_STORE,
+ G_IMPLEMENT_INTERFACE (
+ G_TYPE_INITABLE,
+ camel_imapx_store_initable_init)
+ G_IMPLEMENT_INTERFACE (
+ CAMEL_TYPE_NETWORK_SERVICE,
+ camel_network_service_init)
+ G_IMPLEMENT_INTERFACE (
+ CAMEL_TYPE_SUBSCRIBABLE,
+ camel_subscribable_init))
+
+static guint
+imapx_name_hash (gconstpointer key)
+{
+ const gchar *mailbox = key;
+
+ if (camel_imapx_mailbox_is_inbox (mailbox))
+ mailbox = "INBOX";
+
+ return g_str_hash (mailbox);
+}
+
+static gboolean
+imapx_name_equal (gconstpointer a,
+ gconstpointer b)
+{
+ const gchar *mailbox_a = a;
+ const gchar *mailbox_b = b;
+
+ if (camel_imapx_mailbox_is_inbox (mailbox_a))
+ mailbox_a = "INBOX";
+
+ if (camel_imapx_mailbox_is_inbox (mailbox_b))
+ mailbox_b = "INBOX";
+
+ return g_str_equal (mailbox_a, mailbox_b);
+}
+
+static void
+imapx_store_update_store_flags (CamelStore *store)
+{
+ CamelService *service;
+ CamelSettings *settings;
+ CamelIMAPXSettings *imapx_settings;
+
+ service = CAMEL_SERVICE (store);
+ settings = camel_service_ref_settings (service);
+ imapx_settings = CAMEL_IMAPX_SETTINGS (settings);
+
+ if (camel_imapx_settings_get_use_real_junk_path (imapx_settings)) {
+ store->flags &= ~CAMEL_STORE_VJUNK;
+ store->flags |= CAMEL_STORE_REAL_JUNK_FOLDER;
+ } else {
+ store->flags |= CAMEL_STORE_VJUNK;
+ store->flags &= ~CAMEL_STORE_REAL_JUNK_FOLDER;
+ }
+
+ if (camel_imapx_settings_get_use_real_trash_path (imapx_settings))
+ store->flags &= ~CAMEL_STORE_VTRASH;
+ else
+ store->flags |= CAMEL_STORE_VTRASH;
+
+ g_object_unref (settings);
+}
+
+static void
+imapx_store_settings_notify_cb (CamelSettings *settings,
+ GParamSpec *pspec,
+ CamelStore *store)
+{
+ gboolean folder_info_stale = g_str_equal (pspec->name, "use-subscriptions");
+
+ if (g_str_equal (pspec->name, "use-real-junk-path") ||
+ g_str_equal (pspec->name, "use-real-trash-path") ||
+ g_str_equal (pspec->name, "real-junk-path") ||
+ g_str_equal (pspec->name, "real-trash-path")) {
+ imapx_store_update_store_flags (store);
+ folder_info_stale = TRUE;
+ }
+
+ if (folder_info_stale)
+ camel_store_folder_info_stale (store);
+}
+
+static CamelFolderInfo *
+imapx_store_build_folder_info (CamelIMAPXStore *imapx_store,
+ const gchar *folder_path,
+ CamelFolderInfoFlags flags)
+{
+ CamelStore *store = (CamelStore *) imapx_store;
+ CamelSettings *settings;
+ CamelFolderInfo *fi;
+ const gchar *name;
+
+ store = CAMEL_STORE (imapx_store);
+ settings = camel_service_ref_settings (CAMEL_SERVICE (store));
+
+ fi = camel_folder_info_new ();
+ fi->full_name = g_strdup (folder_path);
+ fi->flags = flags;
+ fi->unread = -1;
+ fi->total = -1;
+
+ name = strrchr (fi->full_name, '/');
+ if (name == NULL)
+ name = fi->full_name;
+ else
+ name++;
+
+ if (camel_imapx_mailbox_is_inbox (fi->full_name)) {
+ fi->display_name = g_strdup (_("Inbox"));
+ fi->flags |= CAMEL_FOLDER_SYSTEM;
+ fi->flags |= CAMEL_FOLDER_TYPE_INBOX;
+ } else {
+ fi->display_name = g_strdup (name);
+ }
+
+ if ((store->flags & CAMEL_STORE_VTRASH) == 0) {
+ const gchar *trash_path;
+
+ trash_path = camel_imapx_settings_get_real_trash_path (
+ CAMEL_IMAPX_SETTINGS (settings));
+ if (g_strcmp0 (trash_path, folder_path) == 0)
+ fi->flags |= CAMEL_FOLDER_TYPE_TRASH;
+ }
+
+ if ((store->flags & CAMEL_STORE_REAL_JUNK_FOLDER) != 0) {
+ const gchar *junk_path;
+
+ junk_path = camel_imapx_settings_get_real_junk_path (
+ CAMEL_IMAPX_SETTINGS (settings));
+ if (g_strcmp0 (junk_path, folder_path) == 0)
+ fi->flags |= CAMEL_FOLDER_TYPE_JUNK;
+ }
+
+ g_object_unref (settings);
+
+ return fi;
+}
+
+static void
+imapx_store_rename_folder_info (CamelIMAPXStore *imapx_store,
+ const gchar *old_folder_path,
+ const gchar *new_folder_path)
+{
+ GPtrArray *array;
+ gint olen = strlen (old_folder_path);
+ guint ii;
+
+ array = camel_store_summary_array (imapx_store->summary);
+
+ for (ii = 0; ii < array->len; ii++) {
+ CamelStoreInfo *si;
+ CamelIMAPXStoreInfo *imapx_si;
+ const gchar *path;
+ gchar *new_path;
+ gchar *new_mailbox_name;
+
+ si = g_ptr_array_index (array, ii);
+ path = camel_store_info_path (imapx_store->summary, si);
+
+ /* We need to adjust not only the entry for the renamed
+ * folder, but also the entries for all the descendants
+ * of the renamed folder. */
+
+ if (!g_str_has_prefix (path, old_folder_path))
+ continue;
+
+ if (strlen (path) > olen)
+ new_path = g_strdup_printf (
+ "%s/%s", new_folder_path, path + olen + 1);
+ else
+ new_path = g_strdup (new_folder_path);
+
+ camel_store_info_set_string (
+ imapx_store->summary, si,
+ CAMEL_STORE_INFO_PATH, new_path);
+
+ imapx_si = (CamelIMAPXStoreInfo *) si;
+ g_warn_if_fail (imapx_si->separator != '\0');
+
+ new_mailbox_name =
+ camel_imapx_folder_path_to_mailbox (
+ new_path, imapx_si->separator);
+
+ /* Takes ownership of new_mailbox_name. */
+ g_free (imapx_si->mailbox_name);
+ imapx_si->mailbox_name = new_mailbox_name;
+
+ camel_store_summary_touch (imapx_store->summary);
+
+ g_free (new_path);
+ }
+
+ camel_store_summary_array_free (imapx_store->summary, array);
+}
+
+static void
+imapx_store_rename_storage_path (CamelIMAPXStore *imapx_store,
+ const gchar *old_mailbox,
+ const gchar *new_mailbox)
+{
+ CamelService *service;
+ const gchar *user_cache_dir;
+ gchar *root_storage_path;
+ gchar *old_storage_path;
+ gchar *new_storage_path;
+
+ service = CAMEL_SERVICE (imapx_store);
+ user_cache_dir = camel_service_get_user_cache_dir (service);
+ root_storage_path = g_build_filename (user_cache_dir, "folders", NULL);
+
+ old_storage_path =
+ imapx_path_to_physical (root_storage_path, old_mailbox);
+ new_storage_path =
+ imapx_path_to_physical (root_storage_path, new_mailbox);
+
+ if (g_rename (old_storage_path, new_storage_path) == -1 && errno != ENOENT) {
+ g_warning (
+ "Could not rename message cache "
+ "'%s' to '%s: %s: cache reset",
+ old_storage_path,
+ new_storage_path,
+ g_strerror (errno));
+ }
+
+ g_free (root_storage_path);
+ g_free (old_storage_path);
+ g_free (new_storage_path);
+}
+
+static void
+imapx_store_add_mailbox_to_folder (CamelIMAPXStore *store,
+ CamelIMAPXMailbox *mailbox)
+{
+ CamelIMAPXFolder *folder;
+ gchar *folder_path;
+
+ /* Add the CamelIMAPXMailbox to a cached CamelIMAPXFolder. */
+
+ folder_path = camel_imapx_mailbox_dup_folder_path (mailbox);
+
+ folder = camel_object_bag_get (
+ CAMEL_STORE (store)->folders, folder_path);
+
+ if (folder != NULL) {
+ camel_imapx_folder_set_mailbox (folder, mailbox);
+ g_object_unref (folder);
+ }
+
+ g_free (folder_path);
+}
+
+static CamelStoreInfoFlags
+imapx_store_mailbox_attributes_to_flags (CamelIMAPXMailbox *mailbox)
+{
+ CamelStoreInfoFlags store_info_flags = 0;
+ const gchar *attribute;
+
+ attribute = CAMEL_IMAPX_LIST_ATTR_NOSELECT;
+ if (camel_imapx_mailbox_has_attribute (mailbox, attribute) &&
+ !camel_imapx_mailbox_is_inbox (camel_imapx_mailbox_get_name (mailbox)))
+ store_info_flags |= CAMEL_STORE_INFO_FOLDER_NOSELECT;
+
+ attribute = CAMEL_IMAPX_LIST_ATTR_NOINFERIORS;
+ if (camel_imapx_mailbox_has_attribute (mailbox, attribute))
+ store_info_flags |= CAMEL_STORE_INFO_FOLDER_NOINFERIORS;
+
+ attribute = CAMEL_IMAPX_LIST_ATTR_HASCHILDREN;
+ if (camel_imapx_mailbox_has_attribute (mailbox, attribute))
+ store_info_flags |= CAMEL_STORE_INFO_FOLDER_CHILDREN;
+
+ attribute = CAMEL_IMAPX_LIST_ATTR_HASNOCHILDREN;
+ if (camel_imapx_mailbox_has_attribute (mailbox, attribute))
+ store_info_flags |= CAMEL_STORE_INFO_FOLDER_NOCHILDREN;
+
+ attribute = CAMEL_IMAPX_LIST_ATTR_SUBSCRIBED;
+ if (camel_imapx_mailbox_has_attribute (mailbox, attribute))
+ store_info_flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+
+ /* XXX Does "\Marked" mean CAMEL_STORE_INFO_FOLDER_FLAGGED?
+ * Who the heck knows; the enum value is undocumented. */
+
+ return store_info_flags;
+}
+
+static void
+imapx_store_process_mailbox_attributes (CamelIMAPXStore *store,
+ CamelIMAPXMailbox *mailbox,
+ const gchar *oldname)
+{
+ CamelFolderInfo *fi;
+ CamelIMAPXStoreInfo *si;
+ CamelStoreInfoFlags flags;
+ CamelSettings *settings;
+ gboolean use_subscriptions;
+ gboolean mailbox_is_subscribed;
+ gboolean mailbox_is_nonexistent;
+ gboolean mailbox_was_in_summary;
+ gboolean mailbox_was_subscribed;
+ gboolean emit_folder_created_subscribed = FALSE;
+ gboolean emit_folder_unsubscribed_deleted = FALSE;
+ gboolean emit_folder_renamed = FALSE;
+ gchar *folder_path;
+ const gchar *mailbox_name;
+ gchar separator;
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (store));
+ use_subscriptions = camel_imapx_settings_get_use_subscriptions (
+ CAMEL_IMAPX_SETTINGS (settings));
+ g_object_unref (settings);
+
+ mailbox_name = camel_imapx_mailbox_get_name (mailbox);
+ separator = camel_imapx_mailbox_get_separator (mailbox);
+
+ mailbox_is_subscribed =
+ camel_imapx_mailbox_has_attribute (
+ mailbox, CAMEL_IMAPX_LIST_ATTR_SUBSCRIBED) ||
+ camel_imapx_mailbox_is_inbox (mailbox_name);
+
+ mailbox_is_nonexistent =
+ camel_imapx_mailbox_has_attribute (
+ mailbox, CAMEL_IMAPX_LIST_ATTR_NONEXISTENT);
+
+ /* XXX The flags type transforms from CamelStoreInfoFlags
+ * to CamelFolderInfoFlags about half-way through this.
+ * We should really eliminate the confusing redundancy. */
+ flags = imapx_store_mailbox_attributes_to_flags (mailbox);
+
+ /* Summary retains ownership of the returned CamelStoreInfo. */
+ si = camel_imapx_store_summary_mailbox (store->summary, mailbox_name);
+ if (!si && oldname)
+ si = camel_imapx_store_summary_mailbox (store->summary, oldname);
+ if (si != NULL) {
+ mailbox_was_in_summary = TRUE;
+ if (si->info.flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)
+ mailbox_was_subscribed = TRUE;
+ else
+ mailbox_was_subscribed = FALSE;
+ } else {
+ /* XXX Shouldn't this take a GError if it can fail? */
+ si = camel_imapx_store_summary_add_from_mailbox (
+ store->summary, mailbox);
+ g_return_if_fail (si != NULL);
+ mailbox_was_in_summary = FALSE;
+ mailbox_was_subscribed = FALSE;
+ }
+
+ /* Check whether the flags disagree. */
+ if (si->info.flags != flags) {
+ si->info.flags = flags;
+ camel_store_summary_touch (store->summary);
+ }
+
+ folder_path = camel_imapx_mailbox_to_folder_path (mailbox_name, separator);
+ fi = imapx_store_build_folder_info (store, folder_path, (CamelFolderInfoFlags) flags);
+
+ /* Figure out which signals to emit, if any. */
+ if (use_subscriptions || camel_imapx_namespace_get_category (camel_imapx_mailbox_get_namespace (mailbox)) != CAMEL_IMAPX_NAMESPACE_PERSONAL) {
+ /* If we are honoring folder subscriptions, then
+ * subscription changes are equivalent to folder
+ * creation / deletion as far as we're concerned. */
+ if (mailbox_is_subscribed && !mailbox_is_nonexistent) {
+ if (oldname != NULL) {
+ emit_folder_renamed = TRUE;
+ } else if (!mailbox_was_subscribed) {
+ emit_folder_created_subscribed = TRUE;
+ }
+ }
+ if (!mailbox_is_subscribed && mailbox_was_subscribed)
+ emit_folder_unsubscribed_deleted = TRUE;
+ if (mailbox_is_nonexistent && mailbox_was_subscribed)
+ emit_folder_unsubscribed_deleted = TRUE;
+ } else {
+ if (!mailbox_is_nonexistent) {
+ if (oldname != NULL) {
+ emit_folder_renamed = TRUE;
+ } else if (!mailbox_was_in_summary) {
+ emit_folder_created_subscribed = TRUE;
+ }
+ }
+ if (mailbox_is_nonexistent && mailbox_was_in_summary)
+ emit_folder_unsubscribed_deleted = TRUE;
+ }
+
+ /* Suppress all signal emissions when synchronizing folders. */
+ if (g_atomic_int_get (&store->priv->syncing_folders) > 0) {
+ emit_folder_created_subscribed = FALSE;
+ emit_folder_unsubscribed_deleted = FALSE;
+ emit_folder_renamed = FALSE;
+ } else {
+ /* At most one signal emission flag should be set. */
+ g_warn_if_fail (
+ (emit_folder_created_subscribed ? 1 : 0) +
+ (emit_folder_unsubscribed_deleted ? 1 : 0) +
+ (emit_folder_renamed ? 1 : 0) <= 1);
+ }
+
+ if (emit_folder_created_subscribed) {
+ camel_store_folder_created (
+ CAMEL_STORE (store), fi);
+ camel_subscribable_folder_subscribed (
+ CAMEL_SUBSCRIBABLE (store), fi);
+ }
+
+ if (emit_folder_unsubscribed_deleted) {
+ camel_subscribable_folder_unsubscribed (
+ CAMEL_SUBSCRIBABLE (store), fi);
+ camel_store_folder_deleted (
+ CAMEL_STORE (store), fi);
+ }
+
+ if (emit_folder_renamed) {
+ gchar *old_folder_path;
+ gchar *new_folder_path;
+
+ old_folder_path = camel_imapx_mailbox_to_folder_path (
+ oldname, separator);
+ new_folder_path = camel_imapx_mailbox_to_folder_path (
+ mailbox_name, separator);
+
+ imapx_store_rename_folder_info (
+ store, old_folder_path, new_folder_path);
+ imapx_store_rename_storage_path (
+ store, old_folder_path, new_folder_path);
+
+ camel_store_folder_renamed (CAMEL_STORE (store), old_folder_path, fi);
+
+ g_free (old_folder_path);
+ g_free (new_folder_path);
+ }
+
+ camel_folder_info_free (fi);
+ g_free (folder_path);
+}
+
+static void
+imapx_store_process_mailbox_status (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *mailbox)
+{
+ CamelStore *store;
+ CamelFolder *folder;
+ gchar *folder_path;
+
+ folder_path = camel_imapx_mailbox_dup_folder_path (mailbox);
+ store = CAMEL_STORE (imapx_store);
+
+ /* Update only already opened folders */
+ folder = camel_object_bag_reserve (store->folders, folder_path);
+ if (folder != NULL) {
+ CamelIMAPXFolder *imapx_folder;
+ CamelIMAPXSummary *imapx_summary;
+ guint32 uidvalidity;
+
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+ imapx_summary = CAMEL_IMAPX_SUMMARY (folder->summary);
+
+ uidvalidity = camel_imapx_mailbox_get_uidvalidity (mailbox);
+
+ if (uidvalidity > 0 && uidvalidity != imapx_summary->validity)
+ camel_imapx_folder_invalidate_local_cache (
+ imapx_folder, uidvalidity);
+
+ g_object_unref (folder);
+ } else {
+ camel_object_bag_abort (store->folders, folder_path);
+ }
+
+ g_free (folder_path);
+}
+
+static void
+imapx_store_connect_to_settings (CamelStore *store)
+{
+ CamelIMAPXStorePrivate *priv;
+ CamelSettings *settings;
+ gulong handler_id;
+
+ /* XXX I considered calling camel_store_folder_info_stale()
+ * here, but I suspect it would create unnecessary extra
+ * work for applications during startup since the signal
+ * is not emitted immediately.
+ *
+ * Let's just say whomever replaces the settings object
+ * in a CamelService is reponsible for deciding whether
+ * camel_store_folder_info_stale() should be called. */
+
+ priv = CAMEL_IMAPX_STORE_GET_PRIVATE (store);
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (store));
+
+ g_mutex_lock (&priv->settings_lock);
+
+ if (priv->settings != NULL) {
+ g_signal_handler_disconnect (
+ priv->settings,
+ priv->settings_notify_handler_id);
+ priv->settings_notify_handler_id = 0;
+ g_clear_object (&priv->settings);
+ }
+
+ priv->settings = g_object_ref (settings);
+
+ handler_id = g_signal_connect (
+ settings, "notify",
+ G_CALLBACK (imapx_store_settings_notify_cb), store);
+ priv->settings_notify_handler_id = handler_id;
+
+ g_mutex_unlock (&priv->settings_lock);
+
+ g_object_unref (settings);
+}
+
+static void
+imapx_store_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONNECTABLE:
+ camel_network_service_set_connectable (
+ CAMEL_NETWORK_SERVICE (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_store_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONNECTABLE:
+ g_value_take_object (
+ value,
+ camel_network_service_ref_connectable (
+ CAMEL_NETWORK_SERVICE (object)));
+ return;
+
+ case PROP_HOST_REACHABLE:
+ g_value_set_boolean (
+ value,
+ camel_network_service_get_host_reachable (
+ CAMEL_NETWORK_SERVICE (object)));
+ return;
+
+ case PROP_CONN_MANAGER:
+ g_value_set_object (
+ value,
+ camel_imapx_store_get_conn_manager (
+ CAMEL_IMAPX_STORE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+imapx_store_dispose (GObject *object)
+{
+ CamelIMAPXStore *imapx_store = CAMEL_IMAPX_STORE (object);
+
+ /* Force disconnect so we don't have it run later,
+ * after we've cleaned up some stuff. */
+ if (imapx_store->priv->conn_man != NULL) {
+ camel_service_disconnect_sync (CAMEL_SERVICE (imapx_store), FALSE, NULL, NULL);
+ g_clear_object (&imapx_store->priv->conn_man);
+ }
+
+ if (imapx_store->priv->settings_notify_handler_id > 0) {
+ g_signal_handler_disconnect (
+ imapx_store->priv->settings,
+ imapx_store->priv->settings_notify_handler_id);
+ imapx_store->priv->settings_notify_handler_id = 0;
+ }
+
+ g_clear_object (&imapx_store->summary);
+
+ g_clear_object (&imapx_store->priv->connecting_server);
+ g_clear_object (&imapx_store->priv->settings);
+ g_clear_object (&imapx_store->priv->namespaces);
+
+ g_hash_table_remove_all (imapx_store->priv->mailboxes);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_imapx_store_parent_class)->dispose (object);
+}
+
+static void
+imapx_store_finalize (GObject *object)
+{
+ CamelIMAPXStorePrivate *priv;
+
+ priv = CAMEL_IMAPX_STORE_GET_PRIVATE (object);
+
+ g_mutex_clear (&priv->get_finfo_lock);
+ g_mutex_clear (&priv->server_lock);
+
+ g_hash_table_destroy (priv->quota_info);
+ g_mutex_clear (&priv->quota_info_lock);
+
+ g_mutex_clear (&priv->settings_lock);
+
+ g_mutex_clear (&priv->namespaces_lock);
+
+ g_hash_table_destroy (priv->mailboxes);
+ g_mutex_clear (&priv->mailboxes_lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_imapx_store_parent_class)->finalize (object);
+}
+
+static void
+imapx_store_notify (GObject *object,
+ GParamSpec *pspec)
+{
+ if (g_str_equal (pspec->name, "settings")) {
+ imapx_store_connect_to_settings (CAMEL_STORE (object));
+ imapx_store_update_store_flags (CAMEL_STORE (object));
+ }
+
+ /* Chain up to parent's notify() method. */
+ G_OBJECT_CLASS (camel_imapx_store_parent_class)->
+ notify (object, pspec);
+}
+
+static gchar *
+imapx_get_name (CamelService *service,
+ gboolean brief)
+{
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ gchar *host;
+ gchar *user;
+ gchar *name;
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ user = camel_network_settings_dup_user (network_settings);
+
+ g_object_unref (settings);
+
+ if (brief)
+ name = g_strdup_printf (
+ _("IMAP server %s"), host);
+ else
+ name = g_strdup_printf (
+ _("IMAP service for %s on %s"), user, host);
+
+ g_free (host);
+ g_free (user);
+
+ return name;
+}
+
+static gboolean
+imapx_connect_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXStore *imapx_store;
+
+ /* Chain up to parent's method. */
+ if (!CAMEL_SERVICE_CLASS (camel_imapx_store_parent_class)->connect_sync (service, cancellable, error))
+ return FALSE;
+
+ imapx_store = CAMEL_IMAPX_STORE (service);
+
+ return camel_imapx_conn_manager_connect_sync (imapx_store->priv->conn_man, cancellable, error);
+}
+
+static gboolean
+imapx_disconnect_sync (CamelService *service,
+ gboolean clean,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXStorePrivate *priv;
+
+ priv = CAMEL_IMAPX_STORE_GET_PRIVATE (service);
+
+ if (priv->conn_man != NULL)
+ camel_imapx_conn_manager_disconnect_sync (priv->conn_man, cancellable, error);
+
+ g_mutex_lock (&priv->server_lock);
+
+ g_clear_object (&priv->connecting_server);
+
+ g_mutex_unlock (&priv->server_lock);
+
+ /* Chain up to parent's method. */
+ return CAMEL_SERVICE_CLASS (camel_imapx_store_parent_class)->disconnect_sync (service, clean, cancellable, error);
+}
+
+static CamelAuthenticationResult
+imapx_authenticate_sync (CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXStorePrivate *priv;
+ CamelIMAPXServer *imapx_server;
+ CamelAuthenticationResult result;
+
+ priv = CAMEL_IMAPX_STORE_GET_PRIVATE (service);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return CAMEL_AUTHENTICATION_ERROR;
+
+ /* This should have been set for us by connect_sync(). */
+ g_mutex_lock (&priv->server_lock);
+ if (!priv->connecting_server) {
+ g_mutex_unlock (&priv->server_lock);
+
+ g_set_error_literal (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("No IMAPx connection object provided"));
+
+ return CAMEL_AUTHENTICATION_ERROR;
+ }
+
+ imapx_server = g_object_ref (priv->connecting_server);
+ g_mutex_unlock (&priv->server_lock);
+
+ result = camel_imapx_server_authenticate_sync (
+ imapx_server, mechanism, cancellable, error);
+
+ g_clear_object (&imapx_server);
+
+ return result;
+}
+
+CamelServiceAuthType camel_imapx_password_authtype = {
+ N_("Password"),
+
+ N_("This option will connect to the IMAP server using a "
+ "plaintext password."),
+
+ "",
+ TRUE
+};
+
+static GList *
+imapx_query_auth_types_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelServiceAuthType *authtype;
+ CamelIMAPXStore *imapx_store;
+ GList *sasl_types = NULL;
+ GList *t, *next;
+ CamelIMAPXServer *server;
+ const struct _capability_info *cinfo;
+
+ imapx_store = CAMEL_IMAPX_STORE (service);
+
+ server = camel_imapx_server_new (imapx_store);
+ camel_imapx_server_set_tagprefix (server, 'Z');
+
+ g_signal_emit_by_name (imapx_store->priv->conn_man, "connection-created", 0, server);
+
+ if (!camel_imapx_server_query_auth_types_sync (server, cancellable, error))
+ goto exit;
+
+ cinfo = camel_imapx_server_get_capability_info (server);
+
+ sasl_types = camel_sasl_authtype_list (FALSE);
+ for (t = sasl_types; t; t = next) {
+ authtype = t->data;
+ next = t->next;
+
+ if (!cinfo || !g_hash_table_lookup (cinfo->auth_types, authtype->authproto)) {
+ sasl_types = g_list_remove_link (sasl_types, t);
+ g_list_free_1 (t);
+ }
+ }
+
+ sasl_types = g_list_prepend (sasl_types, &camel_imapx_password_authtype);
+
+exit:
+ g_object_unref (server);
+
+ return sasl_types;
+}
+
+static CamelFolder *
+get_folder_offline (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ GError **error)
+{
+ CamelIMAPXStore *imapx_store = CAMEL_IMAPX_STORE (store);
+ CamelFolder *new_folder = NULL;
+ CamelStoreInfo *si;
+ CamelService *service;
+ const gchar *user_cache_dir;
+
+ service = CAMEL_SERVICE (store);
+ user_cache_dir = camel_service_get_user_cache_dir (service);
+
+ si = camel_store_summary_path (imapx_store->summary, folder_name);
+
+ if (si != NULL) {
+ gchar *base_dir;
+ gchar *folder_dir;
+
+ base_dir = g_build_filename (user_cache_dir, "folders", NULL);
+ folder_dir = imapx_path_to_physical (base_dir, folder_name);
+ new_folder = camel_imapx_folder_new (
+ store, folder_dir, folder_name, error);
+ g_free (folder_dir);
+ g_free (base_dir);
+
+ camel_store_summary_info_unref (imapx_store->summary, si);
+ } else {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("No such folder %s"), folder_name);
+ }
+
+ return new_folder;
+}
+
+static void
+fill_fi (CamelStore *store,
+ CamelFolderInfo *fi)
+{
+ CamelFolder *folder;
+
+ folder = camel_object_bag_peek (store->folders, fi->full_name);
+ if (folder) {
+ CamelIMAPXFolder *imapx_folder;
+ CamelIMAPXSummary *ims;
+ CamelIMAPXMailbox *mailbox;
+
+ if (folder->summary)
+ ims = (CamelIMAPXSummary *) folder->summary;
+ else
+ ims = (CamelIMAPXSummary *) camel_imapx_summary_new (folder);
+
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+ mailbox = camel_imapx_folder_ref_mailbox (imapx_folder);
+
+ fi->unread = camel_folder_summary_get_unread_count ((CamelFolderSummary *) ims);
+ fi->total = camel_folder_summary_get_saved_count ((CamelFolderSummary *) ims);
+
+ g_clear_object (&mailbox);
+
+ if (!folder->summary)
+ g_object_unref (ims);
+ g_object_unref (folder);
+ }
+}
+
+static void
+imapx_delete_folder_from_cache (CamelIMAPXStore *imapx_store,
+ const gchar *folder_path,
+ gboolean save_summary)
+{
+ gchar *state_file;
+ gchar *folder_dir, *storage_path;
+ CamelFolderInfo *fi;
+ CamelService *service;
+ const gchar *user_cache_dir;
+
+ service = CAMEL_SERVICE (imapx_store);
+ user_cache_dir = camel_service_get_user_cache_dir (service);
+
+ storage_path = g_build_filename (user_cache_dir, "folders", NULL);
+ folder_dir = imapx_path_to_physical (storage_path, folder_path);
+ g_free (storage_path);
+ if (g_access (folder_dir, F_OK) != 0) {
+ g_free (folder_dir);
+ goto event;
+ }
+
+ /* Delete summary and all the data */
+ state_file = g_build_filename (folder_dir, "cmeta", NULL);
+ g_unlink (state_file);
+ g_free (state_file);
+
+ camel_db_delete_folder (
+ CAMEL_STORE (imapx_store)->cdb_w, folder_path, NULL);
+ g_rmdir (folder_dir);
+
+ state_file = g_build_filename (folder_dir, "subfolders", NULL);
+ g_rmdir (state_file);
+ g_free (state_file);
+
+ g_rmdir (folder_dir);
+ g_free (folder_dir);
+
+event:
+ camel_store_summary_remove_path (imapx_store->summary, folder_path);
+ if (save_summary)
+ camel_store_summary_save (imapx_store->summary);
+
+ fi = imapx_store_build_folder_info (imapx_store, folder_path, 0);
+ camel_subscribable_folder_unsubscribed (CAMEL_SUBSCRIBABLE (imapx_store), fi);
+ camel_store_folder_deleted (CAMEL_STORE (imapx_store), fi);
+ camel_folder_info_free (fi);
+}
+
+static CamelFolderInfo *
+get_folder_info_offline (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXStore *imapx_store = CAMEL_IMAPX_STORE (store);
+ CamelService *service;
+ CamelSettings *settings;
+ gboolean include_inbox = FALSE;
+ CamelFolderInfo *fi;
+ GPtrArray *folders;
+ GPtrArray *array;
+ gboolean use_subscriptions;
+ gint top_len;
+ guint ii;
+
+ if (g_strcmp0 (top, CAMEL_VTRASH_NAME) == 0 ||
+ g_strcmp0 (top, CAMEL_VJUNK_NAME) == 0) {
+ CamelFolder *vfolder;
+
+ vfolder = camel_store_get_folder_sync (store, top, 0, cancellable, error);
+ if (!vfolder)
+ return NULL;
+
+ fi = imapx_store_build_folder_info (imapx_store, top, 0);
+ fi->unread = camel_folder_summary_get_unread_count (vfolder->summary);
+ fi->total = camel_folder_summary_get_saved_count (vfolder->summary);
+
+ if (g_strcmp0 (top, CAMEL_VTRASH_NAME) == 0)
+ fi->flags = (fi->flags & ~CAMEL_FOLDER_TYPE_MASK) |
+ CAMEL_FOLDER_VIRTUAL |
+ CAMEL_FOLDER_VTRASH |
+ CAMEL_FOLDER_TYPE_TRASH;
+ else
+ fi->flags = (fi->flags & ~CAMEL_FOLDER_TYPE_MASK) |
+ CAMEL_FOLDER_VIRTUAL |
+ CAMEL_FOLDER_TYPE_JUNK;
+
+ g_object_unref (vfolder);
+
+ return fi;
+ }
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ use_subscriptions = camel_imapx_settings_get_use_subscriptions (
+ CAMEL_IMAPX_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ /* FIXME: obey other flags */
+
+ folders = g_ptr_array_new ();
+
+ if (top == NULL || top[0] == '\0') {
+ include_inbox = TRUE;
+ top = "";
+ }
+
+ top_len = strlen (top);
+
+ /* 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. */
+
+ array = camel_store_summary_array (imapx_store->summary);
+
+ for (ii = 0; ii < array->len; ii++) {
+ CamelStoreInfo *si;
+ const gchar *folder_path;
+ gboolean si_is_inbox;
+ gboolean si_is_match;
+
+ si = g_ptr_array_index (array, ii);
+ folder_path = camel_store_info_path (imapx_store->summary, si);
+ si_is_inbox = (g_ascii_strcasecmp (folder_path, "INBOX") == 0);
+
+ /* Filter by folder path. */
+ si_is_match =
+ (include_inbox && si_is_inbox) ||
+ (g_str_has_prefix (folder_path, top) && (top_len == 0 ||
+ !folder_path[top_len] || folder_path[top_len] == '/'));
+
+ if (!si_is_match)
+ continue;
+
+ /* Filter by subscription flags.
+ *
+ * Skip the folder if:
+ * The user only wants to see subscribed folders
+ * AND the folder is not subscribed
+ * AND the caller only wants SUBSCRIBED folder info
+ * AND the caller does NOT want a SUBSCRIPTION_LIST
+ *
+ * Note that having both SUBSCRIBED and SUBSCRIPTION_LIST
+ * flags set is contradictory. SUBSCRIPTION_LIST wins in
+ * that case.
+ */
+ si_is_match =
+ !use_subscriptions ||
+ (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) ||
+ !(flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) ||
+ (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST);
+
+ if (!si_is_match)
+ continue;
+
+ if (!use_subscriptions && !(si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) &&
+ !(flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST)) {
+ CamelIMAPXMailbox *mailbox;
+
+ mailbox = camel_imapx_store_ref_mailbox (imapx_store, ((CamelIMAPXStoreInfo *) si)->mailbox_name);
+ if (!mailbox || camel_imapx_namespace_get_category (camel_imapx_mailbox_get_namespace (mailbox)) != CAMEL_IMAPX_NAMESPACE_PERSONAL) {
+ /* Skip unsubscribed mailboxes which are not in the Personal namespace */
+ g_clear_object (&mailbox);
+ continue;
+ }
+
+ g_clear_object (&mailbox);
+ }
+
+ fi = imapx_store_build_folder_info (imapx_store, folder_path, 0);
+ fi->unread = si->unread;
+ fi->total = si->total;
+ if ((fi->flags & CAMEL_FOLDER_TYPE_MASK) != 0)
+ fi->flags =
+ (fi->flags & CAMEL_FOLDER_TYPE_MASK) |
+ (si->flags & ~CAMEL_FOLDER_TYPE_MASK);
+ else
+ fi->flags = si->flags;
+
+ /* blah, this gets lost somewhere, i can't be bothered finding out why */
+ if (si_is_inbox) {
+ fi->flags =
+ (fi->flags & ~CAMEL_FOLDER_TYPE_MASK) |
+ CAMEL_FOLDER_TYPE_INBOX;
+ fi->flags |= CAMEL_FOLDER_SYSTEM;
+ }
+
+ if (!(si->flags & CAMEL_FOLDER_NOSELECT))
+ fill_fi ((CamelStore *) imapx_store, fi);
+
+ if (!fi->child)
+ fi->flags |= CAMEL_FOLDER_NOCHILDREN;
+
+ if (fi->unread == -1 && fi->total == -1) {
+ CamelIMAPXMailbox *mailbox;
+
+ mailbox = camel_imapx_store_ref_mailbox (imapx_store, ((CamelIMAPXStoreInfo *) si)->mailbox_name);
+
+ if (mailbox) {
+ fi->unread = camel_imapx_mailbox_get_unseen (mailbox);
+ fi->total = camel_imapx_mailbox_get_messages (mailbox);
+ }
+
+ g_clear_object (&mailbox);
+ }
+
+ g_ptr_array_add (folders, fi);
+ }
+
+ camel_store_summary_array_free (imapx_store->summary, array);
+
+ fi = camel_folder_info_build (folders, top, '/', TRUE);
+
+ g_ptr_array_free (folders, TRUE);
+
+ return fi;
+}
+
+static void
+collect_folder_info_for_list (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *mailbox,
+ GHashTable *folder_info_results)
+{
+ CamelIMAPXStoreInfo *si;
+ CamelFolderInfo *fi;
+ const gchar *folder_path;
+ const gchar *mailbox_name;
+
+ mailbox_name = camel_imapx_mailbox_get_name (mailbox);
+
+ si = camel_imapx_store_summary_mailbox (
+ imapx_store->summary, mailbox_name);
+ g_return_if_fail (si != NULL);
+
+ folder_path = camel_store_info_path (
+ imapx_store->summary, (CamelStoreInfo *) si);
+ fi = imapx_store_build_folder_info (imapx_store, folder_path, 0);
+
+ /* Takes ownership of the CamelFolderInfo. */
+ g_hash_table_insert (folder_info_results, g_strdup (mailbox_name), fi);
+}
+
+static gboolean
+fetch_folder_info_for_pattern (CamelIMAPXConnManager *conn_man,
+ CamelIMAPXNamespace *namespace,
+ const gchar *pattern,
+ CamelStoreGetFolderInfoFlags flags,
+ GHashTable *folder_info_results,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXStore *imapx_store;
+ GList *list, *link;
+ GError *local_error = NULL;
+ gboolean success;
+
+ imapx_store = camel_imapx_conn_manager_ref_store (conn_man);
+
+ success = camel_imapx_conn_manager_list_sync (conn_man, pattern, flags, cancellable, &local_error);
+
+ if (!success) {
+ g_clear_object (&imapx_store);
+
+ if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+ camel_imapx_namespace_get_category (namespace) != CAMEL_IMAPX_NAMESPACE_PERSONAL) {
+ /* Ignore errors for non-personal namespaces; one such error can be:
+ "NO LIST failed: wildcards not permitted in username" */
+ g_clear_error (&local_error);
+ return TRUE;
+ } else if (local_error) {
+ g_propagate_error (error, local_error);
+ }
+
+ return FALSE;
+ }
+
+ list = camel_imapx_store_list_mailboxes (imapx_store, namespace, pattern);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ CamelIMAPXMailbox *mailbox;
+
+ mailbox = CAMEL_IMAPX_MAILBOX (link->data);
+
+ collect_folder_info_for_list (
+ imapx_store, mailbox, folder_info_results);
+ }
+
+ g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+ g_object_unref (imapx_store);
+
+ return TRUE;
+}
+
+static gboolean
+fetch_folder_info_for_inbox (CamelIMAPXConnManager *conn_man,
+ CamelStoreGetFolderInfoFlags flags,
+ GHashTable *folder_info_results,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXStore *imapx_store;
+ gboolean success;
+
+ imapx_store = camel_imapx_conn_manager_ref_store (conn_man);
+
+ success = camel_imapx_conn_manager_list_sync (conn_man, "INBOX", flags, cancellable, error);
+
+ if (success) {
+ CamelIMAPXMailbox *mailbox;
+
+ mailbox = camel_imapx_store_ref_mailbox (imapx_store, "INBOX");
+ g_return_val_if_fail (mailbox != NULL, FALSE);
+
+ collect_folder_info_for_list (
+ imapx_store, mailbox, folder_info_results);
+ }
+
+ g_object_unref (imapx_store);
+
+ return success;
+}
+
+static gboolean
+fetch_folder_info_for_namespace_category (CamelIMAPXStore *imapx_store,
+ CamelIMAPXConnManager *conn_man,
+ CamelIMAPXNamespaceCategory category,
+ CamelStoreGetFolderInfoFlags flags,
+ GHashTable *folder_info_results,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXNamespaceResponse *namespace_response;
+ GList *list, *link;
+ gboolean success = TRUE;
+
+ namespace_response = camel_imapx_store_ref_namespaces (imapx_store);
+ g_return_val_if_fail (namespace_response != NULL, FALSE);
+
+ list = camel_imapx_namespace_response_list (namespace_response);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ CamelIMAPXNamespace *namespace;
+ CamelIMAPXNamespaceCategory ns_category;
+ const gchar *ns_prefix;
+ gchar *pattern;
+
+ namespace = CAMEL_IMAPX_NAMESPACE (link->data);
+ ns_category = camel_imapx_namespace_get_category (namespace);
+ ns_prefix = camel_imapx_namespace_get_prefix (namespace);
+
+ if ((flags & (CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST | CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)) == 0 && ns_category != category)
+ continue;
+
+ pattern = g_strdup_printf ("%s*", ns_prefix);
+
+ success = fetch_folder_info_for_pattern (
+ conn_man, namespace, pattern, flags,
+ folder_info_results, cancellable, error);
+
+ g_free (pattern);
+
+ if (!success)
+ break;
+ }
+
+ g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+ g_object_unref (namespace_response);
+
+ return success;
+}
+
+static gboolean
+fetch_folder_info_from_folder_path (CamelIMAPXStore *imapx_store,
+ CamelIMAPXConnManager *conn_man,
+ const gchar *folder_path,
+ CamelStoreGetFolderInfoFlags flags,
+ GHashTable *folder_info_results,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXNamespaceResponse *namespace_response;
+ CamelIMAPXNamespace *namespace;
+ gchar *mailbox_name;
+ gchar *utf7_mailbox_name;
+ gchar *pattern;
+ gchar separator;
+ gboolean success = FALSE;
+
+ namespace_response = camel_imapx_store_ref_namespaces (imapx_store);
+ g_return_val_if_fail (namespace_response != NULL, FALSE);
+
+ /* Find a suitable IMAP namespace for the folder path. */
+ namespace = camel_imapx_namespace_response_lookup_for_path (
+ namespace_response, folder_path);
+ if (namespace == NULL) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_INVALID,
+ _("No IMAP namespace for folder path '%s'"),
+ folder_path);
+ goto exit;
+ }
+
+ /* Convert the folder path to a mailbox name. */
+ separator = camel_imapx_namespace_get_separator (namespace);
+ mailbox_name = g_strdelimit (g_strdup (folder_path), "/", separator);
+
+ utf7_mailbox_name = camel_utf8_utf7 (mailbox_name);
+ pattern = g_strdup_printf ("%s*", utf7_mailbox_name);
+
+ success = fetch_folder_info_for_pattern (
+ conn_man, namespace, pattern, flags,
+ folder_info_results, cancellable, error);
+
+ g_free (pattern);
+ g_free (utf7_mailbox_name);
+ g_free (mailbox_name);
+
+exit:
+ g_clear_object (&namespace);
+ g_clear_object (&namespace_response);
+
+ return success;
+}
+
+static void
+imapx_store_mark_mailbox_unknown_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ CamelIMAPXMailbox *mailbox = value;
+
+ g_return_if_fail (mailbox != NULL);
+
+ camel_imapx_mailbox_set_state (mailbox, CAMEL_IMAPX_MAILBOX_STATE_UNKNOWN);
+}
+
+static gboolean
+imapx_store_remove_unknown_mailboxes_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ CamelIMAPXMailbox *mailbox = value;
+ CamelIMAPXStore *imapx_store = user_data;
+ CamelStoreInfo *si;
+
+ g_return_val_if_fail (mailbox != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store), FALSE);
+
+ if (camel_imapx_mailbox_get_state (mailbox) == CAMEL_IMAPX_MAILBOX_STATE_CREATED) {
+ CamelFolderInfo *fi;
+ gchar *folder_path;
+
+ folder_path = camel_imapx_mailbox_dup_folder_path (mailbox);
+ fi = imapx_store_build_folder_info (imapx_store, folder_path,
+ (CamelFolderInfoFlags) imapx_store_mailbox_attributes_to_flags (mailbox));
+ camel_store_folder_created (CAMEL_STORE (imapx_store), fi);
+ camel_subscribable_folder_subscribed (CAMEL_SUBSCRIBABLE (imapx_store), fi);
+ camel_folder_info_free (fi);
+ g_free (folder_path);
+ }
+
+ if (camel_imapx_mailbox_get_state (mailbox) != CAMEL_IMAPX_MAILBOX_STATE_UNKNOWN) {
+ return FALSE;
+ }
+
+ si = (CamelStoreInfo *) camel_imapx_store_summary_mailbox (imapx_store->summary, camel_imapx_mailbox_get_name (mailbox));
+ if (si) {
+ const gchar *si_path;
+ gchar *dup_folder_path;
+
+ si_path = camel_store_info_path (imapx_store->summary, si);
+ dup_folder_path = g_strdup (si_path);
+
+ if (dup_folder_path != NULL) {
+ imapx_delete_folder_from_cache (imapx_store, dup_folder_path, FALSE);
+ g_free (dup_folder_path);
+ } else {
+ camel_store_summary_remove (imapx_store->summary, si);
+ }
+
+ camel_store_summary_info_unref (imapx_store->summary, si);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+imapx_store_mailbox_is_unknown (CamelIMAPXStore *imapx_store,
+ GPtrArray *store_infos,
+ const CamelIMAPXStoreInfo *to_check)
+{
+ CamelIMAPXMailbox *mailbox;
+ gboolean is_unknown;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store), FALSE);
+ g_return_val_if_fail (store_infos != NULL, FALSE);
+
+ if (!to_check || !to_check->mailbox_name || !*to_check->mailbox_name)
+ return FALSE;
+
+ mailbox = camel_imapx_store_ref_mailbox (imapx_store, to_check->mailbox_name);
+
+ is_unknown = mailbox && camel_imapx_mailbox_get_state (mailbox) == CAMEL_IMAPX_MAILBOX_STATE_UNKNOWN;
+
+ if (!mailbox && to_check->separator) {
+ CamelSettings *settings;
+ gboolean use_subscriptions;
+ gchar *mailbox_with_separator;
+ gint ii;
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (imapx_store));
+ use_subscriptions = camel_imapx_settings_get_use_subscriptions (CAMEL_IMAPX_SETTINGS (settings));
+ g_object_unref (settings);
+
+ mailbox_with_separator = g_strdup_printf ("%s%c", to_check->mailbox_name, to_check->separator);
+
+ for (ii = 0; ii < store_infos->len; ii++) {
+ CamelIMAPXStoreInfo *si;
+
+ si = g_ptr_array_index (store_infos, ii);
+
+ if (si->mailbox_name && g_str_has_prefix (si->mailbox_name, mailbox_with_separator) && (
+ !use_subscriptions || (((CamelStoreInfo *) si)->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0)) {
+ /* This can be a 'virtual' parent folder of some subscribed subfolder */
+ break;
+ }
+ }
+
+ is_unknown = ii == store_infos->len;
+
+ g_free (mailbox_with_separator);
+ }
+
+ g_clear_object (&mailbox);
+
+ return is_unknown;
+}
+
+static gboolean
+sync_folders (CamelIMAPXStore *imapx_store,
+ const gchar *root_folder_path,
+ CamelStoreGetFolderInfoFlags flags,
+ gboolean initial_setup,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXConnManager *conn_man;
+ GHashTable *folder_info_results;
+ gboolean update_folder_list;
+ gboolean success;
+
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+
+ /* mailbox name -> CamelFolderInfo */
+ folder_info_results = g_hash_table_new_full (
+ (GHashFunc) imapx_name_hash,
+ (GEqualFunc) imapx_name_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) camel_folder_info_free);
+
+ /* This suppresses CamelStore signal emissions
+ * in imapx_store_process_mailbox_attributes(). */
+ g_atomic_int_inc (&imapx_store->priv->syncing_folders);
+
+ update_folder_list = !initial_setup && (!root_folder_path || !*root_folder_path);
+
+ if (update_folder_list) {
+ g_mutex_lock (&imapx_store->priv->mailboxes_lock);
+ g_hash_table_foreach (imapx_store->priv->mailboxes, imapx_store_mark_mailbox_unknown_cb, imapx_store);
+ g_mutex_unlock (&imapx_store->priv->mailboxes_lock);
+ }
+
+ if (root_folder_path != NULL && *root_folder_path != '\0') {
+ success = fetch_folder_info_from_folder_path (
+ imapx_store, conn_man, root_folder_path, flags,
+ folder_info_results, cancellable, error);
+ } else {
+ gboolean have_folder_info_for_inbox;
+
+ success = fetch_folder_info_for_namespace_category (
+ imapx_store, conn_man, CAMEL_IMAPX_NAMESPACE_PERSONAL, flags |
+ (update_folder_list ? CAMEL_STORE_FOLDER_INFO_SUBSCRIBED : 0),
+ folder_info_results, cancellable, error);
+
+ have_folder_info_for_inbox =
+ g_hash_table_contains (folder_info_results, "INBOX");
+
+ /* XXX Slight hack, mainly for Courier servers. If INBOX
+ * is not included in any defined personal namespaces,
+ * then LIST it explicitly. */
+ if (success && !have_folder_info_for_inbox)
+ success = fetch_folder_info_for_inbox (
+ conn_man, flags, folder_info_results,
+ cancellable, error);
+ }
+
+ /* Don't need to test for zero, just decrement atomically. */
+ (void) g_atomic_int_dec_and_test (&imapx_store->priv->syncing_folders);
+
+ if (!success)
+ goto exit;
+
+ if (update_folder_list) {
+ g_mutex_lock (&imapx_store->priv->mailboxes_lock);
+ g_hash_table_foreach_remove (imapx_store->priv->mailboxes, imapx_store_remove_unknown_mailboxes_cb, imapx_store);
+ g_mutex_unlock (&imapx_store->priv->mailboxes_lock);
+ }
+
+ if (!root_folder_path || !*root_folder_path) {
+ GPtrArray *array;
+ guint ii;
+
+ /* Finally update store's summary */
+ array = camel_store_summary_array (imapx_store->summary);
+ for (ii = 0; array && ii < array->len; ii++) {
+ CamelStoreInfo *si;
+ const gchar *si_path;
+
+ si = g_ptr_array_index (array, ii);
+ si_path = camel_store_info_path (imapx_store->summary, si);
+
+ if (imapx_store_mailbox_is_unknown (imapx_store, array, (CamelIMAPXStoreInfo *) si)) {
+ gchar *dup_folder_path = g_strdup (si_path);
+
+ if (dup_folder_path != NULL) {
+ /* Do not unsubscribe from it, it influences UI for non-subscribable folders */
+ imapx_delete_folder_from_cache (imapx_store, dup_folder_path, FALSE);
+ g_free (dup_folder_path);
+ } else {
+ camel_store_summary_remove (imapx_store->summary, si);
+ }
+ }
+ }
+
+ camel_store_summary_array_free (imapx_store->summary, array);
+ }
+
+ camel_store_summary_save (imapx_store->summary);
+
+exit:
+ g_hash_table_destroy (folder_info_results);
+
+ return success;
+}
+
+static void
+imapx_refresh_finfo (CamelSession *session,
+ GCancellable *cancellable,
+ CamelIMAPXStore *store,
+ GError **error)
+{
+ CamelService *service;
+ const gchar *display_name;
+
+ service = CAMEL_SERVICE (store);
+ display_name = camel_service_get_display_name (service);
+
+ camel_operation_push_message (
+ cancellable, _("Retrieving folder list for '%s'"),
+ display_name);
+
+ if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store)))
+ goto exit;
+
+ if (!camel_service_connect_sync (
+ CAMEL_SERVICE (store), cancellable, error))
+ goto exit;
+
+ sync_folders (store, NULL, 0, FALSE, cancellable, error);
+
+ camel_store_summary_save (store->summary);
+
+exit:
+ camel_operation_pop_message (cancellable);
+}
+
+static void
+discover_inbox (CamelIMAPXStore *imapx_store,
+ GCancellable *cancellable)
+{
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox = NULL;
+ const gchar *attribute;
+
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+ mailbox = camel_imapx_store_ref_mailbox (imapx_store, "INBOX");
+
+ if (mailbox == NULL)
+ return;
+
+ attribute = CAMEL_IMAPX_LIST_ATTR_SUBSCRIBED;
+ if (!camel_imapx_mailbox_has_attribute (mailbox, attribute)) {
+ GError *local_error = NULL;
+ gboolean success;
+
+ success = camel_imapx_conn_manager_subscribe_mailbox_sync (conn_man, mailbox, cancellable, &local_error);
+ if (!success && !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_warning ("%s: Failed to subscribe INBOX: %s", G_STRFUNC, local_error ? local_error->message : "Unknown error");
+ }
+
+ g_clear_error (&local_error);
+ }
+
+ g_clear_object (&mailbox);
+}
+
+static gboolean
+imapx_can_refresh_folder (CamelStore *store,
+ CamelFolderInfo *info,
+ GError **error)
+{
+ CamelService *service;
+ CamelSettings *settings;
+ CamelStoreClass *store_class;
+ gboolean check_all;
+ gboolean check_subscribed;
+ gboolean subscribed;
+ gboolean res;
+ GError *local_error = NULL;
+
+ store_class = CAMEL_STORE_CLASS (camel_imapx_store_parent_class);
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ check_all = camel_imapx_settings_get_check_all (
+ CAMEL_IMAPX_SETTINGS (settings));
+
+ check_subscribed = camel_imapx_settings_get_check_subscribed (
+ CAMEL_IMAPX_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ subscribed = ((info->flags & CAMEL_FOLDER_SUBSCRIBED) != 0);
+
+ res = store_class->can_refresh_folder (store, info, &local_error) ||
+ check_all || (check_subscribed && subscribed);
+
+ if (!res && !local_error) {
+ CamelFolder *folder;
+
+ folder = camel_store_get_folder_sync (store, info->full_name, 0, NULL, &local_error);
+ if (folder && CAMEL_IS_IMAPX_FOLDER (folder))
+ res = camel_imapx_folder_get_check_folder (CAMEL_IMAPX_FOLDER (folder));
+
+ g_clear_object (&folder);
+ }
+
+ if (local_error != NULL)
+ g_propagate_error (error, local_error);
+
+ return res;
+}
+
+static CamelFolder *
+imapx_store_get_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelSettings *settings;
+ gboolean use_real_junk_path = FALSE;
+ gboolean use_real_trash_path = FALSE;
+
+ /* XXX This should be taken care of before we get this far. */
+ if (*folder_name == '/')
+ folder_name++;
+
+ folder = get_folder_offline (store, folder_name, flags, error);
+
+ /* Configure the folder flags according to IMAPX settings.
+ *
+ * XXX Since this is only done when the folder is first created,
+ * a restart is required to pick up changes to real Junk/Trash
+ * folder settings. Need to think of a better way.
+ *
+ * Perhaps have CamelStoreSettings grow junk and trash path
+ * string properties, and eliminate the CAMEL_FOLDER_IS_JUNK
+ * and CAMEL_FOLDER_IS_TRASH flags. Then add functions like
+ * camel_folder_is_junk() and camel_folder_is_trash(), which
+ * compare their own full name against CamelStoreSettings.
+ *
+ * Something to think about...
+ */
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (store));
+
+ if (folder != NULL) {
+ use_real_junk_path =
+ camel_imapx_settings_get_use_real_junk_path (
+ CAMEL_IMAPX_SETTINGS (settings));
+ use_real_trash_path =
+ camel_imapx_settings_get_use_real_trash_path (
+ CAMEL_IMAPX_SETTINGS (settings));
+ }
+
+ if (use_real_junk_path) {
+ gchar *real_junk_path;
+
+ real_junk_path =
+ camel_imapx_settings_dup_real_junk_path (
+ CAMEL_IMAPX_SETTINGS (settings));
+
+ /* So we can safely compare strings. */
+ if (real_junk_path == NULL)
+ real_junk_path = g_strdup ("");
+
+ if (g_ascii_strcasecmp (real_junk_path, folder_name) == 0)
+ folder->folder_flags |= CAMEL_FOLDER_IS_JUNK;
+
+ g_free (real_junk_path);
+ }
+
+ if (use_real_trash_path) {
+ gchar *real_trash_path;
+
+ real_trash_path =
+ camel_imapx_settings_dup_real_trash_path (
+ CAMEL_IMAPX_SETTINGS (settings));
+
+ /* So we can safely compare strings. */
+ if (real_trash_path == NULL)
+ real_trash_path = g_strdup ("");
+
+ if (g_ascii_strcasecmp (real_trash_path, folder_name) == 0)
+ folder->folder_flags |= CAMEL_FOLDER_IS_TRASH;
+
+ g_free (real_trash_path);
+ }
+
+ g_object_unref (settings);
+
+ return folder;
+}
+
+static CamelFolderInfo *
+imapx_store_get_folder_info_sync (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXStore *imapx_store;
+ CamelFolderInfo *fi = NULL;
+ CamelService *service;
+ CamelSettings *settings;
+ gboolean initial_setup = FALSE;
+ gboolean use_subscriptions;
+
+ service = CAMEL_SERVICE (store);
+ imapx_store = CAMEL_IMAPX_STORE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ use_subscriptions = camel_imapx_settings_get_use_subscriptions (
+ CAMEL_IMAPX_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ if (top == NULL)
+ top = "";
+
+ g_mutex_lock (&imapx_store->priv->get_finfo_lock);
+
+ if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store))) {
+ fi = get_folder_info_offline (store, top, flags, cancellable, error);
+ goto exit;
+ }
+
+ if (imapx_store->priv->last_refresh_time == 0) {
+ imapx_store->priv->last_refresh_time = time (NULL);
+ initial_setup = TRUE;
+ }
+
+ /* XXX I don't know why the SUBSCRIBED flag matters here. */
+ if (!initial_setup && (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) != 0) {
+ time_t time_since_last_refresh;
+
+ time_since_last_refresh =
+ time (NULL) - imapx_store->priv->last_refresh_time;
+
+ if (time_since_last_refresh > FINFO_REFRESH_INTERVAL) {
+ CamelSession *session;
+ gchar *description;
+
+ imapx_store->priv->last_refresh_time = time (NULL);
+
+ session = camel_service_ref_session (service);
+ if (session) {
+ description = g_strdup_printf (_("Retrieving folder list for '%s'"), camel_service_get_display_name (service));
+
+ camel_session_submit_job (
+ session, description, (CamelSessionCallback)
+ imapx_refresh_finfo,
+ g_object_ref (store),
+ (GDestroyNotify) g_object_unref);
+
+ g_object_unref (session);
+ g_free (description);
+ }
+ }
+ }
+
+ /* Avoid server interaction if the FAST flag is set. */
+ if (!initial_setup && flags & CAMEL_STORE_FOLDER_INFO_FAST) {
+ fi = get_folder_info_offline (store, top, flags, cancellable, error);
+ goto exit;
+ }
+
+ if (!sync_folders (imapx_store, top, flags, initial_setup, cancellable, error))
+ goto exit;
+
+ camel_store_summary_save (imapx_store->summary);
+
+ /* ensure the INBOX is subscribed if lsub was preferred*/
+ if (initial_setup && use_subscriptions)
+ discover_inbox (imapx_store, cancellable);
+
+ fi = get_folder_info_offline (store, top, flags, cancellable, error);
+
+exit:
+ g_mutex_unlock (&imapx_store->priv->get_finfo_lock);
+
+ return fi;
+}
+
+static CamelFolder *
+imapx_store_get_junk_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder = NULL;
+ CamelStoreClass *store_class;
+ CamelSettings *settings;
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (store));
+ if (camel_imapx_settings_get_use_real_junk_path (CAMEL_IMAPX_SETTINGS (settings))) {
+ gchar *real_junk_path;
+
+ real_junk_path = camel_imapx_settings_dup_real_junk_path (CAMEL_IMAPX_SETTINGS (settings));
+ if (real_junk_path) {
+ folder = camel_store_get_folder_sync (store, real_junk_path, 0, cancellable, NULL);
+ g_free (real_junk_path);
+ }
+ }
+ g_object_unref (settings);
+
+ if (folder)
+ return folder;
+
+ store_class = CAMEL_STORE_CLASS (camel_imapx_store_parent_class);
+ folder = store_class->get_junk_folder_sync (store, cancellable, error);
+
+ if (folder) {
+ CamelObject *object = CAMEL_OBJECT (folder);
+ CamelService *service;
+ const gchar *user_cache_dir;
+ gchar *state;
+
+ service = CAMEL_SERVICE (store);
+ user_cache_dir = camel_service_get_user_cache_dir (service);
+
+ state = g_build_filename (
+ user_cache_dir, "system", "Junk.cmeta", NULL);
+
+ camel_object_set_state_filename (object, state);
+ g_free (state);
+ /* no defaults? */
+ camel_object_state_read (object);
+ }
+
+ return folder;
+}
+
+static CamelFolder *
+imapx_store_get_trash_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder = NULL;
+ CamelStoreClass *store_class;
+ CamelSettings *settings;
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (store));
+ if (camel_imapx_settings_get_use_real_trash_path (CAMEL_IMAPX_SETTINGS (settings))) {
+ gchar *real_trash_path;
+
+ real_trash_path = camel_imapx_settings_dup_real_trash_path (CAMEL_IMAPX_SETTINGS (settings));
+ if (real_trash_path) {
+ folder = camel_store_get_folder_sync (store, real_trash_path, 0, cancellable, NULL);
+ g_free (real_trash_path);
+ }
+ }
+ g_object_unref (settings);
+
+ if (folder)
+ return folder;
+
+ store_class = CAMEL_STORE_CLASS (camel_imapx_store_parent_class);
+ folder = store_class->get_trash_folder_sync (store, cancellable, error);
+
+ if (folder) {
+ CamelObject *object = CAMEL_OBJECT (folder);
+ CamelService *service;
+ const gchar *user_cache_dir;
+ gchar *state;
+
+ service = CAMEL_SERVICE (store);
+ user_cache_dir = camel_service_get_user_cache_dir (service);
+
+ state = g_build_filename (
+ user_cache_dir, "system", "Trash.cmeta", NULL);
+
+ camel_object_set_state_filename (object, state);
+ g_free (state);
+ /* no defaults? */
+ camel_object_state_read (object);
+ }
+
+ return folder;
+}
+
+static CamelFolderInfo *
+imapx_store_create_folder_sync (CamelStore *store,
+ const gchar *parent_name,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXNamespaceResponse *namespace_response;
+ CamelIMAPXNamespace *namespace;
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+ CamelFolder *folder;
+ CamelIMAPXMailbox *parent_mailbox = NULL;
+ CamelFolderInfo *fi = NULL;
+ GList *list;
+ const gchar *namespace_prefix;
+ const gchar *parent_mailbox_name;
+ gchar *mailbox_name = NULL;
+ gchar separator;
+ gboolean success;
+
+ imapx_store = CAMEL_IMAPX_STORE (store);
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+
+ if (parent_name == NULL || *parent_name == '\0')
+ goto check_namespace;
+
+ /* Obtain the separator from the parent CamelIMAPXMailbox. */
+
+ folder = camel_store_get_folder_sync (
+ store, parent_name, 0, cancellable, error);
+
+ if (folder != NULL) {
+ parent_mailbox = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (folder), cancellable, error);
+ g_object_unref (folder);
+ }
+
+ if (parent_mailbox == NULL)
+ goto exit;
+
+ separator = camel_imapx_mailbox_get_separator (parent_mailbox);
+ parent_mailbox_name = camel_imapx_mailbox_get_name (parent_mailbox);
+
+ mailbox_name = g_strdup_printf (
+ "%s%c%s", parent_mailbox_name, separator, folder_name);
+
+ g_object_unref (parent_mailbox);
+
+ goto check_separator;
+
+check_namespace:
+
+ /* Obtain the separator from the first personal namespace.
+ *
+ * FIXME The CamelFolder API provides no way to specify a
+ * namespace prefix when creating a top-level mailbox,
+ * This needs fixed to properly support IMAP namespaces.
+ */
+
+ namespace_response = camel_imapx_store_ref_namespaces (imapx_store);
+ g_return_val_if_fail (namespace_response != NULL, NULL);
+
+ list = camel_imapx_namespace_response_list (namespace_response);
+ g_return_val_if_fail (list != NULL, NULL);
+
+ /* The namespace list is in the order received in the NAMESPACE
+ * response so the first element should be a personal namespace. */
+ namespace = CAMEL_IMAPX_NAMESPACE (list->data);
+
+ separator = camel_imapx_namespace_get_separator (namespace);
+ namespace_prefix = camel_imapx_namespace_get_prefix (namespace);
+
+ mailbox_name = g_strconcat (namespace_prefix, folder_name, NULL);
+
+ g_list_free_full (list, (GDestroyNotify) g_object_unref);
+ g_object_unref (namespace_response);
+
+check_separator:
+
+ if (strchr (folder_name, separator) != NULL) {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID_PATH,
+ _("The folder name \"%s\" is invalid "
+ "because it contains the character \"%c\""),
+ folder_name, separator);
+ goto exit;
+ }
+
+ /* This also LISTs the mailbox after creating it, which
+ * triggers the CamelIMAPXStore::mailbox-created signal
+ * and all the local processing that goes along with it. */
+ success = camel_imapx_conn_manager_create_mailbox_sync (conn_man, mailbox_name, cancellable, error);
+
+ if (success) {
+ fi = imapx_store_build_folder_info (
+ imapx_store, folder_name,
+ CAMEL_FOLDER_NOCHILDREN);
+ }
+
+exit:
+ g_free (mailbox_name);
+
+ return fi;
+}
+
+static gboolean
+imapx_store_delete_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox = NULL;
+ gboolean success = FALSE;
+
+ folder = camel_store_get_folder_sync (
+ store, folder_name, 0, cancellable, error);
+
+ if (folder == NULL)
+ return FALSE;
+
+ imapx_store = CAMEL_IMAPX_STORE (store);
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+
+ mailbox = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (folder), cancellable, error);
+ if (mailbox == NULL)
+ goto exit;
+
+ success = camel_imapx_conn_manager_delete_mailbox_sync (conn_man, mailbox, cancellable, error);
+
+ if (success)
+ imapx_delete_folder_from_cache (imapx_store, folder_name, TRUE);
+
+exit:
+ g_clear_object (&folder);
+ g_clear_object (&mailbox);
+
+ return success;
+}
+
+static gboolean
+imapx_store_rename_folder_sync (CamelStore *store,
+ const gchar *old,
+ const gchar *new,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelService *service;
+ CamelSettings *settings;
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox = NULL;
+ CamelIMAPXMailbox *cloned_mailbox;
+ gchar *new_mailbox_name = NULL;
+ gchar separator;
+ gboolean use_subscriptions;
+ gboolean success = FALSE;
+ GError *local_error = NULL;
+
+ service = CAMEL_SERVICE (store);
+ imapx_store = CAMEL_IMAPX_STORE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ use_subscriptions = camel_imapx_settings_get_use_subscriptions (
+ CAMEL_IMAPX_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ /* This suppresses CamelStore signal emissions
+ * in imapx_store_process_mailbox_attributes(). */
+ g_atomic_int_inc (&imapx_store->priv->syncing_folders);
+
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+
+ folder = camel_store_get_folder_sync (
+ store, old, 0, cancellable, error);
+
+ if (folder != NULL) {
+ mailbox = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (folder), cancellable, error);
+ g_object_unref (folder);
+ }
+
+ if (mailbox == NULL)
+ goto exit;
+
+ /* Assume the renamed mailbox will remain in the same namespace,
+ * and therefore use the same separator character. XXX I'm not
+ * sure if IMAP even allows inter-namespace mailbox renames. */
+ separator = camel_imapx_mailbox_get_separator (mailbox);
+ new_mailbox_name = camel_imapx_folder_path_to_mailbox (new, separator);
+
+ if (use_subscriptions) {
+ camel_imapx_conn_manager_unsubscribe_mailbox_sync (conn_man, mailbox, cancellable, &local_error);
+ g_clear_error (&local_error);
+ }
+
+ success = camel_imapx_conn_manager_rename_mailbox_sync (conn_man, mailbox, new_mailbox_name, cancellable, &local_error);
+
+ if (!success) {
+ if (local_error)
+ g_propagate_error (error, local_error);
+ local_error = NULL;
+
+ if (use_subscriptions) {
+ gboolean success_2;
+
+ success_2 = camel_imapx_conn_manager_subscribe_mailbox_sync (conn_man, mailbox, cancellable, &local_error);
+ if (!success_2) {
+ g_warning ("%s: Failed to subscribe '%s': %s", G_STRFUNC, camel_imapx_mailbox_get_name (mailbox),
+ local_error ? local_error->message : "Unknown error");
+ }
+ g_clear_error (&local_error);
+ }
+
+ goto exit;
+ }
+
+ /* Rename summary, and handle broken server. */
+ imapx_store_rename_folder_info (imapx_store, old, new);
+ imapx_store_rename_storage_path (imapx_store, old, new);
+
+ /* Create a cloned CamelIMAPXMailbox with the new mailbox name. */
+ cloned_mailbox = camel_imapx_mailbox_clone (mailbox, new_mailbox_name);
+
+ camel_imapx_folder_set_mailbox (CAMEL_IMAPX_FOLDER (folder), cloned_mailbox);
+
+ if (use_subscriptions) {
+ success = camel_imapx_conn_manager_subscribe_mailbox_sync (conn_man, cloned_mailbox, cancellable, error);
+ }
+
+ g_clear_object (&cloned_mailbox);
+
+exit:
+ g_free (new_mailbox_name);
+
+ g_clear_object (&mailbox);
+
+ /* This enables CamelStore signal emissions
+ * in imapx_store_process_mailbox_attributes() again. */
+ (void) g_atomic_int_dec_and_test (&imapx_store->priv->syncing_folders);
+
+ return success;
+}
+
+static gboolean
+imapx_is_gmail_server (CamelService *service)
+{
+ CamelSettings *settings;
+ gboolean is_gmail = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
+
+ settings = camel_service_ref_settings (service);
+ if (CAMEL_IS_NETWORK_SETTINGS (settings)) {
+ gchar *host;
+
+ host = camel_network_settings_dup_host (CAMEL_NETWORK_SETTINGS (settings));
+
+ is_gmail = host && (
+ camel_strstrcase (host, ".gmail.com") != NULL ||
+ camel_strstrcase (host, ".googlemail.com") != NULL);
+
+ g_free (host);
+ }
+
+ g_clear_object (&settings);
+
+ return is_gmail;
+}
+
+static gchar *
+imapx_find_folder_for_initial_setup (CamelFolderInfo *root,
+ const gchar *path)
+{
+ CamelFolderInfo *finfo, *next;
+ gchar *folder_fullname = NULL;
+ gchar **path_parts;
+ gint ii;
+
+ if (!root || !path)
+ return NULL;
+
+ path_parts = g_strsplit (path, "/", -1);
+ if (!path_parts)
+ return NULL;
+
+ finfo = root;
+
+ for (ii = 0; path_parts[ii] && finfo; ii++) {
+ gchar *folded_path;
+
+ folded_path = g_utf8_casefold (path_parts[ii], -1);
+ if (!folded_path)
+ break;
+
+ for (next = NULL; finfo; finfo = finfo->next) {
+ gchar *folded_display_name;
+ gint cmp;
+
+ if ((finfo->flags & (CAMEL_FOLDER_NOSELECT | CAMEL_FOLDER_VIRTUAL)) != 0)
+ continue;
+
+ folded_display_name = g_utf8_casefold (finfo->display_name, -1);
+ if (!folded_display_name)
+ continue;
+
+ cmp = g_utf8_collate (folded_path, folded_display_name);
+
+ g_free (folded_display_name);
+
+ if (cmp == 0) {
+ next = finfo;
+ break;
+ }
+ }
+
+ g_free (folded_path);
+
+ finfo = next;
+ if (finfo) {
+ if (path_parts[ii + 1])
+ finfo = finfo->child;
+ else
+ folder_fullname = g_strdup (finfo->full_name);
+ }
+ }
+
+ g_strfreev (path_parts);
+
+ return folder_fullname;
+}
+
+static void
+imapx_check_initial_setup_group (CamelIMAPXStore *imapx_store,
+ CamelFolderInfo *finfo,
+ GHashTable *save_setup,
+ const gchar *list_attribute,
+ const gchar *main_key,
+ const gchar *additional_key,
+ const gchar *additional_key_value,
+ const gchar **names,
+ guint n_names)
+{
+ gchar *folder_fullname = NULL;
+ gint ii;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store));
+ g_return_if_fail (finfo != NULL);
+ g_return_if_fail (save_setup != NULL);
+ g_return_if_fail (main_key != NULL);
+ g_return_if_fail (names != NULL);
+ g_return_if_fail (n_names > 0);
+
+ /* Prefer RFC 6154 "SPECIAL-USE" Flags, which are not locale sensitive */
+ if (list_attribute) {
+ CamelIMAPXNamespaceResponse *namespace_response;
+
+ namespace_response = camel_imapx_store_ref_namespaces (imapx_store);
+ if (namespace_response) {
+ GList *namespaces, *mailboxes, *link;
+ CamelIMAPXNamespace *user_namespace = NULL;
+
+ namespaces = camel_imapx_namespace_response_list (namespace_response);
+ for (link = namespaces; link && !user_namespace; link = g_list_next (link)) {
+ CamelIMAPXNamespace *candidate = link->data;
+
+ if (!candidate || camel_imapx_namespace_get_category (candidate) != CAMEL_IMAPX_NAMESPACE_PERSONAL)
+ continue;
+
+ user_namespace = candidate;
+ }
+
+ if (user_namespace) {
+ mailboxes = camel_imapx_store_list_mailboxes (imapx_store, user_namespace, NULL);
+
+ for (link = mailboxes; link && !folder_fullname; link = g_list_next (link)) {
+ CamelIMAPXMailbox *mailbox = link->data;
+
+ if (!mailbox || !camel_imapx_mailbox_has_attribute (mailbox, list_attribute))
+ continue;
+
+ folder_fullname = camel_imapx_mailbox_dup_folder_path (mailbox);
+ }
+
+ g_list_free_full (mailboxes, g_object_unref);
+ }
+
+ g_list_free_full (namespaces, g_object_unref);
+ g_object_unref (namespace_response);
+ }
+ }
+
+ /* First check the folder names in the user's locale */
+ for (ii = 0; ii < n_names && !folder_fullname; ii++) {
+ gchar *name;
+
+ /* In the same level as the Inbox */
+ folder_fullname = imapx_find_folder_for_initial_setup (finfo, g_dpgettext2 (GETTEXT_PACKAGE, "IMAPDefaults", names[ii]));
+
+ if (folder_fullname)
+ break;
+
+ /* as a subfolder of the Inbox */
+ name = g_strconcat ("INBOX/", g_dpgettext2 (GETTEXT_PACKAGE, "IMAPDefaults", names[ii]), NULL);
+ folder_fullname = imapx_find_folder_for_initial_setup (finfo, name);
+ g_free (name);
+ }
+
+ /* Then repeat with the english name (as written in the code) */
+ for (ii = 0; ii < n_names && !folder_fullname; ii++) {
+ gchar *name;
+
+ /* Do not try the same folder name twice */
+ if (g_strcmp0 (g_dpgettext2 (GETTEXT_PACKAGE, "IMAPDefaults", names[ii]), names[ii]) == 0)
+ continue;
+
+ folder_fullname = imapx_find_folder_for_initial_setup (finfo, names[ii]);
+
+ if (folder_fullname)
+ break;
+
+ name = g_strconcat ("INBOX/", names[ii], NULL);
+ folder_fullname = imapx_find_folder_for_initial_setup (finfo, name);
+ g_free (name);
+ }
+
+ if (folder_fullname) {
+ g_hash_table_insert (save_setup,
+ g_strdup (main_key),
+ g_strdup (folder_fullname));
+
+ if (additional_key) {
+ g_hash_table_insert (save_setup,
+ g_strdup (additional_key),
+ g_strdup (additional_key_value));
+ }
+
+ g_free (folder_fullname);
+ }
+}
+
+static gboolean
+imapx_initial_setup_sync (CamelStore *store,
+ GHashTable *save_setup,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* Translators: The strings in "IMAPDefaults" context are folder names as can be presented
+ by the server; There's checked for the localized version of it and for the non-localized
+ version as well. It's always the folder name (eventually path) as provided by the server,
+ when returned in given localization. it can be checked semi-easily in the case of
+ the GMail variants, by changing the GMail interface language in the GMail Preferences. */
+ const gchar *draft_names[] = {
+ NC_("IMAPDefaults", "[Gmail]/Drafts"),
+ NC_("IMAPDefaults", "Drafts"),
+ NC_("IMAPDefaults", "Draft")
+ };
+ const gchar *templates_names[] = {
+ NC_("IMAPDefaults", "Templates")
+ };
+ const gchar *archive_names[] = {
+ NC_("IMAPDefaults", "Archive")
+ };
+ const gchar *sent_names[] = {
+ NC_("IMAPDefaults", "[Gmail]/Sent Mail"),
+ NC_("IMAPDefaults", "Sent"),
+ NC_("IMAPDefaults", "Sent Items"),
+ NC_("IMAPDefaults", "Sent Messages")
+ };
+ const gchar *junk_names[] = {
+ NC_("IMAPDefaults", "[Gmail]/Spam"),
+ NC_("IMAPDefaults", "Junk"),
+ NC_("IMAPDefaults", "Junk E-mail"),
+ NC_("IMAPDefaults", "Junk Email"),
+ NC_("IMAPDefaults", "Spam"),
+ NC_("IMAPDefaults", "Bulk Mail")
+ };
+ const gchar *trash_names[] = {
+ NC_("IMAPDefaults", "[Gmail]/Trash"),
+ NC_("IMAPDefaults", "Trash"),
+ NC_("IMAPDefaults", "Deleted Items"),
+ NC_("IMAPDefaults", "Deleted Messages")
+ };
+
+ CamelIMAPXStore *imapx_store;
+ CamelFolderInfo *finfo;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (store), FALSE);
+ g_return_val_if_fail (save_setup != NULL, FALSE);
+
+ finfo = camel_store_get_folder_info_sync (store, NULL,
+ CAMEL_STORE_FOLDER_INFO_RECURSIVE | CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL,
+ cancellable, &local_error);
+
+ if (!finfo) {
+ if (local_error) {
+ g_propagate_error (error, local_error);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ imapx_store = CAMEL_IMAPX_STORE (store);
+
+ imapx_check_initial_setup_group (imapx_store, finfo, save_setup,
+ CAMEL_IMAPX_LIST_ATTR_DRAFTS,
+ CAMEL_STORE_SETUP_DRAFTS_FOLDER, NULL, NULL,
+ draft_names, G_N_ELEMENTS (draft_names));
+
+ imapx_check_initial_setup_group (imapx_store, finfo, save_setup, NULL,
+ CAMEL_STORE_SETUP_TEMPLATES_FOLDER, NULL, NULL,
+ templates_names, G_N_ELEMENTS (templates_names));
+
+ imapx_check_initial_setup_group (imapx_store, finfo, save_setup,
+ CAMEL_IMAPX_LIST_ATTR_ARCHIVE,
+ CAMEL_STORE_SETUP_ARCHIVE_FOLDER, NULL, NULL,
+ archive_names, G_N_ELEMENTS (archive_names));
+
+ /* Skip changing Sent folder for GMail, because GMail stores sent messages
+ automatically, thus it would make doubled copies on the server. */
+ if (!imapx_is_gmail_server (CAMEL_SERVICE (store))) {
+ imapx_check_initial_setup_group (imapx_store, finfo, save_setup,
+ CAMEL_IMAPX_LIST_ATTR_SENT,
+ CAMEL_STORE_SETUP_SENT_FOLDER, NULL, NULL,
+ sent_names, G_N_ELEMENTS (sent_names));
+ }
+
+ /* It's a folder path inside the account, thus not use the 'f' type, but the 's' type. */
+ imapx_check_initial_setup_group (imapx_store, finfo, save_setup,
+ CAMEL_IMAPX_LIST_ATTR_JUNK,
+ "Backend:Imapx Backend:real-junk-path:s",
+ "Backend:Imapx Backend:use-real-junk-path:b", "true",
+ junk_names, G_N_ELEMENTS (junk_names));
+
+ /* It's a folder path inside the account, thus not use the 'f' type, but the 's' type. */
+ imapx_check_initial_setup_group (imapx_store, finfo, save_setup,
+ CAMEL_IMAPX_LIST_ATTR_TRASH,
+ "Backend:Imapx Backend:real-trash-path:s",
+ "Backend:Imapx Backend:use-real-trash-path:b", "true",
+ trash_names, G_N_ELEMENTS (trash_names));
+
+ camel_folder_info_free (finfo);
+
+ return TRUE;
+}
+
+static void
+imapx_migrate_to_user_cache_dir (CamelService *service)
+{
+ const gchar *user_data_dir, *user_cache_dir;
+
+ g_return_if_fail (service != NULL);
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ user_data_dir = camel_service_get_user_data_dir (service);
+ user_cache_dir = camel_service_get_user_cache_dir (service);
+
+ g_return_if_fail (user_data_dir != NULL);
+ g_return_if_fail (user_cache_dir != NULL);
+
+ /* migrate only if the source directory exists and the destination doesn't */
+ if (g_file_test (user_data_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) &&
+ !g_file_test (user_cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
+ gchar *parent_dir;
+
+ parent_dir = g_path_get_dirname (user_cache_dir);
+ g_mkdir_with_parents (parent_dir, S_IRWXU);
+ g_free (parent_dir);
+
+ if (g_rename (user_data_dir, user_cache_dir) == -1 && errno != ENOENT)
+ g_debug ("%s: Failed to migrate '%s' to '%s': %s", G_STRFUNC, user_data_dir, user_cache_dir, g_strerror (errno));
+ }
+}
+
+static gboolean
+imapx_store_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelIMAPXStore *imapx_store;
+ CamelStore *store;
+ CamelService *service;
+ const gchar *user_cache_dir;
+ gchar *summary;
+
+ imapx_store = CAMEL_IMAPX_STORE (initable);
+ store = CAMEL_STORE (initable);
+ service = CAMEL_SERVICE (initable);
+
+ store->flags |= CAMEL_STORE_USE_CACHE_DIR | CAMEL_STORE_SUPPORTS_INITIAL_SETUP;
+ imapx_migrate_to_user_cache_dir (service);
+
+ /* Chain up to parent interface's init() method. */
+ if (!parent_initable_interface->init (initable, cancellable, error))
+ return FALSE;
+
+ service = CAMEL_SERVICE (initable);
+ user_cache_dir = camel_service_get_user_cache_dir (service);
+
+ imapx_store->summary =
+ g_object_new (CAMEL_TYPE_IMAPX_STORE_SUMMARY, NULL);
+
+ summary = g_build_filename (user_cache_dir, ".ev-store-summary", NULL);
+ camel_store_summary_set_filename (imapx_store->summary, summary);
+ if (camel_store_summary_load (imapx_store->summary) == -1) {
+ camel_store_summary_touch (imapx_store->summary);
+ camel_store_summary_save (imapx_store->summary);
+ }
+
+ g_free (summary);
+
+ return TRUE;
+}
+
+static const gchar *
+imapx_store_get_service_name (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method)
+{
+ const gchar *service_name;
+
+ switch (method) {
+ case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
+ service_name = "imaps";
+ break;
+
+ default:
+ service_name = "imap";
+ break;
+ }
+
+ return service_name;
+}
+
+static guint16
+imapx_store_get_default_port (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method)
+{
+ guint16 default_port;
+
+ switch (method) {
+ case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
+ default_port = IMAPS_PORT;
+ break;
+
+ default:
+ default_port = IMAP_PORT;
+ break;
+ }
+
+ return default_port;
+}
+
+static gboolean
+imapx_store_folder_is_subscribed (CamelSubscribable *subscribable,
+ const gchar *folder_name)
+{
+ CamelIMAPXStore *imapx_store;
+ CamelStoreInfo *si;
+ gint is_subscribed = FALSE;
+
+ imapx_store = CAMEL_IMAPX_STORE (subscribable);
+
+ if (folder_name && *folder_name == '/')
+ folder_name++;
+
+ si = camel_store_summary_path (imapx_store->summary, folder_name);
+ if (si != NULL) {
+ if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)
+ is_subscribed = TRUE;
+ camel_store_summary_info_unref (imapx_store->summary, si);
+ }
+
+ return is_subscribed;
+}
+
+static void
+imapx_ensure_parents_subscribed (CamelIMAPXStore *imapx_store,
+ const gchar *folder_name)
+{
+ GSList *parents = NULL, *iter;
+ CamelSubscribable *subscribable;
+ CamelFolderInfo *fi;
+ gchar *parent, *sep;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store));
+ g_return_if_fail (folder_name != NULL);
+
+ subscribable = CAMEL_SUBSCRIBABLE (imapx_store);
+
+ if (folder_name && *folder_name == '/')
+ folder_name++;
+
+ parent = g_strdup (folder_name);
+ while (sep = strrchr (parent, '/'), sep) {
+ *sep = '\0';
+
+ fi = camel_folder_info_new ();
+
+ fi->display_name = strrchr (parent, '/');
+ if (fi->display_name != NULL)
+ fi->display_name = g_strdup (fi->display_name + 1);
+ else
+ fi->display_name = g_strdup (parent);
+
+ fi->full_name = g_strdup (parent);
+
+ /* Since this is a "fake" folder node, it is not selectable. */
+ fi->flags |= CAMEL_FOLDER_NOSELECT;
+ fi->unread = -1;
+ fi->total = -1;
+
+ parents = g_slist_prepend (parents, fi);
+ }
+
+ for (iter = parents; iter; iter = g_slist_next (iter)) {
+ fi = iter->data;
+
+ camel_subscribable_folder_subscribed (subscribable, fi);
+ camel_folder_info_free (fi);
+ }
+
+ g_slist_free (parents);
+ g_free (parent);
+}
+
+static gboolean
+imapx_store_subscribe_folder_sync (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox = NULL;
+ gboolean success = FALSE;
+
+ imapx_store = CAMEL_IMAPX_STORE (subscribable);
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+
+ folder = camel_store_get_folder_sync (
+ CAMEL_STORE (subscribable),
+ folder_name, 0, cancellable, error);
+
+ if (folder != NULL) {
+ mailbox = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (folder), cancellable, error);
+ g_object_unref (folder);
+ }
+
+ if (mailbox == NULL)
+ goto exit;
+
+ success = camel_imapx_conn_manager_subscribe_mailbox_sync (conn_man, mailbox, cancellable, error);
+
+ if (success) {
+ CamelFolderInfo *fi;
+
+ /* without this the folder is not visible if parents are not subscribed */
+ imapx_ensure_parents_subscribed (imapx_store, folder_name);
+
+ fi = imapx_store_build_folder_info (
+ CAMEL_IMAPX_STORE (subscribable), folder_name, 0);
+ camel_subscribable_folder_subscribed (subscribable, fi);
+ camel_folder_info_free (fi);
+ }
+
+exit:
+ g_clear_object (&mailbox);
+
+ return success;
+}
+
+static gboolean
+imapx_store_unsubscribe_folder_sync (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelIMAPXStore *imapx_store;
+ CamelIMAPXConnManager *conn_man;
+ CamelIMAPXMailbox *mailbox = NULL;
+ gboolean success = FALSE;
+
+ imapx_store = CAMEL_IMAPX_STORE (subscribable);
+ conn_man = camel_imapx_store_get_conn_manager (imapx_store);
+
+ folder = camel_store_get_folder_sync (
+ CAMEL_STORE (subscribable),
+ folder_name, 0, cancellable, error);
+
+ if (folder != NULL) {
+ mailbox = camel_imapx_folder_list_mailbox (
+ CAMEL_IMAPX_FOLDER (folder), cancellable, error);
+ g_object_unref (folder);
+ }
+
+ if (mailbox == NULL)
+ goto exit;
+
+ success = camel_imapx_conn_manager_unsubscribe_mailbox_sync (conn_man, mailbox, cancellable, error);
+
+ if (success) {
+ CamelFolderInfo *fi;
+
+ fi = imapx_store_build_folder_info (
+ CAMEL_IMAPX_STORE (subscribable), folder_name, 0);
+ camel_subscribable_folder_unsubscribed (subscribable, fi);
+ camel_folder_info_free (fi);
+ }
+
+exit:
+ g_clear_object (&mailbox);
+
+ return success;
+}
+
+static void
+imapx_store_mailbox_created (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *mailbox)
+{
+ e (
+ '*',
+ "%s::mailbox-created (\"%s\")\n",
+ G_OBJECT_TYPE_NAME (imapx_store),
+ camel_imapx_mailbox_get_name (mailbox));
+
+ imapx_store_add_mailbox_to_folder (imapx_store, mailbox);
+ imapx_store_process_mailbox_attributes (imapx_store, mailbox, NULL);
+}
+
+static void
+imapx_store_mailbox_renamed (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *mailbox,
+ const gchar *oldname)
+{
+ e (
+ '*',
+ "%s::mailbox-renamed (\"%s\" -> \"%s\")\n",
+ G_OBJECT_TYPE_NAME (imapx_store), oldname,
+ camel_imapx_mailbox_get_name (mailbox));
+
+ imapx_store_process_mailbox_attributes (imapx_store, mailbox, oldname);
+ imapx_store_process_mailbox_status (imapx_store, mailbox);
+}
+
+static void
+imapx_store_mailbox_updated (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *mailbox)
+{
+ e (
+ '*',
+ "%s::mailbox-updated (\"%s\")\n",
+ G_OBJECT_TYPE_NAME (imapx_store),
+ camel_imapx_mailbox_get_name (mailbox));
+
+ imapx_store_process_mailbox_attributes (imapx_store, mailbox, NULL);
+ imapx_store_process_mailbox_status (imapx_store, mailbox);
+}
+
+static void
+camel_imapx_store_class_init (CamelIMAPXStoreClass *class)
+{
+ GObjectClass *object_class;
+ CamelServiceClass *service_class;
+ CamelStoreClass *store_class;
+
+ g_type_class_add_private (class, sizeof (CamelIMAPXStorePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = imapx_store_set_property;
+ object_class->get_property = imapx_store_get_property;
+ object_class->dispose = imapx_store_dispose;
+ object_class->finalize = imapx_store_finalize;
+ object_class->notify = imapx_store_notify;
+
+ service_class = CAMEL_SERVICE_CLASS (class);
+ service_class->settings_type = CAMEL_TYPE_IMAPX_SETTINGS;
+ service_class->get_name = imapx_get_name;
+ service_class->connect_sync = imapx_connect_sync;
+ service_class->disconnect_sync = imapx_disconnect_sync;
+ service_class->authenticate_sync = imapx_authenticate_sync;
+ service_class->query_auth_types_sync = imapx_query_auth_types_sync;
+
+ store_class = CAMEL_STORE_CLASS (class);
+ store_class->hash_folder_name = imapx_name_hash;
+ store_class->equal_folder_name = imapx_name_equal;
+ store_class->can_refresh_folder = imapx_can_refresh_folder;
+ store_class->get_folder_sync = imapx_store_get_folder_sync;
+ store_class->get_folder_info_sync = imapx_store_get_folder_info_sync;
+ store_class->get_junk_folder_sync = imapx_store_get_junk_folder_sync;
+ store_class->get_trash_folder_sync = imapx_store_get_trash_folder_sync;
+ store_class->create_folder_sync = imapx_store_create_folder_sync;
+ store_class->delete_folder_sync = imapx_store_delete_folder_sync;
+ store_class->rename_folder_sync = imapx_store_rename_folder_sync;
+ store_class->initial_setup_sync = imapx_initial_setup_sync;
+
+ class->mailbox_created = imapx_store_mailbox_created;
+ class->mailbox_renamed = imapx_store_mailbox_renamed;
+ class->mailbox_updated = imapx_store_mailbox_updated;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CONN_MANAGER,
+ g_param_spec_object (
+ "conn-manager",
+ "Connection Manager",
+ "The Connection Manager being used for remote operations",
+ CAMEL_TYPE_IMAPX_CONN_MANAGER,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /* Inherited from CamelNetworkService. */
+ g_object_class_override_property (
+ object_class,
+ PROP_CONNECTABLE,
+ "connectable");
+
+ /* Inherited from CamelNetworkService. */
+ g_object_class_override_property (
+ object_class,
+ PROP_HOST_REACHABLE,
+ "host-reachable");
+
+ signals[MAILBOX_CREATED] = g_signal_new (
+ "mailbox-created",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CamelIMAPXStoreClass, mailbox_created),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ CAMEL_TYPE_IMAPX_MAILBOX);
+
+ signals[MAILBOX_RENAMED] = g_signal_new (
+ "mailbox-renamed",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CamelIMAPXStoreClass, mailbox_renamed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ CAMEL_TYPE_IMAPX_MAILBOX,
+ G_TYPE_STRING);
+
+ signals[MAILBOX_UPDATED] = g_signal_new (
+ "mailbox-updated",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (CamelIMAPXStoreClass, mailbox_updated),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ CAMEL_TYPE_IMAPX_MAILBOX);
+}
+
+static void
+camel_imapx_store_initable_init (GInitableIface *iface)
+{
+ parent_initable_interface = g_type_interface_peek_parent (iface);
+
+ iface->init = imapx_store_initable_init;
+}
+
+static void
+camel_network_service_init (CamelNetworkServiceInterface *iface)
+{
+ iface->get_service_name = imapx_store_get_service_name;
+ iface->get_default_port = imapx_store_get_default_port;
+}
+
+static void
+camel_subscribable_init (CamelSubscribableInterface *iface)
+{
+ iface->folder_is_subscribed = imapx_store_folder_is_subscribed;
+ iface->subscribe_folder_sync = imapx_store_subscribe_folder_sync;
+ iface->unsubscribe_folder_sync = imapx_store_unsubscribe_folder_sync;
+}
+
+static void
+camel_imapx_store_init (CamelIMAPXStore *store)
+{
+ store->priv = CAMEL_IMAPX_STORE_GET_PRIVATE (store);
+
+ store->priv->conn_man = camel_imapx_conn_manager_new (CAMEL_STORE (store));
+
+ g_mutex_init (&store->priv->get_finfo_lock);
+
+ g_mutex_init (&store->priv->namespaces_lock);
+ g_mutex_init (&store->priv->mailboxes_lock);
+ /* Hash table key is owned by the CamelIMAPXMailbox. */
+ store->priv->mailboxes = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+
+ /* Initialize to zero to ensure we always obtain fresh folder
+ * info on startup. See imapx_store_get_folder_info_sync(). */
+ store->priv->last_refresh_time = 0;
+
+ g_mutex_init (&store->priv->server_lock);
+
+ store->priv->quota_info = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) camel_folder_quota_info_free);
+ g_mutex_init (&store->priv->quota_info_lock);
+
+ g_mutex_init (&store->priv->settings_lock);
+
+ imapx_utils_init ();
+
+ g_signal_connect (
+ store, "notify::settings",
+ G_CALLBACK (imapx_store_update_store_flags), NULL);
+}
+
+CamelIMAPXConnManager *
+camel_imapx_store_get_conn_manager (CamelIMAPXStore *store)
+{
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (store), NULL);
+
+ return store->priv->conn_man;
+}
+
+/* The caller should hold the store->priv->server_lock already, when calling this */
+void
+camel_imapx_store_set_connecting_server (CamelIMAPXStore *store,
+ CamelIMAPXServer *server,
+ gboolean is_concurrent_connection)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_STORE (store));
+
+ if (server)
+ g_return_if_fail (CAMEL_IS_IMAPX_SERVER (server));
+
+ g_mutex_lock (&store->priv->server_lock);
+
+ if (store->priv->connecting_server != server) {
+ g_clear_object (&store->priv->connecting_server);
+ if (server)
+ store->priv->connecting_server = g_object_ref (server);
+ }
+
+ store->priv->is_concurrent_connection = is_concurrent_connection;
+
+ g_mutex_unlock (&store->priv->server_lock);
+}
+
+gboolean
+camel_imapx_store_is_connecting_concurrent_connection (CamelIMAPXStore *imapx_store)
+{
+ gboolean res;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store), FALSE);
+
+ g_mutex_lock (&imapx_store->priv->server_lock);
+ res = imapx_store->priv->is_concurrent_connection;
+ g_mutex_unlock (&imapx_store->priv->server_lock);
+
+ return res;
+}
+
+/**
+ * camel_imapx_store_ref_namespaces:
+ * @imapx_store: a #CamelIMAPXStore
+ *
+ * Returns the #CamelIMAPXNamespaceResponse for @is. This is obtained
+ * during the connection phase if the IMAP server lists the "NAMESPACE"
+ * keyword in its CAPABILITY response, or else is fabricated from the
+ * first LIST response.
+ *
+ * The returned #CamelIMAPXNamespaceResponse is reference for thread-safety
+ * and must be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelIMAPXNamespaceResponse
+ *
+ * Since: 3.16
+ **/
+CamelIMAPXNamespaceResponse *
+camel_imapx_store_ref_namespaces (CamelIMAPXStore *imapx_store)
+{
+ CamelIMAPXNamespaceResponse *namespaces = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store), NULL);
+
+ g_mutex_lock (&imapx_store->priv->namespaces_lock);
+
+ if (imapx_store->priv->namespaces != NULL)
+ namespaces = g_object_ref (imapx_store->priv->namespaces);
+
+ g_mutex_unlock (&imapx_store->priv->namespaces_lock);
+
+ return namespaces;
+}
+
+void
+camel_imapx_store_set_namespaces (CamelIMAPXStore *imapx_store,
+ CamelIMAPXNamespaceResponse *namespaces)
+{
+ CamelIMAPXSettings *imapx_settings;
+ gboolean ignore_other_users_namespace, ignore_shared_folders_namespace;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store));
+ if (namespaces)
+ g_return_if_fail (CAMEL_IS_IMAPX_NAMESPACE_RESPONSE (namespaces));
+
+ if (namespaces)
+ g_object_ref (namespaces);
+
+ imapx_settings = CAMEL_IMAPX_SETTINGS (camel_service_ref_settings (CAMEL_SERVICE (imapx_store)));
+
+ g_mutex_lock (&imapx_store->priv->namespaces_lock);
+
+ g_clear_object (&imapx_store->priv->namespaces);
+ imapx_store->priv->namespaces = namespaces;
+
+ if (camel_imapx_settings_get_use_namespace (imapx_settings)) {
+ gchar *use_namespace = camel_imapx_settings_dup_namespace (imapx_settings);
+
+ if (use_namespace && *use_namespace) {
+ /* Overwrite personal namespaces to the given */
+ GList *nslist, *link;
+ gchar folder_sep = 0;
+ CamelIMAPXNamespace *override_ns = NULL;
+
+ nslist = camel_imapx_namespace_response_list (namespaces);
+ for (link = nslist; link; link = g_list_next (link)) {
+ CamelIMAPXNamespace *ns = link->data;
+
+ if (!folder_sep)
+ folder_sep = camel_imapx_namespace_get_separator (ns);
+
+ if (camel_imapx_namespace_get_category (ns) == CAMEL_IMAPX_NAMESPACE_PERSONAL) {
+ if (!override_ns) {
+ override_ns = camel_imapx_namespace_new (
+ CAMEL_IMAPX_NAMESPACE_PERSONAL,
+ use_namespace,
+ camel_imapx_namespace_get_separator (ns));
+ }
+
+ camel_imapx_namespace_response_remove (namespaces, ns);
+ }
+ }
+
+ if (!override_ns) {
+ override_ns = camel_imapx_namespace_new (
+ CAMEL_IMAPX_NAMESPACE_PERSONAL,
+ use_namespace,
+ folder_sep);
+ }
+
+ camel_imapx_namespace_response_add (namespaces, override_ns);
+
+ g_list_free_full (nslist, g_object_unref);
+ g_clear_object (&override_ns);
+ }
+
+ g_free (use_namespace);
+ }
+
+ ignore_other_users_namespace = camel_imapx_settings_get_ignore_other_users_namespace (imapx_settings);
+ ignore_shared_folders_namespace = camel_imapx_settings_get_ignore_shared_folders_namespace (imapx_settings);
+
+ if (ignore_other_users_namespace || ignore_shared_folders_namespace) {
+ GList *nslist, *link;
+
+ nslist = camel_imapx_namespace_response_list (namespaces);
+ for (link = nslist; link; link = g_list_next (link)) {
+ CamelIMAPXNamespace *ns = link->data;
+
+ if ((ignore_other_users_namespace && camel_imapx_namespace_get_category (ns) == CAMEL_IMAPX_NAMESPACE_OTHER_USERS) ||
+ (ignore_shared_folders_namespace && camel_imapx_namespace_get_category (ns) == CAMEL_IMAPX_NAMESPACE_SHARED)) {
+ camel_imapx_namespace_response_remove (namespaces, ns);
+ }
+ }
+
+ g_list_free_full (nslist, g_object_unref);
+ }
+
+ g_mutex_unlock (&imapx_store->priv->namespaces_lock);
+
+ g_clear_object (&imapx_settings);
+}
+
+static void
+imapx_store_add_mailbox_unlocked (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *mailbox)
+{
+ const gchar *mailbox_name;
+
+ /* Acquire "mailboxes_lock" before calling. */
+
+ mailbox_name = camel_imapx_mailbox_get_name (mailbox);
+ g_return_if_fail (mailbox_name != NULL);
+
+ /* Use g_hash_table_replace() here instead of g_hash_table_insert().
+ * The hash table key is owned by the hash table value, so if we're
+ * replacing an existing table item we want to replace both the key
+ * and value to avoid data corruption. */
+ g_hash_table_replace (
+ imapx_store->priv->mailboxes,
+ (gpointer) mailbox_name,
+ g_object_ref (mailbox));
+}
+
+static gboolean
+imapx_store_remove_mailbox_unlocked (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *mailbox)
+{
+ const gchar *mailbox_name;
+
+ /* Acquire "mailboxes_lock" before calling. */
+
+ mailbox_name = camel_imapx_mailbox_get_name (mailbox);
+ g_return_val_if_fail (mailbox_name != NULL, FALSE);
+
+ return g_hash_table_remove (imapx_store->priv->mailboxes, mailbox_name);
+}
+
+static CamelIMAPXMailbox *
+imapx_store_ref_mailbox_unlocked (CamelIMAPXStore *imapx_store,
+ const gchar *mailbox_name)
+{
+ CamelIMAPXMailbox *mailbox;
+
+ /* Acquire "mailboxes_lock" before calling. */
+
+ g_return_val_if_fail (mailbox_name != NULL, NULL);
+
+ /* The INBOX mailbox is case-insensitive. */
+ if (g_ascii_strcasecmp (mailbox_name, "INBOX") == 0)
+ mailbox_name = "INBOX";
+
+ mailbox = g_hash_table_lookup (imapx_store->priv->mailboxes, mailbox_name);
+
+ /* Remove non-existent mailboxes as we find them. */
+ if (mailbox != NULL && !camel_imapx_mailbox_exists (mailbox)) {
+ imapx_store_remove_mailbox_unlocked (imapx_store, mailbox);
+ mailbox = NULL;
+ }
+
+ if (mailbox != NULL)
+ g_object_ref (mailbox);
+
+ return mailbox;
+}
+
+static GList *
+imapx_store_list_mailboxes_unlocked (CamelIMAPXStore *imapx_store,
+ CamelIMAPXNamespace *namespace,
+ const gchar *pattern)
+{
+ GHashTableIter iter;
+ GList *list = NULL;
+ gpointer value;
+
+ /* Acquire "mailboxes_lock" before calling. */
+
+ if (pattern == NULL)
+ pattern = "*";
+
+ g_hash_table_iter_init (&iter, imapx_store->priv->mailboxes);
+
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ CamelIMAPXMailbox *mailbox;
+ CamelIMAPXNamespace *mailbox_ns;
+
+ mailbox = CAMEL_IMAPX_MAILBOX (value);
+ mailbox_ns = camel_imapx_mailbox_get_namespace (mailbox);
+
+ if (!camel_imapx_mailbox_exists (mailbox))
+ continue;
+
+ if (!camel_imapx_namespace_equal (namespace, mailbox_ns))
+ continue;
+
+ if (!camel_imapx_mailbox_matches (mailbox, pattern))
+ continue;
+
+ list = g_list_prepend (list, g_object_ref (mailbox));
+ }
+
+ /* Sort the list by mailbox name. */
+ return g_list_sort (list, (GCompareFunc) camel_imapx_mailbox_compare);
+}
+
+static CamelIMAPXMailbox *
+imapx_store_create_mailbox_unlocked (CamelIMAPXStore *imapx_store,
+ CamelIMAPXListResponse *response)
+{
+ CamelIMAPXNamespaceResponse *namespace_response;
+ CamelIMAPXNamespace *namespace;
+ CamelIMAPXMailbox *mailbox = NULL;
+ const gchar *mailbox_name;
+ gchar separator;
+
+ /* Acquire "mailboxes_lock" before calling. */
+
+ namespace_response = camel_imapx_store_ref_namespaces (imapx_store);
+ g_return_val_if_fail (namespace_response != NULL, FALSE);
+
+ mailbox_name = camel_imapx_list_response_get_mailbox_name (response);
+ separator = camel_imapx_list_response_get_separator (response);
+
+ namespace = camel_imapx_namespace_response_lookup (
+ namespace_response, mailbox_name, separator);
+
+ if (namespace != NULL) {
+ mailbox = camel_imapx_mailbox_new (response, namespace);
+ imapx_store_add_mailbox_unlocked (imapx_store, mailbox);
+ g_object_unref (namespace);
+
+ /* XXX Slight hack, mainly for Courier servers. If INBOX does
+ * not match any defined namespace, just create one for it
+ * on the fly. The namespace response won't know about it. */
+ } else if (camel_imapx_mailbox_is_inbox (mailbox_name)) {
+ namespace = camel_imapx_namespace_new (
+ CAMEL_IMAPX_NAMESPACE_PERSONAL, "", separator);
+ mailbox = camel_imapx_mailbox_new (response, namespace);
+ imapx_store_add_mailbox_unlocked (imapx_store, mailbox);
+ g_object_unref (namespace);
+
+ } else {
+ g_warning (
+ "%s: No matching namespace for \"%c\" %s",
+ G_STRFUNC, separator, mailbox_name);
+ }
+
+ g_object_unref (namespace_response);
+
+ return mailbox;
+}
+
+static CamelIMAPXMailbox *
+imapx_store_rename_mailbox_unlocked (CamelIMAPXStore *imapx_store,
+ const gchar *old_mailbox_name,
+ const gchar *new_mailbox_name)
+{
+ CamelIMAPXMailbox *old_mailbox;
+ CamelIMAPXMailbox *new_mailbox;
+ CamelIMAPXNamespace *namespace;
+ gsize old_mailbox_name_length;
+ GList *list, *link;
+ gchar separator;
+ gchar *pattern;
+
+ /* Acquire "mailboxes_lock" before calling. */
+
+ g_return_val_if_fail (old_mailbox_name != NULL, NULL);
+ g_return_val_if_fail (new_mailbox_name != NULL, NULL);
+
+ old_mailbox = imapx_store_ref_mailbox_unlocked (imapx_store, old_mailbox_name);
+ if (old_mailbox == NULL)
+ return NULL;
+
+ old_mailbox_name_length = strlen (old_mailbox_name);
+ namespace = camel_imapx_mailbox_get_namespace (old_mailbox);
+ separator = camel_imapx_mailbox_get_separator (old_mailbox);
+
+ new_mailbox = camel_imapx_mailbox_clone (old_mailbox, new_mailbox_name);
+
+ /* Add the new mailbox, remove the old mailbox.
+ * Note we still have a reference on the old mailbox. */
+ imapx_store_add_mailbox_unlocked (imapx_store, new_mailbox);
+ imapx_store_remove_mailbox_unlocked (imapx_store, old_mailbox);
+
+ /* Rename any child mailboxes. */
+
+ pattern = g_strdup_printf ("%s%c*", old_mailbox_name, separator);
+ list = imapx_store_list_mailboxes_unlocked (imapx_store, namespace, pattern);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ CamelIMAPXMailbox *old_child;
+ CamelIMAPXMailbox *new_child;
+ const gchar *old_child_name;
+ gchar *new_child_name;
+
+ old_child = CAMEL_IMAPX_MAILBOX (link->data);
+ old_child_name = camel_imapx_mailbox_get_name (old_child);
+
+ /* Sanity checks. */
+ g_warn_if_fail (
+ old_child_name != NULL &&
+ strlen (old_child_name) > old_mailbox_name_length &&
+ old_child_name[old_mailbox_name_length] == separator);
+
+ new_child_name = g_strconcat (
+ new_mailbox_name,
+ old_child_name + old_mailbox_name_length, NULL);
+
+ new_child = camel_imapx_mailbox_clone (old_child, new_child_name);
+
+ /* Add the new mailbox, remove the old mailbox.
+ * Note we still have a reference on the old mailbox. */
+ imapx_store_add_mailbox_unlocked (imapx_store, new_child);
+ imapx_store_remove_mailbox_unlocked (imapx_store, old_child);
+
+ g_object_unref (new_child);
+ g_free (new_child_name);
+ }
+
+ g_list_free_full (list, (GDestroyNotify) g_object_unref);
+ g_free (pattern);
+
+ g_object_unref (old_mailbox);
+
+ return new_mailbox;
+}
+
+/**
+ * camel_imapx_store_ref_mailbox:
+ * @imapx_store: a #CamelIMAPXStore
+ * @mailbox_name: a mailbox name
+ *
+ * Looks up a #CamelMailbox by its name. If no match is found, the function
+ * returns %NULL.
+ *
+ * The returned #CamelIMAPXMailbox is referenced for thread-safety and
+ * should be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelIMAPXMailbox, or %NULL
+ *
+ * Since: 3.16
+ **/
+CamelIMAPXMailbox *
+camel_imapx_store_ref_mailbox (CamelIMAPXStore *imapx_store,
+ const gchar *mailbox_name)
+{
+ CamelIMAPXMailbox *mailbox;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store), NULL);
+ g_return_val_if_fail (mailbox_name != NULL, NULL);
+
+ g_mutex_lock (&imapx_store->priv->mailboxes_lock);
+
+ mailbox = imapx_store_ref_mailbox_unlocked (imapx_store, mailbox_name);
+
+ g_mutex_unlock (&imapx_store->priv->mailboxes_lock);
+
+ return mailbox;
+}
+
+/**
+ * camel_imapx_store_list_mailboxes:
+ * @imapx_store: a #CamelIMAPXStore
+ * @namespace_: a #CamelIMAPXNamespace
+ * @pattern: mailbox name with possible wildcards, or %NULL
+ *
+ * Returns a list of #CamelIMAPXMailbox instances which match @namespace and
+ * @pattern. The @pattern may contain wildcard characters '*' and '%', which
+ * are interpreted similar to the IMAP LIST command. A %NULL @pattern lists
+ * all mailboxes in @namespace; equivalent to passing "*".
+ *
+ * The mailboxes returned in the list are referenced for thread-safety.
+ * They must each be unreferenced with g_object_unref() when finished with
+ * them. Free the returned list itself with g_list_free().
+ *
+ * An easy way to free the list properly in one step is as follows:
+ *
+ * |[
+ * g_list_free_full (list, g_object_unref);
+ * ]|
+ *
+ * Returns: a list of #CamelIMAPXMailbox instances
+ *
+ * Since: 3.16
+ **/
+GList *
+camel_imapx_store_list_mailboxes (CamelIMAPXStore *imapx_store,
+ CamelIMAPXNamespace *namespace,
+ const gchar *pattern)
+{
+ GList *list;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store), NULL);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_NAMESPACE (namespace), NULL);
+
+ g_mutex_lock (&imapx_store->priv->mailboxes_lock);
+
+ list = imapx_store_list_mailboxes_unlocked (imapx_store, namespace, pattern);
+
+ g_mutex_unlock (&imapx_store->priv->mailboxes_lock);
+
+ return list;
+}
+
+void
+camel_imapx_store_emit_mailbox_updated (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *mailbox)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store));
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox));
+
+ g_signal_emit (imapx_store, signals[MAILBOX_UPDATED], 0, mailbox);
+}
+
+void
+camel_imapx_store_handle_mailbox_rename (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *old_mailbox,
+ const gchar *new_mailbox_name)
+{
+ CamelIMAPXMailbox *new_mailbox;
+ const gchar *old_mailbox_name;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store));
+ g_return_if_fail (CAMEL_IS_IMAPX_MAILBOX (old_mailbox));
+ g_return_if_fail (new_mailbox_name != NULL);
+
+ old_mailbox_name = camel_imapx_mailbox_get_name (old_mailbox);
+
+ g_mutex_lock (&imapx_store->priv->mailboxes_lock);
+ new_mailbox = imapx_store_rename_mailbox_unlocked (
+ imapx_store, old_mailbox_name, new_mailbox_name);
+ g_mutex_unlock (&imapx_store->priv->mailboxes_lock);
+
+ g_warn_if_fail (new_mailbox != NULL);
+
+ g_signal_emit (
+ imapx_store, signals[MAILBOX_RENAMED], 0,
+ new_mailbox, old_mailbox_name);
+
+ g_clear_object (&new_mailbox);
+}
+
+void
+camel_imapx_store_handle_list_response (CamelIMAPXStore *imapx_store,
+ CamelIMAPXServer *imapx_server,
+ CamelIMAPXListResponse *response)
+{
+ CamelIMAPXMailbox *mailbox = NULL;
+ const gchar *mailbox_name;
+ const gchar *old_mailbox_name;
+ gboolean emit_mailbox_created = FALSE;
+ gboolean emit_mailbox_renamed = FALSE;
+ gboolean emit_mailbox_updated = FALSE;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store));
+ g_return_if_fail (CAMEL_IS_IMAPX_SERVER (imapx_server));
+ g_return_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response));
+
+ mailbox_name = camel_imapx_list_response_get_mailbox_name (response);
+ old_mailbox_name = camel_imapx_list_response_get_oldname (response);
+
+ /* Fabricate a CamelIMAPXNamespaceResponse if the server lacks the
+ * NAMESPACE capability and this is the first LIST / LSUB response. */
+ if (camel_imapx_server_lack_capability (imapx_server, IMAPX_CAPABILITY_NAMESPACE)) {
+ g_mutex_lock (&imapx_store->priv->namespaces_lock);
+ if (imapx_store->priv->namespaces == NULL) {
+ imapx_store->priv->namespaces = camel_imapx_namespace_response_faux_new (response);
+ }
+ g_mutex_unlock (&imapx_store->priv->namespaces_lock);
+ }
+
+ /* Create, rename, or update a corresponding CamelIMAPXMailbox. */
+ g_mutex_lock (&imapx_store->priv->mailboxes_lock);
+ if (old_mailbox_name != NULL) {
+ mailbox = imapx_store_rename_mailbox_unlocked (
+ imapx_store, old_mailbox_name, mailbox_name);
+ emit_mailbox_renamed = (mailbox != NULL);
+ if (mailbox && camel_imapx_mailbox_get_state (mailbox) == CAMEL_IMAPX_MAILBOX_STATE_UNKNOWN)
+ camel_imapx_mailbox_set_state (mailbox, CAMEL_IMAPX_MAILBOX_STATE_RENAMED);
+ }
+ if (mailbox == NULL) {
+ mailbox = imapx_store_ref_mailbox_unlocked (imapx_store, mailbox_name);
+ emit_mailbox_updated = (mailbox != NULL);
+ if (mailbox && camel_imapx_mailbox_get_state (mailbox) == CAMEL_IMAPX_MAILBOX_STATE_UNKNOWN)
+ camel_imapx_mailbox_set_state (mailbox, CAMEL_IMAPX_MAILBOX_STATE_UPDATED);
+ }
+ if (mailbox == NULL) {
+ mailbox = imapx_store_create_mailbox_unlocked (imapx_store, response);
+ emit_mailbox_created = (mailbox != NULL);
+ if (mailbox)
+ camel_imapx_mailbox_set_state (mailbox, CAMEL_IMAPX_MAILBOX_STATE_CREATED);
+ } else {
+ camel_imapx_mailbox_handle_list_response (mailbox, response);
+ }
+ g_mutex_unlock (&imapx_store->priv->mailboxes_lock);
+
+ if (emit_mailbox_created)
+ g_signal_emit (imapx_store, signals[MAILBOX_CREATED], 0, mailbox);
+
+ if (emit_mailbox_renamed)
+ g_signal_emit (
+ imapx_store, signals[MAILBOX_RENAMED], 0,
+ mailbox, old_mailbox_name);
+
+ if (emit_mailbox_updated)
+ g_signal_emit (imapx_store, signals[MAILBOX_UPDATED], 0, mailbox);
+
+ g_clear_object (&mailbox);
+}
+
+void
+camel_imapx_store_handle_lsub_response (CamelIMAPXStore *imapx_store,
+ CamelIMAPXServer *imapx_server,
+ CamelIMAPXListResponse *response)
+{
+ CamelIMAPXMailbox *mailbox;
+ const gchar *mailbox_name;
+ gboolean emit_mailbox_updated = FALSE;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store));
+ g_return_if_fail (CAMEL_IS_IMAPX_SERVER (imapx_server));
+ g_return_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response));
+
+ mailbox_name = camel_imapx_list_response_get_mailbox_name (response);
+
+ /* Fabricate a CamelIMAPXNamespaceResponse if the server lacks the
+ * NAMESPACE capability and this is the first LIST / LSUB response. */
+ if (camel_imapx_server_lack_capability (imapx_server, IMAPX_CAPABILITY_NAMESPACE)) {
+ g_mutex_lock (&imapx_store->priv->namespaces_lock);
+ if (imapx_store->priv->namespaces == NULL) {
+ imapx_store->priv->namespaces = camel_imapx_namespace_response_faux_new (response);
+ }
+ g_mutex_unlock (&imapx_store->priv->namespaces_lock);
+ }
+
+ /* Update a corresponding CamelIMAPXMailbox.
+ *
+ * Note, don't create the CamelIMAPXMailbox like we do for a LIST
+ * response. We always issue LIST before LSUB on a mailbox name,
+ * so if we don't already have a CamelIMAPXMailbox instance then
+ * this is a subscription on a non-existent mailbox. Skip it. */
+ g_mutex_lock (&imapx_store->priv->mailboxes_lock);
+ mailbox = imapx_store_ref_mailbox_unlocked (imapx_store, mailbox_name);
+ if (mailbox != NULL) {
+ camel_imapx_mailbox_handle_lsub_response (mailbox, response);
+ if (camel_imapx_mailbox_get_state (mailbox) == CAMEL_IMAPX_MAILBOX_STATE_UNKNOWN)
+ camel_imapx_mailbox_set_state (mailbox, CAMEL_IMAPX_MAILBOX_STATE_UPDATED);
+ emit_mailbox_updated = TRUE;
+ }
+ g_mutex_unlock (&imapx_store->priv->mailboxes_lock);
+
+ if (emit_mailbox_updated)
+ g_signal_emit (imapx_store, signals[MAILBOX_UPDATED], 0, mailbox);
+
+ g_clear_object (&mailbox);
+}
+
+CamelFolderQuotaInfo *
+camel_imapx_store_dup_quota_info (CamelIMAPXStore *store,
+ const gchar *quota_root_name)
+{
+ CamelFolderQuotaInfo *info;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (store), NULL);
+ g_return_val_if_fail (quota_root_name != NULL, NULL);
+
+ g_mutex_lock (&store->priv->quota_info_lock);
+
+ info = g_hash_table_lookup (
+ store->priv->quota_info, quota_root_name);
+
+ /* camel_folder_quota_info_clone() handles NULL gracefully. */
+ info = camel_folder_quota_info_clone (info);
+
+ g_mutex_unlock (&store->priv->quota_info_lock);
+
+ return info;
+}
+
+void
+camel_imapx_store_set_quota_info (CamelIMAPXStore *store,
+ const gchar *quota_root_name,
+ const CamelFolderQuotaInfo *info)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_STORE (store));
+ g_return_if_fail (quota_root_name != NULL);
+
+ g_mutex_lock (&store->priv->quota_info_lock);
+
+ /* camel_folder_quota_info_clone() handles NULL gracefully. */
+ g_hash_table_insert (
+ store->priv->quota_info,
+ g_strdup (quota_root_name),
+ camel_folder_quota_info_clone (info));
+
+ g_mutex_unlock (&store->priv->quota_info_lock);
+}
+
+/* for debugging purposes only */
+void
+camel_imapx_store_dump_queue_status (CamelIMAPXStore *imapx_store)
+{
+ g_return_if_fail (CAMEL_IS_IMAPX_STORE (imapx_store));
+
+ camel_imapx_conn_manager_dump_queue_status (imapx_store->priv->conn_man);
+}
diff --git a/src/camel/providers/imapx/camel-imapx-store.h b/src/camel/providers/imapx/camel-imapx-store.h
new file mode 100644
index 000000000..e25eccdf3
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-store.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-imap-store.h : class for an imap store
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_IMAPX_STORE_H
+#define CAMEL_IMAPX_STORE_H
+
+#include <camel/camel.h>
+
+#include "camel-imapx-conn-manager.h"
+#include "camel-imapx-server.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_STORE \
+ (camel_imapx_store_get_type ())
+#define CAMEL_IMAPX_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_STORE, CamelIMAPXStore))
+#define CAMEL_IMAPX_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_STORE, CamelIMAPXStoreClass))
+#define CAMEL_IS_IMAPX_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_STORE))
+#define CAMEL_IS_IMAPX_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_STORE))
+#define CAMEL_IMAPX_STORE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_STORE, CamelIMAPXStoreClass))
+
+G_BEGIN_DECLS
+
+/* Avoid a circular reference. */
+struct _CamelIMAPXJob;
+
+typedef struct _CamelIMAPXStore CamelIMAPXStore;
+typedef struct _CamelIMAPXStoreClass CamelIMAPXStoreClass;
+typedef struct _CamelIMAPXStorePrivate CamelIMAPXStorePrivate;
+
+struct _CamelIMAPXStore {
+ CamelOfflineStore parent;
+ CamelIMAPXStorePrivate *priv;
+
+ CamelStoreSummary *summary; /* in-memory list of folders */
+};
+
+struct _CamelIMAPXStoreClass {
+ CamelOfflineStoreClass parent_class;
+
+ /* Signals */
+ void (*mailbox_created) (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *mailbox);
+ void (*mailbox_renamed) (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *mailbox,
+ const gchar *oldname);
+ void (*mailbox_updated) (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *mailbox);
+};
+
+GType camel_imapx_store_get_type (void);
+CamelIMAPXConnManager *
+ camel_imapx_store_get_conn_manager
+ (CamelIMAPXStore *store);
+void camel_imapx_store_set_connecting_server
+ (CamelIMAPXStore *store,
+ CamelIMAPXServer *server,
+ gboolean is_concurrent_connection);
+gboolean camel_imapx_store_is_connecting_concurrent_connection
+ (CamelIMAPXStore *imapx_store);
+CamelIMAPXNamespaceResponse *
+ camel_imapx_store_ref_namespaces
+ (CamelIMAPXStore *imapx_store);
+void camel_imapx_store_set_namespaces
+ (CamelIMAPXStore *imapx_store,
+ CamelIMAPXNamespaceResponse *namespaces);
+CamelIMAPXMailbox *
+ camel_imapx_store_ref_mailbox (CamelIMAPXStore *imapx_store,
+ const gchar *mailbox_name);
+GList * camel_imapx_store_list_mailboxes
+ (CamelIMAPXStore *imapx_store,
+ CamelIMAPXNamespace *namespace_,
+ const gchar *pattern);
+void camel_imapx_store_emit_mailbox_updated
+ (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *mailbox);
+void camel_imapx_store_handle_mailbox_rename
+ (CamelIMAPXStore *imapx_store,
+ CamelIMAPXMailbox *old_mailbox,
+ const gchar *new_mailbox_name);
+void camel_imapx_store_handle_list_response
+ (CamelIMAPXStore *imapx_store,
+ CamelIMAPXServer *imapx_server,
+ CamelIMAPXListResponse *response);
+void camel_imapx_store_handle_lsub_response
+ (CamelIMAPXStore *imapx_store,
+ CamelIMAPXServer *imapx_server,
+ CamelIMAPXListResponse *response);
+CamelFolderQuotaInfo *
+ camel_imapx_store_dup_quota_info
+ (CamelIMAPXStore *store,
+ const gchar *quota_root_name);
+void camel_imapx_store_set_quota_info
+ (CamelIMAPXStore *store,
+ const gchar *quota_root_name,
+ const CamelFolderQuotaInfo *info);
+/* for debugging purposes only */
+void camel_imapx_store_dump_queue_status
+ (CamelIMAPXStore *imapx_store);
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_STORE_H */
+
diff --git a/src/camel/providers/imapx/camel-imapx-summary.c b/src/camel/providers/imapx/camel-imapx-summary.c
new file mode 100644
index 000000000..6e3a2d2c9
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-summary.c
@@ -0,0 +1,417 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Dan Winship <danw@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <camel/camel.h>
+
+#include "camel-imapx-summary.h"
+
+#define CAMEL_IMAPX_SUMMARY_VERSION (4)
+
+enum {
+ INFO_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (
+ CamelIMAPXSummary,
+ camel_imapx_summary,
+ CAMEL_TYPE_FOLDER_SUMMARY)
+
+static gboolean
+imapx_summary_summary_header_from_db (CamelFolderSummary *s,
+ CamelFIRecord *mir)
+{
+ gboolean success;
+
+ /* Chain up to parent's summary_header_from_db() method. */
+ success = CAMEL_FOLDER_SUMMARY_CLASS (
+ camel_imapx_summary_parent_class)->
+ summary_header_from_db (s, mir);
+
+ if (success) {
+ CamelIMAPXSummary *ims;
+ gchar *part = mir->bdata;
+
+ ims = CAMEL_IMAPX_SUMMARY (s);
+
+ ims->version = bdata_extract_digit (&part);
+ ims->validity = bdata_extract_digit (&part);
+
+ if (ims->version >= 4) {
+ ims->uidnext = bdata_extract_digit (&part);
+ ims->modseq = bdata_extract_digit (&part);
+ }
+
+ if (ims->version > CAMEL_IMAPX_SUMMARY_VERSION) {
+ g_warning ("Unknown summary version\n");
+ errno = EINVAL;
+ success = FALSE;
+ }
+ }
+
+ return success;
+}
+
+static CamelFIRecord *
+imapx_summary_summary_header_to_db (CamelFolderSummary *s,
+ GError **error)
+{
+ struct _CamelFIRecord *fir;
+
+ /* Chain up to parent's summary_header_to_db() method. */
+ fir = CAMEL_FOLDER_SUMMARY_CLASS (
+ camel_imapx_summary_parent_class)->
+ summary_header_to_db (s, error);
+
+ if (fir != NULL) {
+ CamelIMAPXSummary *ims;
+
+ ims = CAMEL_IMAPX_SUMMARY (s);
+
+ fir->bdata = g_strdup_printf (
+ "%d"
+ " %" G_GUINT64_FORMAT
+ " %" G_GUINT32_FORMAT
+ " %" G_GUINT64_FORMAT,
+ CAMEL_IMAPX_SUMMARY_VERSION,
+ ims->validity,
+ ims->uidnext,
+ ims->modseq);
+ }
+
+ return fir;
+}
+
+static CamelMessageInfo *
+imapx_summary_message_info_from_db (CamelFolderSummary *s,
+ CamelMIRecord *mir)
+{
+ CamelMessageInfo *info;
+
+ /* Chain up parent's message_info_from_db() method. */
+ info = CAMEL_FOLDER_SUMMARY_CLASS (
+ camel_imapx_summary_parent_class)->
+ message_info_from_db (s, mir);
+
+ if (info != NULL) {
+ CamelIMAPXMessageInfo *imapx_info;
+ gchar *part = mir->bdata;
+
+ imapx_info = (CamelIMAPXMessageInfo *) info;
+ imapx_info->server_flags = bdata_extract_digit (&part);
+ }
+
+ return info;
+}
+
+static CamelMIRecord *
+imapx_summary_message_info_to_db (CamelFolderSummary *s,
+ CamelMessageInfo *info)
+{
+ struct _CamelMIRecord *mir;
+
+ /* Chain up to parent's message_info_to_db() method. */
+ mir = CAMEL_FOLDER_SUMMARY_CLASS (
+ camel_imapx_summary_parent_class)->
+ message_info_to_db (s, info);
+
+ if (mir != NULL) {
+ CamelIMAPXMessageInfo *imapx_info;
+
+ imapx_info = (CamelIMAPXMessageInfo *) info;
+ mir->bdata = g_strdup_printf ("%u", imapx_info->server_flags);
+ }
+
+ return mir;
+}
+
+static CamelMessageContentInfo *
+imapx_summary_content_info_from_db (CamelFolderSummary *summary,
+ CamelMIRecord *mir)
+{
+ gchar *part = mir->cinfo;
+ guint32 type = 0;
+
+ if (part != NULL) {
+ if (*part == ' ')
+ part++;
+ if (part != NULL)
+ type = bdata_extract_digit (&part);
+ }
+ mir->cinfo = part;
+
+ if (type) {
+ /* Chain up to parent's content_info_from_db() method. */
+ return CAMEL_FOLDER_SUMMARY_CLASS (
+ camel_imapx_summary_parent_class)->
+ content_info_from_db (summary, mir);
+ } else {
+ return camel_folder_summary_content_info_new (summary);
+ }
+}
+
+static gboolean
+imapx_summary_content_info_to_db (CamelFolderSummary *summary,
+ CamelMessageContentInfo *info,
+ CamelMIRecord *mir)
+{
+ gchar *oldr;
+
+ if (info->type) {
+ oldr = mir->cinfo;
+ if (oldr != NULL)
+ mir->cinfo = g_strdup_printf ("%s 1", oldr);
+ else
+ mir->cinfo = g_strdup ("1");
+ g_free (oldr);
+
+ /* Chain up to parent's content_info_to_db() method. */
+ return CAMEL_FOLDER_SUMMARY_CLASS (
+ camel_imapx_summary_parent_class)->
+ content_info_to_db (summary, info, mir);
+
+ } else {
+ oldr = mir->cinfo;
+ if (oldr != NULL)
+ mir->cinfo = g_strdup_printf ("%s 0", oldr);
+ else
+ mir->cinfo = g_strdup ("0");
+ g_free (oldr);
+
+ return TRUE;
+ }
+}
+
+static void
+imapx_summary_message_info_free (CamelFolderSummary *summary,
+ CamelMessageInfo *info)
+{
+ CamelIMAPXMessageInfo *imapx_info;
+
+ imapx_info = (CamelIMAPXMessageInfo *) info;
+ camel_flag_list_free (&imapx_info->server_user_flags);
+
+ /* Chain up to parent's message_info_free() method. */
+ CAMEL_FOLDER_SUMMARY_CLASS (camel_imapx_summary_parent_class)->
+ message_info_free (summary, info);
+}
+
+static CamelMessageInfo *
+imapx_summary_message_info_clone (CamelFolderSummary *summary,
+ const CamelMessageInfo *info)
+{
+ CamelMessageInfo *copy;
+ CamelIMAPXMessageInfo *imapx_copy;
+ CamelIMAPXMessageInfo *imapx_info;
+
+ /* Chain up to parent's message_info_clone() method. */
+ copy = CAMEL_FOLDER_SUMMARY_CLASS (
+ camel_imapx_summary_parent_class)->
+ message_info_clone (summary, info);
+
+ imapx_info = (CamelIMAPXMessageInfo *) info;
+ imapx_copy = (CamelIMAPXMessageInfo *) copy;
+
+ if (imapx_info->server_user_flags) {
+ camel_flag_list_copy (
+ &imapx_copy->server_user_flags,
+ &imapx_info->server_user_flags);
+ }
+
+ imapx_copy->server_flags = imapx_info->server_flags;
+
+ /* FIXME: parent clone should do this */
+ imapx_copy->info.content =
+ camel_folder_summary_content_info_new (summary);
+
+ return copy;
+}
+
+static void
+imapx_summary_emit_info_changed (CamelMessageInfo *info)
+{
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (CAMEL_IS_IMAPX_SUMMARY (info->summary));
+
+ g_signal_emit (info->summary, signals[INFO_CHANGED], 0, info);
+}
+
+static gboolean
+imapx_summary_info_set_user_flag (CamelMessageInfo *info,
+ const gchar *id,
+ gboolean state)
+{
+ gboolean changed;
+
+ /* Chain up to parent's method. */
+ changed = CAMEL_FOLDER_SUMMARY_CLASS (camel_imapx_summary_parent_class)->info_set_user_flag (info, id, state);
+
+ if (changed)
+ imapx_summary_emit_info_changed (info);
+
+ return changed;
+}
+
+static gboolean
+imapx_summary_info_set_user_tag (CamelMessageInfo *info,
+ const gchar *name,
+ const gchar *value)
+{
+ gboolean changed;
+
+ /* Chain up to parent's method. */
+ changed = CAMEL_FOLDER_SUMMARY_CLASS (camel_imapx_summary_parent_class)->info_set_user_tag (info, name, value);
+
+ if (changed)
+ imapx_summary_emit_info_changed (info);
+
+ return changed;
+}
+
+static gboolean
+imapx_summary_info_set_flags (CamelMessageInfo *info,
+ guint32 flags,
+ guint32 set)
+{
+ gboolean changed;
+
+ /* Chain up to parent's method. */
+ changed = CAMEL_FOLDER_SUMMARY_CLASS (camel_imapx_summary_parent_class)->info_set_flags (info, flags, set);
+
+ if (changed)
+ imapx_summary_emit_info_changed (info);
+
+ return changed;
+}
+
+static void
+camel_imapx_summary_class_init (CamelIMAPXSummaryClass *class)
+{
+ CamelFolderSummaryClass *folder_summary_class;
+
+ folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (class);
+ folder_summary_class->message_info_size = sizeof (CamelIMAPXMessageInfo);
+ folder_summary_class->content_info_size = sizeof (CamelIMAPXMessageContentInfo);
+ folder_summary_class->summary_header_from_db = imapx_summary_summary_header_from_db;
+ folder_summary_class->summary_header_to_db = imapx_summary_summary_header_to_db;
+ folder_summary_class->message_info_from_db = imapx_summary_message_info_from_db;
+ folder_summary_class->message_info_to_db = imapx_summary_message_info_to_db;
+ folder_summary_class->content_info_from_db = imapx_summary_content_info_from_db;
+ folder_summary_class->content_info_to_db = imapx_summary_content_info_to_db;
+ folder_summary_class->message_info_free = imapx_summary_message_info_free;
+ folder_summary_class->message_info_clone = imapx_summary_message_info_clone;
+ folder_summary_class->info_set_user_flag = imapx_summary_info_set_user_flag;
+ folder_summary_class->info_set_user_tag = imapx_summary_info_set_user_tag;
+ folder_summary_class->info_set_flags = imapx_summary_info_set_flags;
+
+ signals[INFO_CHANGED] = g_signal_new (
+ "info-changed",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST,
+ 0 /* G_STRUCT_OFFSET (CamelIMAPXSummaryClass, info_changed) */,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER /* CamelMessageInfo * */);
+}
+
+static void
+camel_imapx_summary_init (CamelIMAPXSummary *obj)
+{
+}
+
+static gint
+sort_uid_cmp (gpointer enc,
+ gint len1,
+ gpointer data1,
+ gint len2,
+ gpointer data2)
+{
+ static gchar *sa1 = NULL, *sa2 = NULL;
+ static gint l1 = 0, l2 = 0;
+ gint a1, a2;
+
+ if (l1 < len1 + 1) {
+ sa1 = g_realloc (sa1, len1 + 1);
+ l1 = len1 + 1;
+ }
+ if (l2 < len2 + 1) {
+ sa2 = g_realloc (sa2, len2 + 1);
+ l2 = len2 + 1;
+ }
+ strncpy (sa1, data1, len1); sa1[len1] = 0;
+ strncpy (sa2, data2, len2); sa2[len2] = 0;
+
+ a1 = strtoul (sa1, NULL, 10);
+ a2 = strtoul (sa2, NULL, 10);
+
+ return (a1 < a2) ? -1 : (a1 > a2) ? 1 : 0;
+}
+
+/**
+ * camel_imapx_summary_new:
+ * @folder: Parent folder.
+ *
+ * This will create a new CamelIMAPXSummary object and read in the
+ * summary data from disk, if it exists.
+ *
+ * Returns: A new CamelIMAPXSummary object.
+ **/
+CamelFolderSummary *
+camel_imapx_summary_new (CamelFolder *folder)
+{
+ CamelStore *parent_store;
+ CamelFolderSummary *summary;
+ GError *local_error = NULL;
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ summary = g_object_new (CAMEL_TYPE_IMAPX_SUMMARY, "folder", folder, NULL);
+
+ /* Don't do DB sort. Its pretty slow to load */
+ if (folder && 0) {
+ camel_db_set_collate (parent_store->cdb_r, "uid", "imapx_uid_sort", (CamelDBCollate) sort_uid_cmp);
+ summary->sort_by = "uid";
+ summary->collate = "imapx_uid_sort";
+ }
+
+ camel_folder_summary_set_build_content (summary, TRUE);
+
+ if (!camel_folder_summary_load_from_db (summary, &local_error)) {
+ /* FIXME: Isn't this dangerous ? We clear the summary
+ if it cannot be loaded, for some random reason.
+ We need to pass the error and find out why it is not loaded etc. ? */
+ camel_folder_summary_clear (summary, NULL);
+ g_message ("Unable to load summary: %s\n", local_error->message);
+ g_clear_error (&local_error);
+ }
+
+ return summary;
+}
+
diff --git a/src/camel/providers/imapx/camel-imapx-summary.h b/src/camel/providers/imapx/camel-imapx-summary.h
new file mode 100644
index 000000000..c515c1928
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-summary.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Dan Winship <danw@ximian.com>
+ */
+
+#ifndef CAMEL_IMAPX_SUMMARY_H
+#define CAMEL_IMAPX_SUMMARY_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_IMAPX_SUMMARY \
+ (camel_imapx_summary_get_type ())
+#define CAMEL_IMAPX_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_IMAPX_SUMMARY, CamelIMAPXSummary))
+#define CAMEL_IMAPX_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_IMAPX_SUMMARY, CamelIMAPXSummaryClass))
+#define CAMEL_IS_IMAPX_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_IMAPX_SUMMARY))
+#define CAMEL_IS_IMAPX_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_IMAPX_SUMMARY))
+#define CAMEL_IMAPX_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_IMAPX_SUMMARY, CamelIMAPXSummaryClass))
+
+#define CAMEL_IMAPX_SERVER_FLAGS \
+ (CAMEL_MESSAGE_ANSWERED | \
+ CAMEL_MESSAGE_DELETED | \
+ CAMEL_MESSAGE_DRAFT | \
+ CAMEL_MESSAGE_FLAGGED | \
+ CAMEL_MESSAGE_JUNK | \
+ CAMEL_MESSAGE_NOTJUNK | \
+ CAMEL_MESSAGE_SEEN)
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAPXSummary CamelIMAPXSummary;
+typedef struct _CamelIMAPXSummaryClass CamelIMAPXSummaryClass;
+
+typedef struct _CamelIMAPXMessageInfo CamelIMAPXMessageInfo;
+typedef struct _CamelIMAPXMessageContentInfo CamelIMAPXMessageContentInfo;
+
+struct _CamelIMAPXMessageContentInfo {
+ CamelMessageContentInfo info;
+};
+
+struct _CamelIMAPXMessageInfo {
+ CamelMessageInfoBase info;
+
+ guint32 server_flags;
+ CamelFlag *server_user_flags;
+};
+
+struct _CamelIMAPXSummary {
+ CamelFolderSummary parent;
+
+ guint32 version;
+ guint32 uidnext;
+ guint64 validity;
+ guint64 modseq;
+};
+
+struct _CamelIMAPXSummaryClass {
+ CamelFolderSummaryClass parent_class;
+};
+
+GType camel_imapx_summary_get_type (void);
+CamelFolderSummary *
+ camel_imapx_summary_new (CamelFolder *folder);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_SUMMARY_H */
diff --git a/src/camel/providers/imapx/camel-imapx-tokens.txt b/src/camel/providers/imapx/camel-imapx-tokens.txt
new file mode 100644
index 000000000..ed278bd37
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-tokens.txt
@@ -0,0 +1,63 @@
+/* This contains all of the keywords we care about. These
+ can be converted to an id very efficiently */
+struct _imapx_keyword {const gchar *name; camel_imapx_id_t id; };
+struct _imapx_keyword *imapx_tokenise_struct (register const char *str, register unsigned int len);
+%%
+ALERT, IMAPX_ALERT
+ALREADYEXISTS, IMAPX_ALREADYEXISTS
+AUTHENTICATIONFAILED, IMAPX_AUTHENTICATIONFAILED
+AUTHORIZATIONFAILED, IMAPX_AUTHORIZATIONFAILED
+APPENDUID, IMAPX_APPENDUID
+BAD, IMAPX_BAD
+BODY, IMAPX_BODY
+BODYSTRUCTURE, IMAPX_BODYSTRUCTURE
+BYE, IMAPX_BYE
+CANNOT, IMAPX_CANNOT
+CAPABILITY, IMAPX_CAPABILITY
+CLIENTBUG, IMAPX_CLIENTBUG
+CONTACTADMIN, IMAPX_CONTACTADMIN
+COPYUID, IMAPX_COPYUID
+CORRUPTION, IMAPX_CORRUPTION
+CLOSED, IMAPX_CLOSED
+ENVELOPE, IMAPX_ENVELOPE
+EXISTS, IMAPX_EXISTS
+EXPIRED, IMAPX_EXPIRED
+EXPUNGE, IMAPX_EXPUNGE
+EXPUNGEISSUED, IMAPX_EXPUNGEISSUED
+FETCH, IMAPX_FETCH
+FLAGS, IMAPX_FLAGS
+HIGHESTMODSEQ, IMAPX_HIGHESTMODSEQ
+INTERNALDATE, IMAPX_INTERNALDATE
+INUSE, IMAPX_INUSE
+LIMIT, IMAPX_LIMIT
+LIST, IMAPX_LIST
+LSUB, IMAPX_LSUB
+MESSAGES, IMAPX_MESSAGES
+MODSEQ, IMAPX_MODSEQ
+NAMESPACE, IMAPX_NAMESPACE
+NEWNAME, IMAPX_NEWNAME
+NO, IMAPX_NO
+NOMODSEQ, IMAPX_NOMODSEQ
+NONEXISTENT, IMAPX_NONEXISTENT
+NOPERM, IMAPX_NOPERM
+OK, IMAPX_OK
+OVERQUOTA, IMAPX_OVERQUOTA
+PARSE, IMAPX_PARSE
+PERMANENTFLAGS, IMAPX_PERMANENTFLAGS
+PREAUTH, IMAPX_PREAUTH
+PRIVACYREQUIRED, IMAPX_PRIVACYREQUIRED
+READ-ONLY, IMAPX_READ_ONLY
+READ-WRITE, IMAPX_READ_WRITE
+RECENT, IMAPX_RECENT
+RFC822.HEADER, IMAPX_RFC822_HEADER
+RFC822.SIZE, IMAPX_RFC822_SIZE
+RFC822.TEXT, IMAPX_RFC822_TEXT
+SERVERBUG, IMAPX_SERVERBUG
+STATUS, IMAPX_STATUS
+TRYCREATE, IMAPX_TRYCREATE
+UID, IMAPX_UID
+UIDVALIDITY, IMAPX_UIDVALIDITY
+UNAVAILABLE, IMAPX_UNAVAILABLE
+UNSEEN, IMAPX_UNSEEN
+UIDNEXT, IMAPX_UIDNEXT
+VANISHED, IMAPX_VANISHED
diff --git a/src/camel/providers/imapx/camel-imapx-utils.c b/src/camel/providers/imapx/camel-imapx-utils.c
new file mode 100644
index 000000000..c60666d04
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-utils.c
@@ -0,0 +1,3301 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+
+#include "camel-imapx-command.h"
+#include "camel-imapx-folder.h"
+#include "camel-imapx-settings.h"
+#include "camel-imapx-summary.h"
+#include "camel-imapx-store.h"
+#include "camel-imapx-store-summary.h"
+#include "camel-imapx-utils.h"
+
+/* high-level parser state */
+#define p(...) camel_imapx_debug(parse, __VA_ARGS__)
+/* debug */
+#define d(...) camel_imapx_debug(debug, __VA_ARGS__)
+
+gint camel_imapx_debug_flags;
+extern gint camel_verbose_debug;
+
+#define debug_set_flag(flag) do { \
+ if ((CAMEL_IMAPX_DEBUG_ALL & CAMEL_IMAPX_DEBUG_ ## flag) && \
+ camel_debug ("imapx:" #flag)) \
+ camel_imapx_debug_flags |= CAMEL_IMAPX_DEBUG_ ## flag; \
+ } while (0)
+
+static void camel_imapx_set_debug_flags (void)
+{
+ if (camel_verbose_debug || camel_debug ("imapx")) {
+ camel_imapx_debug_flags = CAMEL_IMAPX_DEBUG_ALL;
+ return;
+ }
+
+ debug_set_flag (command);
+ debug_set_flag (debug);
+ debug_set_flag (extra);
+ debug_set_flag (io);
+ debug_set_flag (token);
+ debug_set_flag (parse);
+ debug_set_flag (conman);
+}
+
+#include "camel-imapx-tokenise.h"
+#define SUBFOLDER_DIR_NAME "subfolders"
+
+#ifdef __GNUC__
+__inline
+#endif
+camel_imapx_id_t
+imapx_tokenise (register const gchar *str,
+ register guint len)
+{
+ struct _imapx_keyword *k = imapx_tokenise_struct (str, len);
+
+ if (k)
+ return k->id;
+ return 0;
+}
+
+static const gchar * rename_label_flag (const gchar *flag, gint len, gboolean server_to_evo);
+
+/* flag table */
+static struct {
+ const gchar *name;
+ guint32 flag;
+} flag_table[] = {
+ { "\\ANSWERED", CAMEL_MESSAGE_ANSWERED },
+ { "\\DELETED", CAMEL_MESSAGE_DELETED },
+ { "\\DRAFT", CAMEL_MESSAGE_DRAFT },
+ { "\\FLAGGED", CAMEL_MESSAGE_FLAGGED },
+ { "\\SEEN", CAMEL_MESSAGE_SEEN },
+ { "\\RECENT", CAMEL_IMAPX_MESSAGE_RECENT },
+ { "JUNK", CAMEL_MESSAGE_JUNK },
+ { "NOTJUNK", CAMEL_MESSAGE_NOTJUNK },
+ { "\\*", CAMEL_MESSAGE_USER }
+};
+
+/* utility functions
+ * should this be part of imapx-driver? */
+/* maybe this should be a stream op? */
+gboolean
+imapx_parse_flags (CamelIMAPXInputStream *stream,
+ guint32 *flagsp,
+ CamelFlag **user_flagsp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_imapx_token_t tok;
+ guint len;
+ guchar *token;
+ guint32 flags = 0;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (stream), FALSE);
+
+ *flagsp = flags;
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+
+ if (tok == IMAPX_TOK_ERROR)
+ return FALSE;
+
+ if (tok != '(') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "expecting flag list");
+ return FALSE;
+ }
+
+ do {
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+
+ if (tok == IMAPX_TOK_ERROR)
+ return FALSE;
+
+ if (tok == IMAPX_TOK_TOKEN || tok == IMAPX_TOK_INT) {
+ gboolean match_found = FALSE;
+ gchar *upper;
+ gint ii;
+
+ upper = g_ascii_strup ((gchar *) token, len);
+
+ for (ii = 0; ii < G_N_ELEMENTS (flag_table); ii++) {
+ if (!strcmp (upper, flag_table[ii].name)) {
+ flags |= flag_table[ii].flag;
+ match_found = TRUE;
+ break;
+ }
+ }
+
+ if (!match_found && user_flagsp != NULL) {
+ const gchar *flag_name;
+ gchar *utf8;
+
+ flag_name = rename_label_flag (
+ (gchar *) token,
+ strlen ((gchar *) token), TRUE);
+
+ utf8 = camel_utf7_utf8 (flag_name);
+ if (utf8 && !g_utf8_validate (utf8, -1, NULL)) {
+ g_free (utf8);
+ utf8 = NULL;
+ }
+
+ camel_flag_set (user_flagsp, utf8 ? utf8 : flag_name, TRUE);
+
+ g_free (utf8);
+ }
+
+ g_free (upper);
+
+ } else if (tok != ')') {
+ gboolean success;
+
+ success = camel_imapx_input_stream_skip_until (
+ stream, ")", cancellable, error);
+ if (!success)
+ return FALSE;
+ }
+ } while (tok != ')');
+
+ *flagsp = flags;
+
+ return TRUE;
+}
+
+/*
+ * rename_flag
+ * Converts label flag name on server to name used in Evolution or back.
+ * if the flags does not match returns the original one as it is.
+ * It will never return NULL, it will return empty string, instead.
+ *
+ * @flag: Flag to rename.
+ * @len: Length of the flag name.
+ * @server_to_evo: if TRUE, then converting server names to evo's names, if FALSE then opposite.
+ */
+static const gchar *
+rename_label_flag (const gchar *flag,
+ gint len,
+ gboolean server_to_evo)
+{
+ gint i;
+ const gchar *labels[] = {
+ "$Label1", "$Labelimportant",
+ "$Label2", "$Labelwork",
+ "$Label3", "$Labelpersonal",
+ "$Label4", "$Labeltodo",
+ "$Label5", "$Labellater",
+ NULL, NULL };
+
+ /* It really can pass zero-length flags inside, in that case it was able
+ * to always add first label, which is definitely wrong. */
+ if (!len || !flag || !*flag)
+ return "";
+
+ for (i = 0 + (server_to_evo ? 0 : 1); labels[i]; i = i + 2) {
+ if (!g_ascii_strncasecmp (flag, labels[i], len))
+ return labels[i + (server_to_evo ? 1 : -1)];
+ }
+
+ return flag;
+}
+
+void
+imapx_write_flags (GString *string,
+ guint32 flags,
+ CamelFlag *user_flags)
+{
+ gint i;
+ gboolean first = TRUE;
+
+ g_string_append_c (string, '(');
+
+ for (i = 0; flags != 0 && i< G_N_ELEMENTS (flag_table); i++) {
+ if (flag_table[i].flag & flags) {
+ if (flag_table[i].flag & CAMEL_IMAPX_MESSAGE_RECENT)
+ continue;
+ if (!first)
+ g_string_append_c (string, ' ');
+ first = FALSE;
+ g_string_append (string, flag_table[i].name);
+
+ flags &= ~flag_table[i].flag;
+ }
+ }
+
+ while (user_flags) {
+ const gchar *flag_name;
+ gchar *utf7;
+
+ flag_name = rename_label_flag (
+ user_flags->name, strlen (user_flags->name), FALSE);
+
+ if (!first)
+ g_string_append_c (string, ' ');
+ first = FALSE;
+
+ utf7 = camel_utf8_utf7 (flag_name);
+
+ g_string_append (string, utf7 ? utf7 : flag_name);
+
+ g_free (utf7);
+
+ user_flags = user_flags->next;
+ }
+
+ g_string_append_c (string, ')');
+}
+
+static gboolean
+imapx_update_user_flags (CamelMessageInfo *info,
+ CamelFlag *server_user_flags)
+{
+ gboolean changed = FALSE;
+ CamelMessageInfoBase *binfo = (CamelMessageInfoBase *) info;
+ CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) info;
+ gboolean set_cal = FALSE, set_note = FALSE;
+
+ if (camel_flag_get (&binfo->user_flags, "$has_cal"))
+ set_cal = TRUE;
+ if (camel_flag_get (&binfo->user_flags, "$has_note"))
+ set_note = TRUE;
+
+ changed = camel_flag_list_copy (&binfo->user_flags, &server_user_flags);
+ camel_flag_list_copy (&xinfo->server_user_flags, &server_user_flags);
+
+ /* reset the flags as they were set in messageinfo before */
+ if (set_cal)
+ camel_flag_set (&binfo->user_flags, "$has_cal", TRUE);
+ if (set_note)
+ camel_flag_set (&binfo->user_flags, "$has_note", TRUE);
+
+ return changed;
+}
+
+gboolean
+imapx_update_message_info_flags (CamelMessageInfo *info,
+ guint32 server_flags,
+ CamelFlag *server_user_flags,
+ guint32 permanent_flags,
+ CamelFolder *folder,
+ gboolean unsolicited)
+{
+ gboolean changed = FALSE;
+ CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) info;
+
+ /* Locally made changes should not be overwritten, it'll be (re)saved later */
+ if ((camel_message_info_get_flags (info) & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0) {
+ d ('?', "Skipping update of locally changed uid:'%s'\n", camel_message_info_get_uid (info));
+ return FALSE;
+ }
+
+ /* This makes sure that server flags has precedence from locally stored flags,
+ * thus a user actually sees what is stored on the server */
+ if ((camel_message_info_get_flags (info) & CAMEL_IMAPX_SERVER_FLAGS) != (server_flags & CAMEL_IMAPX_SERVER_FLAGS)) {
+ xinfo->server_flags = (xinfo->server_flags & ~CAMEL_IMAPX_SERVER_FLAGS) |
+ (camel_message_info_get_flags (info) & CAMEL_IMAPX_SERVER_FLAGS);
+ }
+
+ if (server_flags != xinfo->server_flags) {
+ guint32 server_set, server_cleared;
+
+ server_set = server_flags & ~xinfo->server_flags;
+ server_cleared = xinfo->server_flags & ~server_flags;
+
+ /* Don't clear non-permanent server-side flags.
+ * This avoids overwriting local flags that we
+ * do store permanently, such as junk flags. */
+ if (permanent_flags > 0)
+ server_cleared &= permanent_flags;
+
+ changed = camel_message_info_set_flags ((
+ CamelMessageInfo *) xinfo,
+ server_set | server_cleared,
+ (xinfo->info.flags | server_set) & ~server_cleared);
+
+ xinfo->server_flags = server_flags;
+ xinfo->info.dirty = TRUE;
+ }
+
+ if ((permanent_flags & CAMEL_MESSAGE_USER) != 0 && imapx_update_user_flags (info, server_user_flags))
+ changed = TRUE;
+
+ return changed;
+}
+
+void
+imapx_set_message_info_flags_for_new_message (CamelMessageInfo *info,
+ guint32 server_flags,
+ CamelFlag *server_user_flags,
+ gboolean force_user_flags,
+ CamelTag *user_tags,
+ guint32 permanent_flags)
+{
+ CamelMessageInfoBase *binfo = (CamelMessageInfoBase *) info;
+ CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) info;
+
+ binfo->flags |= server_flags;
+ camel_message_info_set_flags (info, server_flags, binfo->flags | server_flags);
+
+ xinfo->server_flags = server_flags;
+
+ if (force_user_flags || (permanent_flags & CAMEL_MESSAGE_USER) != 0)
+ imapx_update_user_flags (info, server_user_flags);
+
+ while (user_tags) {
+ camel_message_info_set_user_tag (info, user_tags->name, user_tags->value);
+ user_tags = user_tags->next;
+ }
+
+ binfo->flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
+ binfo->dirty = TRUE;
+}
+
+void
+imapx_update_store_summary (CamelFolder *folder)
+{
+ CamelStoreInfo *si;
+ CamelStore *parent_store;
+ CamelIMAPXStore *imapx_store;
+ const gchar *full_name;
+ guint32 total;
+ guint32 unread;
+
+ g_return_if_fail (CAMEL_IS_IMAPX_FOLDER (folder));
+
+ full_name = camel_folder_get_full_name (folder);
+ parent_store = camel_folder_get_parent_store (folder);
+
+ imapx_store = CAMEL_IMAPX_STORE (parent_store);
+
+ si = camel_store_summary_path (imapx_store->summary, full_name);
+ if (si == NULL)
+ return;
+
+ total = camel_folder_summary_count (folder->summary);
+ unread = camel_folder_summary_get_unread_count (folder->summary);
+
+ if (si->unread != unread || si->total != total) {
+ si->unread = unread;
+ si->total = total;
+
+ camel_store_summary_touch (imapx_store->summary);
+ camel_store_summary_save (imapx_store->summary);
+ }
+}
+
+gchar *
+camel_imapx_dup_uid_from_summary_index (CamelFolder *folder,
+ guint summary_index)
+{
+ CamelFolderSummary *summary;
+ GPtrArray *array;
+ gchar *uid = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+ summary = folder->summary;
+ g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
+
+ array = camel_folder_summary_get_array (summary);
+ g_return_val_if_fail (array != NULL, NULL);
+
+ if (summary_index < array->len) {
+ folder = camel_folder_summary_get_folder (summary);
+ camel_folder_sort_uids (folder, array);
+ uid = g_strdup (g_ptr_array_index (array, summary_index));
+ }
+
+ camel_folder_summary_free_array (array);
+
+ return uid;
+}
+
+/*
+ * capability_data ::= "CAPABILITY" SPACE [1#capability SPACE] "IMAP4rev1"
+ * [SPACE 1#capability]
+ * ;; IMAP4rev1 servers which offer RFC 1730
+ * ;; compatibility MUST list "IMAP4" as the first
+ * ;; capability.
+ */
+
+struct {
+ const gchar *name;
+ guint32 flag;
+} capa_table[] = { /* used to create capa_htable only */
+ { "IMAP4", IMAPX_CAPABILITY_IMAP4 },
+ { "IMAP4REV1", IMAPX_CAPABILITY_IMAP4REV1 },
+ { "STATUS", IMAPX_CAPABILITY_STATUS } ,
+ { "NAMESPACE", IMAPX_CAPABILITY_NAMESPACE },
+ { "UIDPLUS", IMAPX_CAPABILITY_UIDPLUS },
+ { "LITERAL+", IMAPX_CAPABILITY_LITERALPLUS },
+ { "STARTTLS", IMAPX_CAPABILITY_STARTTLS },
+ { "IDLE", IMAPX_CAPABILITY_IDLE },
+ { "CONDSTORE", IMAPX_CAPABILITY_CONDSTORE },
+ { "QRESYNC", IMAPX_CAPABILITY_QRESYNC },
+ { "LIST-EXTENDED", IMAPX_CAPABILITY_LIST_EXTENDED },
+ { "LIST-STATUS", IMAPX_CAPABILITY_LIST_STATUS },
+ { "QUOTA", IMAPX_CAPABILITY_QUOTA },
+ { "MOVE", IMAPX_CAPABILITY_MOVE },
+ { "NOTIFY", IMAPX_CAPABILITY_NOTIFY },
+ { "SPECIAL-USE", IMAPX_CAPABILITY_SPECIAL_USE }
+};
+
+static GMutex capa_htable_lock; /* capabilities lookup table lock */
+static GHashTable *capa_htable = NULL; /* capabilities lookup table (extensible) */
+
+static void
+create_initial_capabilities_table (void)
+{
+ gint i = 0;
+
+ /* call within g_init_once() only,
+ * or require table lock
+ */
+
+ /* TODO add imapx_utils_uninit()
+ * to free hash table
+ */
+ capa_htable = g_hash_table_new_full (
+ camel_strcase_hash,
+ camel_strcase_equal,
+ g_free,
+ NULL);
+
+ for (i = 0; i < G_N_ELEMENTS (capa_table); i++) {
+ g_hash_table_insert (
+ capa_htable,
+ g_strdup (capa_table[i].name),
+ GUINT_TO_POINTER (capa_table[i].flag));
+ }
+}
+
+struct _capability_info *
+imapx_parse_capability (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint tok;
+ guint len;
+ guchar *token, *p, c;
+ gboolean free_token = FALSE;
+ struct _capability_info * cinfo;
+ GError *local_error = NULL;
+
+ cinfo = g_malloc0 (sizeof (*cinfo));
+
+ cinfo->auth_types = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, NULL);
+
+ /* FIXME: handle auth types */
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+
+ while (tok != '\n' && local_error == NULL) {
+ switch (tok) {
+ case ']':
+ /* Put it back so that imapx_untagged() isn't unhappy */
+ camel_imapx_input_stream_ungettoken (
+ stream, tok, token, len);
+ break;
+ case 43:
+ /* the CAPABILITY shouldn't start with a '+', ignore it then */
+ if (!token)
+ break;
+ token = (guchar *) g_strconcat ((gchar *) token, "+", NULL);
+ free_token = TRUE;
+ /* coverity[fallthrough] */
+ case IMAPX_TOK_TOKEN:
+ case IMAPX_TOK_STRING:
+ p = token;
+ while ((c = *p))
+ *p++ = toupper(c);
+ if (!strncmp ((gchar *) token, "AUTH=", 5)) {
+ g_hash_table_insert (
+ cinfo->auth_types,
+ g_strdup ((gchar *) token + 5),
+ GINT_TO_POINTER (1));
+ break;
+ }
+ case IMAPX_TOK_INT:
+ cinfo->capa |= imapx_lookup_capability ((gchar *) token);
+ if (free_token) {
+ g_free (token);
+ token = NULL;
+ }
+ free_token = FALSE;
+ break;
+ default:
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "capability: expecting name");
+ break;
+ }
+
+ if (tok == ']')
+ break;
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+ }
+
+ /* Some capabilities are extensions of other capabilities.
+ * Make sure all prerequisite capability flags are present. */
+
+ /* LIST-STATUS is an extension of LIST-EXTENDED. */
+ if (CAMEL_IMAPX_HAVE_CAPABILITY (cinfo, LIST_STATUS))
+ cinfo->capa |= imapx_lookup_capability ("LIST-EXTENDED");
+
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ imapx_free_capability (cinfo);
+ cinfo = NULL;
+ }
+
+ return cinfo;
+}
+
+void imapx_free_capability (struct _capability_info *cinfo)
+{
+ g_hash_table_destroy (cinfo->auth_types);
+ g_free (cinfo);
+}
+
+guint32
+imapx_register_capability (const gchar *capability)
+{
+ guint32 capa_id = 0;
+ guint64 check_id = 0;
+ GList *vals = NULL;
+ GList *tmp_vals = NULL;
+
+ g_return_val_if_fail (capability != NULL, 0);
+
+ g_mutex_lock (&capa_htable_lock);
+
+ /* we rely on IMAP being the first flag, non-zero value
+ * (1 << 0), so we can use GPOINTER_TO_UINT (NULL) as
+ * invalid value
+ */
+ capa_id = GPOINTER_TO_UINT (
+ g_hash_table_lookup (capa_htable, capability));
+ if (capa_id > 0)
+ goto exit;
+
+ /* not yet there, find biggest flag so far */
+ vals = g_hash_table_get_values (capa_htable);
+ tmp_vals = vals;
+ while (tmp_vals != NULL) {
+ guint32 tmp_id = GPOINTER_TO_UINT (tmp_vals->data);
+ if (capa_id < tmp_id)
+ capa_id = tmp_id;
+ tmp_vals = g_list_next (tmp_vals);
+ }
+ g_list_free (vals);
+
+ /* shift-left biggest-so-far, sanity-check */
+ check_id = (capa_id << 1);
+ g_return_val_if_fail (check_id <= (guint64) G_MAXUINT32, 0);
+ capa_id = (guint32) check_id;
+
+ /* insert */
+ g_hash_table_insert (
+ capa_htable,
+ g_strdup (capability),
+ GUINT_TO_POINTER (capa_id));
+
+ exit:
+ g_mutex_unlock (&capa_htable_lock);
+
+ return capa_id;
+}
+
+guint32
+imapx_lookup_capability (const gchar *capability)
+{
+ gpointer data;
+
+ g_return_val_if_fail (capability != NULL, 0);
+
+ g_mutex_lock (&capa_htable_lock);
+
+ data = g_hash_table_lookup (capa_htable, capability);
+
+ g_mutex_unlock (&capa_htable_lock);
+
+ return GPOINTER_TO_UINT (data);
+}
+
+/*
+ * body ::= "(" body_type_1part / body_type_mpart ")"
+ *
+ * body_extension ::= nstring / number / "(" 1#body_extension ")"
+ * ;; Future expansion. Client implementations
+ * ;; MUST accept body_extension fields. Server
+ * ;; implementations MUST NOT generate
+ * ;; body_extension fields except as defined by
+ * ;; future standard or standards-track
+ * ;; revisions of this specification.
+ *
+ * body_ext_1part ::= body_fld_md5[SPACE body_fld_dsp
+ * [SPACE body_fld_lang
+ * [SPACE 1#body_extension]]]
+ * ;; MUST NOT be returned on non-extensible
+ * ;; "BODY" fetch
+ *
+ * body_ext_mpart ::= body_fld_param
+ * [SPACE body_fld_dsp SPACE body_fld_lang
+ * [SPACE 1#body_extension]]
+ * ;; MUST NOT be returned on non-extensible
+ * ;; "BODY" fetch
+ *
+ * body_fields ::= body_fld_param SPACE body_fld_id SPACE
+ * body_fld_desc SPACE body_fld_enc SPACE
+ * body_fld_octets
+ *
+ * body_fld_desc ::= nstring
+ *
+ * body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil
+ *
+ * body_fld_enc ::= (<"> ("7BIT" / "8BIT" / "BINARY" / "BASE64"/
+ * "QUOTED-PRINTABLE") <">) / string
+ *
+ * body_fld_id ::= nstring
+ *
+ * body_fld_lang ::= nstring / "(" 1#string ")"
+ *
+ * body_fld_lines ::= number
+ *
+ * body_fld_md5 ::= nstring
+ *
+ * body_fld_octets ::= number
+ *
+ * body_fld_param ::= "(" 1#(string SPACE string) ")" / nil
+ *
+ * body_type_1part ::= (body_type_basic / body_type_msg / body_type_text)
+ * [SPACE body_ext_1part]
+ *
+ * body_type_basic ::= media_basic SPACE body_fields
+ * ;; MESSAGE subtype MUST NOT be "RFC822"
+ *
+ * body_type_mpart ::= 1*body SPACE media_subtype
+ * [SPACE body_ext_mpart]
+ *
+ * body_type_msg ::= media_message SPACE body_fields SPACE envelope
+ * SPACE body SPACE body_fld_lines
+ *
+ * body_type_text ::= media_text SPACE body_fields SPACE body_fld_lines
+ *
+ * envelope ::= "(" env_date SPACE env_subject SPACE env_from
+ * SPACE env_sender SPACE env_reply_to SPACE env_to
+ * SPACE env_cc SPACE env_bcc SPACE env_in_reply_to
+ * SPACE env_message_id ")"
+ *
+ * env_bcc ::= "(" 1*address ")" / nil
+ *
+ * env_cc ::= "(" 1*address ")" / nil
+ *
+ * env_date ::= nstring
+ *
+ * env_from ::= "(" 1*address ")" / nil
+ *
+ * env_in_reply_to ::= nstring
+ *
+ * env_message_id ::= nstring
+ *
+ * env_reply_to ::= "(" 1*address ")" / nil
+ *
+ * env_sender ::= "(" 1*address ")" / nil
+ *
+ * env_subject ::= nstring
+ *
+ * env_to ::= "(" 1*address ")" / nil
+ *
+ * media_basic ::= (<"> ("APPLICATION" / "AUDIO" / "IMAGE" /
+ * "MESSAGE" / "VIDEO") <">) / string)
+ * SPACE media_subtype
+ * ;; Defined in[MIME-IMT]
+ *
+ * media_message ::= <"> "MESSAGE" <"> SPACE <"> "RFC822" <">
+ * ;; Defined in[MIME-IMT]
+ *
+ * media_subtype ::= string
+ * ;; Defined in[MIME-IMT]
+ *
+ * media_text ::= <"> "TEXT" <"> SPACE media_subtype
+ * ;; Defined in[MIME-IMT]
+ *
+ * ( "type" "subtype" body_fields [envelope body body_fld_lines]
+ * [body_fld_lines]
+ *
+ * (("TEXT" "PLAIN" ("CHARSET"
+ * "US-ASCII") NIL NIL "7BIT" 1152 23)("TEXT" "PLAIN"
+ * ("CHARSET" "US-ASCII" "NAME" "cc.diff")
+ * "<960723163407.20117h@cac.washington.edu>"
+ * "Compiler diff" "BASE64" 4554 73) "MIXED"))
+ *
+ */
+
+/*
+struct _body_fields {
+ CamelContentType *ct;
+ gchar *msgid, *desc;
+ CamelTransferEncoding encoding;
+ guint32 size;
+ };*/
+
+void
+imapx_free_body (struct _CamelMessageContentInfo *cinfo)
+{
+ struct _CamelMessageContentInfo *list, *next;
+
+ list = cinfo->childs;
+ while (list) {
+ next = list->next;
+ imapx_free_body (list);
+ list = next;
+ }
+
+ if (cinfo->type)
+ camel_content_type_unref (cinfo->type);
+ g_free (cinfo->id);
+ g_free (cinfo->description);
+ g_free (cinfo->encoding);
+ g_free (cinfo);
+}
+
+gboolean
+imapx_parse_param_list (CamelIMAPXInputStream *stream,
+ struct _camel_header_param **plist,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint tok;
+ guint len;
+ guchar *token;
+ gchar *param;
+ gsize param_len;
+
+ /* body_fld_param ::= "(" 1#(string SPACE string) ")" / nil */
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, NULL);
+ if (tok == '(') {
+ while (1) {
+ tok = camel_imapx_input_stream_token (stream, &token, &len, cancellable, error);
+
+ if (tok == ')' || tok == IMAPX_TOK_ERROR)
+ break;
+
+ camel_imapx_input_stream_ungettoken (
+ stream, tok, token, len);
+
+ if (!camel_imapx_input_stream_astring (stream, &token, cancellable, error))
+ break;
+
+ param_len = strlen ((gchar *) token) + 1;
+ param = alloca (param_len);
+ g_strlcpy (param, (gchar *) token, param_len);
+
+ if (!camel_imapx_input_stream_astring (stream, &token, cancellable, error))
+ break;
+
+ camel_header_set_param (plist, param, (gchar *) token);
+ }
+ } /* else check nil? no need */
+
+ return TRUE;
+}
+
+struct _CamelContentDisposition *
+imapx_parse_ext_optional (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint tok;
+ guint len;
+ guchar *token;
+ struct _CamelContentDisposition *dinfo = NULL;
+ GError *local_error = NULL;
+
+ /* this parses both extension types, from the body_fld_dsp onwards */
+ /* although the grammars are different, they can be parsed the same way */
+
+ /* body_ext_1part ::= body_fld_md5 [SPACE body_fld_dsp
+ * [SPACE body_fld_lang
+ * [SPACE 1#body_extension]]]
+ * ;; MUST NOT be returned on non-extensible
+ * ;; "BODY" fetch */
+
+ /* body_ext_mpart ::= body_fld_param
+ * [SPACE body_fld_dsp SPACE body_fld_lang
+ * [SPACE 1#body_extension]]
+ * ;; MUST NOT be returned on non-extensible
+ * ;; "BODY" fetch */
+
+ /* body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil */
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, NULL);
+ switch (tok) {
+ case '(':
+ dinfo = g_malloc0 (sizeof (*dinfo));
+ dinfo->refcount = 1;
+ /* should be string */
+ if (!camel_imapx_input_stream_astring (stream, &token, cancellable, &local_error)) {
+ if (!local_error)
+ g_set_error (
+ &local_error,
+ CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "expecting string");
+ goto done;
+ }
+
+ dinfo->disposition = g_strdup ((gchar *) token);
+ imapx_parse_param_list (
+ stream, &dinfo->params, cancellable,
+ &local_error);
+
+ if (local_error != NULL)
+ goto done;
+
+ break;
+ case IMAPX_TOK_TOKEN:
+ break;
+ default:
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "body_fld_disp: expecting nil or list");
+ return NULL;
+ }
+
+ /* body_fld_lang ::= nstring / "(" 1#string ")" */
+
+ /* we just drop the lang string/list, save it somewhere? */
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+
+ switch (tok) {
+ case '(':
+ while (1) {
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len,
+ cancellable, &local_error);
+
+ if (tok == ')') {
+ break;
+ } else if (tok != IMAPX_TOK_STRING) {
+ g_clear_error (&local_error);
+ g_set_error (
+ &local_error,
+ CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "expecting string");
+ break;
+ }
+ }
+ break;
+ case IMAPX_TOK_TOKEN:
+ /* treat as 'nil' */
+ break;
+ case IMAPX_TOK_STRING:
+ /* we have a string */
+ break;
+ case IMAPX_TOK_LITERAL:
+ /* we have a literal string */
+ camel_imapx_input_stream_set_literal (stream, len);
+ while (camel_imapx_input_stream_getl (stream, &token, &len, cancellable, NULL) > 0) {
+ /* Skip over it. */
+ }
+ break;
+
+ }
+
+ done:
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ if (dinfo)
+ camel_content_disposition_unref (dinfo);
+ dinfo = NULL;
+ }
+
+ return dinfo;
+}
+
+struct _CamelMessageContentInfo *
+imapx_parse_body_fields (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guchar *token;
+ gchar *type;
+ gsize type_len;
+ guint64 number;
+ struct _CamelMessageContentInfo *cinfo;
+ gboolean success;
+
+ /* body_fields ::= body_fld_param SPACE body_fld_id SPACE
+ * body_fld_desc SPACE body_fld_enc SPACE
+ * body_fld_octets */
+
+ cinfo = g_malloc0 (sizeof (*cinfo));
+
+ /* this should be string not astring */
+ success = camel_imapx_input_stream_astring (
+ stream, &token, cancellable, error);
+
+ if (!success)
+ goto error;
+
+ type_len = strlen ((gchar *) token) + 1;
+ type = alloca (type_len);
+ g_strlcpy (type, (gchar *) token, type_len);
+
+ success = camel_imapx_input_stream_astring (
+ stream, &token, cancellable, error);
+
+ if (!success)
+ goto error;
+
+ cinfo->type = camel_content_type_new (type, (gchar *) token);
+
+ success = imapx_parse_param_list (
+ stream, &cinfo->type->params, cancellable, error);
+
+ if (!success)
+ goto error;
+
+ /* body_fld_id ::= nstring */
+ success = camel_imapx_input_stream_nstring (
+ stream, &token, cancellable, error);
+
+ if (!success)
+ goto error;
+
+ cinfo->id = g_strdup ((gchar *) token);
+
+ /* body_fld_desc ::= nstring */
+ success = camel_imapx_input_stream_nstring (
+ stream, &token, cancellable, error);
+
+ if (!success)
+ goto error;
+
+ cinfo->description = g_strdup ((gchar *) token);
+
+ /* body_fld_enc ::= (<"> ("7BIT" / "8BIT" / "BINARY" / "BASE64"/
+ * "QUOTED-PRINTABLE") <">) / string */
+ success = camel_imapx_input_stream_astring (
+ stream, &token, cancellable, error);
+
+ if (!success)
+ goto error;
+
+ cinfo->encoding = g_strdup ((gchar *) token);
+
+ /* body_fld_octets ::= number */
+ success = camel_imapx_input_stream_number (
+ stream, &number, cancellable, error);
+
+ if (!success)
+ goto error;
+
+ cinfo->size = number;
+
+ return cinfo;
+
+error:
+ imapx_free_body (cinfo);
+
+ return NULL;
+}
+
+CamelHeaderAddress *
+imapx_parse_address_list (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint tok;
+ guint len;
+ guchar *token, *host;
+ gchar *mbox;
+ CamelHeaderAddress *list = NULL;
+ GError *local_error = NULL;
+
+ /* "(" 1*address ")" / nil */
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+ if (local_error) {
+ g_propagate_error (error, local_error);
+ return NULL;
+ }
+
+ if (tok == '(') {
+ CamelHeaderAddress *addr, *group = NULL;
+ while (1) {
+ /* address ::= "(" addr_name SPACE addr_adl SPACE addr_mailbox
+ * SPACE addr_host ")" */
+ tok = camel_imapx_input_stream_token (stream, &token, &len, cancellable, &local_error);
+
+ if (tok == ')')
+ break;
+ if (tok != '(') {
+ g_clear_error (&local_error);
+ camel_header_address_list_clear (&list);
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "missing '(' for address");
+ return NULL;
+ }
+
+ addr = camel_header_address_new ();
+ addr->type = CAMEL_HEADER_ADDRESS_NAME;
+ camel_imapx_input_stream_nstring (stream, &token, cancellable, &local_error);
+ if (local_error) {
+ camel_header_address_unref (addr);
+ goto error;
+ }
+
+ addr->name = g_strdup ((gchar *) token);
+ /* we ignore the route, nobody uses it in the real world */
+ camel_imapx_input_stream_nstring (stream, &token, cancellable, &local_error);
+ if (local_error) {
+ camel_header_address_unref (addr);
+ goto error;
+ }
+
+ mbox = NULL;
+
+ /* [RFC-822] group syntax is indicated by a special
+ * form of address structure in which the host name
+ * field is NIL. If the mailbox name field is also
+ * NIL, this is an end of group marker (semi-colon in
+ * RFC 822 syntax). If the mailbox name field is
+ * non-NIL, this is a start of group marker, and the
+ * mailbox name field holds the group name phrase. */
+
+ camel_imapx_input_stream_nstring (stream, (guchar **) &mbox, cancellable, &local_error);
+ if (local_error) {
+ camel_header_address_unref (addr);
+ goto error;
+ }
+
+ mbox = g_strdup (mbox);
+
+ camel_imapx_input_stream_nstring (stream, &host, cancellable, &local_error);
+ if (local_error) {
+ camel_header_address_unref (addr);
+ goto error;
+ }
+
+ if (host == NULL) {
+ if (mbox == NULL) {
+ group = NULL;
+ camel_header_address_unref (addr);
+ } else {
+ g_free (addr->name);
+ addr->name = mbox;
+ addr->type = CAMEL_HEADER_ADDRESS_GROUP;
+ camel_header_address_list_append (&list, addr);
+ group = addr;
+ }
+ } else {
+ addr->v.addr = g_strdup_printf (
+ "%s@%s", mbox ? mbox : "",
+ (const gchar *) host);
+ g_free (mbox);
+ if (group != NULL)
+ camel_header_address_add_member (group, addr);
+ else
+ camel_header_address_list_append (&list, addr);
+ }
+ do {
+ tok = camel_imapx_input_stream_token (stream, &token, &len, cancellable, &local_error);
+ if (local_error)
+ goto error;
+ } while (tok != ')' && tok != IMAPX_TOK_ERROR);
+ }
+ }
+
+ error:
+ /* CHEN TODO handle exception at required places */
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ if (list)
+ camel_header_address_list_clear (&list);
+ return NULL;
+ }
+
+ return list;
+}
+
+struct _CamelMessageInfo *
+imapx_parse_envelope (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint tok;
+ guint len;
+ guchar *token;
+ CamelHeaderAddress *addr, *addr_from;
+ gchar *addrstr;
+ struct _CamelMessageInfoBase *minfo = NULL;
+ GError *local_error = NULL;
+
+ /* envelope ::= "(" env_date SPACE env_subject SPACE env_from
+ * SPACE env_sender SPACE env_reply_to SPACE env_to
+ * SPACE env_cc SPACE env_bcc SPACE env_in_reply_to
+ * SPACE env_message_id ")" */
+
+ minfo = (CamelMessageInfoBase *) camel_message_info_new (NULL);
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+
+ if (tok != '(') {
+ g_clear_error (&local_error);
+ camel_message_info_unref (minfo);
+ g_set_error (error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED, "envelope: expecting '('");
+ return NULL;
+ }
+
+ /* env_date ::= nstring */
+ camel_imapx_input_stream_nstring (stream, &token, cancellable, &local_error);
+ if (local_error)
+ goto error;
+
+ minfo->date_sent = camel_header_decode_date ((gchar *) token, NULL);
+
+ /* env_subject ::= nstring */
+ camel_imapx_input_stream_nstring (stream, &token, cancellable, &local_error);
+ if (local_error)
+ goto error;
+
+ minfo->subject = camel_pstring_strdup ((gchar *) token);
+
+ /* we merge from/sender into from, append should probably merge more smartly? */
+
+ /* env_from ::= "(" 1*address ")" / nil */
+ addr_from = imapx_parse_address_list (stream, cancellable, &local_error);
+ if (local_error)
+ goto error;
+
+ /* env_sender ::= "(" 1*address ")" / nil */
+ addr = imapx_parse_address_list (stream, cancellable, &local_error);
+ if (local_error)
+ goto error;
+
+ if (addr_from) {
+ camel_header_address_list_clear (&addr);
+ } else {
+ if (addr)
+ addr_from = addr;
+ }
+
+ if (addr_from) {
+ addrstr = camel_header_address_list_format (addr_from);
+ minfo->from = camel_pstring_strdup (addrstr);
+ g_free (addrstr);
+ camel_header_address_list_clear (&addr_from);
+ }
+
+ /* we dont keep reply_to */
+
+ /* env_reply_to ::= "(" 1*address ")" / nil */
+ addr = imapx_parse_address_list (stream, cancellable, &local_error);
+ camel_header_address_list_clear (&addr);
+
+ if (local_error)
+ goto error;
+
+ /* env_to ::= "(" 1*address ")" / nil */
+ addr = imapx_parse_address_list (stream, cancellable, &local_error);
+ if (addr) {
+ addrstr = camel_header_address_list_format (addr);
+ minfo->to = camel_pstring_strdup (addrstr);
+ g_free (addrstr);
+ camel_header_address_list_clear (&addr);
+ }
+
+ if (local_error)
+ goto error;
+
+ /* env_cc ::= "(" 1*address ")" / nil */
+ addr = imapx_parse_address_list (stream, cancellable, &local_error);
+ if (addr) {
+ addrstr = camel_header_address_list_format (addr);
+ minfo->cc = camel_pstring_strdup (addrstr);
+ g_free (addrstr);
+ camel_header_address_list_clear (&addr);
+ }
+
+ if (local_error)
+ goto error;
+
+ /* we dont keep bcc either */
+
+ /* env_bcc ::= "(" 1*address ")" / nil */
+ addr = imapx_parse_address_list (stream, cancellable, &local_error);
+ camel_header_address_list_clear (&addr);
+
+ if (local_error)
+ goto error;
+
+ /* FIXME: need to put in-reply-to into references hash list */
+
+ /* env_in_reply_to ::= nstring */
+ camel_imapx_input_stream_nstring (stream, &token, cancellable, &local_error);
+ if (local_error)
+ goto error;
+
+ /* FIXME: need to put message-id into message-id hash */
+
+ /* env_message_id ::= nstring */
+ camel_imapx_input_stream_nstring (stream, &token, cancellable, &local_error);
+ if (local_error)
+ goto error;
+
+ tok = camel_imapx_input_stream_token (stream, &token, &len, cancellable, &local_error);
+ if (local_error)
+ goto error;
+
+ if (tok != ')') {
+ g_clear_error (&local_error);
+ camel_message_info_unref (minfo);
+ g_set_error (error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED, "expecting ')'");
+ return NULL;
+ }
+
+ error:
+ /* CHEN TODO handle exceptions better */
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ if (minfo)
+ camel_message_info_unref (minfo);
+ return NULL;
+ }
+
+ return (CamelMessageInfo *) minfo;
+}
+
+struct _CamelMessageContentInfo *
+imapx_parse_body (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint tok;
+ guint len;
+ guchar *token;
+ struct _CamelMessageContentInfo * cinfo = NULL;
+ struct _CamelMessageContentInfo *subinfo, *last;
+ struct _CamelContentDisposition * dinfo = NULL;
+ GError *local_error = NULL;
+
+ /* body ::= "(" body_type_1part / body_type_mpart ")" */
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+ if (tok != '(') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "body: expecting '('");
+ return NULL;
+ }
+
+ if (local_error)
+ goto error;
+
+ /* 1*body (optional for multiparts) */
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+
+ camel_imapx_input_stream_ungettoken (stream, tok, token, len);
+
+ if (tok == '(') {
+ /* body_type_mpart ::= 1*body SPACE media_subtype
+ [SPACE body_ext_mpart] */
+
+ cinfo = g_malloc0 (sizeof (*cinfo));
+ last = (struct _CamelMessageContentInfo *) &cinfo->childs;
+ do {
+ subinfo = imapx_parse_body (stream, cancellable, &local_error);
+ if (local_error)
+ goto error;
+
+ last->next = subinfo;
+ last = subinfo;
+ subinfo->parent = cinfo;
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len,
+ cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+
+ camel_imapx_input_stream_ungettoken (
+ stream, tok, token, len);
+ } while (tok == '(');
+
+ if (!camel_imapx_input_stream_astring (stream, &token, cancellable, &local_error) || local_error)
+ goto error;
+
+ cinfo->type = camel_content_type_new (
+ "multipart", (gchar *) token);
+
+ /* body_ext_mpart ::= body_fld_param
+ * [SPACE body_fld_dsp SPACE body_fld_lang
+ * [SPACE 1#body_extension]]
+ * ;; MUST NOT be returned on non-extensible
+ * ;; "BODY" fetch */
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+
+ camel_imapx_input_stream_ungettoken (stream, tok, token, len);
+
+ if (tok == '(') {
+ imapx_parse_param_list (
+ stream, &cinfo->type->params,
+ cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+
+ /* body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil */
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+
+ camel_imapx_input_stream_ungettoken (
+ stream, tok, token, len);
+
+ if (tok == '(' || tok == IMAPX_TOK_TOKEN) {
+ dinfo = imapx_parse_ext_optional (
+ stream, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+ /* other extension fields?, soaked up below */
+ } else {
+ camel_imapx_input_stream_ungettoken (
+ stream, tok, token, len);
+ }
+ }
+ } else {
+ /* body_type_1part ::= (body_type_basic / body_type_msg / body_type_text)
+ * [SPACE body_ext_1part]
+ *
+ * body_type_basic ::= media_basic SPACE body_fields
+ * body_type_text ::= media_text SPACE body_fields SPACE body_fld_lines
+ * body_type_msg ::= media_message SPACE body_fields SPACE envelope
+ * SPACE body SPACE body_fld_lines */
+
+ cinfo = imapx_parse_body_fields (
+ stream, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+
+ /* do we have an envelope following */
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+
+ camel_imapx_input_stream_ungettoken (stream, tok, token, len);
+ if (tok == '(') {
+ struct _CamelMessageInfo * minfo = NULL;
+
+ /* what do we do with the envelope?? */
+ minfo = imapx_parse_envelope (
+ stream, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+
+ /* what do we do with the message content info?? */
+ //((CamelMessageInfoBase *) minfo)->content = imapx_parse_body (stream);
+ camel_message_info_unref (minfo);
+ minfo = NULL;
+ }
+
+ /* do we have fld_lines following? */
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+
+ if (tok == IMAPX_TOK_INT) {
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+ }
+ camel_imapx_input_stream_ungettoken (stream, tok, token, len);
+
+ /* body_ext_1part ::= body_fld_md5 [SPACE body_fld_dsp
+ [SPACE body_fld_lang
+ [SPACE 1#body_extension]]]
+ * ;; MUST NOT be returned on non - extensible
+ * ;; "BODY" fetch */
+
+ if (tok != ')') {
+ camel_imapx_input_stream_nstring (
+ stream, &token, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+
+ /* body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil */
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+
+ camel_imapx_input_stream_ungettoken (
+ stream, tok, token, len);
+ if (tok == '(' || tok == IMAPX_TOK_TOKEN) {
+ dinfo = imapx_parse_ext_optional (
+ stream, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+ /* then other extension fields, soaked up below */
+ }
+ }
+ }
+
+ /* soak up any other extension fields that may be present */
+ /* there should only be simple tokens, no lists */
+ do {
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, &local_error);
+
+ if (local_error)
+ goto error;
+ } while (tok != ')' && tok != IMAPX_TOK_ERROR);
+
+ error:
+ /* CHEN TODO handle exceptions better */
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ if (cinfo)
+ imapx_free_body (cinfo);
+ if (dinfo)
+ camel_content_disposition_unref (dinfo);
+ return NULL;
+ }
+
+ /* FIXME: do something with the disposition, currently we have no way to pass it out? */
+ if (dinfo)
+ camel_content_disposition_unref (dinfo);
+
+ return cinfo;
+}
+
+gchar *
+imapx_parse_section (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint tok;
+ guint len;
+ guchar *token;
+ gchar * section = NULL;
+
+ /* currently we only return the part within the [section] specifier
+ * any header fields are parsed, but dropped */
+
+ /*
+ * section ::= "[" [section_text /
+ * (nz_number *["." nz_number] ["." (section_text / "MIME")])] "]"
+ *
+ * section_text ::= "HEADER" / "HEADER.FIELDS" [".NOT"]
+ * SPACE header_list / "TEXT"
+ */
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, NULL);
+
+ if (tok != '[') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "section: expecting '['");
+ return NULL;
+ }
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, NULL);
+
+ if (tok == IMAPX_TOK_INT || tok == IMAPX_TOK_TOKEN)
+ section = g_strdup ((gchar *) token);
+ else if (tok == ']') {
+ section = g_strdup ("");
+ camel_imapx_input_stream_ungettoken (stream, tok, token, len);
+ } else {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "section: expecting token");
+ return NULL;
+ }
+
+ /* header_list ::= "(" 1#header_fld_name ")"
+ * header_fld_name ::= astring */
+
+ /* we dont need the header specifiers */
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, NULL);
+
+ if (tok == '(') {
+ do {
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, NULL);
+
+ if (tok == IMAPX_TOK_STRING || tok == IMAPX_TOK_TOKEN || tok == IMAPX_TOK_INT) {
+ /* ?do something? */
+ } else if (tok != ')') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "section: header fields: expecting string");
+ g_free (section);
+ return NULL;
+ }
+ } while (tok != ')');
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, NULL);
+ }
+
+ if (tok != ']') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "section: expecting ']'");
+ g_free (section);
+ return NULL;
+ }
+
+ return section;
+}
+
+static guint64
+imapx_parse_modseq (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint64 modseq = 0;
+ gint tok;
+ guint len;
+ guchar *token;
+ gboolean success;
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+
+ if (tok == IMAPX_TOK_ERROR)
+ return 0;
+
+ if (tok != '(') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "fetch: expecting '('");
+ return 0;
+ }
+
+ success = camel_imapx_input_stream_number (
+ stream, &modseq, cancellable, error);
+
+ if (!success)
+ return 0;
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+
+ if (tok == IMAPX_TOK_ERROR)
+ return 0;
+
+ if (tok != ')') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "fetch: expecting '('");
+ return 0;
+ }
+
+ return modseq;
+}
+
+void
+imapx_free_fetch (struct _fetch_info *finfo)
+{
+ if (finfo == NULL)
+ return;
+
+ if (finfo->body)
+ g_bytes_unref (finfo->body);
+ if (finfo->text)
+ g_bytes_unref (finfo->text);
+ if (finfo->header)
+ g_bytes_unref (finfo->header);
+ if (finfo->minfo)
+ camel_message_info_unref (finfo->minfo);
+ if (finfo->cinfo)
+ imapx_free_body (finfo->cinfo);
+ camel_flag_list_free (&finfo->user_flags);
+ g_free (finfo->date);
+ g_free (finfo->section);
+ g_free (finfo->uid);
+ g_free (finfo);
+}
+
+/* debug, dump one out */
+void
+imapx_dump_fetch (struct _fetch_info *finfo)
+{
+ gconstpointer data;
+ gsize size;
+
+ d ('?', "Fetch info:\n");
+ if (finfo == NULL) {
+ d ('?', "Empty\n");
+ return;
+ }
+
+ /* XXX g_output_stream_write_bytes_all() would be awfully
+ * handy here. g_output_stream_write_bytes() may not
+ * write the entire GBytes. */
+
+ if (finfo->body != NULL) {
+ g_print ("Body content:\n");
+ data = g_bytes_get_data (finfo->body, &size);
+ fwrite (data, sizeof (gchar), size, stdout);
+ }
+
+ if (finfo->text != NULL) {
+ g_print ("Text content:\n");
+ data = g_bytes_get_data (finfo->text, &size);
+ fwrite (data, sizeof (gchar), size, stdout);
+ }
+
+ if (finfo->header != NULL) {
+ g_print ("Header content:\n");
+ data = g_bytes_get_data (finfo->header, &size);
+ fwrite (data, sizeof (gchar), size, stdout);
+ }
+
+ if (finfo->minfo != NULL) {
+ g_print ("Message Info:\n");
+ camel_message_info_dump (finfo->minfo);
+ }
+
+ if (finfo->got & FETCH_SIZE)
+ g_print ("Size: %d\n", (gint) finfo->size);
+
+ if (finfo->got & FETCH_BODY)
+ g_print ("Offset: %d\n", (gint) finfo->offset);
+
+ if (finfo->got & FETCH_FLAGS)
+ g_print ("Flags: %08x\n", (gint) finfo->flags);
+
+ if (finfo->date != NULL)
+ g_print ("Date: '%s'\n", finfo->date);
+
+ if (finfo->section != NULL)
+ g_print ("Section: '%s'\n", finfo->section);
+
+ if (finfo->uid != NULL)
+ g_print ("UID: '%s'\n", finfo->uid);
+}
+
+static gboolean
+imapx_parse_fetch_body (CamelIMAPXInputStream *stream,
+ struct _fetch_info *finfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_imapx_token_t tok;
+ guchar *token;
+ guint len;
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+
+ if (tok == IMAPX_TOK_ERROR)
+ return FALSE;
+
+ camel_imapx_input_stream_ungettoken (stream, tok, token, len);
+
+ if (tok == '(') {
+ finfo->cinfo = imapx_parse_body (stream, cancellable, error);
+
+ if (finfo->cinfo != NULL)
+ finfo->got |= FETCH_CINFO;
+
+ return (finfo->cinfo != NULL);
+ }
+
+ if (tok == '[') {
+ gboolean success;
+
+ finfo->section = imapx_parse_section (
+ stream, cancellable, error);
+
+ if (finfo->section == NULL)
+ return FALSE;
+
+ finfo->got |= FETCH_SECTION;
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+
+ if (tok == IMAPX_TOK_ERROR)
+ return FALSE;
+
+ if (token[0] == '<') {
+ finfo->offset = g_ascii_strtoull (
+ (gchar *) token + 1, NULL, 10);
+ } else {
+ camel_imapx_input_stream_ungettoken (
+ stream, tok, token, len);
+ }
+
+ success = camel_imapx_input_stream_nstring_bytes (
+ stream, &finfo->body, cancellable, error);
+
+ /* Sanity check. */
+ g_return_val_if_fail (
+ (success && (finfo->body != NULL)) ||
+ (!success && (finfo->body == NULL)), FALSE);
+
+ if (success)
+ finfo->got |= FETCH_BODY;
+
+ return success;
+ }
+
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "unknown body response");
+
+ return FALSE;
+}
+
+static gboolean
+imapx_parse_fetch_bodystructure (CamelIMAPXInputStream *stream,
+ struct _fetch_info *finfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ finfo->cinfo = imapx_parse_body (stream, cancellable, error);
+
+ if (finfo->cinfo != NULL)
+ finfo->got |= FETCH_CINFO;
+
+ return (finfo->cinfo != NULL);
+}
+
+static gboolean
+imapx_parse_fetch_envelope (CamelIMAPXInputStream *stream,
+ struct _fetch_info *finfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ finfo->minfo = imapx_parse_envelope (stream, cancellable, error);
+
+ if (finfo->minfo != NULL)
+ finfo->got |= FETCH_MINFO;
+
+ return (finfo->minfo != NULL);
+}
+
+static gboolean
+imapx_parse_fetch_flags (CamelIMAPXInputStream *stream,
+ struct _fetch_info *finfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success;
+
+ success = imapx_parse_flags (
+ stream, &finfo->flags, &finfo->user_flags,
+ cancellable, error);
+
+ if (success)
+ finfo->got |= FETCH_FLAGS;
+
+ return success;
+}
+
+static gboolean
+imapx_parse_fetch_internaldate (CamelIMAPXInputStream *stream,
+ struct _fetch_info *finfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guchar *token;
+ gboolean success;
+
+ success = camel_imapx_input_stream_nstring (
+ stream, &token, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ /* XXX Convert to Camel format? */
+ finfo->date = g_strdup ((gchar *) token);
+ finfo->got |= FETCH_DATE;
+
+ return TRUE;
+}
+
+static gboolean
+imapx_parse_fetch_modseq (CamelIMAPXInputStream *stream,
+ struct _fetch_info *finfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ finfo->modseq = imapx_parse_modseq (stream, cancellable, error);
+
+ if (finfo->modseq > 0)
+ finfo->got |= FETCH_MODSEQ;
+
+ return (finfo->modseq > 0);
+}
+
+static gboolean
+imapx_parse_fetch_rfc822_header (CamelIMAPXInputStream *stream,
+ struct _fetch_info *finfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success;
+
+ success = camel_imapx_input_stream_nstring_bytes (
+ stream, &finfo->header, cancellable, error);
+
+ /* Sanity check. */
+ g_return_val_if_fail (
+ (success && (finfo->header != NULL)) ||
+ (!success && (finfo->header == NULL)), FALSE);
+
+ if (success)
+ finfo->got |= FETCH_HEADER;
+
+ return success;
+}
+
+static gboolean
+imapx_parse_fetch_rfc822_size (CamelIMAPXInputStream *stream,
+ struct _fetch_info *finfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint64 number;
+ gboolean success;
+
+ success = camel_imapx_input_stream_number (
+ stream, &number, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ finfo->size = (guint32) number;
+ finfo->got |= FETCH_SIZE;
+
+ return TRUE;
+}
+
+static gboolean
+imapx_parse_fetch_rfc822_text (CamelIMAPXInputStream *stream,
+ struct _fetch_info *finfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success;
+
+ success = camel_imapx_input_stream_nstring_bytes (
+ stream, &finfo->text, cancellable, error);
+
+ /* Sanity check. */
+ g_return_val_if_fail (
+ (success && (finfo->text != NULL)) ||
+ (!success && (finfo->text == NULL)), FALSE);
+
+ if (success)
+ finfo->got |= FETCH_TEXT;
+
+ return success;
+}
+
+static gboolean
+imapx_parse_fetch_uid (CamelIMAPXInputStream *stream,
+ struct _fetch_info *finfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ camel_imapx_token_t tok;
+ guchar *token;
+ guint len;
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+
+ if (tok == IMAPX_TOK_ERROR)
+ return FALSE;
+
+ if (tok != IMAPX_TOK_INT) {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "uid not integer");
+ return FALSE;
+ }
+
+ finfo->uid = g_strdup ((gchar *) token);
+ finfo->got |= FETCH_UID;
+
+ return TRUE;
+}
+
+struct _fetch_info *
+imapx_parse_fetch (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint tok;
+ guint len;
+ guchar *token, *p, c;
+ struct _fetch_info *finfo;
+
+ finfo = g_malloc0 (sizeof (*finfo));
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+
+ if (tok != '(') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "fetch: expecting '('");
+ goto fail;
+ }
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+
+ while (tok == IMAPX_TOK_TOKEN) {
+ gboolean success = FALSE;
+
+ p = token;
+ while ((c=*p))
+ *p++ = toupper(c);
+
+ switch (imapx_tokenise ((gchar *) token, len)) {
+ case IMAPX_BODY:
+ success = imapx_parse_fetch_body (
+ stream, finfo, cancellable, error);
+ break;
+
+ case IMAPX_BODYSTRUCTURE:
+ success = imapx_parse_fetch_bodystructure (
+ stream, finfo, cancellable, error);
+ break;
+
+ case IMAPX_ENVELOPE:
+ success = imapx_parse_fetch_envelope (
+ stream, finfo, cancellable, error);
+ break;
+
+ case IMAPX_FLAGS:
+ success = imapx_parse_fetch_flags (
+ stream, finfo, cancellable, error);
+ break;
+
+ case IMAPX_INTERNALDATE:
+ success = imapx_parse_fetch_internaldate (
+ stream, finfo, cancellable, error);
+ break;
+
+ case IMAPX_MODSEQ:
+ success = imapx_parse_fetch_modseq (
+ stream, finfo, cancellable, error);
+ break;
+
+ case IMAPX_RFC822_HEADER:
+ success = imapx_parse_fetch_rfc822_header (
+ stream, finfo, cancellable, error);
+ break;
+
+ case IMAPX_RFC822_SIZE:
+ success = imapx_parse_fetch_rfc822_size (
+ stream, finfo, cancellable, error);
+ break;
+
+ case IMAPX_RFC822_TEXT:
+ success = imapx_parse_fetch_rfc822_text (
+ stream, finfo, cancellable, error);
+ break;
+
+ case IMAPX_UID:
+ success = imapx_parse_fetch_uid (
+ stream, finfo, cancellable, error);
+ break;
+
+ default:
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "unknown body response");
+ break;
+ }
+
+ if (!success)
+ goto fail;
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ }
+
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+
+ if (tok != ')') {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "missing closing ')' on fetch response");
+ goto fail;
+ }
+
+ goto exit;
+
+fail:
+ imapx_free_fetch (finfo);
+ finfo = NULL;
+
+exit:
+ return finfo;
+}
+
+GArray *
+imapx_parse_uids (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GArray *array;
+ guchar *token = NULL;
+ gchar **splits;
+ guint len, str_len;
+ gint tok, ii;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (stream), NULL);
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok < 0)
+ return NULL;
+
+ if (!token) {
+ g_set_error (error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_IGNORE, "server response truncated");
+ return NULL;
+ }
+
+ array = g_array_new (FALSE, FALSE, sizeof (guint32));
+ splits = g_strsplit ((gchar *) token, ",", -1);
+ str_len = g_strv_length (splits);
+
+ for (ii = 0; ii < str_len; ii++) {
+ guint32 uid;
+
+ if (g_strstr_len (splits[ii], -1, ":")) {
+ gchar **seq = g_strsplit (splits[ii], ":", -1);
+ guint32 first = strtoul (seq[0], NULL, 10);
+ guint32 last = strtoul (seq[1], NULL, 10);
+
+ for (uid = first; uid <= last; uid++)
+ g_array_append_val (array, uid);
+
+ g_strfreev (seq);
+ } else {
+ uid = strtoul (splits[ii], NULL, 10);
+ g_array_append_val (array, uid);
+ }
+ }
+
+ g_strfreev (splits);
+
+ return array;
+}
+
+static gboolean
+imapx_parse_status_appenduid (CamelIMAPXInputStream *stream,
+ struct _status_info *sinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint64 number;
+ gboolean success;
+
+ success = camel_imapx_input_stream_number (
+ stream, &number, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ sinfo->u.appenduid.uidvalidity = number;
+
+ success = camel_imapx_input_stream_number (
+ stream, &number, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ sinfo->u.appenduid.uid = (guint32) number;
+
+ return TRUE;
+}
+
+static gboolean
+imapx_parse_status_capability (CamelIMAPXInputStream *stream,
+ struct _status_info *sinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ sinfo->u.cinfo = imapx_parse_capability (stream, cancellable, error);
+
+ return (sinfo->u.cinfo != NULL);
+}
+
+static gboolean
+imapx_parse_status_copyuid (CamelIMAPXInputStream *stream,
+ struct _status_info *sinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GArray *uids;
+ guint64 number;
+ gboolean success;
+ GError *local_error = NULL;
+
+ success = camel_imapx_input_stream_number (
+ stream, &number, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ sinfo->u.copyuid.uidvalidity = number;
+
+ uids = imapx_parse_uids (stream, cancellable, &local_error);
+ if (uids == NULL) {
+ /* Some broken servers can return truncated response, like:
+ B00083 OK [COPYUID 4154 ] COPY completed.
+ Just ignore such server error.
+ */
+ if (g_error_matches (local_error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_IGNORE)) {
+ g_clear_error (&local_error);
+ return TRUE;
+ }
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return FALSE;
+ }
+
+ sinfo->u.copyuid.uids = uids;
+
+ uids = imapx_parse_uids (stream, cancellable, error);
+ if (uids == NULL) {
+ if (g_error_matches (local_error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_IGNORE)) {
+ g_clear_error (&local_error);
+ return TRUE;
+ }
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return FALSE;
+ }
+
+ sinfo->u.copyuid.copied_uids = uids;
+
+ return TRUE;
+}
+
+static gboolean
+imapx_parse_status_highestmodseq (CamelIMAPXInputStream *stream,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint64 number;
+ gboolean success;
+
+ success = camel_imapx_input_stream_number (
+ stream, &number, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ camel_imapx_mailbox_set_highestmodseq (mailbox, number);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_parse_status_newname (CamelIMAPXInputStream *stream,
+ struct _status_info *sinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guchar *token;
+ gboolean success;
+
+ /* XXX The RFC doesn't specify the BNF grammer for this. */
+
+ success = camel_imapx_input_stream_astring (
+ stream, &token, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ sinfo->u.newname.oldname = g_strdup ((gchar *) token);
+
+ success = camel_imapx_input_stream_astring (
+ stream, &token, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ sinfo->u.newname.newname = g_strdup ((gchar *) token);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_parse_status_permanentflags (CamelIMAPXInputStream *stream,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint32 flags;
+
+ /* We only care about \* for permanent flags, not user flags. */
+ if (!imapx_parse_flags (stream, &flags, NULL, cancellable, error))
+ return FALSE;
+
+ camel_imapx_mailbox_set_permanentflags (mailbox, flags);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_parse_status_uidnext (CamelIMAPXInputStream *stream,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint64 number;
+ gboolean success;
+
+ success = camel_imapx_input_stream_number (
+ stream, &number, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ camel_imapx_mailbox_set_uidnext (mailbox, (guint32) number);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_parse_status_uidvalidity (CamelIMAPXInputStream *stream,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint64 number;
+ gboolean success;
+
+ success = camel_imapx_input_stream_number (
+ stream, &number, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ camel_imapx_mailbox_set_uidvalidity (mailbox, (guint32) number);
+
+ return TRUE;
+}
+
+static gboolean
+imapx_parse_status_unseen (CamelIMAPXInputStream *stream,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint64 number;
+ gboolean success;
+
+ success = camel_imapx_input_stream_number (
+ stream, &number, cancellable, error);
+
+ if (!success)
+ return FALSE;
+
+ camel_imapx_mailbox_set_unseen (mailbox, (guint32) number);
+
+ return TRUE;
+}
+
+/* rfc 2060 section 7.1 Status Responses */
+/* should this start after [ or before the [? token_unget anyone? */
+struct _status_info *
+imapx_parse_status (CamelIMAPXInputStream *stream,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint tok;
+ guint len;
+ guchar *token;
+ struct _status_info *sinfo;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (stream), NULL);
+
+ success = camel_imapx_input_stream_atom (
+ stream, &token, &len, cancellable, error);
+
+ if (!success)
+ return NULL;
+
+ sinfo = g_malloc0 (sizeof (*sinfo));
+
+ /*
+ * resp_cond_auth ::= ("OK" / "PREAUTH") SPACE resp_text
+ * ;; Authentication condition
+ *
+ * resp_cond_bye ::= "BYE" SPACE resp_text
+ *
+ * resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text
+ * ;; Status condition
+ */
+
+ sinfo->result = imapx_tokenise ((gchar *) token, len);
+ switch (sinfo->result) {
+ case IMAPX_OK:
+ case IMAPX_NO:
+ case IMAPX_BAD:
+ case IMAPX_PREAUTH:
+ case IMAPX_BYE:
+ break;
+ default:
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "expecting OK/NO/BAD");
+ goto fail;
+ }
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+
+ if (tok == '[') {
+ gboolean success;
+
+ success = camel_imapx_input_stream_atom (
+ stream, &token, &len, cancellable, error);
+
+ if (!success)
+ goto fail;
+
+ sinfo->condition = imapx_tokenise ((gchar *) token, len);
+
+ /* parse any details */
+ switch (sinfo->condition) {
+ case IMAPX_READ_ONLY:
+ case IMAPX_READ_WRITE:
+ case IMAPX_ALERT:
+ case IMAPX_PARSE:
+ case IMAPX_TRYCREATE:
+ case IMAPX_CLOSED:
+ break;
+
+ case IMAPX_APPENDUID:
+ success = imapx_parse_status_appenduid (
+ stream, sinfo, cancellable, error);
+ break;
+
+ case IMAPX_CAPABILITY:
+ success = imapx_parse_status_capability (
+ stream, sinfo, cancellable, error);
+ break;
+
+ case IMAPX_COPYUID:
+ success = imapx_parse_status_copyuid (
+ stream, sinfo, cancellable, error);
+ break;
+
+ case IMAPX_HIGHESTMODSEQ:
+ success = imapx_parse_status_highestmodseq (
+ stream, mailbox, cancellable, error);
+ break;
+
+ case IMAPX_NEWNAME:
+ success = imapx_parse_status_newname (
+ stream, sinfo, cancellable, error);
+ break;
+
+ case IMAPX_PERMANENTFLAGS:
+ success = imapx_parse_status_permanentflags (
+ stream, mailbox, cancellable, error);
+ break;
+
+ case IMAPX_UIDNEXT:
+ success = imapx_parse_status_uidnext (
+ stream, mailbox, cancellable, error);
+ break;
+
+ case IMAPX_UIDVALIDITY:
+ success = imapx_parse_status_uidvalidity (
+ stream, mailbox, cancellable, error);
+ break;
+
+ case IMAPX_UNSEEN:
+ success = imapx_parse_status_unseen (
+ stream, mailbox, cancellable, error);
+ break;
+
+ /* RFC 5530 Response Codes */
+ case IMAPX_ALREADYEXISTS:
+ case IMAPX_AUTHENTICATIONFAILED:
+ case IMAPX_AUTHORIZATIONFAILED:
+ case IMAPX_CANNOT:
+ case IMAPX_CLIENTBUG:
+ case IMAPX_CONTACTADMIN:
+ case IMAPX_CORRUPTION:
+ case IMAPX_EXPIRED:
+ case IMAPX_EXPUNGEISSUED:
+ case IMAPX_INUSE:
+ case IMAPX_LIMIT:
+ case IMAPX_NONEXISTENT:
+ case IMAPX_NOPERM:
+ case IMAPX_OVERQUOTA:
+ case IMAPX_PRIVACYREQUIRED:
+ case IMAPX_SERVERBUG:
+ case IMAPX_UNAVAILABLE:
+ break;
+
+ default:
+ sinfo->condition = IMAPX_UNKNOWN;
+ }
+
+ if (!success)
+ goto fail;
+
+ /* ignore anything we dont know about */
+ do {
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, NULL);
+ if (tok == '\n' || tok < 0) {
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "server response truncated");
+ goto fail;
+ }
+ } while (tok != ']');
+ } else {
+ camel_imapx_input_stream_ungettoken (stream, tok, token, len);
+ }
+
+ /* and take the human readable response */
+ success = camel_imapx_input_stream_text (
+ stream, (guchar **) &sinfo->text, cancellable, error);
+
+ if (!success)
+ goto fail;
+
+ if (sinfo->text) {
+ g_strstrip (sinfo->text);
+ }
+
+ goto exit;
+
+fail:
+ imapx_free_status (sinfo);
+ sinfo = NULL;
+
+exit:
+ return sinfo;
+}
+
+struct _status_info *
+imapx_copy_status (struct _status_info *sinfo)
+{
+ struct _status_info *out;
+
+ out = g_malloc (sizeof (*out));
+ memcpy (out, sinfo, sizeof (*out));
+ out->text = g_strdup (out->text);
+ if (out->condition == IMAPX_NEWNAME) {
+ out->u.newname.oldname = g_strdup (out->u.newname.oldname);
+ out->u.newname.newname = g_strdup (out->u.newname.newname);
+ }
+
+ return out;
+}
+
+void
+imapx_free_status (struct _status_info *sinfo)
+{
+ if (sinfo == NULL)
+ return;
+
+ switch (sinfo->condition) {
+ case IMAPX_NEWNAME:
+ g_free (sinfo->u.newname.oldname);
+ g_free (sinfo->u.newname.newname);
+ break;
+ case IMAPX_COPYUID:
+ g_array_free (sinfo->u.copyuid.uids, TRUE);
+ g_array_free (sinfo->u.copyuid.copied_uids, TRUE);
+ break;
+ case IMAPX_CAPABILITY:
+ if (sinfo->u.cinfo)
+ imapx_free_capability (sinfo->u.cinfo);
+ break;
+ default:
+ break;
+ }
+
+ g_free (sinfo->text);
+ g_free (sinfo);
+}
+
+gboolean
+camel_imapx_command_add_qresync_parameter (CamelIMAPXCommand *ic,
+ CamelFolder *folder)
+{
+ /* See RFC 5162 Section 3.1 */
+
+ CamelIMAPXFolder *imapx_folder;
+ CamelIMAPXSummary *imapx_summary;
+ CamelIMAPXMailbox *mailbox;
+ guint64 last_known_uidvalidity;
+ guint64 last_known_modsequence;
+ guint32 last_known_message_cnt;
+ guint32 sequence_limit;
+ gchar *known_uid_set = NULL;
+ gint summary_total;
+ gboolean parameter_added = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_COMMAND (ic), FALSE);
+ g_return_val_if_fail (CAMEL_IS_IMAPX_FOLDER (folder), FALSE);
+
+ imapx_folder = CAMEL_IMAPX_FOLDER (folder);
+ imapx_summary = CAMEL_IMAPX_SUMMARY (folder->summary);
+
+ mailbox = camel_imapx_folder_ref_mailbox (imapx_folder);
+ if (!mailbox)
+ return FALSE;
+
+ last_known_uidvalidity = camel_imapx_mailbox_get_uidvalidity (mailbox);
+ last_known_modsequence = imapx_summary->modseq;
+ last_known_message_cnt = camel_imapx_mailbox_get_messages (mailbox);
+
+ /* XXX This should return an unsigned integer to
+ * avoid the possibility of a negative count. */
+ summary_total = camel_folder_summary_count (folder->summary);
+ g_return_val_if_fail (summary_total >= 0, FALSE);
+
+ if (summary_total > 0) {
+ guint last = summary_total - 1;
+ gchar *begin, *end;
+
+ begin = camel_imapx_dup_uid_from_summary_index (folder, 0);
+ end = camel_imapx_dup_uid_from_summary_index (folder, last);
+
+ if (begin != NULL && end != NULL)
+ known_uid_set = g_strconcat (begin, ":", end, NULL);
+
+ g_free (begin);
+ g_free (end);
+ }
+
+ /* Make sure we have valid QRESYNC arguments. */
+
+ if (last_known_uidvalidity == 0)
+ goto exit;
+
+ if (last_known_modsequence == 0)
+ goto exit;
+
+ if (known_uid_set == NULL)
+ goto exit;
+
+ camel_imapx_command_add (
+ ic, " (QRESYNC (%"
+ G_GUINT64_FORMAT " %"
+ G_GUINT64_FORMAT " %s",
+ last_known_uidvalidity,
+ last_known_modsequence,
+ known_uid_set);
+
+ /* Add message sequence match data if we have enough messages. */
+
+ /* XXX Some IMAP servers like Zimbra can't handle invalid sequence
+ * numbers in the optional seq/uid list. So limit the list to
+ * the lesser of the last known message count according to the
+ * server and our own summary count. */
+ sequence_limit = MIN (last_known_message_cnt, summary_total);
+
+ if (sequence_limit > 10) {
+ GString *seqs;
+ GString *uids;
+ guint32 ii = 3;
+
+ seqs = g_string_sized_new (256);
+ uids = g_string_sized_new (256);
+
+ /* Include some seq/uid pairs to avoid a huge VANISHED list.
+ * Work backwards exponentially from the end of the mailbox,
+ * starting with message 9 from the end, then 27 from the
+ * end, then 81 from the end, etc. */
+ do {
+ guint32 summary_index;
+ gchar buf[10];
+ gchar *uid;
+
+ ii = MIN (ii * 3, sequence_limit);
+ summary_index = sequence_limit - ii;
+
+ if (seqs->len > 0)
+ g_string_prepend_c (seqs, ',');
+
+ if (uids->len > 0)
+ g_string_prepend_c (uids, ',');
+
+ /* IMAP sequence numbers are 1-based,
+ * but our folder summary is 0-based. */
+ g_snprintf (
+ buf, sizeof (buf),
+ "%" G_GUINT32_FORMAT,
+ summary_index + 1);
+
+ uid = camel_imapx_dup_uid_from_summary_index (
+ folder, summary_index);
+ if (uid != NULL) {
+ g_string_prepend (seqs, buf);
+ g_string_prepend (uids, uid);
+ g_free (uid);
+ }
+ } while (ii < sequence_limit);
+
+ camel_imapx_command_add (
+ ic, " (%s %s)", seqs->str, uids->str);
+
+ g_string_free (seqs, TRUE);
+ g_string_free (uids, TRUE);
+ }
+
+ camel_imapx_command_add (ic, "))");
+
+ parameter_added = TRUE;
+
+exit:
+ g_free (known_uid_set);
+
+ g_object_unref (mailbox);
+
+ return parameter_added;
+}
+
+/**
+ * camel_imapx_parse_mailbox:
+ * @stream: a #CamelIMAPXInputStream
+ * @separator: the mailbox separator character
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Parses a "mailbox" token from @stream, with the special case for INBOX as
+ * described in <ulink url="http://tools.ietf.org/html/rfc3501#section-5.1">
+ * RFC 3501 section 5.1</ulink>.
+ *
+ * The @separator character is used to identify INBOX and convert its name
+ * to all caps, both for INBOX itself and its descendants. If a separator
+ * character was provided in the server response being parsed (such as for
+ * LIST or LSUB), pass that for @separator. If no separator character was
+ * provided in the server response being parsed (such as for STATUS), then
+ * pass the separator character specifically for INBOX.
+ *
+ * If an error occurs, the function sets @error and returns %NULL.
+ *
+ * Returns: a newly-allocated mailbox name, or %NULL
+ *
+ * Since: 3.10
+ **/
+gchar *
+camel_imapx_parse_mailbox (CamelIMAPXInputStream *stream,
+ gchar separator,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guchar *token;
+ gchar *mailbox_name;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (stream), NULL);
+
+ success = camel_imapx_input_stream_astring (
+ stream, &token, cancellable, error);
+
+ if (!success)
+ return NULL;
+
+ mailbox_name = camel_utf7_utf8 ((gchar *) token);
+
+ camel_imapx_normalize_mailbox (mailbox_name, separator);
+
+ return mailbox_name;
+}
+
+/**
+ * camel_imapx_normalize_mailbox:
+ * @mailbox_name: a mailbox name
+ * @separator: a mailbox separator character
+ *
+ * Converts the special INBOX mailbox to all caps, if it appears at the
+ * beginning of @mailbox_name. The @mailbox_name string is modified in
+ * place. The @separator character helps reliably identify descendants
+ * of INBOX.
+ *
+ * Since: 3.10
+ **/
+void
+camel_imapx_normalize_mailbox (gchar *mailbox_name,
+ gchar separator)
+{
+ gboolean normalize_inbox;
+
+ g_return_if_fail (mailbox_name != NULL);
+
+ /* mailbox ::= "INBOX" / astring
+ * INBOX is case-insensitive. All case variants of
+ * INBOX (e.g., "iNbOx") MUST be interpreted as INBOX
+ * not as an astring. An astring which consists of
+ * the case-insensitive sequence "I" "N" "B" "O" "X"
+ * is considered to be INBOX and not an astring.
+ */
+
+ normalize_inbox =
+ (g_ascii_strncasecmp (mailbox_name, "INBOX", 5) == 0) &&
+ (mailbox_name[5] == separator || mailbox_name[5] == '\0');
+
+ if (normalize_inbox) {
+ mailbox_name[0] = 'I';
+ mailbox_name[1] = 'N';
+ mailbox_name[2] = 'B';
+ mailbox_name[3] = 'O';
+ mailbox_name[4] = 'X';
+ }
+}
+
+/**
+ * camel_imapx_mailbox_is_inbox:
+ * @mailbox_name: a mailbox name
+ *
+ * Returns whether @mailbox_name is the special mailbox INBOX.
+ *
+ * The function just performs a case-insensitive string comparsion; it's
+ * more for readability.
+ *
+ * Returns: %TRUE if @mailbox_name is INBOX, %FALSE if not
+ *
+ * Since: 3.10
+ **/
+gboolean
+camel_imapx_mailbox_is_inbox (const gchar *mailbox_name)
+{
+ g_return_val_if_fail (mailbox_name != NULL, FALSE);
+
+ return (g_ascii_strcasecmp (mailbox_name, "INBOX") == 0);
+}
+
+/**
+ * camel_imapx_mailbox_to_folder_path:
+ * @mailbox_name: a mailbox name
+ * @separator: mailbox separator character
+ *
+ * Converts @mailbox_name to a Camel folder path, which just replaces all
+ * @separator characters with '/'. If '/' appears in @mailbox_name, it is
+ * replaced with @separator. Free the returned string with g_free().
+ *
+ * Returns: a newly-allocated Camel folder path
+ *
+ * Since: 3.10
+ **/
+gchar *
+camel_imapx_mailbox_to_folder_path (const gchar *mailbox_name,
+ gchar separator)
+{
+ gchar *folder_path;
+
+ g_return_val_if_fail (mailbox_name != NULL, NULL);
+
+ folder_path = g_strdup (mailbox_name);
+
+ if (separator != '\0' && separator != '/') {
+ gchar *cp = folder_path;
+
+ while (*cp != '\0') {
+ if (*cp == '/')
+ *cp = separator;
+ else if (*cp == separator)
+ *cp = '/';
+ cp++;
+ }
+ }
+
+ return folder_path;
+}
+
+/**
+ * camel_imapx_folder_path_to_mailbox:
+ * @folder_path: a Camel folder path
+ * @separator: mailbox separator character
+ *
+ * Converts @folder_path to an IMAP mailbox name, which just replaces
+ * all slash ('/') characters with @separator. If @separator appears
+ * in @folder_path, it is replaced with '/'. Free the returned string
+ * with g_free().
+ *
+ * Returns: a newly-allocated IMAP mailbox name
+ *
+ * Since: 3.10
+ **/
+gchar *
+camel_imapx_folder_path_to_mailbox (const gchar *folder_path,
+ gchar separator)
+{
+ g_return_val_if_fail (folder_path != NULL, NULL);
+
+ /* XXX For now, all we're really doing in these conversions is
+ * flip-flopping separator characters. So we can just call
+ * camel_imapx_mailbox_to_folder_path() on a folder path to
+ * get the mailbox name. But it is better to have separate
+ * functions: 1) for readability, and 2) so we don't become
+ * too dependent on this flip-flopping behavior. */
+ return camel_imapx_mailbox_to_folder_path (folder_path, separator);
+}
+
+gboolean
+camel_imapx_parse_quota (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ gchar **out_quota_root_name,
+ CamelFolderQuotaInfo **out_quota_info,
+ GError **error)
+{
+ GQueue queue = G_QUEUE_INIT;
+ CamelFolderQuotaInfo *info;
+ CamelFolderQuotaInfo *next;
+ gint tok;
+ guint len;
+ guchar *token;
+ gchar *quota_root_name = NULL;
+ gchar *resource_name = NULL;
+ guint64 resource_usage;
+ guint64 resource_limit;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (stream), FALSE);
+ g_return_val_if_fail (out_quota_root_name != NULL, FALSE);
+ g_return_val_if_fail (out_quota_info != NULL, FALSE);
+
+ /* quota_response ::= "QUOTA" SP astring SP quota_list
+ * quota_list ::= "(" *quota_resource ")"
+ * quota_resource ::= atom SP number SP number */
+
+ success = camel_imapx_input_stream_astring (
+ stream, &token, cancellable, error);
+
+ if (!success)
+ goto fail;
+
+ quota_root_name = g_strdup ((gchar *) token);
+
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ switch (tok) {
+ case IMAPX_TOK_ERROR:
+ goto fail;
+ case '(':
+ break;
+ default:
+ g_set_error (
+ error, CAMEL_IMAPX_ERROR, CAMEL_IMAPX_ERROR_SERVER_RESPONSE_MALFORMED,
+ "quota_response: expecting '('");
+ goto fail;
+ }
+
+ while (TRUE) {
+ /* Peek at the next token, and break
+ * out of the loop if we get a close-paren. */
+ tok = camel_imapx_input_stream_token (
+ stream, &token, &len, cancellable, error);
+ if (tok == ')')
+ break;
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+ camel_imapx_input_stream_ungettoken (
+ stream, tok, token, len);
+
+ success = camel_imapx_input_stream_atom (
+ stream, &token, &len, cancellable, error);
+
+ if (!success)
+ goto fail;
+
+ resource_name = g_strdup ((gchar *) token);
+
+ success = camel_imapx_input_stream_number (
+ stream, &resource_usage, cancellable, error);
+
+ if (!success)
+ goto fail;
+
+ success = camel_imapx_input_stream_number (
+ stream, &resource_limit, cancellable, error);
+
+ if (!success)
+ goto fail;
+
+ info = camel_folder_quota_info_new (
+ resource_name, resource_usage, resource_limit);
+ g_queue_push_tail (&queue, info);
+
+ g_free (resource_name);
+ resource_name = NULL;
+ }
+
+ /* Eat the newline. */
+ success = camel_imapx_input_stream_skip (
+ stream, cancellable, error);
+
+ if (!success)
+ goto fail;
+
+ /* String together all the CamelFolderQuotaInfo structs. */
+
+ info = next = NULL;
+
+ while (!g_queue_is_empty (&queue)) {
+ info = g_queue_pop_tail (&queue);
+ info->next = next;
+ next = info;
+ }
+
+ *out_quota_root_name = quota_root_name;
+ *out_quota_info = info;
+
+ return TRUE;
+
+fail:
+ g_free (quota_root_name);
+ g_free (resource_name);
+
+ while (!g_queue_is_empty (&queue)) {
+ info = g_queue_pop_head (&queue);
+ camel_folder_quota_info_free (info);
+ }
+
+ return FALSE;
+}
+
+gboolean
+camel_imapx_parse_quotaroot (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ gchar **out_mailbox_name,
+ gchar ***out_quota_roots,
+ GError **error)
+{
+ GQueue queue = G_QUEUE_INIT;
+ gint tok;
+ guint len;
+ guchar *token;
+ gchar *mailbox_name = NULL;
+ gchar **quota_roots = NULL;
+ gboolean success;
+ gint ii = 0;
+
+ g_return_val_if_fail (CAMEL_IS_IMAPX_INPUT_STREAM (stream), FALSE);
+ g_return_val_if_fail (out_mailbox_name != NULL, FALSE);
+ g_return_val_if_fail (out_quota_roots != NULL, FALSE);
+
+ /* quotaroot_response ::= "QUOTAROOT" SP astring *(SP astring) */
+
+ success = camel_imapx_input_stream_astring (
+ CAMEL_IMAPX_INPUT_STREAM (stream),
+ &token, cancellable, error);
+
+ if (!success)
+ goto fail;
+
+ mailbox_name = camel_utf7_utf8 ((gchar *) token);
+
+ while (TRUE) {
+ /* Peek at the next token, and break
+ * out of the loop if we get a newline. */
+ tok = camel_imapx_input_stream_token (
+ CAMEL_IMAPX_INPUT_STREAM (stream),
+ &token, &len, cancellable, error);
+ if (tok == '\n')
+ break;
+ if (tok == IMAPX_TOK_ERROR)
+ goto fail;
+ camel_imapx_input_stream_ungettoken (
+ CAMEL_IMAPX_INPUT_STREAM (stream),
+ tok, token, len);
+
+ success = camel_imapx_input_stream_astring (
+ CAMEL_IMAPX_INPUT_STREAM (stream),
+ &token, cancellable, error);
+
+ if (!success)
+ goto fail;
+
+ g_queue_push_tail (&queue, g_strdup ((gchar *) token));
+ }
+
+ quota_roots = g_new0 (gchar *, queue.length + 1);
+ while (!g_queue_is_empty (&queue))
+ quota_roots[ii++] = g_queue_pop_head (&queue);
+
+ *out_mailbox_name = mailbox_name;
+ *out_quota_roots = quota_roots;
+
+ return TRUE;
+
+fail:
+ g_free (mailbox_name);
+
+ while (!g_queue_is_empty (&queue))
+ g_free (g_queue_pop_head (&queue));
+
+ return FALSE;
+}
+
+/* ********************************************************************** */
+
+/*
+ * From rfc2060
+ *
+ * ATOM_CHAR ::= <any CHAR except atom_specials>
+ *
+ * atom_specials ::= "(" / ")" / "{" / SPACE / CTL / list_wildcards /
+ * quoted_specials
+ *
+ * CHAR ::= <any 7 - bit US - ASCII character except NUL,
+ * 0x01 - 0x7f>
+ *
+ * CTL ::= <any ASCII control character and DEL,
+ * 0x00 - 0x1f, 0x7f>
+ *
+ * SPACE ::= <ASCII SP, space, 0x20>
+ *
+ * list_wildcards ::= "%" / "*"
+ *
+ * quoted_specials ::= <"> / "\"
+ *
+ * string ::= quoted / literal
+ *
+ * literal ::= "{" number "}" CRLF *CHAR8
+ * ;; Number represents the number of CHAR8 octets
+ *
+ * quoted ::= <"> *QUOTED_CHAR <">
+ *
+ * QUOTED_CHAR ::= <any TEXT_CHAR except quoted_specials> /
+ * "\" quoted_specials
+ *
+ * TEXT_CHAR ::= <any CHAR except CR and LF>
+ */
+
+/*
+ * ATOM = 1
+ * SIMPLE? = 2
+ * NOTID? = 4
+ *
+ * QSPECIAL = 8
+ */
+
+guchar imapx_specials[256] = {
+/* 00 */0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 4, 0, 0,
+/* 10 */0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 20 */4, 1, 0, 1, 1, 0, 1, 1, 0, 0, 2, 7, 1, 1, 1, 1,
+/* 30 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+/* 40 */7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+/* 50 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 0, 7, 1, 1,
+/* 60 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+/* 70 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+#define list_wildcards "*%"
+#define quoted_specials "\\\""
+#define atom_specials "(){" list_wildcards quoted_specials /* + CTL */
+
+/* special types for the tokeniser, come out as raw tokens */
+#define token_specials "\n*()[]+"
+#define notid_specials "\x20\r\n()[]"
+
+void
+imapx_utils_init (void)
+{
+ static gsize imapx_utils_initialized = 0;
+
+ if (g_once_init_enter (&imapx_utils_initialized)) {
+ gint i;
+ guchar v;
+
+ for (i = 0; i < 128; i++) {
+ v = 0;
+ if (i >= 1 && i <= 0x7f) {
+ v |= IMAPX_TYPE_CHAR;
+ if (i != 0x0a && i != 0x0d) {
+ v |= IMAPX_TYPE_TEXT_CHAR;
+ if (i != '"' && i != '\\')
+ v |= IMAPX_TYPE_QUOTED_CHAR;
+ }
+ if (i> 0x20 && i <0x7f && strchr (atom_specials, i) == NULL)
+ v |= IMAPX_TYPE_ATOM_CHAR;
+ if (strchr (token_specials, i) != NULL)
+ v |= IMAPX_TYPE_TOKEN_CHAR;
+ if (strchr (notid_specials, i) != NULL)
+ v |= IMAPX_TYPE_NOTID_CHAR;
+ }
+
+ imapx_specials[i] = v;
+ }
+
+ create_initial_capabilities_table ();
+ camel_imapx_set_debug_flags ();
+
+ g_once_init_leave (&imapx_utils_initialized, 1);
+ }
+}
+
+guchar
+imapx_is_mask (const gchar *p)
+{
+ guchar v = 0xff;
+
+ while (*p) {
+ v &= imapx_specials[((guchar) * p) & 0xff];
+ p++;
+ }
+
+ return v;
+}
+
+gchar *
+imapx_path_to_physical (const gchar *prefix,
+ const gchar *vpath)
+{
+ GString *out = g_string_new (prefix);
+ const gchar *p = vpath;
+ gchar c, *res;
+
+ g_string_append_c (out, '/');
+ p = vpath;
+ while ((c = *p++)) {
+ if (c == '/') {
+ g_string_append (out, "/" SUBFOLDER_DIR_NAME "/");
+ while (*p == '/')
+ p++;
+ } else
+ g_string_append_c (out, c);
+ }
+
+ res = out->str;
+ g_string_free (out, FALSE);
+
+ return res;
+}
+
+gchar *
+imapx_get_temp_uid (void)
+{
+ gchar *res;
+
+ static gint counter = 0;
+ G_LOCK_DEFINE_STATIC (lock);
+
+ G_LOCK (lock);
+ res = g_strdup_printf (
+ "tempuid-%lx-%d",
+ (gulong) time (NULL),
+ counter++);
+ G_UNLOCK (lock);
+
+ return res;
+}
+
+gboolean
+imapx_util_all_is_ascii (const gchar *str)
+{
+ gint ii;
+ gboolean all_ascii = TRUE;
+
+ g_return_val_if_fail (str != NULL, FALSE);
+
+ for (ii = 0; str[ii] && all_ascii; ii++) {
+ all_ascii = str[ii] > 0;
+ }
+
+ return all_ascii;
+}
diff --git a/src/camel/providers/imapx/camel-imapx-utils.h b/src/camel/providers/imapx/camel-imapx-utils.h
new file mode 100644
index 000000000..7fc055b1c
--- /dev/null
+++ b/src/camel/providers/imapx/camel-imapx-utils.h
@@ -0,0 +1,401 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_IMAPX_UTILS_H
+#define CAMEL_IMAPX_UTILS_H
+
+#include <camel/camel.h>
+
+#include "camel-imapx-input-stream.h"
+#include "camel-imapx-mailbox.h"
+
+G_BEGIN_DECLS
+
+/* FIXME Split off a camel-imapx-types.h file with supplemental
+ * enum/struct definitions and helper macros, so we don't
+ * have these conflicting header dependencies. */
+struct _CamelIMAPXCommand;
+struct _CamelFlag;
+struct _CamelIMAPXStore;
+
+/* list of strings we know about that can be *quickly* tokenised */
+typedef enum _camel_imapx_id_t {
+ IMAPX_UNKNOWN = 0,
+
+ IMAPX_ALERT,
+ IMAPX_APPENDUID,
+ IMAPX_BAD,
+ IMAPX_BODY,
+ IMAPX_BODYSTRUCTURE,
+ IMAPX_BYE,
+ IMAPX_CAPABILITY,
+ IMAPX_CLOSED,
+ IMAPX_COPYUID,
+ IMAPX_ENVELOPE,
+ IMAPX_EXISTS,
+ IMAPX_EXPUNGE,
+ IMAPX_FETCH,
+ IMAPX_FLAGS,
+ IMAPX_HIGHESTMODSEQ,
+ IMAPX_INTERNALDATE,
+ IMAPX_LIST,
+ IMAPX_LSUB,
+ IMAPX_MESSAGES,
+ IMAPX_MODSEQ,
+ IMAPX_NAMESPACE,
+ IMAPX_NEWNAME,
+ IMAPX_NO,
+ IMAPX_NOMODSEQ,
+ IMAPX_OK,
+ IMAPX_PARSE,
+ IMAPX_PERMANENTFLAGS,
+ IMAPX_PREAUTH,
+ IMAPX_READ_ONLY,
+ IMAPX_READ_WRITE,
+ IMAPX_RECENT,
+ IMAPX_RFC822_HEADER,
+ IMAPX_RFC822_SIZE,
+ IMAPX_RFC822_TEXT,
+ IMAPX_STATUS,
+ IMAPX_TRYCREATE,
+ IMAPX_UID,
+ IMAPX_UIDVALIDITY,
+ IMAPX_UNSEEN,
+ IMAPX_UIDNEXT,
+ IMAPX_VANISHED,
+
+ /* RFC 5530: IMAP Response Codes */
+ IMAPX_ALREADYEXISTS,
+ IMAPX_AUTHENTICATIONFAILED,
+ IMAPX_AUTHORIZATIONFAILED,
+ IMAPX_CANNOT,
+ IMAPX_CLIENTBUG,
+ IMAPX_CONTACTADMIN,
+ IMAPX_CORRUPTION,
+ IMAPX_EXPIRED,
+ IMAPX_EXPUNGEISSUED,
+ IMAPX_INUSE,
+ IMAPX_LIMIT,
+ IMAPX_NONEXISTENT,
+ IMAPX_NOPERM,
+ IMAPX_OVERQUOTA,
+ IMAPX_PRIVACYREQUIRED,
+ IMAPX_SERVERBUG,
+ IMAPX_UNAVAILABLE
+} camel_imapx_id_t;
+
+#define CAMEL_IMAPX_UNTAGGED_BAD "BAD"
+#define CAMEL_IMAPX_UNTAGGED_BYE "BYE"
+#define CAMEL_IMAPX_UNTAGGED_CAPABILITY "CAPABILITY"
+#define CAMEL_IMAPX_UNTAGGED_EXISTS "EXISTS"
+#define CAMEL_IMAPX_UNTAGGED_EXPUNGE "EXPUNGE"
+#define CAMEL_IMAPX_UNTAGGED_FETCH "FETCH"
+#define CAMEL_IMAPX_UNTAGGED_FLAGS "FLAGS"
+#define CAMEL_IMAPX_UNTAGGED_LIST "LIST"
+#define CAMEL_IMAPX_UNTAGGED_LSUB "LSUB"
+#define CAMEL_IMAPX_UNTAGGED_NAMESPACE "NAMESPACE"
+#define CAMEL_IMAPX_UNTAGGED_NO "NO"
+#define CAMEL_IMAPX_UNTAGGED_OK "OK"
+#define CAMEL_IMAPX_UNTAGGED_PREAUTH "PREAUTH"
+#define CAMEL_IMAPX_UNTAGGED_QUOTA "QUOTA"
+#define CAMEL_IMAPX_UNTAGGED_QUOTAROOT "QUOTAROOT"
+#define CAMEL_IMAPX_UNTAGGED_RECENT "RECENT"
+#define CAMEL_IMAPX_UNTAGGED_SEARCH "SEARCH"
+#define CAMEL_IMAPX_UNTAGGED_STATUS "STATUS"
+#define CAMEL_IMAPX_UNTAGGED_VANISHED "VANISHED"
+
+/* str MUST be in upper case, tokenised using gperf function */
+camel_imapx_id_t
+ imapx_tokenise (register const gchar *str,
+ register guint len);
+
+/* this flag should be part of imapfoldersummary */
+enum {
+ CAMEL_IMAPX_MESSAGE_RECENT = (1 << 21),
+};
+
+/* ********************************************************************** */
+
+GArray * imapx_parse_uids (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+gboolean imapx_parse_flags (CamelIMAPXInputStream *stream,
+ guint32 *flagsp,
+ struct _CamelFlag **user_flagsp,
+ GCancellable *cancellable,
+ GError **error);
+void imapx_write_flags (GString *string,
+ guint32 flags,
+ struct _CamelFlag *user_flags);
+gboolean imapx_update_message_info_flags (CamelMessageInfo *info,
+ guint32 server_flags,
+ CamelFlag *server_user_flags,
+ guint32 permanent_flags,
+ CamelFolder *folder,
+ gboolean unsolicited);
+void imapx_set_message_info_flags_for_new_message
+ (CamelMessageInfo *info,
+ guint32 server_flags,
+ CamelFlag *server_user_flags,
+ gboolean force_user_flags,
+ CamelTag *user_tags,
+ guint32 permanent_flags);
+void imapx_update_store_summary (CamelFolder *folder);
+
+gchar * camel_imapx_dup_uid_from_summary_index
+ (CamelFolder *folder,
+ guint summary_index);
+
+/* ********************************************************************** */
+
+/* Handy server capability test macros.
+ * Both return FALSE if capabilities are unknown. */
+#define CAMEL_IMAPX_HAVE_CAPABILITY(info, name) \
+ ((info) != NULL && ((info)->capa & IMAPX_CAPABILITY_##name) != 0)
+#define CAMEL_IMAPX_LACK_CAPABILITY(info, name) \
+ ((info) != NULL && ((info)->capa & IMAPX_CAPABILITY_##name) == 0)
+
+enum {
+ IMAPX_CAPABILITY_IMAP4 = (1 << 0),
+ IMAPX_CAPABILITY_IMAP4REV1 = (1 << 1),
+ IMAPX_CAPABILITY_STATUS = (1 << 2),
+ IMAPX_CAPABILITY_NAMESPACE = (1 << 3),
+ IMAPX_CAPABILITY_UIDPLUS = (1 << 4),
+ IMAPX_CAPABILITY_LITERALPLUS = (1 << 5),
+ IMAPX_CAPABILITY_STARTTLS = (1 << 6),
+ IMAPX_CAPABILITY_IDLE = (1 << 7),
+ IMAPX_CAPABILITY_CONDSTORE = (1 << 8),
+ IMAPX_CAPABILITY_QRESYNC = (1 << 9),
+ IMAPX_CAPABILITY_LIST_STATUS = (1 << 10),
+ IMAPX_CAPABILITY_LIST_EXTENDED = (1 << 11),
+ IMAPX_CAPABILITY_QUOTA = (1 << 12),
+ IMAPX_CAPABILITY_MOVE = (1 << 13),
+ IMAPX_CAPABILITY_NOTIFY = (1 << 14),
+ IMAPX_CAPABILITY_SPECIAL_USE = (1 << 15)
+};
+
+struct _capability_info {
+ guint32 capa;
+ GHashTable *auth_types;
+};
+
+struct _capability_info *
+ imapx_parse_capability (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+void imapx_free_capability (struct _capability_info *);
+guint32 imapx_register_capability (const gchar *capability);
+guint32 imapx_lookup_capability (const gchar *capability);
+
+gboolean imapx_parse_param_list (CamelIMAPXInputStream *stream,
+ struct _camel_header_param **plist,
+ GCancellable *cancellable,
+ GError **error);
+struct _CamelContentDisposition *
+ imapx_parse_ext_optional (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+struct _CamelMessageContentInfo *
+ imapx_parse_body_fields (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+CamelHeaderAddress *
+ imapx_parse_address_list (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+struct _CamelMessageInfo *
+ imapx_parse_envelope (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+struct _CamelMessageContentInfo *
+ imapx_parse_body (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+gchar * imapx_parse_section (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+void imapx_free_body (struct _CamelMessageContentInfo *cinfo);
+
+/* ********************************************************************** */
+/* all the possible stuff we might get from a fetch request */
+/* this assumes the caller/server doesn't send any one of these types twice */
+struct _fetch_info {
+ guint32 got; /* what we got, see below */
+ GBytes *body; /* BODY[.*](<.*>)? */
+ GBytes *text; /* RFC822.TEXT */
+ GBytes *header; /* RFC822.HEADER */
+ CamelMessageInfo *minfo; /* ENVELOPE */
+ CamelMessageContentInfo *cinfo; /* BODYSTRUCTURE,BODY */
+ guint32 size; /* RFC822.SIZE */
+ guint32 offset; /* start offset of a BODY[]<offset.length> request */
+ guint32 flags; /* FLAGS */
+ guint64 modseq; /* MODSEQ */
+ CamelFlag *user_flags;
+ gchar *date; /* INTERNALDATE */
+ gchar *section; /* section for a BODY[section] request */
+ gchar *uid; /* UID */
+};
+
+#define FETCH_BODY (1 << 0)
+#define FETCH_TEXT (1 << 1)
+#define FETCH_HEADER (1 << 2)
+#define FETCH_MINFO (1 << 3)
+#define FETCH_CINFO (1 << 4)
+#define FETCH_SIZE (1 << 5)
+#define FETCH_OFFSET (1 << 6)
+#define FETCH_FLAGS (1 << 7)
+#define FETCH_DATE (1 << 8)
+#define FETCH_SECTION (1 << 9)
+#define FETCH_UID (1 << 10)
+#define FETCH_MODSEQ (1 << 11)
+
+struct _fetch_info *
+ imapx_parse_fetch (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+void imapx_free_fetch (struct _fetch_info *finfo);
+void imapx_dump_fetch (struct _fetch_info *finfo);
+
+/* ********************************************************************** */
+
+struct _status_info {
+ camel_imapx_id_t result; /* ok/no/bad/preauth only, user_cancel - client response */
+ camel_imapx_id_t condition; /* read-only/read-write/alert/parse/trycreate/newname/permanentflags/uidvalidity/unseen/highestmodseq */
+
+ union {
+ struct {
+ gchar *oldname;
+ gchar *newname;
+ } newname;
+ struct {
+ guint64 uidvalidity;
+ guint32 uid;
+ } appenduid;
+ struct {
+ guint64 uidvalidity;
+ GArray *uids;
+ GArray *copied_uids;
+ } copyuid;
+ struct _capability_info *cinfo;
+ } u;
+
+ gchar *text;
+};
+
+struct _status_info *
+ imapx_parse_status (CamelIMAPXInputStream *stream,
+ CamelIMAPXMailbox *mailbox,
+ GCancellable *cancellable,
+ GError **error);
+struct _status_info *
+ imapx_copy_status (struct _status_info *sinfo);
+void imapx_free_status (struct _status_info *sinfo);
+
+/* ********************************************************************** */
+
+gboolean camel_imapx_command_add_qresync_parameter
+ (struct _CamelIMAPXCommand *ic,
+ CamelFolder *folder);
+
+/* ********************************************************************** */
+
+gchar * camel_imapx_parse_mailbox (CamelIMAPXInputStream *stream,
+ gchar separator,
+ GCancellable *cancellable,
+ GError **error);
+void camel_imapx_normalize_mailbox (gchar *mailbox_name,
+ gchar separator);
+gboolean camel_imapx_mailbox_is_inbox (const gchar *mailbox_name);
+gchar * camel_imapx_mailbox_to_folder_path
+ (const gchar *mailbox_name,
+ gchar separator);
+gchar * camel_imapx_folder_path_to_mailbox
+ (const gchar *folder_path,
+ gchar separator);
+
+/* ********************************************************************** */
+
+gboolean camel_imapx_parse_quota (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ gchar **out_quota_root_name,
+ CamelFolderQuotaInfo **out_quota_info,
+ GError **error);
+gboolean camel_imapx_parse_quotaroot (CamelIMAPXInputStream *stream,
+ GCancellable *cancellable,
+ gchar **out_mailbox_name,
+ gchar ***out_quota_roots,
+ GError **error);
+
+/* ********************************************************************** */
+
+extern guchar imapx_specials[256];
+
+#define IMAPX_TYPE_CHAR (1 << 0)
+#define IMAPX_TYPE_TEXT_CHAR (1 << 1)
+#define IMAPX_TYPE_QUOTED_CHAR (1 << 2)
+#define IMAPX_TYPE_ATOM_CHAR (1 << 3)
+#define IMAPX_TYPE_TOKEN_CHAR (1 << 4)
+#define IMAPX_TYPE_NOTID_CHAR (1 << 5)
+
+guchar imapx_is_mask (const gchar *p);
+
+#define imapx_is_quoted_char(c) \
+ ((imapx_specials[((guchar)(c)) & 0xff] & IMAPX_TYPE_QUOTED_CHAR) != 0)
+#define imapx_is_token_char(c) \
+ ((imapx_specials[((guchar)(c)) & 0xff] & IMAPX_TYPE_TOKEN_CHAR) != 0)
+#define imapx_is_notid_char(c) \
+ ((imapx_specials[((guchar)(c)) & 0xff] & IMAPX_TYPE_NOTID_CHAR) != 0)
+
+extern gint camel_imapx_debug_flags;
+#define CAMEL_IMAPX_DEBUG_command (1 << 0)
+#define CAMEL_IMAPX_DEBUG_debug (1 << 1)
+#define CAMEL_IMAPX_DEBUG_extra (1 << 2)
+#define CAMEL_IMAPX_DEBUG_io (1 << 3)
+#define CAMEL_IMAPX_DEBUG_token (1 << 4)
+#define CAMEL_IMAPX_DEBUG_parse (1 << 5)
+#define CAMEL_IMAPX_DEBUG_conman (1 << 6)
+
+/* Set this to zero to remove all debug output at build time */
+#define CAMEL_IMAPX_DEBUG_ALL (~0)
+
+#define camel_debug_flag(type) \
+ ((camel_imapx_debug_flags & \
+ CAMEL_IMAPX_DEBUG_ALL & CAMEL_IMAPX_DEBUG_ ## type) != 0)
+#define camel_imapx_debug(type, tagprefix, fmt, ...) \
+ G_STMT_START { \
+ if (camel_debug_flag (type)) { \
+ printf ("[imapx:%c] " fmt, tagprefix , ##__VA_ARGS__); \
+ fflush (stdout); \
+ } \
+ } G_STMT_END
+
+/* ********************************************************************** */
+
+void imapx_utils_init (void);
+
+/* chen adds from old imap provider - place it in right place */
+gchar * imapx_path_to_physical (const gchar *prefix,
+ const gchar *vpath);
+gchar * imapx_get_temp_uid (void);
+
+gboolean imapx_util_all_is_ascii (const gchar *str);
+
+G_END_DECLS
+
+#endif /* CAMEL_IMAPX_UTILS_H */
+
diff --git a/src/camel/providers/imapx/libcamelimapx.urls b/src/camel/providers/imapx/libcamelimapx.urls
new file mode 100644
index 000000000..273c33659
--- /dev/null
+++ b/src/camel/providers/imapx/libcamelimapx.urls
@@ -0,0 +1 @@
+imapx
diff --git a/src/camel/providers/local/CMakeLists.txt b/src/camel/providers/local/CMakeLists.txt
new file mode 100644
index 000000000..169e48907
--- /dev/null
+++ b/src/camel/providers/local/CMakeLists.txt
@@ -0,0 +1,84 @@
+set(SOURCES
+ camel-local-folder.c
+ camel-local-folder.h
+ camel-local-store.c
+ camel-local-store.h
+ camel-local-summary.c
+ camel-local-summary.h
+ camel-local-private.c
+ camel-local-private.h
+ camel-local-provider.c
+ camel-maildir-folder.c
+ camel-maildir-folder.h
+ camel-maildir-store.c
+ camel-maildir-store.h
+ camel-maildir-summary.c
+ camel-maildir-summary.h
+ camel-mbox-folder.c
+ camel-mbox-folder.h
+ camel-mbox-store.c
+ camel-mbox-store.h
+ camel-mbox-summary.c
+ camel-mbox-summary.h
+ camel-mh-folder.c
+ camel-mh-folder.h
+ camel-mh-settings.c
+ camel-mh-settings.h
+ camel-mh-store.c
+ camel-mh-store.h
+ camel-mh-summary.c
+ camel-mh-summary.h
+)
+
+set(DEPENDENCIES
+ camel
+)
+
+if(NOT WIN32)
+ list(APPEND SOURCES
+ camel-spool-folder.c
+ camel-spool-folder.h
+ camel-spool-settings.c
+ camel-spool-settings.h
+ camel-spool-store.c
+ camel-spool-store.h
+ camel-spool-summary.c
+ camel-spool-summary.h
+ )
+endif(NOT WIN32)
+
+add_library(camellocal MODULE ${SOURCES})
+
+add_dependencies(camellocal
+ ${DEPENDENCIES}
+)
+
+target_compile_definitions(camellocal PRIVATE
+ -DG_LOG_DOMAIN=\"camel-local-provider\"
+)
+
+target_compile_options(camellocal PUBLIC
+ ${CAMEL_CFLAGS}
+)
+
+target_include_directories(camellocal PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/camel
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_SOURCE_DIR}/src/camel
+ ${CAMEL_INCLUDE_DIRS}
+)
+
+target_link_libraries(camellocal
+ ${DEPENDENCIES}
+ ${CAMEL_LDFLAGS}
+)
+
+install(TARGETS camellocal
+ DESTINATION ${camel_providerdir}
+)
+
+install(FILES libcamellocal.urls
+ DESTINATION ${camel_providerdir}
+)
diff --git a/src/camel/providers/local/camel-local-folder.c b/src/camel/providers/local/camel-local-folder.c
new file mode 100644
index 000000000..47ba7611d
--- /dev/null
+++ b/src/camel/providers/local/camel-local-folder.c
@@ -0,0 +1,723 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#if !defined (G_OS_WIN32) && !defined (_POSIX_PATH_MAX)
+#include <posix1_lim.h>
+#endif
+
+#include "camel-local-folder.h"
+#include "camel-local-private.h"
+#include "camel-local-store.h"
+#include "camel-local-summary.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+#ifndef PATH_MAX
+#define PATH_MAX _POSIX_PATH_MAX
+#endif
+
+#define CAMEL_LOCAL_FOLDER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_LOCAL_FOLDER, CamelLocalFolderPrivate))
+
+/* The custom property ID is a CamelArg artifact.
+ * It still identifies the property in state files. */
+enum {
+ PROP_0,
+ PROP_INDEX_BODY = 0x2400
+};
+
+G_DEFINE_TYPE (CamelLocalFolder, camel_local_folder, CAMEL_TYPE_FOLDER)
+
+static void
+local_folder_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_INDEX_BODY:
+ camel_local_folder_set_index_body (
+ CAMEL_LOCAL_FOLDER (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+local_folder_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_INDEX_BODY:
+ g_value_set_boolean (
+ value, camel_local_folder_get_index_body (
+ CAMEL_LOCAL_FOLDER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+local_folder_dispose (GObject *object)
+{
+ CamelFolder *folder;
+ CamelLocalFolder *local_folder;
+
+ folder = CAMEL_FOLDER (object);
+ local_folder = CAMEL_LOCAL_FOLDER (object);
+
+ if (folder->summary) {
+ /* Something can hold the reference to the folder longer than
+ the parent store is alive, thus count with it. */
+ if (camel_folder_get_parent_store (folder)) {
+ camel_local_summary_sync (
+ CAMEL_LOCAL_SUMMARY (folder->summary),
+ FALSE, local_folder->changes, NULL, NULL);
+ }
+ }
+
+ g_clear_object (&folder->summary);
+ g_clear_object (&local_folder->search);
+ g_clear_object (&local_folder->index);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_local_folder_parent_class)->dispose (object);
+}
+
+static void
+local_folder_finalize (GObject *object)
+{
+ CamelLocalFolder *local_folder;
+
+ local_folder = CAMEL_LOCAL_FOLDER (object);
+
+ 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->index_path);
+
+ camel_folder_change_info_free (local_folder->changes);
+
+ g_mutex_clear (&local_folder->priv->search_lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_local_folder_parent_class)->finalize (object);
+}
+
+static void
+local_folder_constructed (GObject *object)
+{
+ CamelLocalSettings *local_settings;
+ CamelProvider *provider;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelFolder *folder;
+ CamelStore *parent_store;
+ const gchar *full_name;
+ const gchar *tmp;
+ gchar *description;
+ gchar *root_path;
+ gchar *path;
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_local_folder_parent_class)->constructed (object);
+
+ folder = CAMEL_FOLDER (object);
+ full_name = camel_folder_get_full_name (folder);
+ parent_store = camel_folder_get_parent_store (folder);
+
+ service = CAMEL_SERVICE (parent_store);
+ provider = camel_service_get_provider (service);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ root_path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ if (root_path == NULL)
+ return;
+
+ path = g_strdup_printf ("%s/%s", root_path, full_name);
+
+ if ((tmp = getenv ("HOME")) && strncmp (tmp, path, strlen (tmp)) == 0)
+ /* Translators: This is used for a folder description,
+ * for folders being under $HOME. The first %s is replaced
+ * with a relative path under $HOME, the second %s is
+ * replaced with a protocol name, like mbox/maldir/... */
+ description = g_strdup_printf (
+ _("~%s (%s)"),
+ path + strlen (tmp),
+ provider->protocol);
+ else if ((tmp = "/var/spool/mail") && strncmp (tmp, path, strlen (tmp)) == 0)
+ /* Translators: This is used for a folder description, for
+ * folders being under /var/spool/mail. The first %s is
+ * replaced with a relative path under /var/spool/mail,
+ * the second %s is replaced with a protocol name, like
+ * mbox/maldir/... */
+ description = g_strdup_printf (
+ _("mailbox: %s (%s)"),
+ path + strlen (tmp),
+ provider->protocol);
+ else if ((tmp = "/var/mail") && strncmp (tmp, path, strlen (tmp)) == 0)
+ /* Translators: This is used for a folder description, for
+ * folders being under /var/mail. The first %s is replaced
+ * with a relative path under /var/mail, the second %s is
+ * replaced with a protocol name, like mbox/maldir/... */
+ description = g_strdup_printf (
+ _("mailbox: %s (%s)"),
+ path + strlen (tmp),
+ provider->protocol);
+ else
+ /* Translators: This is used for a folder description.
+ * The first %s is replaced with a folder's full path,
+ * the second %s is replaced with a protocol name, like
+ * mbox/maldir/... */
+ description = g_strdup_printf (
+ _("%s (%s)"), path,
+ provider->protocol);
+
+ camel_folder_set_description (folder, description);
+
+ g_free (description);
+ g_free (root_path);
+ g_free (path);
+}
+
+static GPtrArray *
+local_folder_search_by_expression (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ 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);
+ if (camel_local_folder_get_index_body (local_folder))
+ camel_folder_search_set_body_index (local_folder->search, local_folder->index);
+ else
+ camel_folder_search_set_body_index (local_folder->search, NULL);
+ matches = camel_folder_search_search (local_folder->search, expression, NULL, cancellable, error);
+
+ CAMEL_LOCAL_FOLDER_UNLOCK (folder, search_lock);
+
+ return matches;
+}
+
+static GPtrArray *
+local_folder_search_by_uids (CamelFolder *folder,
+ const gchar *expression,
+ GPtrArray *uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ 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);
+ if (camel_local_folder_get_index_body (local_folder))
+ camel_folder_search_set_body_index (local_folder->search, local_folder->index);
+ else
+ camel_folder_search_set_body_index (local_folder->search, NULL);
+ matches = camel_folder_search_search (local_folder->search, expression, uids, cancellable, error);
+
+ CAMEL_LOCAL_FOLDER_UNLOCK (folder, search_lock);
+
+ return matches;
+}
+
+static void
+local_folder_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);
+}
+
+static void
+local_folder_delete (CamelFolder *folder)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *) folder;
+
+ if (lf->index)
+ camel_index_delete (lf->index);
+
+ CAMEL_FOLDER_CLASS (camel_local_folder_parent_class)->delete_ (folder);
+}
+
+static void
+local_folder_rename (CamelFolder *folder,
+ const gchar *newname)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *) folder;
+ gchar *statepath;
+ CamelLocalStore *ls;
+ CamelStore *parent_store;
+
+ parent_store = camel_folder_get_parent_store (folder);
+ ls = CAMEL_LOCAL_STORE (parent_store);
+
+ d (printf ("renaming local folder paths to '%s'\n", newname));
+
+ /* Sync? */
+
+ g_free (lf->folder_path);
+ g_free (lf->index_path);
+
+ lf->folder_path = camel_local_store_get_full_path (ls, newname);
+ lf->index_path = camel_local_store_get_meta_path (ls, newname, ".ibex");
+ statepath = camel_local_store_get_meta_path (ls, newname, ".cmeta");
+ camel_object_set_state_filename (CAMEL_OBJECT (lf), statepath);
+ g_free (statepath);
+
+ /* FIXME: Poke some internals, sigh */
+ g_free (((CamelLocalSummary *) folder->summary)->folder_path);
+ ((CamelLocalSummary *) folder->summary)->folder_path = g_strdup (lf->folder_path);
+
+ CAMEL_FOLDER_CLASS (camel_local_folder_parent_class)->rename (folder, newname);
+}
+
+static guint32
+local_folder_count_by_expression (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalFolder *local_folder = CAMEL_LOCAL_FOLDER (folder);
+ gint 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);
+ if (camel_local_folder_get_index_body (local_folder))
+ camel_folder_search_set_body_index (local_folder->search, local_folder->index);
+ else
+ camel_folder_search_set_body_index (local_folder->search, NULL);
+ matches = camel_folder_search_count (local_folder->search, expression, cancellable, error);
+
+ CAMEL_LOCAL_FOLDER_UNLOCK (folder, search_lock);
+
+ return matches;
+}
+
+static GPtrArray *
+local_folder_get_uncached_uids (CamelFolder *folder,
+ GPtrArray *uids,
+ GError **error)
+{
+ /* By default, we would have everything local.
+ * No need to fetch from anywhere. */
+ return g_ptr_array_new ();
+}
+
+static gboolean
+local_folder_expunge_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* Just do a sync with expunge, serves the same purpose */
+ /* call the callback directly, to avoid locking problems */
+ return CAMEL_FOLDER_GET_CLASS (folder)->synchronize_sync (
+ folder, TRUE, cancellable, error);
+}
+
+static gboolean
+local_folder_refresh_info_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *parent_store;
+ CamelLocalFolder *lf = (CamelLocalFolder *) folder;
+ gboolean need_summary_check;
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ need_summary_check = camel_local_store_get_need_summary_check (
+ CAMEL_LOCAL_STORE (parent_store));
+
+ if (need_summary_check &&
+ camel_local_summary_check ((CamelLocalSummary *) folder->summary, lf->changes, cancellable, error) == -1)
+ return FALSE;
+
+ if (camel_folder_change_info_changed (lf->changes)) {
+ camel_folder_changed (folder, lf->changes);
+ camel_folder_change_info_clear (lf->changes);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+local_folder_synchronize_sync (CamelFolder *folder,
+ gboolean expunge,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalFolder *lf = CAMEL_LOCAL_FOLDER (folder);
+ gboolean success;
+
+ d (printf ("local sync '%s' , expunge=%s\n", folder->full_name, expunge?"true":"false"));
+
+ if (camel_local_folder_lock (lf, CAMEL_LOCK_WRITE, error) == -1)
+ return FALSE;
+
+ camel_object_state_write (CAMEL_OBJECT (lf));
+
+ /* if sync fails, we'll pass it up on exit through ex */
+ success = (camel_local_summary_sync (
+ (CamelLocalSummary *) folder->summary,
+ expunge, lf->changes, cancellable, error) == 0);
+ camel_local_folder_unlock (lf);
+
+ if (camel_folder_change_info_changed (lf->changes)) {
+ camel_folder_changed (folder, lf->changes);
+ camel_folder_change_info_clear (lf->changes);
+ }
+
+ return success;
+}
+
+static gint
+local_folder_lock (CamelLocalFolder *lf,
+ CamelLockType type,
+ GError **error)
+{
+ return 0;
+}
+
+static void
+local_folder_unlock (CamelLocalFolder *lf)
+{
+ /* nothing */
+}
+
+static void
+camel_local_folder_class_init (CamelLocalFolderClass *class)
+{
+ GObjectClass *object_class;
+ CamelFolderClass *folder_class;
+
+ g_type_class_add_private (class, sizeof (CamelLocalFolderPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = local_folder_set_property;
+ object_class->get_property = local_folder_get_property;
+ object_class->dispose = local_folder_dispose;
+ object_class->finalize = local_folder_finalize;
+ object_class->constructed = local_folder_constructed;
+
+ folder_class = CAMEL_FOLDER_CLASS (class);
+ folder_class->search_by_expression = local_folder_search_by_expression;
+ folder_class->search_by_uids = local_folder_search_by_uids;
+ folder_class->search_free = local_folder_search_free;
+ folder_class->delete_ = local_folder_delete;
+ folder_class->rename = local_folder_rename;
+ folder_class->count_by_expression = local_folder_count_by_expression;
+ folder_class->get_uncached_uids = local_folder_get_uncached_uids;
+ folder_class->expunge_sync = local_folder_expunge_sync;
+ folder_class->refresh_info_sync = local_folder_refresh_info_sync;
+ folder_class->synchronize_sync = local_folder_synchronize_sync;
+
+ class->lock = local_folder_lock;
+ class->unlock = local_folder_unlock;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_INDEX_BODY,
+ g_param_spec_boolean (
+ "index-body",
+ "Index Body",
+ _("_Index message body data"),
+ FALSE,
+ G_PARAM_READWRITE |
+ CAMEL_PARAM_PERSISTENT));
+}
+
+static void
+camel_local_folder_init (CamelLocalFolder *local_folder)
+{
+ CamelFolder *folder = CAMEL_FOLDER (local_folder);
+
+ local_folder->priv = CAMEL_LOCAL_FOLDER_GET_PRIVATE (local_folder);
+ g_mutex_init (&local_folder->priv->search_lock);
+
+ folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_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;
+}
+
+CamelLocalFolder *
+camel_local_folder_construct (CamelLocalFolder *lf,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ gchar *statepath;
+#ifndef G_OS_WIN32
+#ifdef __GLIBC__
+ gchar *folder_path;
+#else
+ gchar folder_path[PATH_MAX];
+#endif
+#endif
+ gint forceindex;
+ CamelLocalStore *ls;
+ CamelStore *parent_store;
+ const gchar *full_name;
+ gboolean need_summary_check;
+
+ folder = CAMEL_FOLDER (lf);
+ full_name = camel_folder_get_full_name (folder);
+ parent_store = camel_folder_get_parent_store (folder);
+
+ service = CAMEL_SERVICE (parent_store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ lf->base_path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ ls = CAMEL_LOCAL_STORE (parent_store);
+ need_summary_check = camel_local_store_get_need_summary_check (ls);
+
+ lf->folder_path = camel_local_store_get_full_path (ls, full_name);
+ lf->index_path = camel_local_store_get_meta_path (ls, full_name, ".ibex");
+ statepath = camel_local_store_get_meta_path (ls, full_name, ".cmeta");
+
+ camel_object_set_state_filename (CAMEL_OBJECT (lf), statepath);
+ g_free (statepath);
+
+ lf->flags = flags;
+
+ if (camel_object_state_read (CAMEL_OBJECT (lf)) == -1) {
+ /* No metadata - load defaults and persitify */
+ camel_local_folder_set_index_body (lf, TRUE);
+ camel_object_state_write (CAMEL_OBJECT (lf));
+ }
+
+ /* XXX Canonicalizing the folder path portably is a messy affair.
+ * The proposed GLib function in [1] would be useful here.
+ *
+ * [1] https://bugzilla.gnome.org/show_bug.cgi?id=111848
+ */
+#ifndef G_OS_WIN32
+ /* follow any symlinks to the mailbox */
+#ifdef __GLIBC__
+ if ((folder_path = realpath (lf->folder_path, NULL)) != NULL) {
+#else
+ if (realpath (lf->folder_path, folder_path) != NULL) {
+#endif
+ g_free (lf->folder_path);
+ lf->folder_path = g_strdup (folder_path);
+#ifdef __GLIBC__
+ /* Not a typo. Use free() here, not g_free().
+ * The path string was allocated by realpath(). */
+ free (folder_path);
+#endif
+ }
+#endif
+ 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 */
+ g_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) {
+ gint 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", g_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 *) CAMEL_LOCAL_FOLDER_GET_CLASS (lf)->create_summary (lf, lf->folder_path, lf->index);
+ if (!(flags & CAMEL_STORE_IS_MIGRATING) && !camel_local_summary_load ((CamelLocalSummary *) folder->summary, forceindex, NULL)) {
+ /* ? */
+ if (need_summary_check &&
+ camel_local_summary_check ((CamelLocalSummary *) folder->summary, lf->changes, cancellable, error) == 0) {
+ /* 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, cancellable, error) == -1) {
+ g_object_unref (folder);
+ return NULL;
+ }
+ }
+ }
+
+ /* TODO: This probably shouldn't be here? */
+ if ((flags & CAMEL_STORE_FOLDER_CREATE) != 0) {
+ CamelFolderInfo *fi;
+
+ /* Use 'recursive' mode, even for just created folder, to have set whether
+ the folder has children or not properly. */
+ fi = camel_store_get_folder_info_sync (parent_store, full_name, CAMEL_STORE_FOLDER_INFO_RECURSIVE, NULL, NULL);
+ g_return_val_if_fail (fi != NULL, lf);
+
+ camel_store_folder_created (parent_store, fi);
+ camel_folder_info_free (fi);
+ }
+
+ return lf;
+}
+
+gboolean
+camel_local_folder_get_index_body (CamelLocalFolder *local_folder)
+{
+ g_return_val_if_fail (CAMEL_IS_LOCAL_FOLDER (local_folder), FALSE);
+
+ return (local_folder->flags & CAMEL_STORE_FOLDER_BODY_INDEX);
+}
+
+void
+camel_local_folder_set_index_body (CamelLocalFolder *local_folder,
+ gboolean index_body)
+{
+ g_return_if_fail (CAMEL_IS_LOCAL_FOLDER (local_folder));
+
+ if (index_body)
+ local_folder->flags |= CAMEL_STORE_FOLDER_BODY_INDEX;
+ else
+ local_folder->flags &= ~CAMEL_STORE_FOLDER_BODY_INDEX;
+
+ g_object_notify (G_OBJECT (local_folder), "index-body");
+}
+
+/* lock the folder, may be called repeatedly (with matching unlock calls),
+ * with type the same or less than the first call */
+gint
+camel_local_folder_lock (CamelLocalFolder *lf,
+ CamelLockType type,
+ GError **error)
+{
+ if (lf->locked > 0) {
+ /* lets be anal here - its important the code knows what its doing */
+ g_return_val_if_fail (lf->locktype == type || lf->locktype == CAMEL_LOCK_WRITE, -1);
+ } else {
+ if (CAMEL_LOCAL_FOLDER_GET_CLASS (lf)->lock (lf, type, error) == -1)
+ return -1;
+ lf->locktype = type;
+ }
+
+ lf->locked++;
+
+ return 0;
+}
+
+/* unlock folder */
+gint
+camel_local_folder_unlock (CamelLocalFolder *lf)
+{
+ g_return_val_if_fail (lf->locked > 0, -1);
+ lf->locked--;
+ if (lf->locked == 0)
+ CAMEL_LOCAL_FOLDER_GET_CLASS (lf)->unlock (lf);
+
+ return 0;
+}
+
+void
+set_cannot_get_message_ex (GError **error,
+ gint err_code,
+ const gchar *msgID,
+ const gchar *folder_path,
+ const gchar *detailErr)
+{
+ /* Translators: The first %s is replaced with a message ID,
+ * the second %s is replaced with the folder path,
+ * the third %s is replaced with a detailed error string */
+ g_set_error (
+ error, CAMEL_ERROR, err_code,
+ _("Cannot get message %s from folder %s\n%s"),
+ msgID, folder_path, detailErr);
+}
diff --git a/src/camel/providers/local/camel-local-folder.h b/src/camel/providers/local/camel-local-folder.h
new file mode 100644
index 000000000..00bcb8e31
--- /dev/null
+++ b/src/camel/providers/local/camel-local-folder.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_LOCAL_FOLDER_H
+#define CAMEL_LOCAL_FOLDER_H
+
+#include <camel/camel.h>
+
+#include "camel-local-summary.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_LOCAL_FOLDER \
+ (camel_local_folder_get_type ())
+#define CAMEL_LOCAL_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_LOCAL_FOLDER, CamelLocalFolder))
+#define CAMEL_LOCAL_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_LOCAL_FOLDER, CamelLocalFolderClass))
+#define CAMEL_IS_LOCAL_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_LOCAL_FOLDER))
+#define CAMEL_IS_LOCAL_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_LOCAL_FOLDER))
+#define CAMEL_LOCAL_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_LOCAL_FOLDER, CamelLocalFolderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelLocalFolder CamelLocalFolder;
+typedef struct _CamelLocalFolderClass CamelLocalFolderClass;
+typedef struct _CamelLocalFolderPrivate CamelLocalFolderPrivate;
+
+struct _CamelLocalFolder {
+ CamelFolder parent;
+ CamelLocalFolderPrivate *priv;
+
+ guint32 flags; /* open mode flags */
+
+ gint locked; /* lock counter */
+ CamelLockType locktype; /* what type of lock we have */
+
+ gchar *base_path; /* base path of the local folder */
+ gchar *folder_path; /* the path to the folder itself */
+ gchar *index_path; /* where the index file lives */
+
+ CamelIndex *index; /* index for this folder */
+
+ /* Used to run searches, we just use the real thing (tm). */
+ CamelFolderSearch *search;
+
+ /* Used to store changes to the folder during processing. */
+ CamelFolderChangeInfo *changes;
+};
+
+struct _CamelLocalFolderClass {
+ CamelFolderClass parent_class;
+
+ /* Virtual methods */
+
+ /* summary factory, only used at init */
+ CamelLocalSummary *
+ (*create_summary) (CamelLocalFolder *lf,
+ const gchar *folder,
+ CamelIndex *index);
+
+ /* Lock the folder for my operations */
+ gint (*lock) (CamelLocalFolder *lf,
+ CamelLockType type,
+ GError **error);
+
+ /* Unlock the folder for my operations */
+ void (*unlock) (CamelLocalFolder *);
+};
+
+GType camel_local_folder_get_type (void);
+
+/* flags are taken from CAMEL_STORE_FOLDER_* flags */
+CamelLocalFolder *
+ camel_local_folder_construct (CamelLocalFolder *local_folder,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_local_folder_get_index_body
+ (CamelLocalFolder *local_folder);
+void camel_local_folder_set_index_body
+ (CamelLocalFolder *local_folder,
+ gboolean index_body);
+
+/* Lock the folder for internal use. May be called repeatedly */
+/* UNIMPLEMENTED */
+gint camel_local_folder_lock (CamelLocalFolder *local_folder,
+ CamelLockType type,
+ GError **error);
+gint camel_local_folder_unlock (CamelLocalFolder *local_folder);
+
+void set_cannot_get_message_ex (GError **error,
+ gint err_code,
+ const gchar *msgID,
+ const gchar *folder_path,
+ const gchar *detailErr);
+
+G_END_DECLS
+
+#endif /* CAMEL_LOCAL_FOLDER_H */
diff --git a/src/camel/providers/local/camel-local-private.c b/src/camel/providers/local/camel-local-private.c
new file mode 100644
index 000000000..f19c784aa
--- /dev/null
+++ b/src/camel/providers/local/camel-local-private.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Srinivsa Ragavan <sragavan@novell.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "camel-local-private.h"
+
+gint
+camel_local_frompos_sort (gpointer enc,
+ gint len1,
+ gpointer data1,
+ gint len2,
+ gpointer data2)
+{
+ static gchar *sa1 = NULL, *sa2 = NULL;
+ static gint l1 = 0, l2 = 0;
+ gint a1, a2;
+
+ if (l1 < len1 + 1) {
+ sa1 = g_realloc (sa1, len1 + 1);
+ l1 = len1 + 1;
+ }
+ if (l2 < len2 + 1) {
+ sa2 = g_realloc (sa2, len2 + 1);
+ l2 = len2 + 1;
+ }
+ strncpy (sa1, data1, len1); sa1[len1] = 0;
+ strncpy (sa2, data2, len2); sa2[len2] = 0;
+
+ a1 = strtoul (sa1, NULL, 10);
+ a2 = strtoul (sa2, NULL, 10);
+
+ return a1 - a2;
+}
diff --git a/src/camel/providers/local/camel-local-private.h b/src/camel/providers/local/camel-local-private.h
new file mode 100644
index 000000000..71eebc837
--- /dev/null
+++ b/src/camel/providers/local/camel-local-private.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_LOCAL_PRIVATE_H
+#define CAMEL_LOCAL_PRIVATE_H
+
+/* need a way to configure and save this data, if this header is to
+ * be installed. For now, dont install it */
+
+#include "evolution-data-server-config.h"
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+struct _CamelLocalFolderPrivate {
+ GMutex search_lock; /* for locking the search object */
+};
+
+#define CAMEL_LOCAL_FOLDER_LOCK(f, l) \
+ (g_mutex_lock (&((CamelLocalFolder *) f)->priv->l))
+#define CAMEL_LOCAL_FOLDER_UNLOCK(f, l) \
+ (g_mutex_unlock (&((CamelLocalFolder *) f)->priv->l))
+
+gint camel_local_frompos_sort (gpointer enc,
+ gint len1,
+ gpointer data1,
+ gint len2,
+ gpointer data2);
+
+G_END_DECLS
+
+#endif /* CAMEL_LOCAL_PRIVATE_H */
+
diff --git a/src/camel/providers/local/camel-local-provider.c b/src/camel/providers/local/camel-local-provider.c
new file mode 100644
index 000000000..0613dd0a8
--- /dev/null
+++ b/src/camel/providers/local/camel-local-provider.c
@@ -0,0 +1,258 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-maildir-store.h"
+#include "camel-mbox-store.h"
+#include "camel-mh-store.h"
+#include "camel-spool-store.h"
+
+#define d(x)
+
+#ifndef G_OS_WIN32
+
+static CamelProviderConfEntry mh_conf_entries[] = {
+ { CAMEL_PROVIDER_CONF_SECTION_START, "general", NULL, N_("Options") },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "use-dot-folders", NULL,
+ N_("_Use the '.folders' folder summary file (exmh)"), "0" },
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+ { CAMEL_PROVIDER_CONF_END }
+};
+
+static CamelProvider mh_provider = {
+ "mh",
+ N_("MH-format mail directories"),
+ N_("For storing local mail in MH-like mail directories."),
+ "mail",
+ CAMEL_PROVIDER_IS_SOURCE | CAMEL_PROVIDER_IS_STORAGE | CAMEL_PROVIDER_IS_LOCAL,
+ CAMEL_URL_NEED_PATH | CAMEL_URL_NEED_PATH_DIR | CAMEL_URL_PATH_IS_ABSOLUTE | CAMEL_URL_FRAGMENT_IS_PATH,
+ mh_conf_entries,
+ NULL,
+ /* ... */
+};
+
+#endif
+
+static CamelProviderConfEntry mbox_conf_entries[] = {
+ { CAMEL_PROVIDER_CONF_END }
+};
+
+static CamelProvider mbox_provider = {
+ "mbox",
+ N_("Local delivery"),
+ N_("For retrieving (moving) local mail from standard mbox-formatted spools into folders managed by Evolution."),
+ "mail",
+ CAMEL_PROVIDER_IS_SOURCE | CAMEL_PROVIDER_IS_STORAGE | CAMEL_PROVIDER_IS_LOCAL,
+ CAMEL_URL_NEED_PATH | CAMEL_URL_PATH_IS_ABSOLUTE | CAMEL_URL_FRAGMENT_IS_PATH,
+ mbox_conf_entries, /* semi-empty entries, thus evolution will show "Receiving Options" page */
+ NULL,
+ /* ... */
+};
+
+static CamelProviderConfEntry maildir_conf_entries[] = {
+ { CAMEL_PROVIDER_CONF_SECTION_START, "general", NULL, N_("Options") },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "filter-inbox", NULL,
+ N_("_Apply filters to new messages in Inbox"), "0" },
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+ { CAMEL_PROVIDER_CONF_END }
+};
+
+static CamelProvider maildir_provider = {
+ "maildir",
+ N_("Maildir-format mail directories"),
+ N_("For storing local mail in maildir directories."),
+ "mail",
+ CAMEL_PROVIDER_IS_SOURCE | CAMEL_PROVIDER_IS_STORAGE | CAMEL_PROVIDER_IS_LOCAL,
+ CAMEL_URL_NEED_PATH | CAMEL_URL_NEED_PATH_DIR | CAMEL_URL_PATH_IS_ABSOLUTE | CAMEL_URL_FRAGMENT_IS_PATH,
+ maildir_conf_entries,
+ NULL,
+ /* ... */
+};
+
+#ifndef G_OS_WIN32
+
+static CamelProviderConfEntry spool_conf_entries[] = {
+ { CAMEL_PROVIDER_CONF_SECTION_START, "general", NULL, N_("Options") },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "filter-inbox", NULL,
+ N_("_Apply filters to new messages in Inbox"), "0" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "use-xstatus-headers", NULL, N_("_Store status headers in Elm/Pine/Mutt format"), "0" },
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+ { CAMEL_PROVIDER_CONF_END }
+};
+
+static CamelProvider spool_file_provider = {
+ "spool",
+ N_("Standard Unix mbox spool file"),
+ N_("For reading and storing local mail in external standard mbox "
+ "spool files.\nMay also be used to read a tree of Elm, Pine, or Mutt "
+ "style folders."),
+ "mail",
+ CAMEL_PROVIDER_IS_SOURCE | CAMEL_PROVIDER_IS_STORAGE,
+ CAMEL_URL_NEED_PATH | CAMEL_URL_PATH_IS_ABSOLUTE | CAMEL_URL_FRAGMENT_IS_PATH,
+ spool_conf_entries,
+ NULL,
+ /* ... */
+};
+
+static CamelProvider spool_directory_provider = {
+ "spooldir",
+ N_("Standard Unix mbox spool directory"),
+ N_("For reading and storing local mail in external standard mbox "
+ "spool files.\nMay also be used to read a tree of Elm, Pine, or Mutt "
+ "style folders."),
+ "mail",
+ CAMEL_PROVIDER_IS_SOURCE | CAMEL_PROVIDER_IS_STORAGE,
+ CAMEL_URL_NEED_PATH | CAMEL_URL_NEED_PATH_DIR | CAMEL_URL_PATH_IS_ABSOLUTE | CAMEL_URL_FRAGMENT_IS_PATH,
+ spool_conf_entries,
+ NULL,
+ /* ... */
+};
+
+#endif
+
+/* build a canonical 'path' */
+static gchar *
+make_can_path (gchar *p,
+ gchar *o)
+{
+ gchar c, last, *start = o;
+
+ d (printf ("canonical '%s' = ", p));
+
+ last = 0;
+ while ((c = *p++)) {
+ if (c != '/'
+ || (c == '/' && last != '/'))
+ *o++ = c;
+ last = c;
+ }
+ if (o > start && o[-1] == '/')
+ o[-1] = 0;
+ else
+ *o = 0;
+
+ d (printf ("'%s'\n", start));
+
+ return start;
+}
+
+/* 'helper' function for it */
+#define get_can_path(p) ((p == NULL) ? NULL : (make_can_path ((p), g_alloca (strlen (p) + 1))))
+
+static guint
+local_url_hash (gconstpointer v)
+{
+ const CamelURL *u = v;
+ guint hash = 0;
+
+#define ADD_HASH(s) if (s && *s) hash ^= g_str_hash (s);
+
+ ADD_HASH (u->protocol);
+ ADD_HASH (u->user);
+ ADD_HASH (u->authmech);
+ ADD_HASH (u->host);
+ if (u->path)
+ hash ^= g_str_hash (get_can_path (u->path));
+ ADD_HASH (u->path);
+ ADD_HASH (u->query);
+ hash ^= u->port;
+
+ return hash;
+}
+
+static gint
+check_equal (gchar *s1,
+ gchar *s2)
+{
+ if (s1 == NULL || *s1 == 0) {
+ if (s2 == NULL || *s2 == 0)
+ return TRUE;
+ else
+ return FALSE;
+ }
+
+ if (s2 == NULL)
+ return FALSE;
+
+ return strcmp (s1, s2) == 0;
+}
+
+static gint
+local_url_equal (gconstpointer v,
+ gconstpointer v2)
+{
+ const CamelURL *u1 = v, *u2 = v2;
+ gchar *p1, *p2;
+
+ p1 = get_can_path (u1->path);
+ p2 = get_can_path (u2->path);
+ return check_equal (p1, p2)
+ && check_equal (u1->protocol, u2->protocol);
+}
+
+void
+camel_provider_module_init (void)
+{
+ static gint init = 0;
+
+ if (init)
+ abort ();
+ init = 1;
+
+#ifndef G_OS_WIN32
+ mh_conf_entries[0].value = ""; /* default path */
+ mh_provider.object_types[CAMEL_PROVIDER_STORE] = camel_mh_store_get_type ();
+ mh_provider.url_hash = local_url_hash;
+ mh_provider.url_equal = local_url_equal;
+ mh_provider.translation_domain = GETTEXT_PACKAGE;
+ camel_provider_register (&mh_provider);
+#endif
+
+ mbox_provider.object_types[CAMEL_PROVIDER_STORE] = CAMEL_TYPE_MBOX_STORE;
+ mbox_provider.url_hash = local_url_hash;
+ mbox_provider.url_equal = local_url_equal;
+ mbox_provider.translation_domain = GETTEXT_PACKAGE;
+ camel_provider_register (&mbox_provider);
+
+#ifndef G_OS_WIN32
+ spool_file_provider.object_types[CAMEL_PROVIDER_STORE] = camel_spool_store_get_type ();
+ spool_file_provider.url_hash = local_url_hash;
+ spool_file_provider.url_equal = local_url_equal;
+ spool_file_provider.translation_domain = GETTEXT_PACKAGE;
+ camel_provider_register (&spool_file_provider);
+
+ spool_directory_provider.object_types[CAMEL_PROVIDER_STORE] = camel_spool_store_get_type ();
+ spool_directory_provider.url_hash = local_url_hash;
+ spool_directory_provider.url_equal = local_url_equal;
+ spool_directory_provider.translation_domain = GETTEXT_PACKAGE;
+ camel_provider_register (&spool_directory_provider);
+#endif
+
+ maildir_provider.object_types[CAMEL_PROVIDER_STORE] = camel_maildir_store_get_type ();
+ maildir_provider.url_hash = local_url_hash;
+ maildir_provider.url_equal = local_url_equal;
+ maildir_provider.translation_domain = GETTEXT_PACKAGE;
+ camel_provider_register (&maildir_provider);
+}
diff --git a/src/camel/providers/local/camel-local-store.c b/src/camel/providers/local/camel-local-store.c
new file mode 100644
index 000000000..324a44e7e
--- /dev/null
+++ b/src/camel/providers/local/camel-local-store.c
@@ -0,0 +1,815 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include "camel-local-folder.h"
+#include "camel-local-store.h"
+
+#define d(x)
+
+#define CAMEL_LOCAL_STORE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_LOCAL_STORE, CamelLocalStorePrivate))
+
+struct _CamelLocalStorePrivate {
+ gboolean need_summary_check;
+};
+
+enum {
+ PROP_0,
+ PROP_NEED_SUMMARY_CHECK
+};
+
+G_DEFINE_TYPE (CamelLocalStore, camel_local_store, CAMEL_TYPE_STORE)
+
+static gint
+xrename (const gchar *oldp,
+ const gchar *newp,
+ const gchar *prefix,
+ const gchar *suffix,
+ gint missingok,
+ GError **error)
+{
+ gchar *old, *new;
+ gchar *basename;
+ gint ret = -1;
+ gint err = 0;
+
+ basename = g_strconcat (oldp, suffix, NULL);
+ old = g_build_filename (prefix, basename, NULL);
+ g_free (basename);
+
+ basename = g_strconcat (newp, suffix, NULL);
+ new = g_build_filename (prefix, basename, NULL);
+ g_free (basename);
+
+ d (printf ("renaming %s%s to %s%s\n", oldp, suffix, newp, suffix));
+
+ if (g_rename (old, new) == -1 &&
+ !(errno == ENOENT && missingok)) {
+ err = errno;
+ ret = -1;
+ } else {
+ ret = 0;
+ }
+
+ if (ret == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (err),
+ _("Could not rename folder %s to %s: %s"),
+ old, new, g_strerror (err));
+ }
+
+ g_free (old);
+ g_free (new);
+ return ret;
+}
+
+static void
+local_store_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_NEED_SUMMARY_CHECK:
+ camel_local_store_set_need_summary_check (
+ CAMEL_LOCAL_STORE (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+local_store_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_NEED_SUMMARY_CHECK:
+ g_value_set_boolean (
+ value,
+ camel_local_store_get_need_summary_check (
+ CAMEL_LOCAL_STORE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+local_store_constructed (GObject *object)
+{
+ CamelLocalStore *local_store;
+ CamelService *service;
+ const gchar *uid;
+
+ local_store = CAMEL_LOCAL_STORE (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (camel_local_store_parent_class)->constructed (object);
+
+ service = CAMEL_SERVICE (object);
+ uid = camel_service_get_uid (service);
+
+ /* XXX This is Evolution-specific policy. */
+ local_store->is_main_store = (g_strcmp0 (uid, "local") == 0);
+}
+
+static gchar *
+local_store_get_name (CamelService *service,
+ gboolean brief)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ gchar *path;
+ gchar *name;
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ if (brief)
+ name = g_strdup (path);
+ else
+ name = g_strdup_printf (_("Local mail file %s"), path);
+
+ g_free (path);
+
+ return name;
+}
+
+static gboolean
+local_store_can_refresh_folder (CamelStore *store,
+ CamelFolderInfo *info,
+ GError **error)
+{
+ /* any local folder can be refreshed */
+ return TRUE;
+}
+
+static CamelFolder *
+local_store_get_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelFolder *folder = NULL;
+ struct stat st;
+ gchar *path;
+
+ service = CAMEL_SERVICE (store);
+
+ settings= camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ if (!g_path_is_absolute (path)) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Store root %s is not an absolute path"), path);
+ goto exit;
+ }
+
+ if (g_stat (path, &st) == 0) {
+ if (!S_ISDIR (st.st_mode)) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Store root %s is not a regular directory"), path);
+ return NULL;
+ }
+ folder = (CamelFolder *) 0xdeadbeef;
+ goto exit;
+ }
+
+ if (errno != ENOENT
+ || (flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot get folder: %s: %s"),
+ path, g_strerror (errno));
+ goto exit;
+ }
+
+ /* need to create the dir heirarchy */
+ if (g_mkdir_with_parents (path, 0700) == -1 && errno != EEXIST) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot get folder: %s: %s"),
+ path, g_strerror (errno));
+ goto exit;
+ }
+
+ folder = (CamelFolder *) 0xdeadbeef;
+
+exit:
+ g_free (path);
+
+ return folder;
+}
+
+static CamelFolderInfo *
+local_store_get_folder_info_sync (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* FIXME: This is broken, but it corresponds to what was
+ * there before.
+ */
+
+ d (printf ("-- LOCAL STORE -- get folder info: %s\n", top));
+
+ return NULL;
+}
+
+static CamelFolder *
+local_store_get_inbox_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Local stores do not have an inbox"));
+
+ return NULL;
+}
+
+static CamelFolder *
+local_store_get_junk_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+
+ /* Chain up to parent's get_junk_folder_sync() method. */
+ folder = CAMEL_STORE_CLASS (camel_local_store_parent_class)->
+ get_junk_folder_sync (store, cancellable, error);
+
+ if (folder) {
+ CamelObject *object = CAMEL_OBJECT (folder);
+ gchar *state;
+
+ state = camel_local_store_get_meta_path (
+ CAMEL_LOCAL_STORE (store),
+ CAMEL_VJUNK_NAME, ".cmeta");
+ camel_object_set_state_filename (object, state);
+ g_free (state);
+
+ /* no defaults? */
+ camel_object_state_read (object);
+ }
+
+ return folder;
+}
+
+static CamelFolder *
+local_store_get_trash_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+
+ /* Chain up to parent's get_trash_folder_sync() method. */
+ folder = CAMEL_STORE_CLASS (camel_local_store_parent_class)->
+ get_trash_folder_sync (store, cancellable, error);
+
+ if (folder) {
+ CamelObject *object = CAMEL_OBJECT (folder);
+ gchar *state;
+
+ state = camel_local_store_get_meta_path (
+ CAMEL_LOCAL_STORE (store),
+ CAMEL_VTRASH_NAME, ".cmeta");
+ camel_object_set_state_filename (object, state);
+ g_free (state);
+
+ /* no defaults? */
+ camel_object_state_read (object);
+ }
+
+ return folder;
+}
+
+static CamelFolderInfo *
+local_store_create_folder_sync (CamelStore *store,
+ const gchar *parent_name,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelFolder *folder;
+ CamelFolderInfo *info = NULL;
+ gchar *name = NULL;
+ gchar *path;
+ struct stat st;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ /* This is a pretty hacky version of create folder, but should basically work */
+
+ if (!g_path_is_absolute (path)) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Store root %s is not an absolute path"), path);
+ goto exit;
+ }
+
+ if (parent_name && *parent_name)
+ name = g_strdup_printf ("%s/%s/%s", path, parent_name, folder_name);
+ else
+ name = g_strdup_printf ("%s/%s", path, folder_name);
+
+ if (g_stat (name, &st) == 0 || errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot get folder: %s: %s"),
+ name, g_strerror (errno));
+ goto exit;
+ }
+
+ g_free (name);
+
+ if (parent_name && *parent_name)
+ name = g_strdup_printf ("%s/%s", parent_name, folder_name);
+ else
+ name = g_strdup_printf ("%s", folder_name);
+
+ folder = CAMEL_STORE_GET_CLASS (store)->get_folder_sync (
+ store, name, CAMEL_STORE_FOLDER_CREATE, cancellable, error);
+ if (folder) {
+ g_object_unref (folder);
+ info = CAMEL_STORE_GET_CLASS (store)->get_folder_info_sync (
+ store, name, 0, cancellable, error);
+ }
+
+exit:
+ g_free (name);
+ g_free (path);
+
+ return info;
+}
+
+/* default implementation, only delete metadata */
+static gboolean
+local_store_delete_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelFolderInfo *fi;
+ CamelFolder *lf;
+ gchar *str = NULL;
+ gchar *name;
+ gchar *path;
+ gboolean success = TRUE;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ /* remove metadata only */
+ name = g_build_filename (path, folder_name, NULL);
+ str = g_strdup_printf ("%s.ibex", name);
+ if (camel_text_index_remove (str) == -1 && errno != ENOENT && errno != ENOTDIR) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not delete folder index file '%s': %s"),
+ str, g_strerror (errno));
+ success = FALSE;
+ goto exit;
+ }
+
+ g_free (str);
+ str = NULL;
+
+ if ((lf = camel_store_get_folder_sync (store, folder_name, 0, cancellable, NULL))) {
+ CamelObject *object = CAMEL_OBJECT (lf);
+ const gchar *state_filename;
+
+ state_filename = camel_object_get_state_filename (object);
+ str = g_strdup (state_filename);
+
+ camel_object_set_state_filename (object, NULL);
+
+ g_object_unref (lf);
+ }
+
+ if (str == NULL)
+ str = g_strdup_printf ("%s.cmeta", name);
+
+ if (g_unlink (str) == -1 && errno != ENOENT && errno != ENOTDIR) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not delete folder meta file '%s': %s"),
+ str, g_strerror (errno));
+ success = FALSE;
+ goto exit;
+ }
+
+ fi = camel_folder_info_new ();
+ fi->full_name = g_strdup (folder_name);
+ fi->display_name = g_path_get_basename (folder_name);
+ fi->unread = -1;
+
+ camel_store_folder_deleted (store, fi);
+ camel_folder_info_free (fi);
+
+exit:
+ g_free (name);
+ g_free (path);
+ g_free (str);
+
+ return success;
+}
+
+/* default implementation, rename all */
+static gboolean
+local_store_rename_folder_sync (CamelStore *store,
+ const gchar *old,
+ const gchar *new,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalFolder *folder;
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ gchar *old_basename;
+ gchar *new_basename;
+ gchar *newibex;
+ gchar *oldibex;
+ gchar *path;
+ gboolean success = TRUE;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ old_basename = g_strdup_printf ("%s.ibex", old);
+ new_basename = g_strdup_printf ("%s.ibex", new);
+
+ oldibex = g_build_filename (path, old_basename, NULL);
+ newibex = g_build_filename (path, new_basename, NULL);
+
+ g_free (old_basename);
+ g_free (new_basename);
+
+ /* try to rollback failures, has obvious races */
+
+ d (printf ("local rename folder '%s' '%s'\n", old, new));
+
+ folder = camel_object_bag_get (store->folders, old);
+ if (folder && folder->index) {
+ if (camel_index_rename (folder->index, newibex) == -1)
+ 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)
+ goto ibex_failed;
+ }
+
+ if (xrename (old, new, path, ".ev-summary", TRUE, error))
+ goto summary_failed;
+
+ if (xrename (old, new, path, ".ev-summary-meta", TRUE, error))
+ goto summary_failed;
+
+ if (xrename (old, new, path, ".cmeta", TRUE, error))
+ goto cmeta_failed;
+
+ if (xrename (old, new, path, "", FALSE, error))
+ goto base_failed;
+
+ g_free (newibex);
+ g_free (oldibex);
+
+ if (folder)
+ g_object_unref (folder);
+
+ goto exit;
+
+ /* The (f)utility of this recovery effort is quesitonable */
+
+base_failed:
+ xrename (new, old, path, ".cmeta", TRUE, NULL);
+
+cmeta_failed:
+ xrename (new, old, path, ".ev-summary", TRUE, NULL);
+ xrename (new, old, path, ".ev-summary-meta", TRUE, NULL);
+summary_failed:
+ if (folder) {
+ if (folder->index)
+ camel_index_rename (folder->index, oldibex);
+ } else
+ camel_text_index_rename (newibex, oldibex);
+ibex_failed:
+ if (error && !*error)
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not rename '%s': %s"),
+ old, g_strerror (errno));
+
+ g_free (newibex);
+ g_free (oldibex);
+
+ if (folder)
+ g_object_unref (folder);
+
+ success = FALSE;
+
+exit:
+ g_free (path);
+
+ return success;
+}
+
+static gchar *
+local_store_get_full_path (CamelLocalStore *ls,
+ const gchar *full_name)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ gchar *filename;
+ gchar *path;
+
+ service = CAMEL_SERVICE (ls);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ filename = g_build_filename (path, full_name, NULL);
+
+ g_free (path);
+
+ return filename;
+}
+
+static gchar *
+local_store_get_meta_path (CamelLocalStore *ls,
+ const gchar *full_name,
+ const gchar *ext)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ gchar *basename;
+ gchar *filename;
+ gchar *path;
+
+ service = CAMEL_SERVICE (ls);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ basename = g_strconcat (full_name, ext, NULL);
+ filename = g_build_filename (path, basename, NULL);
+ g_free (basename);
+
+ g_free (path);
+
+ return filename;
+}
+
+static void
+camel_local_store_class_init (CamelLocalStoreClass *class)
+{
+ GObjectClass *object_class;
+ CamelServiceClass *service_class;
+ CamelStoreClass *store_class;
+
+ g_type_class_add_private (class, sizeof (CamelLocalStorePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = local_store_set_property;
+ object_class->get_property = local_store_get_property;
+ object_class->constructed = local_store_constructed;
+
+ service_class = CAMEL_SERVICE_CLASS (class);
+ service_class->settings_type = CAMEL_TYPE_LOCAL_SETTINGS;
+ service_class->get_name = local_store_get_name;
+
+ store_class = CAMEL_STORE_CLASS (class);
+ store_class->can_refresh_folder = local_store_can_refresh_folder;
+ store_class->get_folder_sync = local_store_get_folder_sync;
+ store_class->get_folder_info_sync = local_store_get_folder_info_sync;
+ store_class->get_inbox_folder_sync = local_store_get_inbox_folder_sync;
+ store_class->get_junk_folder_sync = local_store_get_junk_folder_sync;
+ store_class->get_trash_folder_sync = local_store_get_trash_folder_sync;
+ store_class->create_folder_sync = local_store_create_folder_sync;
+ store_class->delete_folder_sync = local_store_delete_folder_sync;
+ store_class->rename_folder_sync = local_store_rename_folder_sync;
+
+ class->get_full_path = local_store_get_full_path;
+ class->get_meta_path = local_store_get_meta_path;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_NEED_SUMMARY_CHECK,
+ g_param_spec_boolean (
+ "need-summary-check",
+ "Need Summary Check",
+ "Whether to check for unexpected file changes",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_local_store_init (CamelLocalStore *local_store)
+{
+ local_store->priv = CAMEL_LOCAL_STORE_GET_PRIVATE (local_store);
+}
+
+/* Returns whether is this store used as 'On This Computer' main store */
+gboolean
+camel_local_store_is_main_store (CamelLocalStore *store)
+{
+ g_return_val_if_fail (store != NULL, FALSE);
+
+ return store->is_main_store;
+}
+
+gchar *
+camel_local_store_get_full_path (CamelLocalStore *store,
+ const gchar *full_name)
+{
+ CamelLocalStoreClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_LOCAL_STORE (store), NULL);
+ /* XXX Guard against full_name == NULL? */
+
+ class = CAMEL_LOCAL_STORE_GET_CLASS (store);
+ g_return_val_if_fail (class->get_full_path != NULL, NULL);
+
+ return class->get_full_path (store, full_name);
+}
+
+gchar *
+camel_local_store_get_meta_path (CamelLocalStore *store,
+ const gchar *full_name,
+ const gchar *ext)
+{
+ CamelLocalStoreClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_LOCAL_STORE (store), NULL);
+ /* XXX Guard against full_name == NULL? */
+ /* XXX Guard against ext == NULL? */
+
+ class = CAMEL_LOCAL_STORE_GET_CLASS (store);
+ g_return_val_if_fail (class->get_meta_path != NULL, NULL);
+
+ return class->get_meta_path (store, full_name, ext);
+}
+
+guint32
+camel_local_store_get_folder_type_by_full_name (CamelLocalStore *store,
+ const gchar *full_name)
+{
+ g_return_val_if_fail (store != NULL, 0);
+ g_return_val_if_fail (full_name != NULL, 0);
+
+ if (!camel_local_store_is_main_store (store))
+ return CAMEL_FOLDER_TYPE_NORMAL;
+
+ if (g_ascii_strcasecmp (full_name, "Inbox") == 0)
+ return CAMEL_FOLDER_TYPE_INBOX;
+ else if (g_ascii_strcasecmp (full_name, "Outbox") == 0)
+ return CAMEL_FOLDER_TYPE_OUTBOX;
+ else if (g_ascii_strcasecmp (full_name, "Sent") == 0)
+ return CAMEL_FOLDER_TYPE_SENT;
+
+ return CAMEL_FOLDER_TYPE_NORMAL;
+}
+
+/**
+ * camel_local_store_get_need_summary_check:
+ * @store: a #CamelLocalStore
+ *
+ * Returns whether local mail files for @store should be check for
+ * consistency and the summary database synchronized with them. This
+ * is necessary to handle another mail application altering the files,
+ * such as local mail delivery using fetchmail.
+ *
+ * Returns: whether to check for changes in local mail files
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_local_store_get_need_summary_check (CamelLocalStore *store)
+{
+ g_return_val_if_fail (CAMEL_IS_LOCAL_STORE (store), FALSE);
+
+ return store->priv->need_summary_check;
+}
+
+/**
+ * camel_local_store_set_need_summary_check:
+ * @store: a #CamelLocalStore
+ * @need_summary_check: whether to check for changes in local mail files
+ *
+ * Sets whether local mail files for @store should be checked for
+ * consistency and the summary database synchronized with them. This
+ * is necessary to handle another mail application altering the files,
+ * such as local mail delivery using fetchmail.
+ *
+ * Since: 3.2
+ **/
+void
+camel_local_store_set_need_summary_check (CamelLocalStore *store,
+ gboolean need_summary_check)
+{
+ g_return_if_fail (CAMEL_IS_LOCAL_STORE (store));
+
+ if (store->priv->need_summary_check == need_summary_check)
+ return;
+
+ store->priv->need_summary_check = need_summary_check;
+
+ g_object_notify (G_OBJECT (store), "need-summary-check");
+}
diff --git a/src/camel/providers/local/camel-local-store.h b/src/camel/providers/local/camel-local-store.h
new file mode 100644
index 000000000..32e492f6b
--- /dev/null
+++ b/src/camel/providers/local/camel-local-store.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-mbox-store.h : class for an mbox store
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_LOCAL_STORE_H
+#define CAMEL_LOCAL_STORE_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_LOCAL_STORE \
+ (camel_local_store_get_type ())
+#define CAMEL_LOCAL_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_LOCAL_STORE, CamelLocalStore))
+#define CAMEL_LOCAL_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_LOCAL_STORE, CamelLocalStoreClass))
+#define CAMEL_IS_LOCAL_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_LOCAL_STORE))
+#define CAMEL_IS_LOCAL_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_LOCAL_STORE))
+#define CAMEL_LOCAL_STORE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_LOCAL_STORE, CamelLocalStoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelLocalStore CamelLocalStore;
+typedef struct _CamelLocalStoreClass CamelLocalStoreClass;
+typedef struct _CamelLocalStorePrivate CamelLocalStorePrivate;
+
+struct _CamelLocalStore {
+ CamelStore parent;
+ CamelLocalStorePrivate *priv;
+
+ gboolean is_main_store;
+};
+
+struct _CamelLocalStoreClass {
+ CamelStoreClass parent_class;
+
+ gchar * (*get_full_path) (CamelLocalStore *ls,
+ const gchar *full_name);
+ gchar * (*get_meta_path) (CamelLocalStore *ls,
+ const gchar *full_name,
+ const gchar *ext);
+};
+
+GType camel_local_store_get_type (void);
+gboolean camel_local_store_is_main_store (CamelLocalStore *store);
+gchar * camel_local_store_get_full_path (CamelLocalStore *store,
+ const gchar *full_name);
+gchar * camel_local_store_get_meta_path (CamelLocalStore *store,
+ const gchar *full_name,
+ const gchar *ext);
+guint32 camel_local_store_get_folder_type_by_full_name
+ (CamelLocalStore *store,
+ const gchar *full_name);
+gboolean camel_local_store_get_need_summary_check
+ (CamelLocalStore *store);
+void camel_local_store_set_need_summary_check
+ (CamelLocalStore *store,
+ gboolean need_summary_check);
+
+G_END_DECLS
+
+#endif /* CAMEL_LOCAL_STORE_H */
+
diff --git a/src/camel/providers/local/camel-local-summary.c b/src/camel/providers/local/camel-local-summary.c
new file mode 100644
index 000000000..9fee506af
--- /dev/null
+++ b/src/camel/providers/local/camel-local-summary.c
@@ -0,0 +1,770 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include "camel-local-summary.h"
+
+#define w(x)
+#define io(x)
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+#define CAMEL_LOCAL_SUMMARY_VERSION (1)
+
+#define EXTRACT_FIRST_DIGIT(val) val=strtoul (part, &part, 10);
+
+static CamelFIRecord *
+ summary_header_to_db (CamelFolderSummary *,
+ GError **error);
+static gboolean summary_header_from_db (CamelFolderSummary *,
+ CamelFIRecord *);
+
+static CamelMessageInfo *
+ message_info_new_from_header (CamelFolderSummary *,
+ struct _camel_header_raw *);
+
+static gint local_summary_decode_x_evolution
+ (CamelLocalSummary *cls,
+ const gchar *xev,
+ CamelLocalMessageInfo *mi);
+static gchar * local_summary_encode_x_evolution
+ (CamelLocalSummary *cls,
+ const CamelLocalMessageInfo *mi);
+
+static gint local_summary_load (CamelLocalSummary *cls,
+ gint forceindex,
+ GError **error);
+static gint local_summary_check (CamelLocalSummary *cls,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error);
+static gint local_summary_sync (CamelLocalSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error);
+static CamelMessageInfo *
+ local_summary_add (CamelLocalSummary *cls,
+ CamelMimeMessage *msg,
+ const CamelMessageInfo *info,
+ CamelFolderChangeInfo *,
+ GError **error);
+static gint local_summary_need_index (void);
+
+G_DEFINE_TYPE (CamelLocalSummary, camel_local_summary, CAMEL_TYPE_FOLDER_SUMMARY)
+
+static void
+local_summary_dispose (GObject *object)
+{
+ CamelLocalSummary *local_summary;
+
+ local_summary = CAMEL_LOCAL_SUMMARY (object);
+
+ if (local_summary->index != NULL) {
+ g_object_unref (local_summary->index);
+ local_summary->index = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_local_summary_parent_class)->dispose (object);
+}
+
+static void
+local_summary_finalize (GObject *object)
+{
+ CamelLocalSummary *local_summary;
+
+ local_summary = CAMEL_LOCAL_SUMMARY (object);
+
+ g_free (local_summary->folder_path);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_local_summary_parent_class)->finalize (object);
+}
+
+static void
+camel_local_summary_class_init (CamelLocalSummaryClass *class)
+{
+ GObjectClass *object_class;
+ CamelFolderSummaryClass *folder_summary_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = local_summary_dispose;
+ object_class->finalize = local_summary_finalize;
+
+ folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (class);
+ folder_summary_class->message_info_size = sizeof (CamelLocalMessageInfo);
+ folder_summary_class->content_info_size = sizeof (CamelMessageContentInfo);
+ folder_summary_class->summary_header_from_db = summary_header_from_db;
+ folder_summary_class->summary_header_to_db = summary_header_to_db;
+ folder_summary_class->message_info_new_from_header = message_info_new_from_header;
+
+ class->load = local_summary_load;
+ class->check = local_summary_check;
+ class->sync = local_summary_sync;
+ class->add = local_summary_add;
+ class->encode_x_evolution = local_summary_encode_x_evolution;
+ class->decode_x_evolution = local_summary_decode_x_evolution;
+ class->need_index = local_summary_need_index;
+}
+
+static void
+camel_local_summary_init (CamelLocalSummary *local_summary)
+{
+ CamelFolderSummary *folder_summary;
+
+ folder_summary = CAMEL_FOLDER_SUMMARY (local_summary);
+
+ /* and a unique file version */
+ folder_summary->version += CAMEL_LOCAL_SUMMARY_VERSION;
+}
+
+void
+camel_local_summary_construct (CamelLocalSummary *new,
+ const gchar *local_name,
+ CamelIndex *index)
+{
+ camel_folder_summary_set_build_content (CAMEL_FOLDER_SUMMARY (new), FALSE);
+ new->folder_path = g_strdup (local_name);
+ new->index = index;
+ if (index)
+ g_object_ref (index);
+}
+
+static gboolean
+local_summary_load (CamelLocalSummary *cls,
+ gint forceindex,
+ GError **error)
+{
+ d (g_print ("\nlocal_summary_load called \n"));
+ return camel_folder_summary_load_from_db ((CamelFolderSummary *) cls, error);
+}
+
+/* load/check the summary */
+gboolean
+camel_local_summary_load (CamelLocalSummary *cls,
+ gint forceindex,
+ GError **error)
+{
+ CamelLocalSummaryClass *class;
+
+ d (printf ("Loading summary ...\n"));
+
+ class = CAMEL_LOCAL_SUMMARY_GET_CLASS (cls);
+
+ if ((forceindex && class->need_index ())
+ || !class->load (cls, forceindex, error)) {
+ w (g_warning ("Could not load summary: flags may be reset"));
+ camel_folder_summary_clear ((CamelFolderSummary *) cls, NULL);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void camel_local_summary_check_force (CamelLocalSummary *cls)
+{
+ cls->check_force = 1;
+}
+
+gchar *
+camel_local_summary_encode_x_evolution (CamelLocalSummary *cls,
+ const CamelLocalMessageInfo *info)
+{
+ return CAMEL_LOCAL_SUMMARY_GET_CLASS (cls)->encode_x_evolution (cls, info);
+}
+
+gint
+camel_local_summary_decode_x_evolution (CamelLocalSummary *cls,
+ const gchar *xev,
+ CamelLocalMessageInfo *info)
+{
+ return CAMEL_LOCAL_SUMMARY_GET_CLASS (cls)->decode_x_evolution (cls, xev, info);
+}
+
+/*#define DOSTATS*/
+#ifdef DOSTATS
+struct _stat_info {
+ gint mitotal;
+ gint micount;
+ gint citotal;
+ gint cicount;
+ gint msgid;
+ gint msgcount;
+};
+
+static void
+do_stat_ci (CamelLocalSummary *cls,
+ struct _stat_info *info,
+ CamelMessageContentInfo *ci)
+{
+ info->cicount++;
+ info->citotal += ((CamelFolderSummary *) cls)->content_info_size /*+ 4 memchunks are 1/4 byte overhead per mi */;
+ if (ci->id)
+ info->citotal += strlen (ci->id) + 4;
+ if (ci->description)
+ info->citotal += strlen (ci->description) + 4;
+ if (ci->encoding)
+ info->citotal += strlen (ci->encoding) + 4;
+ if (ci->type) {
+ CamelContentType *ct = ci->type;
+ struct _camel_header_param *param;
+
+ info->citotal += sizeof (*ct) + 4;
+ if (ct->type)
+ info->citotal += strlen (ct->type) + 4;
+ if (ct->subtype)
+ info->citotal += strlen (ct->subtype) + 4;
+ param = ct->params;
+ while (param) {
+ info->citotal += sizeof (*param) + 4;
+ if (param->name)
+ info->citotal += strlen (param->name) + 4;
+ if (param->value)
+ info->citotal += strlen (param->value) + 4;
+ param = param->next;
+ }
+ }
+ ci = ci->childs;
+ while (ci) {
+ do_stat_ci (cls, info, ci);
+ ci = ci->next;
+ }
+}
+
+static void
+do_stat_mi (CamelLocalSummary *cls,
+ struct _stat_info *info,
+ CamelMessageInfo *mi)
+{
+ info->micount++;
+ info->mitotal += ((CamelFolderSummary *) cls)->content_info_size /*+ 4 */;
+
+ if (mi->subject)
+ info->mitotal += strlen (mi->subject) + 4;
+ if (mi->to)
+ info->mitotal += strlen (mi->to) + 4;
+ if (mi->from)
+ info->mitotal += strlen (mi->from) + 4;
+ if (mi->cc)
+ info->mitotal += strlen (mi->cc) + 4;
+ if (mi->uid)
+ info->mitotal += strlen (mi->uid) + 4;
+
+ if (mi->references) {
+ info->mitotal += (mi->references->size - 1) * sizeof (CamelSummaryMessageID) + sizeof (CamelSummaryReferences) + 4;
+ info->msgid += (mi->references->size) * sizeof (CamelSummaryMessageID);
+ info->msgcount += mi->references->size;
+ }
+
+ /* dont have any user flags yet */
+
+ if (mi->content) {
+ do_stat_ci (cls, info, mi->content);
+ }
+}
+
+#endif
+
+gint
+camel_local_summary_check (CamelLocalSummary *cls,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSummaryClass *local_summary_class;
+ gint ret;
+
+ local_summary_class = CAMEL_LOCAL_SUMMARY_GET_CLASS (cls);
+ ret = local_summary_class->check (cls, changeinfo, cancellable, error);
+
+#ifdef DOSTATS
+ if (ret != -1) {
+ gint i;
+ CamelFolderSummary *s = (CamelFolderSummary *) cls;
+ GPtrArray *known_uids;
+ struct _stat_info stats = { 0 };
+
+ known_uids = camel_folder_summary_get_array (s);
+ for (i = 0; i < camel_folder_summary_count (s); i++) {
+ CamelMessageInfo *info = camel_folder_summary_get (s, g_ptr_array_index (known_uids, i));
+ do_stat_mi (cls, &stats, info);
+ camel_message_info_unref (info);
+ }
+ camel_folder_summary_free_array (known_uids);
+
+ printf ("\nMemory used by summary:\n\n");
+ printf (
+ "Total of %d messages\n",
+ camel_folder_summary_count (s));
+ printf (
+ "Total: %d bytes (ave %f)\n",
+ stats.citotal + stats.mitotal,
+ (gdouble) (stats.citotal + stats.mitotal) /
+ (gdouble) camel_folder_summary_count (s));
+ printf (
+ "Message Info: %d (ave %f)\n",
+ stats.mitotal,
+ (gdouble) stats.mitotal / (gdouble) stats.micount);
+ printf (
+ "Content Info; %d (ave %f) count %d\n",
+ stats.citotal,
+ (gdouble) stats.citotal / (gdouble) stats.cicount,
+ stats.cicount);
+ printf (
+ "message id's: %d (ave %f) count %d\n",
+ stats.msgid,
+ (gdouble) stats.msgid / (gdouble) stats.msgcount,
+ stats.msgcount);
+ }
+#endif
+ return ret;
+}
+
+gint
+camel_local_summary_sync (CamelLocalSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSummaryClass *local_summary_class;
+
+ local_summary_class = CAMEL_LOCAL_SUMMARY_GET_CLASS (cls);
+
+ return local_summary_class->sync (cls, expunge, changeinfo, cancellable, error);
+}
+
+CamelMessageInfo *
+camel_local_summary_add (CamelLocalSummary *cls,
+ CamelMimeMessage *msg,
+ const CamelMessageInfo *info,
+ CamelFolderChangeInfo *ci,
+ GError **error)
+{
+ CamelLocalSummaryClass *local_summary_class;
+
+ local_summary_class = CAMEL_LOCAL_SUMMARY_GET_CLASS (cls);
+
+ return local_summary_class->add (cls, msg, info, ci, error);
+}
+
+/**
+ * camel_local_summary_write_headers:
+ * @fd:
+ * @header:
+ * @xevline:
+ * @status:
+ * @xstatus:
+ *
+ * Write a bunch of headers to the file @fd. IF xevline is non NULL, then
+ * an X-Evolution header line is created at the end of all of the headers.
+ * If @status is non NULL, then a Status header line is also written.
+ * The headers written are termianted with a blank line.
+ *
+ * Returns: -1 on error, otherwise the number of bytes written.
+ **/
+gint
+camel_local_summary_write_headers (gint fd,
+ struct _camel_header_raw *header,
+ const gchar *xevline,
+ const gchar *status,
+ const gchar *xstatus)
+{
+ gint outlen = 0, len;
+ gint newfd;
+ FILE *out;
+
+ /* dum de dum, maybe the whole sync function should just use stdio for output */
+ newfd = dup (fd);
+ if (newfd == -1)
+ return -1;
+
+ out = fdopen (newfd, "w");
+ if (out == NULL) {
+ close (newfd);
+ errno = EINVAL;
+ return -1;
+ }
+
+ while (header) {
+ if (strcmp (header->name, "X-Evolution") != 0
+ && (status == NULL || strcmp (header->name, "Status") != 0)
+ && (xstatus == NULL || strcmp (header->name, "X-Status") != 0)) {
+ len = fprintf (out, "%s:%s\n", header->name, header->value);
+ if (len == -1) {
+ fclose (out);
+ return -1;
+ }
+ outlen += len;
+ }
+ header = header->next;
+ }
+
+ if (status) {
+ len = fprintf (out, "Status: %s\n", status);
+ if (len == -1) {
+ fclose (out);
+ return -1;
+ }
+ outlen += len;
+ }
+
+ if (xstatus) {
+ len = fprintf (out, "X-Status: %s\n", xstatus);
+ if (len == -1) {
+ fclose (out);
+ return -1;
+ }
+ outlen += len;
+ }
+
+ if (xevline) {
+ len = fprintf (out, "X-Evolution: %s\n", xevline);
+ if (len == -1) {
+ fclose (out);
+ return -1;
+ }
+ outlen += len;
+ }
+
+ len = fprintf (out, "\n");
+ if (len == -1) {
+ fclose (out);
+ return -1;
+ }
+ outlen += len;
+
+ if (fclose (out) == -1)
+ return -1;
+
+ return outlen;
+}
+
+static gint
+local_summary_check (CamelLocalSummary *cls,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* FIXME: sync index here ? */
+ return 0;
+}
+
+static gint
+local_summary_sync (CamelLocalSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderSummary *folder_summary;
+
+ folder_summary = CAMEL_FOLDER_SUMMARY (cls);
+
+ if (!camel_folder_summary_save_to_db (folder_summary, error)) {
+ g_warning ("Could not save summary for local providers");
+ return -1;
+ }
+
+ if (cls->index && camel_index_sync (cls->index) == -1) {
+ g_warning ("Could not sync index for %s: %s", cls->folder_path, g_strerror (errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static gint
+local_summary_need_index (void)
+{
+ return 1;
+}
+
+static CamelMessageInfo *
+local_summary_add (CamelLocalSummary *cls,
+ CamelMimeMessage *msg,
+ const CamelMessageInfo *info,
+ CamelFolderChangeInfo *ci,
+ GError **error)
+{
+ CamelFolderSummary *summary;
+ CamelMessageInfo *mi;
+ CamelMessageInfoBase *mi_base;
+ gchar *xev;
+
+ d (printf ("Adding message to summary\n"));
+
+ summary = CAMEL_FOLDER_SUMMARY (cls);
+
+ mi = camel_folder_summary_info_new_from_message (summary, msg, NULL);
+ camel_folder_summary_add (summary, mi);
+
+ mi_base = (CamelMessageInfoBase *) mi;
+
+ if (info) {
+ const CamelTag *tag = camel_message_info_get_user_tags (info);
+ const CamelFlag *flag = camel_message_info_get_user_flags (info);
+
+ while (flag) {
+ camel_message_info_set_user_flag (mi, flag->name, TRUE);
+ flag = flag->next;
+ }
+
+ while (tag) {
+ camel_message_info_set_user_tag (mi, tag->name, tag->value);
+ tag = tag->next;
+ }
+
+ camel_message_info_set_flags (mi, 0xffff, camel_message_info_get_flags (info));
+ mi_base->size = camel_message_info_get_size (info);
+ }
+
+ /* we need to calculate the size ourselves */
+ if (camel_message_info_get_size (mi) == 0) {
+ CamelStreamNull *sn = (CamelStreamNull *) camel_stream_null_new ();
+
+ camel_data_wrapper_write_to_stream_sync (
+ (CamelDataWrapper *) msg,
+ (CamelStream *) sn, NULL, NULL);
+ mi_base->size = sn->written;
+ g_object_unref (sn);
+ }
+
+ mi_base->flags &= ~(CAMEL_MESSAGE_FOLDER_NOXEV);
+ xev = camel_local_summary_encode_x_evolution (
+ cls, (CamelLocalMessageInfo *) mi);
+ camel_medium_set_header ((CamelMedium *) msg, "X-Evolution", xev);
+ g_free (xev);
+ camel_folder_change_info_add_uid (ci, camel_message_info_get_uid (mi));
+
+ return mi;
+}
+
+static gchar *
+local_summary_encode_x_evolution (CamelLocalSummary *cls,
+ const CamelLocalMessageInfo *mi)
+{
+ GString *out = g_string_new ("");
+ struct _camel_header_param *params = NULL;
+ CamelFlag *flag = mi->info.user_flags;
+ CamelTag *tag = mi->info.user_tags;
+ gchar *ret;
+ const gchar *p, *uidstr;
+ guint32 uid;
+
+ /* FIXME: work out what to do with uid's that aren't stored here? */
+ /* FIXME: perhaps make that a mbox folder only issue?? */
+ p = uidstr = camel_message_info_get_uid (mi);
+ while (*p && isdigit (*p))
+ p++;
+ if (*p == 0 && sscanf (uidstr, "%u", &uid) == 1) {
+ g_string_printf (out, "%08x-%04x", uid, mi->info.flags & 0xffff);
+ } else {
+ g_string_printf (out, "%s-%04x", uidstr, mi->info.flags & 0xffff);
+ }
+
+ if (flag || tag) {
+ GString *val = g_string_new ("");
+
+ if (flag) {
+ while (flag) {
+ g_string_append (val, flag->name);
+ if (flag->next)
+ g_string_append_c (val, ',');
+ flag = flag->next;
+ }
+ camel_header_set_param (&params, "flags", val->str);
+ g_string_truncate (val, 0);
+ }
+ if (tag) {
+ while (tag) {
+ g_string_append (val, tag->name);
+ g_string_append_c (val, '=');
+ g_string_append (val, tag->value);
+ if (tag->next)
+ g_string_append_c (val, ',');
+ tag = tag->next;
+ }
+ camel_header_set_param (&params, "tags", val->str);
+ }
+ g_string_free (val, TRUE);
+ camel_header_param_list_format_append (out, params);
+ camel_header_param_list_free (params);
+ }
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ return ret;
+}
+
+static gint
+local_summary_decode_x_evolution (CamelLocalSummary *cls,
+ const gchar *xev,
+ CamelLocalMessageInfo *mi)
+{
+ struct _camel_header_param *params, *scan;
+ guint32 uid, flags;
+ gchar *header;
+ gint i;
+ gchar uidstr[20];
+
+ uidstr[0] = 0;
+
+ /* check for uid/flags */
+ header = camel_header_token_decode (xev);
+ if (header && strlen (header) == strlen ("00000000-0000")
+ && sscanf (header, "%08x-%04x", &uid, &flags) == 2) {
+ if (mi)
+ g_snprintf (uidstr, sizeof (uidstr), "%u", uid);
+ } else {
+ g_free (header);
+ return -1;
+ }
+ g_free (header);
+
+ if (mi == NULL)
+ return 0;
+
+ /* check for additional data */
+ header = strchr (xev, ';');
+ if (header) {
+ params = camel_header_param_list_decode (header + 1);
+ scan = params;
+ while (scan) {
+ if (!g_ascii_strcasecmp (scan->name, "flags")) {
+ gchar **flagv = g_strsplit (scan->value, ",", 1000);
+
+ for (i = 0; flagv[i]; i++)
+ camel_message_info_set_user_flag ((CamelMessageInfo *) mi, flagv[i], TRUE);
+ g_strfreev (flagv);
+ } else if (!g_ascii_strcasecmp (scan->name, "tags")) {
+ gchar **tagv = g_strsplit (scan->value, ",", 10000);
+ gchar *val;
+
+ for (i = 0; tagv[i]; i++) {
+ val = strchr (tagv[i], '=');
+ if (val) {
+ *val++ = 0;
+ camel_message_info_set_user_tag ((CamelMessageInfo *) mi, tagv[i], val);
+ val[-1]='=';
+ }
+ }
+ g_strfreev (tagv);
+ }
+ scan = scan->next;
+ }
+ camel_header_param_list_free (params);
+ }
+
+ mi->info.uid = camel_pstring_strdup (uidstr);
+ mi->info.flags = flags;
+
+ return 0;
+}
+
+static gboolean
+summary_header_from_db (CamelFolderSummary *s,
+ CamelFIRecord *fir)
+{
+ CamelLocalSummary *cls = (CamelLocalSummary *) s;
+ gchar *part, *tmp;
+
+ /* We dont actually add our own headers, but version that we don't anyway */
+
+ if (!CAMEL_FOLDER_SUMMARY_CLASS (camel_local_summary_parent_class)->summary_header_from_db (s, fir))
+ return FALSE;
+
+ part = fir->bdata;
+ if (part) {
+ EXTRACT_FIRST_DIGIT (cls->version)
+ }
+
+ /* keep only the rest of the bdata there (strip our version digit) */
+ tmp = g_strdup (part);
+ g_free (fir->bdata);
+ fir->bdata = tmp;
+
+ return TRUE;
+}
+
+static struct _CamelFIRecord *
+summary_header_to_db (CamelFolderSummary *s,
+ GError **error)
+{
+ CamelFolderSummaryClass *folder_summary_class;
+ struct _CamelFIRecord *fir;
+
+ /* Chain up to parent's summary_header_to_db() method. */
+ folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (camel_local_summary_parent_class);
+ fir = folder_summary_class->summary_header_to_db (s, NULL);
+ if (fir)
+ fir->bdata = g_strdup_printf ("%d", CAMEL_LOCAL_SUMMARY_VERSION);
+
+ return fir;
+}
+
+static CamelMessageInfo *
+message_info_new_from_header (CamelFolderSummary *s,
+ struct _camel_header_raw *h)
+{
+ CamelLocalMessageInfo *mi;
+ CamelLocalSummary *cls = (CamelLocalSummary *) s;
+
+ mi = (CamelLocalMessageInfo *) CAMEL_FOLDER_SUMMARY_CLASS (camel_local_summary_parent_class)->message_info_new_from_header (s, h);
+ if (mi) {
+ const gchar *xev;
+ gint doindex = FALSE;
+
+ xev = camel_header_raw_find (&h, "X-Evolution", NULL);
+ if (xev == NULL || camel_local_summary_decode_x_evolution (cls, xev, mi) == -1) {
+ /* to indicate it has no xev header */
+ mi->info.flags |= CAMEL_MESSAGE_FOLDER_FLAGGED | CAMEL_MESSAGE_FOLDER_NOXEV;
+ mi->info.dirty = TRUE;
+ camel_pstring_free (mi->info.uid);
+ mi->info.uid = camel_pstring_add (camel_folder_summary_next_uid_string (s), TRUE);
+
+ /* shortcut, no need to look it up in the index library */
+ doindex = TRUE;
+ }
+
+ if (cls->index
+ && (doindex
+ || cls->index_force
+ || !camel_index_has_name (cls->index, camel_message_info_get_uid (mi)))) {
+ d (printf ("Am indexing message %s\n", camel_message_info_get_uid (mi)));
+ camel_folder_summary_set_index (s, cls->index);
+ } else {
+ d (printf ("Not indexing message %s\n", camel_message_info_get_uid (mi)));
+ camel_folder_summary_set_index (s, NULL);
+ }
+ }
+
+ return (CamelMessageInfo *) mi;
+}
diff --git a/src/camel/providers/local/camel-local-summary.h b/src/camel/providers/local/camel-local-summary.h
new file mode 100644
index 000000000..496b7875e
--- /dev/null
+++ b/src/camel/providers/local/camel-local-summary.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_LOCAL_SUMMARY_H
+#define CAMEL_LOCAL_SUMMARY_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_LOCAL_SUMMARY \
+ (camel_local_summary_get_type ())
+#define CAMEL_LOCAL_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_LOCAL_SUMMARY, CamelLocalSummary))
+#define CAMEL_LOCAL_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_LOCAL_SUMMARY, CamelLocalSummaryClass))
+#define CAMEL_IS_LOCAL_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_LOCAL_SUMMARY))
+#define CAMEL_IS_LOCAL_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_LOCAL_SUMMARY))
+#define CAMEL_LOCAL_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_LOCAL_SUMMARY, CamelLocalSummaryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelLocalSummary CamelLocalSummary;
+typedef struct _CamelLocalSummaryClass CamelLocalSummaryClass;
+
+/* extra summary flags */
+enum {
+ CAMEL_MESSAGE_FOLDER_NOXEV = 1 << 17,
+ CAMEL_MESSAGE_FOLDER_XEVCHANGE = 1 << 18,
+ CAMEL_MESSAGE_FOLDER_NOTSEEN = 1 << 19 /* have we seen this in processing this loop? */
+};
+
+typedef struct _CamelLocalMessageInfo CamelLocalMessageInfo;
+
+struct _CamelLocalMessageInfo {
+ CamelMessageInfoBase info;
+};
+
+struct _CamelLocalSummary {
+ CamelFolderSummary parent;
+
+ guint32 version; /* file version being loaded */
+
+ gchar *folder_path; /* name of matching folder */
+
+ CamelIndex *index;
+ guint index_force:1; /* do we force index during creation? */
+ guint check_force:1; /* does a check force a full check? */
+};
+
+struct _CamelLocalSummaryClass {
+ CamelFolderSummaryClass parent_class;
+
+ gboolean (*load)(CamelLocalSummary *cls, gint forceindex, GError **error);
+ gint (*check)(CamelLocalSummary *cls, CamelFolderChangeInfo *changeinfo, GCancellable *cancellable, GError **error);
+ gint (*sync)(CamelLocalSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, GCancellable *cancellable, GError **error);
+ CamelMessageInfo *(*add)(CamelLocalSummary *cls, CamelMimeMessage *msg, const CamelMessageInfo *info, CamelFolderChangeInfo *, GError **error);
+
+ gchar *(*encode_x_evolution)(CamelLocalSummary *cls, const CamelLocalMessageInfo *info);
+ gint (*decode_x_evolution)(CamelLocalSummary *cls, const gchar *xev, CamelLocalMessageInfo *info);
+ gint (*need_index)(void);
+};
+
+GType camel_local_summary_get_type (void);
+void camel_local_summary_construct (CamelLocalSummary *new, const gchar *local_name, CamelIndex *index);
+
+/* load/check the summary */
+gboolean camel_local_summary_load (CamelLocalSummary *cls, gint forceindex, GError **error);
+/* check for new/removed messages */
+gint camel_local_summary_check (CamelLocalSummary *cls, CamelFolderChangeInfo *, GCancellable *cancellable, GError **error);
+/* perform a folder sync or expunge, if needed */
+gint camel_local_summary_sync (CamelLocalSummary *cls, gboolean expunge, CamelFolderChangeInfo *, GCancellable *cancellable, GError **error);
+/* add a new message to the summary */
+CamelMessageInfo *camel_local_summary_add (CamelLocalSummary *cls, CamelMimeMessage *msg, const CamelMessageInfo *info, CamelFolderChangeInfo *, GError **error);
+
+/* force the next check to be a full check/rebuild */
+void camel_local_summary_check_force (CamelLocalSummary *cls);
+
+/* generate an X-Evolution header line */
+gchar *camel_local_summary_encode_x_evolution (CamelLocalSummary *cls, const CamelLocalMessageInfo *info);
+gint camel_local_summary_decode_x_evolution (CamelLocalSummary *cls, const gchar *xev, CamelLocalMessageInfo *info);
+
+/* utility functions - write headers to a file with optional X-Evolution header and/or status header */
+gint camel_local_summary_write_headers (gint fd, struct _camel_header_raw *header, const gchar *xevline, const gchar *status, const gchar *xstatus);
+
+G_END_DECLS
+
+#endif /* CAMEL_LOCAL_SUMMARY_H */
diff --git a/src/camel/providers/local/camel-maildir-folder.c b/src/camel/providers/local/camel-maildir-folder.c
new file mode 100644
index 000000000..7ec56fdb3
--- /dev/null
+++ b/src/camel/providers/local/camel-maildir-folder.c
@@ -0,0 +1,513 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include "camel-maildir-folder.h"
+#include "camel-maildir-store.h"
+#include "camel-maildir-summary.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+G_DEFINE_TYPE (CamelMaildirFolder, camel_maildir_folder, CAMEL_TYPE_LOCAL_FOLDER)
+
+static gint
+maildir_folder_cmp_uids (CamelFolder *folder,
+ const gchar *uid1,
+ const gchar *uid2)
+{
+ CamelMessageInfo *a, *b;
+ time_t tma, tmb;
+
+ g_return_val_if_fail (folder != NULL, 0);
+ g_return_val_if_fail (folder->summary != NULL, 0);
+
+ a = camel_folder_summary_get (folder->summary, uid1);
+ b = camel_folder_summary_get (folder->summary, uid2);
+
+ if (!a || !b) {
+ /* It's not a problem when one of the messages is not in the summary */
+ if (a)
+ camel_message_info_unref (a);
+ if (b)
+ camel_message_info_unref (b);
+
+ if (a == b)
+ return 0;
+ if (!a)
+ return -1;
+ return 1;
+ }
+
+ tma = camel_message_info_get_date_received (a);
+ tmb = camel_message_info_get_date_received (b);
+
+ camel_message_info_unref (a);
+ camel_message_info_unref (b);
+
+ return tma < tmb ? -1 : tma == tmb ? 0 : 1;
+}
+
+static void
+maildir_folder_sort_uids (CamelFolder *folder,
+ GPtrArray *uids)
+{
+ g_return_if_fail (camel_maildir_folder_parent_class != NULL);
+ g_return_if_fail (folder != NULL);
+
+ if (uids && uids->len > 1)
+ camel_folder_summary_prepare_fetch_all (folder->summary, NULL);
+
+ /* Chain up to parent's sort_uids() method. */
+ CAMEL_FOLDER_CLASS (camel_maildir_folder_parent_class)->sort_uids (folder, uids);
+}
+
+static gchar *
+maildir_folder_get_filename (CamelFolder *folder,
+ const gchar *uid,
+ GError **error)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *) folder;
+ CamelMaildirMessageInfo *mdi;
+ CamelMessageInfo *info;
+ gchar *res;
+
+ /* get the message summary info */
+ if ((info = camel_folder_summary_get (folder->summary, uid)) == NULL) {
+ set_cannot_get_message_ex (
+ error, CAMEL_FOLDER_ERROR_INVALID_UID,
+ uid, lf->folder_path, _("No such message"));
+ return NULL;
+ }
+
+ mdi = (CamelMaildirMessageInfo *) info;
+
+ /* If filename is NULL, it means folder_summary_check is not yet executed.
+ * Try to find the file in the folder and use it, otherwise construct its
+ * name based on actual flags.
+ */
+ if (!camel_maildir_info_filename (mdi)) {
+ const gchar *uid = camel_message_info_get_uid (info);
+
+ if (uid) {
+ GDir *dir;
+ gchar *dirname;
+
+ dirname = g_strdup_printf ("%s/cur", lf->folder_path);
+ dir = g_dir_open (dirname, 0, NULL);
+ g_free (dirname);
+
+ if (dir) {
+ const gchar *filename;
+ gint uid_len = strlen (uid);
+
+ while (filename = g_dir_read_name (dir), filename) {
+ if (g_str_has_prefix (filename, uid) && (filename[uid_len] == '\0' || filename[uid_len] == CAMEL_MAILDIR_FLAG_SEP)) {
+ camel_maildir_info_set_filename (mdi, g_strdup (filename));
+ break;
+ }
+ }
+
+ g_dir_close (dir);
+ }
+ }
+
+ if (!camel_maildir_info_filename (mdi)) {
+ camel_maildir_info_set_filename (mdi, camel_maildir_summary_info_to_name (mdi));
+ }
+ }
+
+ res = g_strdup_printf ("%s/cur/%s", lf->folder_path, camel_maildir_info_filename (mdi));
+
+ camel_message_info_unref (info);
+
+ return res;
+}
+
+static gboolean
+maildir_folder_append_message_sync (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *) folder;
+ CamelStream *output_stream;
+ CamelMessageInfo *mi;
+ CamelMaildirMessageInfo *mdi;
+ gchar *name, *dest = NULL;
+ gboolean success = TRUE, has_attachment;
+
+ d (printf ("Appending message\n"));
+
+ /* If we can't lock, don't do anything */
+ if (!lf || camel_local_folder_lock (lf, CAMEL_LOCK_WRITE, error) == -1)
+ return FALSE;
+
+ /* add it to the summary/assign the uid, etc */
+ mi = camel_local_summary_add (
+ CAMEL_LOCAL_SUMMARY (folder->summary),
+ message, info, lf->changes, error);
+ if (mi == NULL)
+ goto check_changed;
+
+ has_attachment = camel_mime_message_has_attachment (message);
+ if (((camel_message_info_get_flags (mi) & CAMEL_MESSAGE_ATTACHMENTS) && !has_attachment) ||
+ ((camel_message_info_get_flags (mi) & CAMEL_MESSAGE_ATTACHMENTS) == 0 && has_attachment)) {
+ camel_message_info_set_flags (mi, CAMEL_MESSAGE_ATTACHMENTS, has_attachment ? CAMEL_MESSAGE_ATTACHMENTS : 0);
+ }
+
+ mdi = (CamelMaildirMessageInfo *) mi;
+
+ d (printf ("Appending message: uid is %s filename is %s\n", camel_message_info_get_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_get_uid (mi));
+ output_stream = camel_stream_fs_new_with_name (
+ name, O_WRONLY | O_CREAT, 0600, error);
+ if (output_stream == NULL)
+ goto fail_write;
+
+ if (camel_data_wrapper_write_to_stream_sync (
+ (CamelDataWrapper *) message, output_stream, cancellable, error) == -1
+ || camel_stream_close (output_stream, cancellable, error) == -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 (g_rename (name, dest) == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ goto fail_write;
+ }
+
+ g_free (dest);
+ g_free (name);
+
+ if (appended_uid)
+ *appended_uid = g_strdup(camel_message_info_get_uid(mi));
+
+ if (output_stream)
+ g_object_unref (output_stream);
+
+ goto check_changed;
+
+ fail_write:
+
+ /* remove the summary info so we are not out-of-sync with the mh folder */
+ camel_folder_summary_remove (CAMEL_FOLDER_SUMMARY (folder->summary), mi);
+
+ g_prefix_error (
+ error, _("Cannot append message to maildir folder: %s: "),
+ name);
+
+ if (output_stream) {
+ g_object_unref (output_stream);
+ unlink (name);
+ }
+
+ g_free (name);
+ g_free (dest);
+
+ success = FALSE;
+
+ check_changed:
+ camel_local_folder_unlock (lf);
+
+ if (camel_folder_change_info_changed (lf->changes)) {
+ camel_folder_changed (folder, lf->changes);
+ camel_folder_change_info_clear (lf->changes);
+ }
+
+ return success;
+}
+
+static CamelMimeMessage *
+maildir_folder_get_message_sync (CamelFolder *folder,
+ const gchar *uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *) folder;
+ CamelStream *message_stream = NULL;
+ CamelMimeMessage *message = NULL;
+ gchar *name = NULL;
+
+ d (printf ("getting message: %s\n", uid));
+
+ if (!lf || camel_local_folder_lock (lf, CAMEL_LOCK_WRITE, error) == -1)
+ return NULL;
+
+ name = maildir_folder_get_filename (folder, uid, error);
+ if (!name)
+ goto fail;
+
+ message_stream = camel_stream_fs_new_with_name (
+ name, O_RDONLY, 0, error);
+ if (message_stream == NULL) {
+ g_prefix_error (
+ error, _("Cannot get message %s from folder %s: "),
+ uid, lf->folder_path);
+ goto fail;
+ }
+
+ message = camel_mime_message_new ();
+ if (!camel_data_wrapper_construct_from_stream_sync (
+ (CamelDataWrapper *) message,
+ message_stream, cancellable, error)) {
+ g_prefix_error (
+ error, _("Cannot get message %s from folder %s: "),
+ uid, lf->folder_path);
+ g_object_unref (message);
+ message = NULL;
+
+ }
+ g_object_unref (message_stream);
+ fail:
+ g_free (name);
+
+ camel_local_folder_unlock (lf);
+
+ if (lf && camel_folder_change_info_changed (lf->changes)) {
+ camel_folder_changed (folder, lf->changes);
+ camel_folder_change_info_clear (lf->changes);
+ }
+
+ return message;
+}
+
+static gboolean
+maildir_folder_transfer_messages_to_sync (CamelFolder *source,
+ GPtrArray *uids,
+ CamelFolder *dest,
+ gboolean delete_originals,
+ GPtrArray **transferred_uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean fallback = FALSE;
+
+ if (delete_originals && CAMEL_IS_MAILDIR_FOLDER (source) && CAMEL_IS_MAILDIR_FOLDER (dest)
+ && camel_folder_get_parent_store (source) == camel_folder_get_parent_store (dest)) {
+ gint i;
+ CamelLocalFolder *lf = (CamelLocalFolder *) source;
+ CamelLocalFolder *df = (CamelLocalFolder *) dest;
+
+ camel_operation_push_message (
+ cancellable, _("Moving messages"));
+
+ camel_folder_freeze (dest);
+ camel_folder_freeze (source);
+
+ for (i = 0; i < uids->len; i++) {
+ gchar *uid = (gchar *) uids->pdata[i];
+ gchar *s_filename, *d_filename, *new_filename;
+ CamelMaildirMessageInfo *mdi;
+ CamelMessageInfo *info;
+
+ if ((info = camel_folder_summary_get (source->summary, uid)) == NULL) {
+ set_cannot_get_message_ex (
+ error, CAMEL_FOLDER_ERROR_INVALID_UID,
+ uid, lf->folder_path, _("No such message"));
+ return FALSE;
+ }
+
+ mdi = (CamelMaildirMessageInfo *) info;
+ new_filename = camel_maildir_summary_info_to_name (mdi);
+
+ d_filename = g_strdup_printf ("%s/cur/%s", df->folder_path, new_filename);
+ s_filename = g_strdup_printf ("%s/cur/%s", lf->folder_path, camel_maildir_info_filename (mdi));
+
+ if (g_rename (s_filename, d_filename) != 0) {
+ if (errno == EXDEV) {
+ i = uids->len + 1;
+ fallback = TRUE;
+ } else {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot transfer message to destination folder: %s"),
+ g_strerror (errno));
+ camel_message_info_unref (info);
+ g_free (s_filename);
+ g_free (d_filename);
+ g_free (new_filename);
+ break;
+ }
+ } else {
+ CamelMessageInfo *clone;
+ CamelMaildirMessageInfo *mclone;
+
+ clone = camel_message_info_clone (info);
+ clone->summary = dest->summary;
+
+ mclone = (CamelMaildirMessageInfo *) clone;
+ /* preserve also UID, as it matches the file name */
+ mclone->info.info.uid = camel_pstring_strdup (camel_message_info_get_uid (info));
+ camel_maildir_info_set_filename (clone, g_strdup (new_filename));
+ /* unset deleted flag when transferring from trash folder */
+ if ((source->folder_flags & CAMEL_FOLDER_IS_TRASH) != 0)
+ camel_message_info_set_flags (info, CAMEL_MESSAGE_DELETED, 0);
+ /* unset junk flag when transferring from junk folder */
+ if ((source->folder_flags & CAMEL_FOLDER_IS_JUNK) != 0)
+ camel_message_info_set_flags (info, CAMEL_MESSAGE_JUNK, 0);
+ camel_folder_summary_add (dest->summary, clone);
+
+ camel_folder_change_info_add_uid (df->changes, camel_message_info_get_uid (clone));
+
+ camel_folder_set_message_flags (
+ source, uid, CAMEL_MESSAGE_DELETED |
+ CAMEL_MESSAGE_SEEN, ~0);
+ camel_folder_change_info_remove_uid (lf->changes, camel_message_info_get_uid (info));
+ camel_folder_summary_remove (source->summary, info);
+ }
+
+ camel_message_info_unref (info);
+ g_free (s_filename);
+ g_free (d_filename);
+ g_free (new_filename);
+ }
+
+ if (lf && camel_folder_change_info_changed (lf->changes)) {
+ camel_folder_changed (source, lf->changes);
+ camel_folder_change_info_clear (lf->changes);
+ }
+
+ if (df && camel_folder_change_info_changed (df->changes)) {
+ camel_folder_changed (dest, df->changes);
+ camel_folder_change_info_clear (df->changes);
+ }
+
+ camel_folder_thaw (source);
+ camel_folder_thaw (dest);
+
+ camel_operation_pop_message (cancellable);
+ } else
+ fallback = TRUE;
+
+ if (fallback) {
+ CamelFolderClass *folder_class;
+
+ /* Chain up to parent's transfer_messages_to() method. */
+ folder_class = CAMEL_FOLDER_CLASS (camel_maildir_folder_parent_class);
+ return folder_class->transfer_messages_to_sync (
+ source, uids, dest, delete_originals,
+ transferred_uids, cancellable, error);
+ }
+
+ return TRUE;
+}
+
+static CamelLocalSummary *
+maildir_folder_create_summary (CamelLocalFolder *lf,
+ const gchar *folder,
+ CamelIndex *index)
+{
+ return (CamelLocalSummary *) camel_maildir_summary_new (
+ CAMEL_FOLDER (lf), folder, index);
+}
+
+static void
+camel_maildir_folder_class_init (CamelMaildirFolderClass *class)
+{
+ CamelFolderClass *folder_class;
+ CamelLocalFolderClass *local_folder_class;
+
+ folder_class = CAMEL_FOLDER_CLASS (class);
+ folder_class->cmp_uids = maildir_folder_cmp_uids;
+ folder_class->sort_uids = maildir_folder_sort_uids;
+ folder_class->get_filename = maildir_folder_get_filename;
+ folder_class->append_message_sync = maildir_folder_append_message_sync;
+ folder_class->get_message_sync = maildir_folder_get_message_sync;
+ folder_class->transfer_messages_to_sync = maildir_folder_transfer_messages_to_sync;
+
+ local_folder_class = CAMEL_LOCAL_FOLDER_CLASS (class);
+ local_folder_class->create_summary = maildir_folder_create_summary;
+}
+
+static void
+camel_maildir_folder_init (CamelMaildirFolder *maildir_folder)
+{
+}
+
+CamelFolder *
+camel_maildir_folder_new (CamelStore *parent_store,
+ const gchar *full_name,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelService *service;
+ CamelSettings *settings;
+ gboolean filter_inbox;
+ gchar *basename;
+
+ d (printf ("Creating maildir folder: %s\n", full_name));
+
+ if (g_strcmp0 (full_name, ".") == 0)
+ basename = g_strdup (_("Inbox"));
+ else
+ basename = g_path_get_basename (full_name);
+
+ folder = g_object_new (
+ CAMEL_TYPE_MAILDIR_FOLDER,
+ "display-name", basename,
+ "full-name", full_name,
+ "parent-store", parent_store,
+ NULL);
+
+ service = CAMEL_SERVICE (parent_store);
+
+ settings = camel_service_ref_settings (service);
+
+ filter_inbox = camel_store_settings_get_filter_inbox (
+ CAMEL_STORE_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ if (filter_inbox && (g_str_equal (full_name, ".") || g_ascii_strcasecmp (full_name, "Inbox") == 0))
+ folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
+
+ folder = (CamelFolder *) camel_local_folder_construct (
+ CAMEL_LOCAL_FOLDER (folder), flags, cancellable, error);
+
+ g_free (basename);
+
+ /* indexing doesn't work with maildir properly, thus disable it */
+ g_object_set (folder, "index-body", FALSE, NULL);
+
+ return folder;
+}
+
diff --git a/src/camel/providers/local/camel-maildir-folder.h b/src/camel/providers/local/camel-maildir-folder.h
new file mode 100644
index 000000000..08a08de21
--- /dev/null
+++ b/src/camel/providers/local/camel-maildir-folder.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_MAILDIR_FOLDER_H
+#define CAMEL_MAILDIR_FOLDER_H
+
+#include "camel-local-folder.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MAILDIR_FOLDER \
+ (camel_maildir_folder_get_type ())
+#define CAMEL_MAILDIR_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MAILDIR_FOLDER, CamelMaildirFolder))
+#define CAMEL_MAILDIR_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MAILDIR_FOLDER, CamelMaildirFolderClass))
+#define CAMEL_IS_MAILDIR_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MAILDIR_FOLDER))
+#define CAMEL_IS_MAILDIR_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MAILDIR_FOLDER))
+#define CAMEL_MAILDIR_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MAILDIR_FOLDER, CamelMaildirFolderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMaildirFolder CamelMaildirFolder;
+typedef struct _CamelMaildirFolderClass CamelMaildirFolderClass;
+
+struct _CamelMaildirFolder {
+ CamelLocalFolder parent;
+};
+
+struct _CamelMaildirFolderClass {
+ CamelLocalFolderClass parent_class;
+};
+
+GType camel_maildir_folder_get_type (void);
+CamelFolder * camel_maildir_folder_new (CamelStore *parent_store,
+ const gchar *full_name,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_MAILDIR_FOLDER_H */
diff --git a/src/camel/providers/local/camel-maildir-store.c b/src/camel/providers/local/camel-maildir-store.c
new file mode 100644
index 000000000..be483b15e
--- /dev/null
+++ b/src/camel/providers/local/camel-maildir-store.c
@@ -0,0 +1,1375 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-maildir-folder.h"
+#include "camel-maildir-store.h"
+#include "camel-maildir-summary.h"
+
+#define d(x)
+
+#define CAMEL_MAILDIR_STORE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MAILDIR_STORE, CamelMaildirStorePrivate))
+
+/* after the space is a version of the folder structure */
+#define MAILDIR_CONTENT_VERSION_STR "maildir++ 1"
+#define MAILDIR_CONTENT_VERSION 1
+
+#define HIER_SEP "."
+#define HIER_SEP_CHAR '.'
+
+#define CHECK_MKDIR(x,d) G_STMT_START { \
+ gint mkdir_ret = (x); \
+ if (mkdir_ret == -1 && errno != EEXIST) { \
+ g_debug ("%s: mkdir of '%s' failed: %s", G_STRFUNC, (d), g_strerror (errno)); \
+ } \
+ } G_STMT_END
+
+struct _CamelMaildirStorePrivate {
+ gboolean already_migrated;
+ gboolean can_escape_dots;
+};
+
+static CamelFolder * maildir_store_get_folder_sync (CamelStore *store, const gchar *folder_name, CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable, GError **error);
+static CamelFolderInfo *maildir_store_create_folder_sync (CamelStore *store, const gchar *parent_name, const gchar *folder_name,
+ GCancellable *cancellable, GError **error);
+static gboolean maildir_store_delete_folder_sync (CamelStore * store, const gchar *folder_name, GCancellable *cancellable, GError **error);
+
+static gchar *maildir_full_name_to_dir_name (gboolean can_escape_dots, const gchar *full_name);
+static gchar *maildir_dir_name_to_fullname (gboolean can_escape_dots, const gchar *dir_name);
+static gchar *maildir_get_full_path (CamelLocalStore *ls, const gchar *full_name);
+static gchar *maildir_get_meta_path (CamelLocalStore *ls, const gchar *full_name, const gchar *ext);
+static void maildir_migrate_hierarchy (CamelMaildirStore *mstore, gint maildir_version, GCancellable *cancellable, GError **error);
+static gboolean maildir_version_requires_migrate (const gchar *meta_filename, gboolean *file_exists, gint *maildir_version);
+
+G_DEFINE_TYPE (CamelMaildirStore, camel_maildir_store, CAMEL_TYPE_LOCAL_STORE)
+
+/* This fixes up some historical cruft of names starting with "./" */
+static const gchar *
+md_canon_name (const gchar *a)
+{
+ if (a != NULL) {
+ if (a[0] == '/')
+ a++;
+ if (a[0] == '.' && a[1] == '/')
+ a+=2;
+ }
+
+ return a;
+}
+
+static CamelFolderInfo *
+maildir_store_create_folder_sync (CamelStore *store,
+ const gchar *parent_name,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelFolder *folder;
+ CamelFolderInfo *info = NULL;
+ gchar *name = NULL, *fullname = NULL;
+ gchar *path;
+ struct stat st;
+
+ /* This is a pretty hacky version of create folder, but should basically work */
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ if (!g_path_is_absolute (path)) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Store root %s is not an absolute path"), path);
+ goto exit;
+ }
+
+ if (folder_name && !CAMEL_MAILDIR_STORE (store)->priv->can_escape_dots && strchr (folder_name, HIER_SEP_CHAR)) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_INVALID,
+ _("Cannot create folder containing '%s'"), HIER_SEP);
+ goto exit;
+ }
+
+ if ((!parent_name || !*parent_name) && !g_ascii_strcasecmp (folder_name, "Inbox")) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Folder %s already exists"), folder_name);
+ goto exit;
+ }
+
+ if (parent_name && *parent_name) {
+ fullname = g_strdup_printf ("%s/%s", parent_name, folder_name);
+ name = maildir_full_name_to_dir_name (CAMEL_MAILDIR_STORE (store)->priv->can_escape_dots, fullname);
+ g_free (fullname);
+ } else
+ name = maildir_full_name_to_dir_name (CAMEL_MAILDIR_STORE (store)->priv->can_escape_dots, folder_name);
+
+ fullname = g_build_filename (path, name, NULL);
+
+ g_free (name);
+ name = NULL;
+
+ if (g_stat (fullname, &st) == 0) {
+ g_set_error (
+ error, G_IO_ERROR, G_IO_ERROR_EXISTS,
+ _("Folder %s already exists"), folder_name);
+ goto exit;
+
+ } else if (errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot get folder: %s: %s"),
+ folder_name, g_strerror (errno));
+ goto exit;
+ }
+
+ g_free (fullname);
+ fullname = NULL;
+
+ if (parent_name && *parent_name)
+ name = g_strdup_printf ("%s/%s", parent_name, folder_name);
+ else
+ name = g_strdup_printf ("%s", folder_name);
+
+ folder = maildir_store_get_folder_sync (
+ store, name, CAMEL_STORE_FOLDER_CREATE, cancellable, error);
+ if (folder) {
+ g_object_unref (folder);
+ info = CAMEL_STORE_GET_CLASS (store)->get_folder_info_sync (
+ store, name, 0, cancellable, error);
+ }
+
+exit:
+ g_free (fullname);
+ g_free (name);
+ g_free (path);
+
+ return info;
+}
+
+static CamelFolder *
+maildir_store_get_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStoreClass *store_class;
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelMaildirStore *maildir_store;
+ gchar *name, *tmp, *cur, *new, *dir_name;
+ gchar *path;
+ struct stat st;
+ CamelFolder *folder = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_MAILDIR_STORE (store), NULL);
+
+ maildir_store = CAMEL_MAILDIR_STORE (store);
+
+ if (!maildir_store->priv->already_migrated &&
+ maildir_store->priv->can_escape_dots) {
+ CamelFolderInfo *folder_info;
+
+ /* Not interested in any errors here, this is to invoke folder
+ content migration only. */
+ folder_info = camel_store_get_folder_info_sync (store, NULL, CAMEL_STORE_FOLDER_INFO_RECURSIVE, cancellable, NULL);
+ if (folder_info)
+ camel_folder_info_free (folder_info);
+ }
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ folder_name = md_canon_name (folder_name);
+ dir_name = maildir_full_name_to_dir_name (CAMEL_MAILDIR_STORE (store)->priv->can_escape_dots, folder_name);
+
+ /* maildir++ directory names start with a '.' */
+ name = g_build_filename (path, dir_name, NULL);
+
+ g_free (dir_name);
+ g_free (path);
+
+ /* Chain up to parent's get_folder() method. */
+ store_class = CAMEL_STORE_CLASS (camel_maildir_store_parent_class);
+ if (!store_class->get_folder_sync (store, name, flags, cancellable, error)) {
+ g_free (name);
+ return NULL;
+ }
+
+ tmp = g_strdup_printf ("%s/tmp", name);
+ cur = g_strdup_printf ("%s/cur", name);
+ new = g_strdup_printf ("%s/new", name);
+
+ if (!g_ascii_strcasecmp (folder_name, "Inbox")) {
+ /* special case "." (aka inbox), may need to be created */
+ if (g_stat (tmp, &st) != 0 || !S_ISDIR (st.st_mode)
+ || g_stat (cur, &st) != 0 || !S_ISDIR (st.st_mode)
+ || g_stat (new, &st) != 0 || !S_ISDIR (st.st_mode)) {
+ if ((g_mkdir (tmp, 0700) != 0 && errno != EEXIST)
+ || (g_mkdir (cur, 0700) != 0 && errno != EEXIST)
+ || (g_mkdir (new, 0700) != 0 && errno != EEXIST)) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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, cancellable, error);
+ } else if (g_stat (name, &st) == -1) {
+ /* folder doesn't exist, see if we should create it */
+ if (errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot get folder '%s': %s"),
+ folder_name, g_strerror (errno));
+ } else if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Cannot get folder '%s': folder does not exist."),
+ folder_name);
+ } else {
+ if ((g_mkdir (name, 0700) != 0 && errno != EEXIST)
+ || (g_mkdir (tmp, 0700) != 0 && errno != EEXIST)
+ || (g_mkdir (cur, 0700) != 0 && errno != EEXIST)
+ || (g_mkdir (new, 0700) != 0 && errno != EEXIST)) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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, cancellable, error);
+ }
+ }
+ } else if (!S_ISDIR (st.st_mode)
+ || g_stat (tmp, &st) != 0 || !S_ISDIR (st.st_mode)
+ || g_stat (cur, &st) != 0 || !S_ISDIR (st.st_mode)
+ || g_stat (new, &st) != 0 || !S_ISDIR (st.st_mode)) {
+ /* folder exists, but not maildir */
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot get folder '%s': not a maildir directory."),
+ name);
+ } else {
+ folder = camel_maildir_folder_new (store, folder_name, flags, cancellable, error);
+ }
+fail:
+ g_free (name);
+ g_free (tmp);
+ g_free (cur);
+ g_free (new);
+
+ return folder;
+}
+
+static gboolean
+maildir_store_delete_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ gchar *name, *tmp, *cur, *new, *dir_name;
+ gchar *path;
+ struct stat st;
+ gboolean success = TRUE;
+
+ if (g_ascii_strcasecmp (folder_name, "Inbox") == 0) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Cannot delete folder: %s: Invalid operation"),
+ _("Inbox"));
+ return FALSE;
+ }
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ /* maildir++ directory names start with a '.' */
+ dir_name = maildir_full_name_to_dir_name (CAMEL_MAILDIR_STORE (store)->priv->can_escape_dots, folder_name);
+ name = g_build_filename (path, dir_name, NULL);
+ g_free (dir_name);
+
+ g_free (path);
+
+ tmp = g_strdup_printf ("%s/tmp", name);
+ cur = g_strdup_printf ("%s/cur", name);
+ new = g_strdup_printf ("%s/new", name);
+
+ if (g_stat (name, &st) == -1 || !S_ISDIR (st.st_mode)
+ || g_stat (tmp, &st) == -1 || !S_ISDIR (st.st_mode)
+ || g_stat (cur, &st) == -1 || !S_ISDIR (st.st_mode)
+ || g_stat (new, &st) == -1 || !S_ISDIR (st.st_mode)) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not delete folder '%s': %s"),
+ folder_name, errno ? g_strerror (errno) :
+ _("not a maildir directory"));
+ } else {
+ gint 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))) {
+ gchar *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 */
+ CHECK_MKDIR (g_mkdir (name, 0700), name);
+ CHECK_MKDIR (g_mkdir (cur, 0700), cur);
+ CHECK_MKDIR (g_mkdir (new, 0700), new);
+ CHECK_MKDIR (g_mkdir (tmp, 0700), tmp);
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (err),
+ _("Could not delete folder '%s': %s"),
+ folder_name, g_strerror (err));
+ } else {
+ CamelStoreClass *store_class;
+
+ /* Chain up to parent's delete_folder() method. */
+ store_class = CAMEL_STORE_CLASS (camel_maildir_store_parent_class);
+ success = store_class->delete_folder_sync (
+ store, folder_name, cancellable, error);
+ }
+ }
+
+ g_free (name);
+ g_free (tmp);
+ g_free (cur);
+ g_free (new);
+
+ return success;
+}
+
+static void
+fill_fi (CamelStore *store,
+ CamelFolderInfo *fi,
+ guint32 flags,
+ GCancellable *cancellable)
+{
+ CamelFolder *folder;
+
+ folder = camel_object_bag_peek (store->folders, fi->full_name);
+ if (folder) {
+ if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
+ camel_folder_refresh_info_sync (folder, cancellable, NULL);
+ fi->unread = camel_folder_get_unread_message_count (folder);
+ fi->total = camel_folder_get_message_count (folder);
+ g_object_unref (folder);
+ } else {
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ gchar *folderpath, *dir_name;
+ CamelFolderSummary *s;
+ gchar *root;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ root = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ /* This should be fast enough not to have to test for INFO_FAST */
+ dir_name = maildir_full_name_to_dir_name (CAMEL_MAILDIR_STORE (store)->priv->can_escape_dots, fi->full_name);
+
+ if (!strcmp (dir_name, "."))
+ folderpath = g_strdup (root);
+ else
+ folderpath = g_build_filename (root, dir_name, NULL);
+
+ g_free (root);
+
+ s = (CamelFolderSummary *) camel_maildir_summary_new (NULL, folderpath, NULL);
+ if (camel_folder_summary_header_load_from_db (s, store, fi->full_name, NULL)) {
+ fi->unread = camel_folder_summary_get_unread_count (s);
+ fi->total = camel_folder_summary_get_saved_count (s);
+ }
+ g_object_unref (s);
+ g_free (folderpath);
+ g_free (dir_name);
+ }
+
+ if (camel_local_store_is_main_store (CAMEL_LOCAL_STORE (store)) && fi->full_name
+ && (fi->flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_NORMAL)
+ fi->flags = (fi->flags & ~CAMEL_FOLDER_TYPE_MASK)
+ | camel_local_store_get_folder_type_by_full_name (CAMEL_LOCAL_STORE (store), fi->full_name);
+}
+
+static CamelFolderInfo *
+scan_fi (CamelStore *store,
+ guint32 flags,
+ const gchar *full,
+ const gchar *name,
+ GCancellable *cancellable)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelFolderInfo *fi;
+ gchar *tmp, *cur, *new, *dir_name;
+ gchar *path;
+ struct stat st;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ g_return_val_if_fail (path != NULL, NULL);
+
+ fi = camel_folder_info_new ();
+ fi->full_name = g_strdup (full);
+ fi->display_name = g_strdup (name);
+
+ 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;
+
+ dir_name = maildir_full_name_to_dir_name (CAMEL_MAILDIR_STORE (store)->priv->can_escape_dots, fi->full_name);
+ d (printf ("Adding maildir info: '%s' '%s' '%s'\n", fi->name, dir_name, fi->uri));
+
+ tmp = g_build_filename (path, dir_name, "tmp", NULL);
+ cur = g_build_filename (path, dir_name, "cur", NULL);
+ new = g_build_filename (path, dir_name, "new", NULL);
+
+ if (!(g_stat (cur, &st) == 0 && S_ISDIR (st.st_mode)
+ && g_stat (new, &st) == 0 && S_ISDIR (st.st_mode)
+ /* Create 'tmp' dir on demand, if other directories are there,
+ because some backup tools can drop the 'tmp' directory. */
+ && ((g_stat (tmp, &st) == 0 && S_ISDIR (st.st_mode)) || g_mkdir (tmp, 0700) == 0)))
+ fi->flags |= CAMEL_FOLDER_NOSELECT;
+
+ g_free (new);
+ g_free (cur);
+ g_free (tmp);
+ g_free (dir_name);
+
+ fill_fi (store, fi, flags, cancellable);
+
+ g_free (path);
+
+ return fi;
+}
+
+/* Folder names begin with a dot */
+static gchar *
+maildir_full_name_to_dir_name (gboolean can_escape_dots,
+ const gchar *full_name)
+{
+ gchar *path;
+
+ if (g_ascii_strcasecmp (full_name, "Inbox") == 0) {
+ path = g_strdup (".");
+ } else {
+ if (g_ascii_strncasecmp (full_name, "Inbox/", 6) == 0)
+ path = g_strconcat ("/", full_name + 5, NULL);
+ else
+ path = g_strconcat ("/", full_name, NULL);
+
+ if (can_escape_dots && (strchr (path, HIER_SEP_CHAR) || strchr (path, '_'))) {
+ GString *tmp = g_string_new ("");
+ const gchar *pp;
+
+ for (pp = path; *pp; pp++) {
+ if (*pp == HIER_SEP_CHAR || *pp == '_')
+ g_string_append_printf (tmp, "_%02X", *pp);
+ else
+ g_string_append_c (tmp, *pp);
+ }
+
+ g_free (path);
+ path = g_string_free (tmp, FALSE);
+ }
+
+ g_strdelimit (path, "/", HIER_SEP_CHAR);
+ }
+
+ return path;
+}
+
+static gchar *
+maildir_dir_name_to_fullname (gboolean can_escape_dots,
+ const gchar *dir_name)
+{
+ gchar *full_name;
+
+ if (!g_ascii_strncasecmp (dir_name, "..", 2))
+ full_name = g_strconcat ("Inbox/", dir_name + 2, NULL);
+ else
+ full_name = g_strdup (dir_name + 1);
+
+ g_strdelimit (full_name, HIER_SEP, '/');
+
+ if (can_escape_dots && strchr (full_name, '_')) {
+ gint ii, jj;
+
+ for (ii = 0, jj = 0; full_name[ii]; ii++, jj++) {
+ if (full_name[ii] == '_' && g_ascii_isxdigit (full_name[ii + 1]) && g_ascii_isxdigit (full_name[ii + 2])) {
+ full_name[jj] = 16 * g_ascii_xdigit_value (full_name[ii + 1]) + g_ascii_xdigit_value (full_name[ii + 2]);
+ ii += 2;
+ } else if (ii != jj) {
+ full_name[jj] = full_name[ii];
+ }
+ }
+
+ full_name[jj] = '\0';
+ }
+
+ return full_name;
+}
+
+static gint
+scan_dirs (CamelStore *store,
+ guint32 flags,
+ gboolean can_inbox_sibling,
+ CamelFolderInfo **topfi,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSettings *local_settings;
+ CamelMaildirStore *maildir_store;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelFolderInfo *fi;
+ GPtrArray *folders;
+ gint res = -1;
+ DIR *dir;
+ struct dirent *d;
+ gchar *path;
+
+ service = CAMEL_SERVICE (store);
+ maildir_store = CAMEL_MAILDIR_STORE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ g_return_val_if_fail (path != NULL, -1);
+
+ folders = g_ptr_array_new ();
+ if (!g_ascii_strcasecmp ((*topfi)->full_name, "Inbox"))
+ g_ptr_array_add (folders, (*topfi));
+
+ dir = opendir (path);
+ if (dir == NULL) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not scan folder '%s': %s"),
+ path, g_strerror (errno));
+ goto exit;
+ }
+
+ if (!maildir_store->priv->already_migrated &&
+ maildir_store->priv->can_escape_dots) {
+ gchar *meta_path = NULL, *ptr;
+ gint maildir_version = 0;
+ gboolean file_exists = FALSE, requires_migrate;
+
+ meta_path = maildir_get_meta_path ((CamelLocalStore *) store, "?", "maildir++");
+ ptr = strrchr (meta_path, '?');
+ if (!ptr) {
+ g_warn_if_reached ();
+ closedir (dir);
+ res = -1;
+
+ goto exit;
+ }
+
+ maildir_store->priv->already_migrated = TRUE;
+
+ /* Do not migrate folders out of user's data data, which is completely
+ handled by Camel/Evolution, thus these tweaks can be done there. */
+ maildir_store->priv->can_escape_dots = g_str_has_prefix (meta_path, camel_service_get_user_data_dir (service));
+
+ /* cannot pass dot inside maildir_get_meta_path(), because it escapes it */
+ ptr[0] = '.';
+
+ requires_migrate = maildir_version_requires_migrate (meta_path, &file_exists, &maildir_version);
+ if (file_exists) {
+ /* Users can enable dot escaping by adding ..maildir++ file into the root Maildir folder */
+ maildir_store->priv->can_escape_dots = TRUE;
+ }
+
+ if (requires_migrate && maildir_store->priv->can_escape_dots)
+ maildir_migrate_hierarchy ((CamelMaildirStore *) store, maildir_version, cancellable, error);
+
+ g_free (meta_path);
+ }
+
+ while ((d = readdir (dir))) {
+ gchar *full_name, *filename;
+ const gchar *short_name;
+ struct stat st;
+
+ if (strcmp (d->d_name, "tmp") == 0
+ || strcmp (d->d_name, "cur") == 0
+ || strcmp (d->d_name, "new") == 0
+ || strcmp (d->d_name, ".#evolution") == 0
+ || strcmp (d->d_name, ".") == 0
+ || strcmp (d->d_name, "..") == 0
+ || !g_str_has_prefix (d->d_name, "."))
+
+ continue;
+
+ filename = g_build_filename (path, d->d_name, NULL);
+ if (!(g_stat (filename, &st) == 0 && S_ISDIR (st.st_mode))) {
+ g_free (filename);
+ continue;
+ }
+ g_free (filename);
+ full_name = maildir_dir_name_to_fullname (maildir_store->priv->can_escape_dots, d->d_name);
+ short_name = strrchr (full_name, '/');
+ if (!short_name)
+ short_name = full_name;
+ else
+ short_name++;
+
+ if ((g_ascii_strcasecmp ((*topfi)->full_name, "Inbox") != 0
+ && (!g_str_has_prefix (full_name, (*topfi)->full_name) ||
+ (full_name[strlen ((*topfi)->full_name)] != '\0' &&
+ full_name[strlen ((*topfi)->full_name)] != '/')))
+ || (!can_inbox_sibling
+ && g_ascii_strcasecmp ((*topfi)->full_name, "Inbox") == 0
+ && (!g_str_has_prefix (full_name, (*topfi)->full_name) ||
+ (full_name[strlen ((*topfi)->full_name)] != '\0' &&
+ full_name[strlen ((*topfi)->full_name)] != '/')))) {
+ g_free (full_name);
+ continue;
+ }
+
+ fi = scan_fi (store, flags, full_name, short_name, cancellable);
+ g_free (full_name);
+
+ g_ptr_array_add (folders, fi);
+ }
+
+ closedir (dir);
+
+ if (folders->len != 0) {
+ if (!g_ascii_strcasecmp ((*topfi)->full_name, "Inbox")) {
+ *topfi = camel_folder_info_build (folders, "", '/', TRUE);
+ } else {
+ CamelFolderInfo *old_topfi = *topfi;
+
+ *topfi = camel_folder_info_build (folders, (*topfi)->full_name, '/', TRUE);
+ camel_folder_info_free (old_topfi);
+ }
+
+ fi = *topfi;
+
+ if (fi && (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) != 0) {
+ while (fi) {
+ if (fi->child) {
+ fi->flags = fi->flags & (~CAMEL_FOLDER_NOCHILDREN);
+ fi->flags = fi->flags | CAMEL_FOLDER_CHILDREN;
+
+ fi = fi->child;
+ } else if (fi->next) {
+ fi = fi->next;
+ } else {
+ while (fi) {
+ fi = fi->parent;
+ if (fi && fi->next) {
+ fi = fi->next;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ res = 0;
+ } else
+ res = -1;
+
+exit:
+ g_ptr_array_free (folders, TRUE);
+
+ g_free (path);
+
+ return res;
+}
+
+static guint
+maildir_store_hash_folder_name (gconstpointer a)
+{
+ return g_str_hash (md_canon_name (a));
+}
+
+static gboolean
+maildir_store_equal_folder_name (gconstpointer a,
+ gconstpointer b)
+{
+ return g_str_equal (md_canon_name (a), md_canon_name (b));
+}
+
+static CamelFolderInfo *
+maildir_store_get_folder_info_sync (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderInfo *fi = NULL;
+
+ if (top == NULL || top[0] == 0) {
+ /* create a dummy "." parent inbox, use to scan, then put back at the top level */
+ fi = scan_fi (store, flags, "Inbox", _("Inbox"), cancellable);
+ if (scan_dirs (store, flags, TRUE, &fi, cancellable, error) == -1)
+ goto fail;
+
+ fi->flags |= CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_TYPE_INBOX;
+ } else if (!strcmp (top, ".")) {
+ fi = scan_fi (store, flags, "Inbox", _("Inbox"), cancellable);
+ fi->flags |= CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_TYPE_INBOX;
+ } else {
+ const gchar *name = strrchr (top, '/');
+
+ fi = scan_fi (store, flags, top, name ? name + 1 : top, cancellable);
+ if (g_strcmp0 (fi->full_name, CAMEL_VTRASH_NAME) != 0 &&
+ g_strcmp0 (fi->full_name, CAMEL_VJUNK_NAME) != 0 &&
+ scan_dirs (store, flags, FALSE, &fi, cancellable, error) == -1)
+ goto fail;
+ }
+
+ return fi;
+
+fail:
+ camel_folder_info_free (fi);
+
+ return NULL;
+}
+
+static CamelFolder *
+maildir_store_get_inbox_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return camel_store_get_folder_sync (
+ store, "Inbox", CAMEL_STORE_FOLDER_CREATE, cancellable, error);
+}
+
+static gboolean
+rename_traverse_fi (CamelStore *store,
+ CamelStoreClass *store_class,
+ CamelFolderInfo *fi,
+ const gchar *old_full_name_prefix,
+ const gchar *new_full_name_prefix,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint old_prefix_len = strlen (old_full_name_prefix);
+ gboolean ret = TRUE;
+
+ while (fi && ret) {
+ if (fi->full_name && g_str_has_prefix (fi->full_name, old_full_name_prefix)) {
+ gchar *new_full_name, *old_dir, *new_dir;
+
+ new_full_name = g_strconcat (new_full_name_prefix, fi->full_name + old_prefix_len, NULL);
+ old_dir = maildir_full_name_to_dir_name (CAMEL_MAILDIR_STORE (store)->priv->can_escape_dots, fi->full_name);
+ new_dir = maildir_full_name_to_dir_name (CAMEL_MAILDIR_STORE (store)->priv->can_escape_dots, new_full_name);
+
+ /* Chain up to parent's rename_folder_sync() method. */
+ ret = store_class->rename_folder_sync (store, old_dir, new_dir, cancellable, error);
+
+ g_free (old_dir);
+ g_free (new_dir);
+ g_free (new_full_name);
+ }
+
+ if (fi->child && !rename_traverse_fi (store, store_class, fi->child, old_full_name_prefix, new_full_name_prefix, cancellable, error))
+ return FALSE;
+
+ fi = fi->next;
+ }
+
+ return ret;
+}
+
+static gboolean
+maildir_store_rename_folder_sync (CamelStore *store,
+ const gchar *old,
+ const gchar *new,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStoreClass *store_class;
+ gboolean ret;
+ gchar *old_dir, *new_dir;
+ CamelFolderInfo *subfolders;
+
+ if (strcmp (old, ".") == 0) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Cannot rename folder: %s: Invalid operation"),
+ _("Inbox"));
+ return FALSE;
+ }
+
+ if (!g_ascii_strcasecmp (new, "Inbox")) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Folder %s already exists"), new);
+ return FALSE;
+ }
+
+ if (new && !CAMEL_MAILDIR_STORE (store)->priv->can_escape_dots && strchr (new, HIER_SEP_CHAR)) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_INVALID,
+ _("Cannot create folder containing '%s'"), HIER_SEP);
+ return FALSE;
+ }
+
+ subfolders = maildir_store_get_folder_info_sync (
+ store, old,
+ CAMEL_STORE_FOLDER_INFO_RECURSIVE |
+ CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL,
+ cancellable, NULL);
+
+ old_dir = maildir_full_name_to_dir_name (CAMEL_MAILDIR_STORE (store)->priv->can_escape_dots, old);
+ new_dir = maildir_full_name_to_dir_name (CAMEL_MAILDIR_STORE (store)->priv->can_escape_dots, new);
+
+ /* Chain up to parent's rename_folder_sync() method. */
+ store_class = CAMEL_STORE_CLASS (camel_maildir_store_parent_class);
+ ret = store_class->rename_folder_sync (
+ store, old_dir, new_dir, cancellable, error);
+
+ if (subfolders) {
+ if (ret)
+ ret = rename_traverse_fi (
+ store, store_class,
+ subfolders->child,
+ old, new,
+ cancellable, error);
+
+ camel_folder_info_free (subfolders);
+ }
+
+ g_free (old_dir);
+ g_free (new_dir);
+
+ return ret;
+}
+
+static void
+camel_maildir_store_class_init (CamelMaildirStoreClass *class)
+{
+ CamelStoreClass *store_class;
+ CamelLocalStoreClass *local_class;
+
+ g_type_class_add_private (class, sizeof (CamelMaildirStorePrivate));
+
+ store_class = CAMEL_STORE_CLASS (class);
+ store_class->hash_folder_name = maildir_store_hash_folder_name;
+ store_class->equal_folder_name = maildir_store_equal_folder_name;
+ store_class->create_folder_sync = maildir_store_create_folder_sync;
+ store_class->get_folder_sync = maildir_store_get_folder_sync;
+ store_class->get_folder_info_sync = maildir_store_get_folder_info_sync;
+ store_class->get_inbox_folder_sync = maildir_store_get_inbox_sync;
+ store_class->delete_folder_sync = maildir_store_delete_folder_sync;
+ store_class->rename_folder_sync = maildir_store_rename_folder_sync;
+
+ local_class = CAMEL_LOCAL_STORE_CLASS (class);
+ local_class->get_full_path = maildir_get_full_path;
+ local_class->get_meta_path = maildir_get_meta_path;
+}
+
+static void
+camel_maildir_store_init (CamelMaildirStore *maildir_store)
+{
+ maildir_store->priv = CAMEL_MAILDIR_STORE_GET_PRIVATE (maildir_store);
+ maildir_store->priv->already_migrated = FALSE;
+ maildir_store->priv->can_escape_dots = TRUE;
+}
+
+static gchar *
+maildir_get_full_path (CamelLocalStore *ls,
+ const gchar *full_name)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ gchar *filename;
+ gchar *dir_name;
+ gchar *path;
+
+ service = CAMEL_SERVICE (ls);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ dir_name = maildir_full_name_to_dir_name (CAMEL_MAILDIR_STORE (ls)->priv->can_escape_dots, full_name);
+ filename = g_build_filename (path, dir_name, NULL);
+ g_free (dir_name);
+
+ g_free (path);
+
+ return filename;
+}
+
+static gchar *
+maildir_get_meta_path (CamelLocalStore *ls,
+ const gchar *full_name,
+ const gchar *ext)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ gchar *filename;
+ gchar *dir_name;
+ gchar *path;
+ gchar *tmp;
+
+ service = CAMEL_SERVICE (ls);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ dir_name = maildir_full_name_to_dir_name (CAMEL_MAILDIR_STORE (ls)->priv->can_escape_dots, full_name);
+ tmp = g_build_filename (path, dir_name, NULL);
+ filename = g_strconcat (tmp, ext, NULL);
+ g_free (tmp);
+ g_free (dir_name);
+
+ g_free (path);
+
+ return filename;
+}
+
+/* Migration from old to maildir++ hierarchy */
+
+struct _scan_node {
+ CamelFolderInfo *fi;
+
+ dev_t dnode;
+ ino_t inode;
+};
+
+static guint
+scan_hash (gconstpointer d)
+{
+ const struct _scan_node *v = d;
+
+ return v->inode ^ v->dnode;
+}
+
+static gboolean
+scan_equal (gconstpointer a,
+ gconstpointer b)
+{
+ const struct _scan_node *v1 = a, *v2 = b;
+
+ return v1->inode == v2->inode && v1->dnode == v2->dnode;
+}
+
+static void
+scan_free (gpointer k,
+ gpointer v,
+ gpointer d)
+{
+ g_free (k);
+}
+
+static gint
+scan_old_dir_info (CamelStore *store,
+ CamelFolderInfo *topfi,
+ GError **error)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ GQueue queue = G_QUEUE_INIT;
+ struct _scan_node *sn;
+ gchar *path;
+ gchar *tmp;
+ GHashTable *visited;
+ struct stat st;
+ gint res = -1;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ visited = g_hash_table_new (scan_hash, scan_equal);
+
+ sn = g_malloc0 (sizeof (*sn));
+ sn->fi = topfi;
+ g_queue_push_tail (&queue, sn);
+ g_hash_table_insert (visited, sn, sn);
+
+ while (!g_queue_is_empty (&queue)) {
+ gchar *name;
+ DIR *dir;
+ struct dirent *d;
+ CamelFolderInfo *last;
+
+ sn = g_queue_pop_head (&queue);
+
+ last = (CamelFolderInfo *) &sn->fi->child;
+
+ if (!strcmp (sn->fi->full_name, "."))
+ name = g_strdup (path);
+ else
+ name = g_build_filename (path, sn->fi->full_name, NULL);
+
+ dir = opendir (name);
+ if (dir == NULL) {
+ g_free (name);
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not scan folder '%s': %s"),
+ path, g_strerror (errno));
+ goto exit;
+ }
+
+ 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, ".#evolution") == 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));
+ gchar *full;
+ CamelFolderInfo *fi = NULL;
+
+ 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);
+
+ fi = camel_folder_info_new ();
+ fi->full_name = full;
+ fi->display_name = g_strdup (d->d_name);
+ snew->fi = fi;
+
+ last->next = snew->fi;
+ last = snew->fi;
+ snew->fi->parent = sn->fi;
+
+ g_hash_table_insert (visited, snew, snew);
+ g_queue_push_tail (&queue, snew);
+ }
+ }
+ g_free (tmp);
+ }
+ closedir (dir);
+ g_free (name);
+ }
+
+ res = 0;
+
+exit:
+ g_hash_table_foreach (visited, scan_free, NULL);
+ g_hash_table_destroy (visited);
+
+ g_free (path);
+
+ return res;
+}
+
+static void
+maildir_maybe_rename_old_folder (CamelMaildirStore *mstore,
+ CamelFolderInfo *fi,
+ gint maildir_version,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar *new_name = NULL;
+
+ if (g_str_equal (fi->full_name, ".") || g_str_equal (fi->full_name, ".."))
+ return;
+
+ if (maildir_version == -1) {
+ /* this is when maildir was not converted yet to maildir++ at all,
+ * the '_' and '.' are still there and the dir separator is slash
+ */
+ new_name = maildir_full_name_to_dir_name (mstore->priv->can_escape_dots, fi->full_name);
+ } else if (maildir_version == 0) {
+ /* this is a conversion with maildir folder being already there,
+ * only with no version; there should be escaped only '_', because
+ * the '.' is already garbled;
+ * fi->full_name is a dir name here
+ */
+ gchar *full_name;
+
+ if (!g_ascii_strncasecmp (fi->full_name, "..", 2))
+ full_name = g_strconcat ("Inbox/", fi->full_name + 2, NULL);
+ else if (fi->full_name[0] == '.')
+ full_name = g_strdup (fi->full_name + 1);
+ else
+ full_name = g_strdup (fi->full_name);
+
+ g_strdelimit (full_name, HIER_SEP, '/');
+
+ new_name = maildir_full_name_to_dir_name (mstore->priv->can_escape_dots, full_name);
+
+ g_free (full_name);
+ } else {
+ return;
+ }
+
+ if (!g_str_equal (fi->full_name, new_name)) {
+ CamelStoreClass *store_class;
+ GError *local_error = NULL;
+
+ store_class = CAMEL_STORE_CLASS (camel_maildir_store_parent_class);
+ /* Do not propagate these errors, only warn about them on console */
+ store_class->rename_folder_sync ((CamelStore *) mstore, fi->full_name, new_name, cancellable, &local_error);
+
+ if (local_error) {
+ g_warning ("%s: Failed to rename '%s' to '%s': %s", G_STRFUNC, fi->full_name, new_name, local_error->message);
+ g_error_free (local_error);
+ }
+ }
+
+ g_free (new_name);
+}
+
+static void
+traverse_rename_folder_info (CamelMaildirStore *mstore,
+ CamelFolderInfo *fi,
+ gint maildir_version,
+ GCancellable *cancellable,
+ GError **error)
+{
+ while (fi != NULL) {
+ if (fi->child)
+ traverse_rename_folder_info (mstore, fi->child, maildir_version, cancellable, error);
+
+ maildir_maybe_rename_old_folder (mstore, fi, maildir_version, cancellable, error);
+
+ fi = fi->next;
+ }
+}
+
+static gboolean
+maildir_version_requires_migrate (const gchar *meta_filename,
+ gboolean *file_exists,
+ gint *maildir_version)
+{
+ FILE *metafile;
+ gint cc;
+ gint verpos = 0;
+ gboolean res = FALSE;
+
+ g_return_val_if_fail (meta_filename != NULL, FALSE);
+ g_return_val_if_fail (file_exists != NULL, FALSE);
+ g_return_val_if_fail (maildir_version != NULL, FALSE);
+
+ /* nonexistent file is -1 */
+ *maildir_version = -1;
+ *file_exists = FALSE;
+
+ if (!g_file_test (meta_filename, G_FILE_TEST_EXISTS))
+ return TRUE;
+
+ /* existing file without version is 0 */
+ *maildir_version = 0;
+ *file_exists = TRUE;
+
+ metafile = fopen (meta_filename, "rb");
+ if (!metafile)
+ return FALSE;
+
+ while (cc = fgetc (metafile), !res && !feof (metafile)) {
+ if (verpos > 1 && MAILDIR_CONTENT_VERSION_STR[verpos - 1] == ' ') {
+ if (cc >= '0' && cc <= '9') {
+ (*maildir_version) = (*maildir_version) * 10 + cc - '0';
+ } else if (cc == ' ' || cc == '\n' || cc == '\r' || cc == '\t') {
+ break;
+ } else {
+ res = TRUE;
+ }
+ } else if (cc == MAILDIR_CONTENT_VERSION_STR[verpos]) {
+ verpos++;
+ } else {
+ res = TRUE;
+ }
+ }
+
+ fclose (metafile);
+
+ return res || (*maildir_version) < MAILDIR_CONTENT_VERSION;
+}
+
+static void
+maildir_migrate_hierarchy (CamelMaildirStore *mstore,
+ gint maildir_version,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderInfo *topfi;
+ gchar *meta_path = NULL, *ptr;
+
+ g_return_if_fail (mstore->priv->can_escape_dots);
+
+ topfi = camel_folder_info_new ();
+ topfi->full_name = g_strdup (".");
+ topfi->display_name = g_strdup ("Inbox");
+
+ if (scan_old_dir_info ((CamelStore *) mstore, topfi, error) == -1) {
+ g_warning ("%s: Failed to scan the old folder info", G_STRFUNC);
+ goto done;
+ }
+
+ meta_path = maildir_get_meta_path ((CamelLocalStore *) mstore, "?", "maildir++");
+ ptr = strrchr (meta_path, '?');
+ g_return_if_fail (ptr != NULL);
+
+ /* cannot pass dot inside maildir_get_meta_path(), because it is escaped */
+ ptr[0] = '.';
+
+ /* First create/overwrite the file, only then do the migration, otherwise,
+ if the file creation fails, the folder structure would be migrated repeatedly. */
+ if (!g_file_set_contents (meta_path, MAILDIR_CONTENT_VERSION_STR, -1, error) || (error && *error)) {
+ g_warning ("Failed to save the maildir version in ‘%s’.", meta_path);
+ goto done;
+ }
+
+ if (maildir_version < 1) {
+ traverse_rename_folder_info (mstore, topfi, maildir_version, cancellable, error);
+ }
+
+done:
+ camel_folder_info_free (topfi);
+ g_free (meta_path);
+}
diff --git a/src/camel/providers/local/camel-maildir-store.h b/src/camel/providers/local/camel-maildir-store.h
new file mode 100644
index 000000000..d949597e4
--- /dev/null
+++ b/src/camel/providers/local/camel-maildir-store.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_MAILDIR_STORE_H
+#define CAMEL_MAILDIR_STORE_H
+
+#include "camel-local-store.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MAILDIR_STORE \
+ (camel_maildir_store_get_type ())
+#define CAMEL_MAILDIR_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MAILDIR_STORE, CamelMaildirStore))
+#define CAMEL_MAILDIR_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MAILDIR_STORE, CamelMaildirStoreClass))
+#define CAMEL_IS_MAILDIR_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MAILDIR_STORE))
+#define CAMEL_IS_MAILDIR_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MAILDIR_STORE))
+#define CAMEL_MAILDIR_STORE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MAILDIR_STORE, CamelMaildirStoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMaildirStore CamelMaildirStore;
+typedef struct _CamelMaildirStoreClass CamelMaildirStoreClass;
+typedef struct _CamelMaildirStorePrivate CamelMaildirStorePrivate;
+
+struct _CamelMaildirStore {
+ CamelLocalStore parent;
+ CamelMaildirStorePrivate *priv;
+};
+
+struct _CamelMaildirStoreClass {
+ CamelLocalStoreClass parent_class;
+};
+
+GType camel_maildir_store_get_type (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_MAILDIR_STORE_H */
diff --git a/src/camel/providers/local/camel-maildir-summary.c b/src/camel/providers/local/camel-maildir-summary.c
new file mode 100644
index 000000000..2c5a56b7a
--- /dev/null
+++ b/src/camel/providers/local/camel-maildir-summary.c
@@ -0,0 +1,878 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Not Zed <notzed@lostzed.mmc.com.au>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/uio.h>
+#else
+#include <winsock2.h>
+#endif
+
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-maildir-summary.h"
+
+#define CAMEL_MAILDIR_SUMMARY_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MAILDIR_SUMMARY, CamelMaildirSummaryPrivate))
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+#define CAMEL_MAILDIR_SUMMARY_VERSION (0x2000)
+
+static CamelMessageInfo *
+ message_info_new_from_header (CamelFolderSummary *,
+ struct _camel_header_raw *);
+static CamelMessageInfo *
+ maildir_message_info_from_db (CamelFolderSummary *summary,
+ CamelMIRecord *record);
+static void message_info_free (CamelFolderSummary *,
+ CamelMessageInfo *mi);
+
+static gint maildir_summary_load (CamelLocalSummary *cls,
+ gint forceindex,
+ GError **error);
+static gint maildir_summary_check (CamelLocalSummary *cls,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error);
+static gint maildir_summary_sync (CamelLocalSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error);
+static CamelMessageInfo *
+ maildir_summary_add (CamelLocalSummary *cls,
+ CamelMimeMessage *msg,
+ const CamelMessageInfo *info,
+ CamelFolderChangeInfo *,
+ GError **error);
+
+static gchar * maildir_summary_next_uid_string (CamelFolderSummary *s);
+static gint maildir_summary_decode_x_evolution
+ (CamelLocalSummary *cls,
+ const gchar *xev,
+ CamelLocalMessageInfo *mi);
+static gchar * maildir_summary_encode_x_evolution
+ (CamelLocalSummary *cls,
+ const CamelLocalMessageInfo *mi);
+
+typedef struct _CamelMaildirMessageContentInfo CamelMaildirMessageContentInfo;
+
+struct _CamelMaildirSummaryPrivate {
+ gchar *current_file;
+ gchar *hostname;
+
+ GHashTable *load_map;
+ GMutex summary_lock;
+};
+
+struct _CamelMaildirMessageContentInfo {
+ CamelMessageContentInfo info;
+};
+
+G_DEFINE_TYPE (
+ CamelMaildirSummary,
+ camel_maildir_summary,
+ CAMEL_TYPE_LOCAL_SUMMARY)
+
+static void
+maildir_summary_finalize (GObject *object)
+{
+ CamelMaildirSummaryPrivate *priv;
+
+ priv = CAMEL_MAILDIR_SUMMARY_GET_PRIVATE (object);
+
+ g_free (priv->hostname);
+ g_mutex_clear (&priv->summary_lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_maildir_summary_parent_class)->finalize (object);
+}
+
+static void
+camel_maildir_summary_class_init (CamelMaildirSummaryClass *class)
+{
+ GObjectClass *object_class;
+ CamelFolderSummaryClass *folder_summary_class;
+ CamelLocalSummaryClass *local_summary_class;
+
+ g_type_class_add_private (class, sizeof (CamelMaildirSummaryPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = maildir_summary_finalize;
+
+ folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (class);
+ folder_summary_class->message_info_size = sizeof (CamelMaildirMessageInfo);
+ folder_summary_class->content_info_size = sizeof (CamelMaildirMessageContentInfo);
+ folder_summary_class->message_info_new_from_header = message_info_new_from_header;
+ folder_summary_class->message_info_from_db = maildir_message_info_from_db;
+ folder_summary_class->message_info_free = message_info_free;
+ folder_summary_class->next_uid_string = maildir_summary_next_uid_string;
+
+ local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (class);
+ local_summary_class->load = maildir_summary_load;
+ local_summary_class->check = maildir_summary_check;
+ local_summary_class->sync = maildir_summary_sync;
+ local_summary_class->add = maildir_summary_add;
+ local_summary_class->encode_x_evolution = maildir_summary_encode_x_evolution;
+ local_summary_class->decode_x_evolution = maildir_summary_decode_x_evolution;
+}
+
+static void
+camel_maildir_summary_init (CamelMaildirSummary *maildir_summary)
+{
+ CamelFolderSummary *folder_summary;
+ gchar hostname[256];
+
+ folder_summary = CAMEL_FOLDER_SUMMARY (maildir_summary);
+
+ maildir_summary->priv =
+ CAMEL_MAILDIR_SUMMARY_GET_PRIVATE (maildir_summary);
+
+ /* set unique file version */
+ folder_summary->version += CAMEL_MAILDIR_SUMMARY_VERSION;
+
+ if (gethostname (hostname, 256) == 0) {
+ maildir_summary->priv->hostname = g_strdup (hostname);
+ } else {
+ maildir_summary->priv->hostname = g_strdup ("localhost");
+ }
+ g_mutex_init (&maildir_summary->priv->summary_lock);
+}
+
+/**
+ * camel_maildir_summary_new:
+ * @folder: parent folder.
+ * @index: Index if one is reqiured.
+ *
+ * Create a new CamelMaildirSummary object.
+ *
+ * Returns: A new #CamelMaildirSummary object.
+ **/
+CamelMaildirSummary
+*camel_maildir_summary_new(struct _CamelFolder *folder, const gchar *maildirdir, CamelIndex *index)
+{
+ CamelMaildirSummary *o;
+
+ o = g_object_new (CAMEL_TYPE_MAILDIR_SUMMARY, "folder", folder, NULL);
+ if (folder) {
+ CamelStore *parent_store;
+
+ parent_store = camel_folder_get_parent_store (folder);
+ camel_db_set_collate (parent_store->cdb_r, "dreceived", NULL, NULL);
+ ((CamelFolderSummary *) o)->sort_by = "dreceived";
+ ((CamelFolderSummary *) o)->collate = NULL;
+ }
+ camel_local_summary_construct ((CamelLocalSummary *) o, maildirdir, index);
+ return o;
+}
+
+/* the 'standard' maildir flags. should be defined in sorted order. */
+static struct {
+ gchar flag;
+ guint32 flagbit;
+} flagbits[] = {
+ { 'D', CAMEL_MESSAGE_DRAFT },
+ { 'F', CAMEL_MESSAGE_FLAGGED },
+ /*{ 'P', CAMEL_MESSAGE_FORWARDED },*/
+ { 'R', CAMEL_MESSAGE_ANSWERED },
+ { 'S', CAMEL_MESSAGE_SEEN },
+ { 'T', CAMEL_MESSAGE_DELETED },
+};
+
+/* convert the uid + flags into a unique:info maildir format */
+gchar *camel_maildir_summary_info_to_name (const CamelMaildirMessageInfo *info)
+{
+ const gchar *uid;
+ gchar *p, *buf;
+ gint i;
+
+ uid = camel_message_info_get_uid (info);
+ buf = g_alloca (strlen (uid) + strlen (CAMEL_MAILDIR_FLAG_SEP_S "2,") + G_N_ELEMENTS (flagbits) + 1);
+ p = buf + sprintf (buf, "%s" CAMEL_MAILDIR_FLAG_SEP_S "2,", uid);
+ for (i = 0; i < G_N_ELEMENTS (flagbits); i++) {
+ if (info->info.info.flags & flagbits[i].flagbit)
+ *p++ = flagbits[i].flag;
+ }
+
+ *p = 0;
+
+ return g_strdup (buf);
+}
+
+/* returns 0 if the info matches (or there was none), otherwise we changed it */
+gint camel_maildir_summary_name_to_info (CamelMaildirMessageInfo *info, const gchar *name)
+{
+ gchar *p, c;
+ guint32 set = 0; /* what we set */
+ /*guint32 all = 0;*/ /* all flags */
+ gint i;
+
+ p = strstr (name, CAMEL_MAILDIR_FLAG_SEP_S "2,");
+
+ if (p) {
+ p+=3;
+ while ((c = *p++)) {
+ /* we could assume that the flags are in order, but its just as easy not to require */
+ for (i = 0; i < G_N_ELEMENTS (flagbits); i++) {
+ if (flagbits[i].flag == c && (info->info.info.flags & flagbits[i].flagbit) == 0) {
+ set |= flagbits[i].flagbit;
+ }
+ /*all |= flagbits[i].flagbit;*/
+ }
+ }
+
+ /* changed? */
+ /*if ((info->flags & all) != set) {*/
+ if ((info->info.info.flags & set) != set) {
+ /* ok, they did change, only add the new flags ('merge flags'?) */
+ /*info->flags &= all; if we wanted to set only the new flags, which we probably dont */
+ info->info.info.flags |= set;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* for maildir, x-evolution isn't used, so dont try and get anything out of it */
+static gint maildir_summary_decode_x_evolution (CamelLocalSummary *cls, const gchar *xev, CamelLocalMessageInfo *mi)
+{
+ return -1;
+}
+
+static gchar *maildir_summary_encode_x_evolution (CamelLocalSummary *cls, const CamelLocalMessageInfo *mi)
+{
+ return NULL;
+}
+
+/* FIXME:
+ * both 'new' and 'add' will try and set the filename, this is not ideal ...
+*/
+static CamelMessageInfo *
+maildir_summary_add (CamelLocalSummary *cls,
+ CamelMimeMessage *msg,
+ const CamelMessageInfo *info,
+ CamelFolderChangeInfo *changes,
+ GError **error)
+{
+ CamelLocalSummaryClass *local_summary_class;
+ CamelMaildirMessageInfo *mi;
+
+ /* Chain up to parent's add() method. */
+ local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (camel_maildir_summary_parent_class);
+ mi = (CamelMaildirMessageInfo *) local_summary_class->add (
+ cls, msg, info, changes, error);
+ if (mi) {
+ if (info) {
+ camel_maildir_info_set_filename (mi, camel_maildir_summary_info_to_name (mi));
+ d (printf ("Setting filename to %s\n", camel_maildir_info_filename (mi)));
+
+ /* Inherit the Received date from the passed-in info only if it is set and
+ the new message info doesn't have it set or it's set to the default
+ value, derived from the message UID. */
+ if (camel_message_info_get_date_received (info) > 0 &&
+ (camel_message_info_get_date_received (mi) <= 0 ||
+ (camel_message_info_get_uid (mi) &&
+ camel_message_info_get_date_received (mi) == strtoul (camel_message_info_get_uid (mi), NULL, 10))))
+ mi->info.info.date_received = camel_message_info_get_date_received (info);
+ }
+ }
+
+ return (CamelMessageInfo *) mi;
+}
+
+static CamelMessageInfo *
+message_info_new_from_header (CamelFolderSummary *s,
+ struct _camel_header_raw *h)
+{
+ CamelMessageInfo *mi, *info;
+ CamelMaildirSummary *mds = (CamelMaildirSummary *) s;
+ CamelMaildirMessageInfo *mdi;
+ const gchar *uid;
+
+ mi = ((CamelFolderSummaryClass *) camel_maildir_summary_parent_class)->message_info_new_from_header (s, h);
+ /* assign the uid and new filename */
+ if (mi) {
+ mdi = (CamelMaildirMessageInfo *) mi;
+
+ uid = camel_message_info_get_uid (mi);
+ if (uid == NULL || uid[0] == 0)
+ mdi->info.info.uid = camel_pstring_add (camel_folder_summary_next_uid_string (s), TRUE);
+
+ /* handle 'duplicates' */
+ info = camel_folder_summary_peek_loaded (s, uid);
+ if (info) {
+ d (printf ("already seen uid '%s', just summarising instead\n", uid));
+ camel_message_info_unref (mi);
+ mdi = (CamelMaildirMessageInfo *)(mi = info);
+ }
+
+ if (mdi->info.info.date_received <= 0) {
+ /* with maildir we know the real received date, from the filename */
+ mdi->info.info.date_received = strtoul (camel_message_info_get_uid (mi), NULL, 10);
+ }
+
+ if (mds->priv->current_file) {
+#if 0
+ gchar *p1, *p2, *p3;
+ gulong uid;
+#endif
+ /* if setting from a file, grab the flags from it */
+ camel_maildir_info_set_filename (mi, g_strdup (mds->priv->current_file));
+ camel_maildir_summary_name_to_info (mdi, mds->priv->current_file);
+
+#if 0
+ /* Actually, I dont think all this effort is worth it at all ... */
+
+ /* also, see if we can extract the next-id from tne name, and safe-if-fy ourselves against collisions */
+ /* we check for something.something_number.something */
+ p1 = strchr (mdi->filename, '.');
+ if (p1) {
+ p2 = strchr (p1 + 1, '.');
+ p3 = strchr (p1 + 1, '_');
+ if (p2 && p3 && p3 < p2) {
+ uid = strtoul (p3 + 1, &p1, 10);
+ if (p1 == p2 && uid > 0)
+ camel_folder_summary_set_uid (s, uid);
+ }
+ }
+#endif
+ } else {
+ /* if creating a file, set its name from the flags we have */
+ camel_maildir_info_set_filename (mdi, camel_maildir_summary_info_to_name (mdi));
+ d (printf ("Setting filename to %s\n", camel_maildir_info_filename (mi)));
+ }
+ }
+
+ return mi;
+}
+
+static CamelMessageInfo *
+maildir_message_info_from_db (CamelFolderSummary *summary,
+ CamelMIRecord *record)
+{
+ CamelMessageInfo *mi;
+
+ mi = ((CamelFolderSummaryClass *) camel_maildir_summary_parent_class)->message_info_from_db (summary, record);
+ if (mi) {
+ CamelMaildirMessageInfo *mdi = (CamelMaildirMessageInfo *) mi;
+
+ camel_maildir_info_set_filename (mdi, camel_maildir_summary_info_to_name (mdi));
+ }
+
+ return mi;
+}
+
+static void
+message_info_free (CamelFolderSummary *s,
+ CamelMessageInfo *mi)
+{
+ CamelMaildirMessageInfo *mdi = (CamelMaildirMessageInfo *) mi;
+
+ g_free (mdi->filename);
+
+ ((CamelFolderSummaryClass *) camel_maildir_summary_parent_class)->message_info_free (s, mi);
+}
+
+static gchar *maildir_summary_next_uid_string (CamelFolderSummary *s)
+{
+ CamelMaildirSummary *mds = (CamelMaildirSummary *) s;
+
+ d (printf ("next uid string called?\n"));
+
+ /* if we have a current file, then use that to get the uid */
+ if (mds->priv->current_file) {
+ gchar *cln;
+
+ cln = strchr (mds->priv->current_file, CAMEL_MAILDIR_FLAG_SEP);
+ if (cln)
+ return g_strndup (mds->priv->current_file, cln - mds->priv->current_file);
+ else
+ return g_strdup (mds->priv->current_file);
+ } else {
+ /* the first would probably work, but just to be safe, check for collisions */
+#if 0
+ return g_strdup_printf ("%ld.%d_%u.%s", time (0), getpid (), camel_folder_summary_next_uid (s), mds->priv->hostname);
+#else
+ CamelLocalSummary *cls = (CamelLocalSummary *) s;
+ gchar *name = NULL, *uid = NULL;
+ struct stat st;
+ gint retry = 0;
+ guint32 nextuid = camel_folder_summary_next_uid (s);
+
+ /* we use time.pid_count.hostname */
+ do {
+ if (retry > 0) {
+ g_free (name);
+ g_free (uid);
+ g_usleep (2 * G_USEC_PER_SEC);
+ }
+ uid = g_strdup_printf ("%" G_GINT64_FORMAT ".%d_%u.%s", (gint64) time (NULL), getpid (), nextuid, mds->priv->hostname);
+ name = g_strdup_printf ("%s/tmp/%s", cls->folder_path, uid);
+ retry++;
+ } while (g_stat (name, &st) == 0 && retry < 3);
+
+ /* I dont know what we're supposed to do if it fails to find a unique name?? */
+
+ g_free (name);
+ return uid;
+#endif
+ }
+}
+
+static gint
+maildir_summary_load (CamelLocalSummary *cls,
+ gint forceindex,
+ GError **error)
+{
+ CamelLocalSummaryClass *local_summary_class;
+ gchar *cur;
+ DIR *dir;
+ struct dirent *d;
+ CamelMaildirSummary *mds = (CamelMaildirSummary *) cls;
+ gchar *uid;
+ CamelMemPool *pool;
+ gint ret;
+
+ cur = g_strdup_printf ("%s/cur", cls->folder_path);
+
+ d (printf ("pre-loading uid <> filename map\n"));
+
+ dir = opendir (cur);
+ if (dir == NULL) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot open maildir directory path: %s: %s"),
+ cls->folder_path, g_strerror (errno));
+ g_free (cur);
+ return -1;
+ }
+
+ mds->priv->load_map = g_hash_table_new (g_str_hash, g_str_equal);
+ pool = camel_mempool_new (1024, 512, CAMEL_MEMPOOL_ALIGN_BYTE);
+
+ while ((d = readdir (dir))) {
+ if (d->d_name[0] == '.')
+ continue;
+
+ /* map the filename -> uid */
+ uid = strchr (d->d_name, CAMEL_MAILDIR_FLAG_SEP);
+ if (uid) {
+ gint len = uid - d->d_name;
+ uid = camel_mempool_alloc (pool, len + 1);
+ memcpy (uid, d->d_name, len);
+ uid[len] = 0;
+ g_hash_table_insert (mds->priv->load_map, uid, camel_mempool_strdup (pool, d->d_name));
+ } else {
+ uid = camel_mempool_strdup (pool, d->d_name);
+ g_hash_table_insert (mds->priv->load_map, uid, uid);
+ }
+ }
+ closedir (dir);
+ g_free (cur);
+
+ /* Chain up to parent's load() method. */
+ local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (camel_maildir_summary_parent_class);
+ ret = local_summary_class->load (cls, forceindex, error);
+
+ g_hash_table_destroy (mds->priv->load_map);
+ mds->priv->load_map = NULL;
+ camel_mempool_destroy (pool);
+
+ return ret;
+}
+
+static gint
+camel_maildir_summary_add (CamelLocalSummary *cls,
+ const gchar *name,
+ gint forceindex,
+ GCancellable *cancellable)
+{
+ CamelMessageInfo *info;
+ CamelFolderSummary *summary;
+ CamelMaildirSummary *maildirs = (CamelMaildirSummary *) cls;
+ gchar *filename = g_strdup_printf ("%s/cur/%s", cls->folder_path, name);
+ gint fd;
+ CamelMimeParser *mp;
+
+ d (printf ("summarising: %s\n", name));
+
+ summary = CAMEL_FOLDER_SUMMARY (cls);
+
+ fd = open (filename, O_RDONLY | O_LARGEFILE);
+ if (fd == -1) {
+ g_warning ("Cannot summarise/index: %s: %s", filename, g_strerror (errno));
+ g_free (filename);
+ return -1;
+ }
+ mp = camel_mime_parser_new ();
+ camel_mime_parser_scan_from (mp, FALSE);
+ camel_mime_parser_init_with_fd (mp, fd);
+ if (cls->index && (forceindex || !camel_index_has_name (cls->index, name))) {
+ d (printf ("forcing indexing of message content\n"));
+ camel_folder_summary_set_index (summary, cls->index);
+ } else {
+ camel_folder_summary_set_index (summary, NULL);
+ }
+ maildirs->priv->current_file = (gchar *) name;
+
+ info = camel_folder_summary_info_new_from_parser (summary, mp);
+ camel_folder_summary_add (summary, info);
+
+ g_object_unref (mp);
+ maildirs->priv->current_file = NULL;
+ camel_folder_summary_set_index (summary, NULL);
+ g_free (filename);
+ return 0;
+}
+
+struct _remove_data {
+ CamelLocalSummary *cls;
+ CamelFolderChangeInfo *changes;
+};
+
+static void
+remove_summary (gchar *key,
+ CamelMessageInfo *info,
+ struct _remove_data *rd)
+{
+ d (printf ("removing message %s from summary\n", key));
+ if (rd->cls->index)
+ camel_index_delete_name (rd->cls->index, camel_message_info_get_uid (info));
+ if (rd->changes)
+ camel_folder_change_info_remove_uid (rd->changes, key);
+ camel_folder_summary_remove ((CamelFolderSummary *) rd->cls, info);
+ camel_message_info_unref (info);
+}
+
+static gint
+maildir_summary_check (CamelLocalSummary *cls,
+ CamelFolderChangeInfo *changes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ DIR *dir;
+ struct dirent *d;
+ gchar *p;
+ CamelMessageInfo *info;
+ CamelMaildirMessageInfo *mdi;
+ CamelFolderSummary *s = (CamelFolderSummary *) cls;
+ GHashTable *left;
+ gint i, count, total;
+ gint forceindex;
+ gchar *new, *cur;
+ gchar *uid;
+ struct _remove_data rd = { cls, changes };
+ GPtrArray *known_uids;
+
+ g_mutex_lock (&((CamelMaildirSummary *) cls)->priv->summary_lock);
+
+ new = g_strdup_printf ("%s/new", cls->folder_path);
+ cur = g_strdup_printf ("%s/cur", cls->folder_path);
+
+ d (printf ("checking summary ...\n"));
+
+ camel_operation_push_message (
+ cancellable, _("Checking folder consistency"));
+
+ /* scan the directory, check for mail files not in the index, or index entries that
+ * no longer exist */
+ dir = opendir (cur);
+ if (dir == NULL) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot open maildir directory path: %s: %s"),
+ cls->folder_path, g_strerror (errno));
+ g_free (cur);
+ g_free (new);
+ camel_operation_pop_message (cancellable);
+ g_mutex_unlock (&((CamelMaildirSummary *) cls)->priv->summary_lock);
+ return -1;
+ }
+
+ /* keeps track of all uid's that have not been processed */
+ left = g_hash_table_new (g_str_hash, g_str_equal);
+ camel_folder_summary_prepare_fetch_all (s, error);
+ known_uids = camel_folder_summary_get_array (s);
+ forceindex = !known_uids || known_uids->len == 0;
+ for (i = 0; known_uids && i < known_uids->len; i++) {
+ info = camel_folder_summary_get ((CamelFolderSummary *) cls, g_ptr_array_index (known_uids, i));
+ if (info) {
+ g_hash_table_insert (left, (gchar *) camel_message_info_get_uid (info), info);
+ }
+ }
+
+ /* joy, use this to pre-count the total, so we can report progress meaningfully */
+ total = 0;
+ count = 0;
+ while (readdir (dir))
+ total++;
+ rewinddir (dir);
+
+ while ((d = readdir (dir))) {
+ gint pc;
+
+ /* Avoid a potential division by zero if the first loop
+ * (to calculate total) is executed on an empty
+ * directory, then the directory is populated before
+ * this loop is executed. */
+ total = MAX (total, count + 1);
+ pc = (total > 0) ? count * 100 / total : 0;
+
+ camel_operation_progress (cancellable, pc);
+ count++;
+
+ /* FIXME: also run stat to check for regular file */
+ p = d->d_name;
+ if (p[0] == '.')
+ continue;
+
+ /* map the filename -> uid */
+ uid = strchr (d->d_name, CAMEL_MAILDIR_FLAG_SEP);
+ if (uid)
+ uid = g_strndup (d->d_name, uid - d->d_name);
+ else
+ uid = g_strdup (d->d_name);
+
+ info = g_hash_table_lookup (left, uid);
+ if (info) {
+ g_hash_table_remove (left, uid);
+ camel_message_info_unref (info);
+ }
+
+ info = camel_folder_summary_get ((CamelFolderSummary *) cls, uid);
+ if (info == NULL) {
+ /* must be a message incorporated by another client, this is not a 'recent' uid */
+ if (camel_maildir_summary_add (cls, d->d_name, forceindex, cancellable) == 0)
+ if (changes)
+ camel_folder_change_info_add_uid (changes, uid);
+ } else {
+ const gchar *filename;
+
+ if (cls->index && (!camel_index_has_name (cls->index, uid))) {
+ /* message_info_new will handle duplicates */
+ camel_maildir_summary_add (cls, d->d_name, forceindex, cancellable);
+ }
+
+ mdi = (CamelMaildirMessageInfo *) info;
+ filename = camel_maildir_info_filename (mdi);
+ /* TODO: only store the extension in the mdi->filename struct, not the whole lot */
+ if (filename == NULL || strcmp (filename, d->d_name) != 0) {
+ g_free (mdi->filename);
+ mdi->filename = g_strdup (d->d_name);
+ }
+ camel_message_info_unref (info);
+ }
+ g_free (uid);
+ }
+ closedir (dir);
+ g_hash_table_foreach (left, (GHFunc) remove_summary, &rd);
+ g_hash_table_destroy (left);
+
+ camel_operation_pop_message (cancellable);
+
+ camel_operation_push_message (
+ cancellable, _("Checking for new messages"));
+
+ /* now, scan new for new messages, and copy them to cur, and so forth */
+ dir = opendir (new);
+ if (dir != NULL) {
+ total = 0;
+ count = 0;
+ while (readdir (dir))
+ total++;
+ rewinddir (dir);
+
+ while ((d = readdir (dir))) {
+ gchar *name, *newname, *destname, *destfilename;
+ gchar *src, *dest;
+ gint pc;
+
+ /* Avoid a potential division by zero if the first loop
+ * (to calculate total) is executed on an empty
+ * directory, then the directory is populated before
+ * this loop is executed. */
+ total = MAX (total, count + 1);
+ pc = (total > 0) ? count * 100 / total : 0;
+
+ camel_operation_progress (cancellable, pc);
+ count++;
+
+ name = d->d_name;
+ if (name[0] == '.')
+ continue;
+
+ /* already in summary? shouldn't happen, but just incase ... */
+ if ((info = camel_folder_summary_get ((CamelFolderSummary *) cls, name))) {
+ camel_message_info_unref (info);
+ newname = destname = camel_folder_summary_next_uid_string (s);
+ } else {
+ gchar *nm;
+ newname = g_strdup (name);
+ nm =strrchr (newname, CAMEL_MAILDIR_FLAG_SEP);
+ if (nm)
+ *nm = '\0';
+ destname = newname;
+ }
+
+ /* copy this to the destination folder, use 'standard' semantics for maildir info field */
+ src = g_strdup_printf ("%s/%s", new, name);
+ destfilename = g_strdup_printf ("%s" CAMEL_MAILDIR_FLAG_SEP_S "2,", destname);
+ dest = g_strdup_printf ("%s/%s", cur, destfilename);
+
+ /* FIXME: This should probably use link/unlink */
+
+ if (g_rename (src, dest) == 0) {
+ camel_maildir_summary_add (cls, destfilename, forceindex, cancellable);
+ if (changes) {
+ camel_folder_change_info_add_uid (changes, destname);
+ camel_folder_change_info_recent_uid (changes, destname);
+ }
+ } else {
+ /* else? we should probably care about failures, but wont */
+ g_warning ("Failed to move new maildir message %s to cur %s", src, dest);
+ }
+
+ /* c strings are painful to work with ... */
+ g_free (destfilename);
+ g_free (newname);
+ g_free (src);
+ g_free (dest);
+ }
+
+ camel_operation_pop_message (cancellable);
+ closedir (dir);
+ }
+
+ g_free (new);
+ g_free (cur);
+
+ camel_folder_summary_free_array (known_uids);
+ g_mutex_unlock (&((CamelMaildirSummary *) cls)->priv->summary_lock);
+
+ return 0;
+}
+
+/* sync the summary with the ondisk files. */
+static gint
+maildir_summary_sync (CamelLocalSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSummaryClass *local_summary_class;
+ gint i;
+ CamelMessageInfo *info;
+ CamelMaildirMessageInfo *mdi;
+ GList *removed_uids = NULL;
+ gchar *name;
+ struct stat st;
+ GPtrArray *known_uids;
+
+ d (printf ("summary_sync(expunge=%s)\n", expunge?"true":"false"));
+
+ if (camel_local_summary_check (cls, changes, cancellable, error) == -1)
+ return -1;
+
+ camel_operation_push_message (cancellable, _("Storing folder"));
+
+ camel_folder_summary_prepare_fetch_all ((CamelFolderSummary *) cls, error);
+ known_uids = camel_folder_summary_get_array ((CamelFolderSummary *) cls);
+ for (i = (known_uids ? known_uids->len : 0) - 1; i >= 0; i--) {
+ camel_operation_progress (cancellable, (known_uids->len - i) * 100 / known_uids->len);
+
+ info = camel_folder_summary_get ((CamelFolderSummary *) cls, g_ptr_array_index (known_uids, i));
+ mdi = (CamelMaildirMessageInfo *) info;
+ if (mdi && (mdi->info.info.flags & CAMEL_MESSAGE_DELETED) && expunge) {
+ name = g_strdup_printf ("%s/cur/%s", cls->folder_path, camel_maildir_info_filename (mdi));
+ d (printf ("deleting %s\n", name));
+ if (unlink (name) == 0 || errno == ENOENT) {
+
+ /* FIXME: put this in folder_summary::remove()? */
+ if (cls->index)
+ camel_index_delete_name (cls->index, camel_message_info_get_uid (info));
+
+ camel_folder_change_info_remove_uid (changes, camel_message_info_get_uid (info));
+ removed_uids = g_list_prepend (removed_uids, (gpointer) camel_pstring_strdup (camel_message_info_get_uid (info)));
+ }
+ g_free (name);
+ } else if (mdi && (mdi->info.info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED)) {
+ gchar *newname = camel_maildir_summary_info_to_name (mdi);
+ gchar *dest;
+
+ /* do we care about additional metainfo stored inside the message? */
+ /* probably should all go in the filename? */
+
+ /* have our flags/ i.e. name changed? */
+ if (strcmp (newname, camel_maildir_info_filename (mdi))) {
+ name = g_strdup_printf ("%s/cur/%s", cls->folder_path, camel_maildir_info_filename (mdi));
+ dest = g_strdup_printf ("%s/cur/%s", cls->folder_path, newname);
+ if (g_rename (name, dest) == -1) {
+ g_warning ("%s: Failed to rename '%s' to '%s': %s", G_STRFUNC, name, dest, g_strerror (errno));
+ }
+ if (g_stat (dest, &st) == -1) {
+ /* we'll assume it didn't work, but dont change anything else */
+ g_free (newname);
+ } else {
+ /* TODO: If this is made mt-safe, then this code could be a problem, since
+ * the estrv is being modified.
+ * Sigh, this may mean the maildir name has to be cached another way */
+ g_free (mdi->filename);
+ mdi->filename = newname;
+ }
+ g_free (name);
+ g_free (dest);
+ } else {
+ g_free (newname);
+ }
+
+ /* strip FOLDER_MESSAGE_FLAGED, etc */
+ mdi->info.info.flags &= 0xffff;
+ }
+ camel_message_info_unref (info);
+ }
+
+ if (removed_uids) {
+ camel_folder_summary_remove_uids (CAMEL_FOLDER_SUMMARY (cls), removed_uids);
+ g_list_free_full (removed_uids, (GDestroyNotify) camel_pstring_free);
+ }
+
+ camel_folder_summary_free_array (known_uids);
+ camel_operation_pop_message (cancellable);
+
+ /* Chain up to parent's sync() method. */
+ local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (camel_maildir_summary_parent_class);
+ return local_summary_class->sync (cls, expunge, changes, cancellable, error);
+}
+
diff --git a/src/camel/providers/local/camel-maildir-summary.h b/src/camel/providers/local/camel-maildir-summary.h
new file mode 100644
index 000000000..77cbdd390
--- /dev/null
+++ b/src/camel/providers/local/camel-maildir-summary.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Not Zed <notzed@lostzed.mmc.com.au>
+ */
+
+#ifndef CAMEL_MAILDIR_SUMMARY_H
+#define CAMEL_MAILDIR_SUMMARY_H
+
+#include "camel-local-summary.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MAILDIR_SUMMARY \
+ (camel_maildir_summary_get_type ())
+#define CAMEL_MAILDIR_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MAILDIR_SUMMARY, CamelMaildirSummary))
+#define CAMEL_MAILDIR_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MAILDIR_SUMMARY, CamelMaildirSummaryClass))
+#define CAMEL_IS_MAILDIR_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MAILDIR_SUMMARY))
+#define CAMEL_IS_MAILDIR_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MAILDIR_SUMMARY))
+#define CAMEL_MAILDIR_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MAILDIR_SUMMARY, CamelMaildirSummaryClass))
+
+#ifdef G_OS_WIN32
+#define CAMEL_MAILDIR_FLAG_SEP '!'
+#define CAMEL_MAILDIR_FLAG_SEP_S "!"
+#else
+#define CAMEL_MAILDIR_FLAG_SEP ':'
+#define CAMEL_MAILDIR_FLAG_SEP_S ":"
+#endif
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMaildirSummary CamelMaildirSummary;
+typedef struct _CamelMaildirSummaryClass CamelMaildirSummaryClass;
+typedef struct _CamelMaildirSummaryPrivate CamelMaildirSummaryPrivate;
+
+typedef struct _CamelMaildirMessageInfo {
+ CamelLocalMessageInfo info;
+
+ gchar *filename; /* maildir has this annoying status on the end of the filename, use this to get the real message id */
+} CamelMaildirMessageInfo;
+
+struct _CamelMaildirSummary {
+ CamelLocalSummary parent;
+ CamelMaildirSummaryPrivate *priv;
+};
+
+struct _CamelMaildirSummaryClass {
+ CamelLocalSummaryClass parent_class;
+};
+
+GType camel_maildir_summary_get_type (void);
+CamelMaildirSummary *camel_maildir_summary_new (struct _CamelFolder *folder, const gchar *maildirdir, CamelIndex *index);
+
+/* convert some info->flags to/from the messageinfo */
+gchar *camel_maildir_summary_info_to_name (const CamelMaildirMessageInfo *info);
+gint camel_maildir_summary_name_to_info (CamelMaildirMessageInfo *info, const gchar *name);
+
+/* TODO: could proably use get_string stuff */
+#define camel_maildir_info_filename(x) (((CamelMaildirMessageInfo *)x)->filename)
+#define camel_maildir_info_set_filename(x, s) (g_free(((CamelMaildirMessageInfo *)x)->filename),((CamelMaildirMessageInfo *)x)->filename = s)
+
+G_END_DECLS
+
+#endif /* CAMEL_MAILDIR_SUMMARY_H */
diff --git a/src/camel/providers/local/camel-mbox-folder.c b/src/camel/providers/local/camel-mbox-folder.c
new file mode 100644
index 000000000..56ca4285d
--- /dev/null
+++ b/src/camel/providers/local/camel-mbox-folder.c
@@ -0,0 +1,524 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include "camel-mbox-folder.h"
+#include "camel-mbox-store.h"
+#include "camel-mbox-summary.h"
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+G_DEFINE_TYPE (CamelMboxFolder, camel_mbox_folder, CAMEL_TYPE_LOCAL_FOLDER)
+
+static gint
+mbox_folder_cmp_uids (CamelFolder *folder,
+ const gchar *uid1,
+ const gchar *uid2)
+{
+ CamelMboxMessageInfo *a, *b;
+ gint res;
+
+ g_return_val_if_fail (folder != NULL, 0);
+ g_return_val_if_fail (folder->summary != NULL, 0);
+
+ a = (CamelMboxMessageInfo *) camel_folder_summary_get (folder->summary, uid1);
+ b = (CamelMboxMessageInfo *) camel_folder_summary_get (folder->summary, uid2);
+
+ if (!a || !b) {
+ /* It's not a problem when one of the messages is not in the summary */
+ if (a)
+ camel_message_info_unref (a);
+ if (b)
+ camel_message_info_unref (b);
+
+ if (a == b)
+ return 0;
+ if (!a)
+ return -1;
+ return 1;
+ }
+
+ res = a->frompos < b->frompos ? -1 : a->frompos == b->frompos ? 0 : 1;
+
+ camel_message_info_unref (a);
+ camel_message_info_unref (b);
+
+ return res;
+}
+
+static void
+mbox_folder_sort_uids (CamelFolder *folder,
+ GPtrArray *uids)
+{
+ g_return_if_fail (camel_mbox_folder_parent_class != NULL);
+ g_return_if_fail (folder != NULL);
+
+ if (uids && uids->len > 1)
+ camel_folder_summary_prepare_fetch_all (folder->summary, NULL);
+
+ CAMEL_FOLDER_CLASS (camel_mbox_folder_parent_class)->sort_uids (folder, uids);
+}
+
+static gchar *
+mbox_folder_get_filename (CamelFolder *folder,
+ const gchar *uid,
+ GError **error)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *) folder;
+ CamelMboxMessageInfo *info;
+ goffset frompos;
+ gchar *filename = NULL;
+
+ 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, error) == -1)
+ return NULL;
+
+ /* check for new messages always */
+ if (camel_local_summary_check ((CamelLocalSummary *) folder->summary, lf->changes, NULL, error) == -1) {
+ camel_local_folder_unlock (lf);
+ return NULL;
+ }
+
+ /* get the message summary info */
+ info = (CamelMboxMessageInfo *) camel_folder_summary_get (folder->summary, uid);
+
+ if (info == NULL) {
+ set_cannot_get_message_ex (
+ error, CAMEL_FOLDER_ERROR_INVALID_UID,
+ uid, lf->folder_path, _("No such message"));
+ goto fail;
+ }
+
+ if (info->frompos == -1) {
+ camel_message_info_unref (info);
+ goto fail;
+ }
+
+ frompos = info->frompos;
+ camel_message_info_unref (info);
+
+ filename = g_strdup_printf ("%s%s!%" PRId64, lf->folder_path, G_DIR_SEPARATOR_S, (gint64) frompos);
+
+fail:
+ /* and unlock now we're finished with it */
+ camel_local_folder_unlock (lf);
+
+ return filename;
+}
+
+static gboolean
+mbox_folder_append_message_sync (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *) folder;
+ CamelStream *output_stream = NULL, *filter_stream = NULL;
+ CamelMimeFilter *filter_from;
+ CamelMboxSummary *mbs = (CamelMboxSummary *) folder->summary;
+ CamelMessageInfo *mi;
+ gchar *fromline = NULL;
+ struct stat st;
+ gint retval;
+ gboolean has_attachment;
+#if 0
+ gchar *xev;
+#endif
+ /* If we can't lock, dont do anything */
+ if (camel_local_folder_lock (lf, CAMEL_LOCK_WRITE, error) == -1)
+ return FALSE;
+
+ 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, cancellable, error);
+ 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, error);
+ if (mi == NULL)
+ goto fail;
+
+ d (printf ("Appending message: uid is %s\n", camel_message_info_get_uid (mi)));
+
+ has_attachment = camel_mime_message_has_attachment (message);
+ if (((camel_message_info_get_flags (mi) & CAMEL_MESSAGE_ATTACHMENTS) && !has_attachment) ||
+ ((camel_message_info_get_flags (mi) & CAMEL_MESSAGE_ATTACHMENTS) == 0 && has_attachment)) {
+ camel_message_info_set_flags (mi, CAMEL_MESSAGE_ATTACHMENTS, has_attachment ? CAMEL_MESSAGE_ATTACHMENTS : 0);
+ }
+
+ output_stream = camel_stream_fs_new_with_name (
+ lf->folder_path, O_WRONLY | O_APPEND |
+ O_LARGEFILE, 0666, error);
+ if (output_stream == NULL) {
+ g_prefix_error (
+ error, _("Cannot open mailbox: %s: "),
+ lf->folder_path);
+ 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), cancellable, error) == -1)
+ goto fail_write;
+
+ /* and write the content to the filtering stream, that translates '\nFrom' into '\n>From' */
+ filter_stream = camel_stream_filter_new (output_stream);
+ filter_from = camel_mime_filter_from_new ();
+ camel_stream_filter_add ((CamelStreamFilter *) filter_stream, filter_from);
+ g_object_unref (filter_from);
+
+ if (camel_data_wrapper_write_to_stream_sync (
+ (CamelDataWrapper *) message, filter_stream, cancellable, error) == -1 ||
+ camel_stream_write (filter_stream, "\n", 1, cancellable, error) == -1 ||
+ camel_stream_flush (filter_stream, cancellable, error) == -1)
+ goto fail_write;
+
+ /* filter stream ref's the output stream itself, so we need to unref it too */
+ g_object_unref (filter_stream);
+ g_object_unref (output_stream);
+ g_free (fromline);
+
+ if (!((CamelMessageInfoBase *) mi)->preview && camel_folder_summary_get_need_preview (folder->summary)) {
+ if (camel_mime_message_build_preview ((CamelMimePart *) message, mi) && ((CamelMessageInfoBase *) mi)->preview)
+ camel_folder_summary_add_preview (folder->summary, mi);
+ }
+
+ /* 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 (g_stat (lf->folder_path, &st) == 0) {
+ ((CamelFolderSummary *) mbs)->time = st.st_mtime;
+ mbs->folder_size = st.st_size;
+ }
+
+ /* unlock as soon as we can */
+ camel_local_folder_unlock (lf);
+
+ if (camel_folder_change_info_changed (lf->changes)) {
+ camel_folder_changed (folder, lf->changes);
+ camel_folder_change_info_clear (lf->changes);
+ }
+
+ if (appended_uid)
+ *appended_uid = g_strdup(camel_message_info_get_uid(mi));
+
+ return TRUE;
+
+fail_write:
+ g_prefix_error (
+ error, _("Cannot append message to mbox file: %s: "),
+ lf->folder_path);
+
+ if (output_stream) {
+ gint fd;
+
+ fd = camel_stream_fs_get_fd (CAMEL_STREAM_FS (output_stream));
+ if (fd != -1) {
+ /* reset the file to original size */
+ do {
+ retval = ftruncate (fd, mbs->folder_size);
+ } while (retval == -1 && errno == EINTR);
+ }
+
+ g_object_unref (output_stream);
+ }
+
+ if (filter_stream)
+ g_object_unref (filter_stream);
+
+ g_free (fromline);
+
+ /* remove the summary info so we are not out-of-sync with the mbox */
+ camel_folder_summary_remove (CAMEL_FOLDER_SUMMARY (mbs), mi);
+
+ /* and tell the summary it's up-to-date */
+ if (g_stat (lf->folder_path, &st) == 0) {
+ ((CamelFolderSummary *) mbs)->time = st.st_mtime;
+ mbs->folder_size = st.st_size;
+ }
+
+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_folder_changed (folder, lf->changes);
+ camel_folder_change_info_clear (lf->changes);
+ }
+
+ return FALSE;
+}
+
+static CamelMimeMessage *
+mbox_folder_get_message_sync (CamelFolder *folder,
+ const gchar *uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *) folder;
+ CamelMimeMessage *message = NULL;
+ CamelMboxMessageInfo *info;
+ CamelMimeParser *parser = NULL;
+ gint fd, retval;
+ gint retried = FALSE;
+ goffset 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, error) == -1)
+ return NULL;
+
+ /* check for new messages always */
+ if (camel_local_summary_check ((CamelLocalSummary *) folder->summary, lf->changes, cancellable, error) == -1) {
+ camel_local_folder_unlock (lf);
+ return NULL;
+ }
+
+retry:
+ /* get the message summary info */
+ info = (CamelMboxMessageInfo *) camel_folder_summary_get (folder->summary, uid);
+
+ if (info == NULL) {
+ set_cannot_get_message_ex (
+ error, CAMEL_FOLDER_ERROR_INVALID_UID,
+ uid, lf->folder_path, _("No such message"));
+ goto fail;
+ }
+
+ if (info->frompos == -1) {
+ camel_message_info_unref (info);
+ goto fail;
+ }
+
+ frompos = info->frompos;
+ camel_message_info_unref (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 = g_open (lf->folder_path, O_LARGEFILE | O_RDONLY | O_BINARY, 0);
+ if (fd == -1) {
+ set_cannot_get_message_ex (
+ error, CAMEL_ERROR_GENERIC,
+ 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", (glong) frompos,
+ (glong) camel_mime_parser_tell_start_from (parser),
+ camel_mime_parser_state (parser));
+
+ g_object_unref (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, cancellable, error);
+ if (retval != -1)
+ goto retry;
+ }
+
+ set_cannot_get_message_ex (
+ error, CAMEL_FOLDER_ERROR_INVALID,
+ 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_sync (
+ (CamelMimePart *) message, parser, cancellable, error)) {
+ g_prefix_error (
+ error, _("Cannot get message %s from folder %s: "),
+ uid, lf->folder_path);
+ g_object_unref (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)
+ g_object_unref (parser);
+
+ /* use the opportunity to notify of changes (particularly if we had a rebuild) */
+ if (camel_folder_change_info_changed (lf->changes)) {
+ camel_folder_changed (folder, lf->changes);
+ camel_folder_change_info_clear (lf->changes);
+ }
+
+ return message;
+}
+
+static CamelLocalSummary *
+mbox_folder_create_summary (CamelLocalFolder *lf,
+ const gchar *folder,
+ CamelIndex *index)
+{
+ return (CamelLocalSummary *) camel_mbox_summary_new ((CamelFolder *) lf, folder, index);
+}
+
+static gint
+mbox_folder_lock (CamelLocalFolder *lf,
+ CamelLockType type,
+ GError **error)
+{
+#ifndef G_OS_WIN32
+ CamelMboxFolder *mf = (CamelMboxFolder *) lf;
+
+ /* make sure we have matching unlocks for locks, camel-local-folder class should enforce this */
+ g_return_val_if_fail (mf->lockfd == -1, -1);
+
+ mf->lockfd = open (lf->folder_path, O_RDWR | O_LARGEFILE, 0);
+ if (mf->lockfd == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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, error) == -1) {
+ close (mf->lockfd);
+ mf->lockfd = -1;
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+static void
+mbox_folder_unlock (CamelLocalFolder *lf)
+{
+#ifndef G_OS_WIN32
+ CamelMboxFolder *mf = (CamelMboxFolder *) lf;
+
+ g_return_if_fail (mf->lockfd != -1);
+ if (mf->lockfd != -1) {
+ camel_unlock_folder (lf->folder_path, mf->lockfd);
+ close (mf->lockfd);
+ }
+ mf->lockfd = -1;
+#endif
+}
+
+static void
+camel_mbox_folder_class_init (CamelMboxFolderClass *class)
+{
+ CamelFolderClass *folder_class;
+ CamelLocalFolderClass *local_folder_class;
+
+ folder_class = CAMEL_FOLDER_CLASS (class);
+ folder_class->cmp_uids = mbox_folder_cmp_uids;
+ folder_class->sort_uids = mbox_folder_sort_uids;
+ folder_class->get_filename = mbox_folder_get_filename;
+ folder_class->append_message_sync = mbox_folder_append_message_sync;
+ folder_class->get_message_sync = mbox_folder_get_message_sync;
+
+ local_folder_class = CAMEL_LOCAL_FOLDER_CLASS (class);
+ local_folder_class->create_summary = mbox_folder_create_summary;
+ local_folder_class->lock = mbox_folder_lock;
+ local_folder_class->unlock = mbox_folder_unlock;
+}
+
+static void
+camel_mbox_folder_init (CamelMboxFolder *mbox_folder)
+{
+ mbox_folder->lockfd = -1;
+}
+
+CamelFolder *
+camel_mbox_folder_new (CamelStore *parent_store,
+ const gchar *full_name,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ gchar *basename;
+
+ basename = g_path_get_basename (full_name);
+
+ folder = g_object_new (
+ CAMEL_TYPE_MBOX_FOLDER,
+ "display-name", basename, "full-name", full_name,
+ "parent-store", parent_store, NULL);
+ folder = (CamelFolder *) camel_local_folder_construct (
+ (CamelLocalFolder *) folder, flags, cancellable, error);
+
+ g_free (basename);
+
+ return folder;
+}
+
diff --git a/src/camel/providers/local/camel-mbox-folder.h b/src/camel/providers/local/camel-mbox-folder.h
new file mode 100644
index 000000000..f309d0292
--- /dev/null
+++ b/src/camel/providers/local/camel-mbox-folder.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_MBOX_FOLDER_H
+#define CAMEL_MBOX_FOLDER_H
+
+#include "camel-local-folder.h"
+#include "camel-mbox-summary.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MBOX_FOLDER \
+ (camel_mbox_folder_get_type ())
+#define CAMEL_MBOX_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MBOX_FOLDER, CamelMboxFolder))
+#define CAMEL_MBOX_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MBOX_FOLDER, CamelMboxFolderClass))
+#define CAMEL_IS_MBOX_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MBOX_FOLDER))
+#define CAMEL_IS_MBOX_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MBOX_FOLDER))
+#define CAMEL_MBOX_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MBOX_FOLDER, CamelMboxFolderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMboxFolder CamelMboxFolder;
+typedef struct _CamelMboxFolderClass CamelMboxFolderClass;
+
+struct _CamelMboxFolder {
+ CamelLocalFolder parent;
+
+ gint lockfd; /* for when we have a lock on the folder */
+};
+
+struct _CamelMboxFolderClass {
+ CamelLocalFolderClass parent_class;
+};
+
+GType camel_mbox_folder_get_type (void);
+CamelFolder * camel_mbox_folder_new (CamelStore *parent_store,
+ const gchar *full_name,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_MBOX_FOLDER_H */
diff --git a/src/camel/providers/local/camel-mbox-store.c b/src/camel/providers/local/camel-mbox-store.c
new file mode 100644
index 000000000..cff6ff1de
--- /dev/null
+++ b/src/camel/providers/local/camel-mbox-store.c
@@ -0,0 +1,1017 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include "camel-mbox-folder.h"
+#include "camel-mbox-store.h"
+
+#define d(x)
+
+G_DEFINE_TYPE (CamelMboxStore, camel_mbox_store, CAMEL_TYPE_LOCAL_STORE)
+
+static const gchar *extensions[] = {
+ ".msf",
+ ".ev-summary",
+ ".ev-summary-meta",
+ ".ibex.index",
+ ".ibex.index.data",
+ ".cmeta",
+ ".lock",
+ ".db",
+ ".journal"
+};
+
+/* used to find out where we've visited already */
+struct _inode {
+ dev_t dnode;
+ ino_t inode;
+};
+
+static guint
+inode_hash (gconstpointer d)
+{
+ const struct _inode *v = d;
+
+ return v->inode ^ v->dnode;
+}
+
+static gboolean
+inode_equal (gconstpointer a,
+ gconstpointer b)
+{
+ const struct _inode *v1 = a, *v2 = b;
+
+ return v1->inode == v2->inode && v1->dnode == v2->dnode;
+}
+
+static void
+inode_free (gpointer k,
+ gpointer v,
+ gpointer d)
+{
+ g_free (k);
+}
+
+static gboolean
+ignore_file (const gchar *filename,
+ gboolean sbd)
+{
+ gint 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 < G_N_ELEMENTS (extensions); 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;
+}
+
+/* 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_peek (store->folders, fi->full_name);
+ if (folder) {
+ if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
+ camel_folder_refresh_info_sync (folder, NULL, NULL);
+ fi->unread = camel_folder_get_unread_message_count (folder);
+ fi->total = camel_folder_get_message_count (folder);
+ g_object_unref (folder);
+ } else {
+ CamelLocalStore *local_store;
+ gchar *folderpath;
+ CamelMboxSummary *mbs;
+
+ local_store = CAMEL_LOCAL_STORE (store);
+
+ /* This should be fast enough not
+ * to have to test for INFO_FAST. */
+ folderpath = camel_local_store_get_full_path (
+ local_store, fi->full_name);
+
+ mbs = (CamelMboxSummary *) camel_mbox_summary_new (
+ NULL, folderpath, NULL);
+ /* FIXME[disk-summary] track exception */
+ if (camel_folder_summary_header_load_from_db ((CamelFolderSummary *) mbs, store, fi->full_name, NULL)) {
+ fi->unread = camel_folder_summary_get_unread_count (
+ (CamelFolderSummary *) mbs);
+ fi->total = camel_folder_summary_get_saved_count (
+ (CamelFolderSummary *) mbs);
+ }
+
+ g_object_unref (mbs);
+ g_free (folderpath);
+ }
+
+ if (camel_local_store_is_main_store (CAMEL_LOCAL_STORE (store)) && fi->full_name
+ && (fi->flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_NORMAL)
+ fi->flags =
+ (fi->flags & ~CAMEL_FOLDER_TYPE_MASK) |
+ camel_local_store_get_folder_type_by_full_name (
+ CAMEL_LOCAL_STORE (store), fi->full_name);
+}
+
+static CamelFolderInfo *
+scan_dir (CamelStore *store,
+ GHashTable *visited,
+ CamelFolderInfo *parent,
+ const gchar *root,
+ const gchar *name,
+ guint32 flags,
+ GError **error)
+{
+ CamelFolderInfo *folders, *tail, *fi;
+ GHashTable *folder_hash;
+ const gchar *dent;
+ GDir *dir;
+
+ tail = folders = NULL;
+
+ if (!(dir = g_dir_open (root, 0, NULL)))
+ 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 = g_dir_read_name (dir))) {
+ gchar *short_name, *full_name, *path, *ext;
+ struct stat st;
+
+ if (dent[0] == '.')
+ continue;
+
+ if (ignore_file (dent, FALSE))
+ continue;
+
+ path = g_strdup_printf ("%s/%s", root, dent);
+ if (g_stat (path, &st) == -1) {
+ g_free (path);
+ continue;
+ }
+#ifndef G_OS_WIN32
+ 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;
+ }
+ }
+#endif
+ short_name = g_strdup (dent);
+ 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 = camel_folder_info_new ();
+ fi->parent = parent;
+
+ fi->full_name = full_name;
+ fi->display_name = short_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->display_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) {
+#ifndef G_OS_WIN32
+ struct _inode *inew = g_new (struct _inode, 1);
+
+ *inew = in;
+ g_hash_table_insert (visited, inew, inew);
+#endif
+ if ((fi->child = scan_dir (store, visited, fi, path, fi->full_name, flags, error)))
+ fi->flags |= CAMEL_FOLDER_CHILDREN;
+ else
+ fi->flags = (fi->flags & ~CAMEL_FOLDER_CHILDREN) | CAMEL_FOLDER_NOCHILDREN;
+ }
+ }
+
+ g_free (path);
+ }
+
+ g_dir_close (dir);
+
+ g_hash_table_destroy (folder_hash);
+
+ return folders;
+}
+
+static gint
+xrename (CamelStore *store,
+ const gchar *old_name,
+ const gchar *new_name,
+ const gchar *ext,
+ gboolean missingok)
+{
+ CamelLocalStore *ls = (CamelLocalStore *) store;
+ gchar *oldpath, *newpath;
+ struct stat st;
+ gint ret = -1;
+
+ if (ext != NULL) {
+ oldpath = camel_local_store_get_meta_path (ls, old_name, ext);
+ newpath = camel_local_store_get_meta_path (ls, new_name, ext);
+ } else {
+ oldpath = camel_local_store_get_full_path (ls, old_name);
+ newpath = camel_local_store_get_full_path (ls, new_name);
+ }
+
+ if (g_stat (oldpath, &st) == -1) {
+ if (missingok && errno == ENOENT) {
+ ret = 0;
+ } else {
+ ret = -1;
+ }
+#ifndef G_OS_WIN32
+ } else if (S_ISDIR (st.st_mode)) {
+ /* use rename for dirs */
+ if (g_rename (oldpath, newpath) == 0 || g_stat (newpath, &st) == 0) {
+ ret = 0;
+ } else {
+ ret = -1;
+ }
+ } else if (link (oldpath, newpath) == 0 /* and link for files */
+ ||(g_stat (newpath, &st) == 0 && st.st_nlink == 2)) {
+ if (unlink (oldpath) == 0) {
+ ret = 0;
+ } else {
+ unlink (newpath);
+ ret = -1;
+ }
+ } else {
+ ret = -1;
+#else
+ } else if ((!g_file_test (newpath, G_FILE_TEST_EXISTS) || g_remove (newpath) == 0) &&
+ g_rename (oldpath, newpath) == 0) {
+ ret = 0;
+ } else {
+ ret = -1;
+#endif
+ }
+
+ g_free (oldpath);
+ g_free (newpath);
+
+ return ret;
+}
+
+static CamelFolder *
+mbox_store_get_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStoreClass *store_class;
+ CamelLocalStore *local_store;
+ struct stat st;
+ gchar *name;
+
+ /* Chain up to parent's get_folder_sync() method. */
+ store_class = CAMEL_STORE_CLASS (camel_mbox_store_parent_class);
+ if (!store_class->get_folder_sync (store, folder_name, flags, cancellable, error))
+ return NULL;
+
+ local_store = CAMEL_LOCAL_STORE (store);
+ name = camel_local_store_get_full_path (local_store, folder_name);
+
+ if (g_stat (name, &st) == -1) {
+ gchar *basename;
+ gchar *dirname;
+ gint fd;
+
+ if (errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot get folder '%s': %s"),
+ folder_name, g_strerror (errno));
+ g_free (name);
+ return NULL;
+ }
+
+ if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Cannot get folder '%s': folder does not exist."),
+ folder_name);
+ g_free (name);
+ return NULL;
+ }
+
+ /* sanity check the folder name */
+ basename = g_path_get_basename (folder_name);
+
+ if (basename[0] == '.' || ignore_file (basename, TRUE)) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot create a folder by this name."));
+ g_free (name);
+ g_free (basename);
+ return NULL;
+ }
+ g_free (basename);
+
+ dirname = g_path_get_dirname (name);
+ if (g_mkdir_with_parents (dirname, 0700) == -1 && errno != EEXIST) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot create folder '%s': %s"),
+ folder_name, g_strerror (errno));
+ g_free (dirname);
+ g_free (name);
+ return NULL;
+ }
+
+ g_free (dirname);
+
+ fd = g_open (
+ name,
+ O_LARGEFILE |
+ O_WRONLY |
+ O_CREAT |
+ O_APPEND |
+ O_BINARY, 0666);
+
+ if (fd == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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)) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot get folder '%s': not a regular file."),
+ folder_name);
+ g_free (name);
+ return NULL;
+ } else
+ g_free (name);
+
+ return camel_mbox_folder_new (store, folder_name, flags, cancellable, error);
+}
+
+static CamelFolderInfo *
+mbox_store_get_folder_info_sync (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalStore *local_store;
+ GHashTable *visited;
+#ifndef G_OS_WIN32
+ struct _inode *inode;
+#endif
+ gchar *path, *subdir;
+ CamelFolderInfo *fi;
+ gchar *basename;
+ struct stat st;
+
+ if (top == NULL)
+ top = "";
+
+ local_store = CAMEL_LOCAL_STORE (store);
+ path = camel_local_store_get_full_path (local_store, top);
+
+ if (*top == '\0') {
+ /* requesting root dir scan */
+ if (g_stat (path, &st) == -1 || !S_ISDIR (st.st_mode)) {
+ g_free (path);
+ return NULL;
+ }
+
+ visited = g_hash_table_new (inode_hash, inode_equal);
+#ifndef G_OS_WIN32
+ inode = g_malloc0 (sizeof (*inode));
+ inode->dnode = st.st_dev;
+ inode->inode = st.st_ino;
+
+ g_hash_table_insert (visited, inode, inode);
+#endif
+ fi = scan_dir (store, visited, NULL, path, NULL, flags, error);
+ g_hash_table_foreach (visited, inode_free, NULL);
+ g_hash_table_destroy (visited);
+ g_free (path);
+
+ return fi;
+ }
+
+ /* requesting scan of specific folder */
+ if (g_stat (path, &st) == -1 || !S_ISREG (st.st_mode)) {
+ gchar *test_if_subdir = g_strdup_printf ("%s.sbd", path);
+
+ if (g_stat (test_if_subdir, &st) == -1) {
+ g_free (path);
+ g_free (test_if_subdir);
+ return NULL;
+ }
+ g_free (test_if_subdir);
+ }
+
+ visited = g_hash_table_new (inode_hash, inode_equal);
+
+ basename = g_path_get_basename (top);
+
+ fi = camel_folder_info_new ();
+ fi->parent = NULL;
+ fi->full_name = g_strdup (top);
+ fi->display_name = basename;
+ fi->unread = -1;
+ fi->total = -1;
+
+ fill_fi (store, fi, flags);
+
+ subdir = g_strdup_printf ("%s.sbd", path);
+ if (g_stat (subdir, &st) == 0) {
+ if (S_ISDIR (st.st_mode))
+ fi->child = scan_dir (store, visited, fi, subdir, top, flags, error);
+ }
+
+ 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;
+}
+
+static CamelFolderInfo *
+mbox_store_create_folder_sync (CamelStore *store,
+ const gchar *parent_name,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* 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? */
+ CamelLocalSettings *local_settings;
+ CamelLocalStore *local_store;
+ CamelFolderInfo *info = NULL;
+ CamelSettings *settings;
+ CamelService *service;
+ gchar *root_path = NULL;
+ gchar *name = NULL;
+ gchar *path = NULL;
+ gchar *dir;
+ CamelFolder *folder;
+ struct stat st;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ root_path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ local_store = CAMEL_LOCAL_STORE (store);
+
+ if (!g_path_is_absolute (root_path)) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Store root %s is not an absolute path"),
+ root_path);
+ goto exit;
+ }
+
+ if (folder_name[0] == '.' || ignore_file (folder_name, TRUE)) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot create a folder by this name."));
+ goto exit;
+ }
+
+ if (parent_name && *parent_name)
+ name = g_strdup_printf ("%s/%s", parent_name, folder_name);
+ else
+ name = g_strdup (folder_name);
+
+ path = camel_local_store_get_full_path (local_store, name);
+
+ dir = g_path_get_dirname (path);
+ if (g_mkdir_with_parents (dir, 0777) == -1 && errno != EEXIST) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot create directory '%s': %s."),
+ dir, g_strerror (errno));
+ g_free (dir);
+ goto exit;
+ }
+
+ g_free (dir);
+
+ if (g_stat (path, &st) == 0 || errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot create folder: %s: %s"),
+ path, errno ? g_strerror (errno) :
+ _("Folder already exists"));
+ goto exit;
+ }
+
+ folder = CAMEL_STORE_GET_CLASS (store)->get_folder_sync (
+ store, name, CAMEL_STORE_FOLDER_CREATE, cancellable, error);
+ if (folder) {
+ g_object_unref (folder);
+ info = CAMEL_STORE_GET_CLASS (store)->get_folder_info_sync (
+ store, name, 0, cancellable, error);
+ }
+
+exit:
+ g_free (root_path);
+ g_free (name);
+ g_free (path);
+
+ return info;
+}
+
+static gboolean
+mbox_store_delete_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalStore *local_store;
+ CamelFolderInfo *fi;
+ CamelFolder *lf;
+ gchar *name, *path;
+ struct stat st;
+
+ local_store = CAMEL_LOCAL_STORE (store);
+ name = camel_local_store_get_full_path (local_store, folder_name);
+ path = g_strdup_printf ("%s.sbd", name);
+
+ if (g_rmdir (path) == -1 && errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not delete folder '%s':\n%s"),
+ folder_name, g_strerror (errno));
+ g_free (path);
+ g_free (name);
+ return FALSE;
+ }
+
+ g_free (path);
+
+ if (g_stat (name, &st) == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not delete folder '%s':\n%s"),
+ folder_name, g_strerror (errno));
+ g_free (name);
+ return FALSE;
+ }
+
+ if (!S_ISREG (st.st_mode)) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("'%s' is not a regular file."), name);
+ g_free (name);
+ return FALSE;
+ }
+
+ if (st.st_size != 0) {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_NON_EMPTY,
+ _("Folder '%s' is not empty. Not deleted."),
+ folder_name);
+ g_free (name);
+ return FALSE;
+ }
+
+ if (g_unlink (name) == -1 && errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not delete folder '%s':\n%s"),
+ name, g_strerror (errno));
+ g_free (name);
+ return FALSE;
+ }
+
+ /* 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 = camel_local_store_get_meta_path (
+ local_store, folder_name, ".ev-summary");
+ if (g_unlink (path) == -1 && errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not delete folder summary file '%s': %s"),
+ path, g_strerror (errno));
+ g_free (path);
+ g_free (name);
+ return FALSE;
+ }
+
+ g_free (path);
+
+ path = camel_local_store_get_meta_path (
+ local_store, folder_name, ".ev-summary-meta");
+ if (g_unlink (path) == -1 && errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not delete folder summary file '%s': %s"),
+ path, g_strerror (errno));
+ g_free (path);
+ g_free (name);
+ return FALSE;
+ }
+
+ g_free (path);
+
+ path = camel_local_store_get_meta_path (
+ local_store, folder_name, ".ibex");
+ if (camel_text_index_remove (path) == -1 && errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not delete folder index file '%s': %s"),
+ path, g_strerror (errno));
+ g_free (path);
+ g_free (name);
+ return FALSE;
+ }
+
+ g_free (path);
+
+ path = NULL;
+ if ((lf = camel_store_get_folder_sync (store, folder_name, 0, cancellable, NULL))) {
+ CamelObject *object = CAMEL_OBJECT (lf);
+ const gchar *state_filename;
+
+ state_filename = camel_object_get_state_filename (object);
+ path = g_strdup (state_filename);
+
+ camel_object_set_state_filename (object, NULL);
+
+ g_object_unref (lf);
+ }
+
+ if (path == NULL)
+ path = camel_local_store_get_meta_path (
+ local_store, folder_name, ".cmeta");
+
+ if (g_unlink (path) == -1 && errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not delete folder meta file '%s': %s"),
+ path, g_strerror (errno));
+
+ g_free (path);
+ g_free (name);
+ return FALSE;
+ }
+
+ g_free (path);
+ g_free (name);
+
+ fi = camel_folder_info_new ();
+ fi->full_name = g_strdup (folder_name);
+ fi->display_name = g_path_get_basename (folder_name);
+ fi->unread = -1;
+
+ camel_store_folder_deleted (store, fi);
+ camel_folder_info_free (fi);
+
+ return TRUE;
+}
+
+static gboolean
+mbox_store_rename_folder_sync (CamelStore *store,
+ const gchar *old,
+ const gchar *new,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalStore *local_store;
+ CamelLocalFolder *folder = NULL;
+ gchar *oldibex, *newibex, *newdir;
+ gint errnosav;
+
+ if (new[0] == '.' || ignore_file (new, TRUE)) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("The new folder name is illegal."));
+ return FALSE;
+ }
+
+ /* try to rollback failures, has obvious races */
+
+ local_store = CAMEL_LOCAL_STORE (store);
+ oldibex = camel_local_store_get_meta_path (local_store, old, ".ibex");
+ newibex = camel_local_store_get_meta_path (local_store, new, ".ibex");
+
+ newdir = g_path_get_dirname (newibex);
+ if (g_mkdir_with_parents (newdir, 0700) == -1) {
+ if (errno != EEXIST) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not rename '%s': '%s': %s"),
+ old, new, g_strerror (errno));
+ g_free (oldibex);
+ g_free (newibex);
+ g_free (newdir);
+
+ return FALSE;
+ }
+
+ 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, ".ev-summary-meta", 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)
+ g_object_unref (folder);
+
+ return TRUE;
+
+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);
+ xrename (store, new, old, ".ev-summary-meta", 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 */
+ g_rmdir (newdir);
+ g_free (newdir);
+ }
+
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errnosav),
+ _("Could not rename '%s' to %s: %s"),
+ old, new, g_strerror (errnosav));
+
+ g_free (newibex);
+ g_free (oldibex);
+
+ if (folder)
+ g_object_unref (folder);
+
+ return FALSE;
+}
+
+static gchar *
+mbox_store_get_full_path (CamelLocalStore *ls,
+ const gchar *full_name)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ GString *full_path;
+ gchar *root_path;
+ const gchar *cp;
+
+ service = CAMEL_SERVICE (ls);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ root_path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ g_return_val_if_fail (root_path != NULL, NULL);
+
+ full_path = g_string_new (root_path);
+
+ /* Root path may or may not have a trailing separator. */
+ if (!g_str_has_suffix (root_path, G_DIR_SEPARATOR_S))
+ g_string_append_c (full_path, G_DIR_SEPARATOR);
+
+ cp = full_name;
+ while (*cp != '\0') {
+ if (G_IS_DIR_SEPARATOR (*cp)) {
+ g_string_append (full_path, ".sbd");
+ g_string_append_c (full_path, *cp++);
+
+ /* Skip extranaeous separators. */
+ while (G_IS_DIR_SEPARATOR (*cp))
+ cp++;
+ } else {
+ g_string_append_c (full_path, *cp++);
+ }
+ }
+
+ g_free (root_path);
+
+ return g_string_free (full_path, FALSE);
+}
+
+static gchar *
+mbox_store_get_meta_path (CamelLocalStore *ls,
+ const gchar *full_name,
+ const gchar *ext)
+{
+/*#define USE_HIDDEN_META_FILES*/
+#ifdef USE_HIDDEN_META_FILES
+ gchar *name, *slash;
+ gsize name_len;
+
+ name_len = strlen (full_name) + strlen (ext) + 2;
+ name = g_alloca (name_len);
+ if ((slash = strrchr (full_name, '/')))
+ g_snprintf (
+ name, name_len, "%.*s.%s%s",
+ slash - full_name + 1,
+ full_name, slash + 1, ext);
+ else
+ g_snprintf (name, name_len, ".%s%s", full_name, ext);
+
+ return mbox_store_get_full_path (ls, name);
+#else
+ gchar *full_path, *path;
+
+ full_path = mbox_store_get_full_path (ls, full_name);
+ path = g_strdup_printf ("%s%s", full_path, ext);
+ g_free (full_path);
+
+ return path;
+#endif
+}
+
+static void
+camel_mbox_store_class_init (CamelMboxStoreClass *class)
+{
+ CamelStoreClass *store_class;
+ CamelLocalStoreClass *local_store_class;
+
+ store_class = CAMEL_STORE_CLASS (class);
+ store_class->get_folder_sync = mbox_store_get_folder_sync;
+ store_class->get_folder_info_sync = mbox_store_get_folder_info_sync;
+ store_class->create_folder_sync = mbox_store_create_folder_sync;
+ store_class->delete_folder_sync = mbox_store_delete_folder_sync;
+ store_class->rename_folder_sync = mbox_store_rename_folder_sync;
+
+ local_store_class = CAMEL_LOCAL_STORE_CLASS (class);
+ local_store_class->get_full_path = mbox_store_get_full_path;
+ local_store_class->get_meta_path = mbox_store_get_meta_path;
+}
+
+static void
+camel_mbox_store_init (CamelMboxStore *mbox_store)
+{
+}
+
diff --git a/src/camel/providers/local/camel-mbox-store.h b/src/camel/providers/local/camel-mbox-store.h
new file mode 100644
index 000000000..be38efd43
--- /dev/null
+++ b/src/camel/providers/local/camel-mbox-store.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_MBOX_STORE_H
+#define CAMEL_MBOX_STORE_H
+
+#include "camel-local-store.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MBOX_STORE \
+ (camel_mbox_store_get_type ())
+#define CAMEL_MBOX_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MBOX_STORE, CamelMboxStore))
+#define CAMEL_MBOX_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MBOX_STORE, CamelMboxStoreClass))
+#define CAMEL_IS_MBOX_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MBOX_STORE))
+#define CAMEL_IS_MBOX_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MBOX_STORE))
+#define CAMEL_MBOX_STORE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MBOX_STORE, CamelMboxStoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMboxStore CamelMboxStore;
+typedef struct _CamelMboxStoreClass CamelMboxStoreClass;
+
+struct _CamelMboxStore {
+ CamelLocalStore parent;
+};
+
+struct _CamelMboxStoreClass {
+ CamelLocalStoreClass parent_class;
+};
+
+GType camel_mbox_store_get_type (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_MBOX_STORE_H */
+
diff --git a/src/camel/providers/local/camel-mbox-summary.c b/src/camel/providers/local/camel-mbox-summary.c
new file mode 100644
index 000000000..4e82fdc6b
--- /dev/null
+++ b/src/camel/providers/local/camel-mbox-summary.c
@@ -0,0 +1,1407 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include "camel-mbox-summary.h"
+#include "camel-local-private.h"
+
+#define io(x)
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+/* Enable the use of elm/pine style "Status" & "X-Status" headers */
+#define STATUS_PINE
+
+#define CAMEL_MBOX_SUMMARY_VERSION (1)
+
+#define CHECK_CALL(x) G_STMT_START { \
+ if ((x) == -1) { \
+ g_debug ("%s: Call of '" #x "' failed: %s", G_STRFUNC, g_strerror (errno)); \
+ } \
+ } G_STMT_END
+
+typedef struct _CamelMboxMessageContentInfo CamelMboxMessageContentInfo;
+
+struct _CamelMboxMessageContentInfo {
+ CamelMessageContentInfo info;
+};
+
+static CamelFIRecord *
+ summary_header_to_db (CamelFolderSummary *,
+ GError **error);
+static gboolean summary_header_from_db (CamelFolderSummary *,
+ CamelFIRecord *);
+static CamelMessageInfo *
+ message_info_from_db (CamelFolderSummary *s,
+ CamelMIRecord *record);
+static CamelMIRecord *
+ message_info_to_db (CamelFolderSummary *s,
+ CamelMessageInfo *info);
+
+static CamelMessageInfo *
+ message_info_new_from_header (CamelFolderSummary *,
+ struct _camel_header_raw *);
+static CamelMessageInfo *
+ message_info_new_from_parser (CamelFolderSummary *,
+ CamelMimeParser *);
+
+static gchar * mbox_summary_encode_x_evolution (CamelLocalSummary *cls,
+ const CamelLocalMessageInfo *mi);
+
+static gint mbox_summary_check (CamelLocalSummary *cls,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error);
+static gint mbox_summary_sync (CamelLocalSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error);
+#ifdef STATUS_PINE
+static CamelMessageInfo *
+ mbox_summary_add (CamelLocalSummary *cls,
+ CamelMimeMessage *msg,
+ const CamelMessageInfo *info,
+ CamelFolderChangeInfo *ci,
+ GError **error);
+#endif
+
+static gint mbox_summary_sync_quick (CamelMboxSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error);
+static gint mbox_summary_sync_full (CamelMboxSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error);
+
+#ifdef STATUS_PINE
+/* Which status flags are stored in each separate header */
+#define STATUS_XSTATUS \
+ (CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_DELETED)
+#define STATUS_STATUS (CAMEL_MESSAGE_SEEN)
+
+static void encode_status (guint32 flags, gchar status[8]);
+static guint32 decode_status (const gchar *status);
+#endif
+
+G_DEFINE_TYPE (
+ CamelMboxSummary,
+ camel_mbox_summary,
+ CAMEL_TYPE_LOCAL_SUMMARY)
+
+static gboolean
+mbox_info_set_user_flag (CamelMessageInfo *mi,
+ const gchar *name,
+ gboolean value)
+{
+ gint res;
+
+ res = CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_parent_class)->info_set_user_flag (mi, name, value);
+ if (res)
+ ((CamelLocalMessageInfo *) mi)->info.flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
+
+ return res;
+}
+
+static gboolean
+mbox_info_set_user_tag (CamelMessageInfo *mi,
+ const gchar *name,
+ const gchar *value)
+{
+ gint res;
+
+ res = CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_parent_class)->info_set_user_tag (mi, name, value);
+ if (res)
+ ((CamelLocalMessageInfo *) mi)->info.flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
+
+ return res;
+}
+
+#ifdef STATUS_PINE
+static gboolean
+mbox_info_set_flags (CamelMessageInfo *mi,
+ guint32 flags,
+ guint32 set)
+{
+ /* Basically, if anything could change the Status line, presume it does */
+ if (((CamelMboxSummary *) mi->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 CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_parent_class)->
+ info_set_flags (mi, flags, set);
+}
+#endif
+
+static void
+camel_mbox_summary_class_init (CamelMboxSummaryClass *class)
+{
+ CamelFolderSummaryClass *folder_summary_class;
+ CamelLocalSummaryClass *local_summary_class;
+
+ folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (class);
+ folder_summary_class->message_info_size = sizeof (CamelMboxMessageInfo);
+ folder_summary_class->content_info_size = sizeof (CamelMboxMessageContentInfo);
+ folder_summary_class->summary_header_from_db = summary_header_from_db;
+ folder_summary_class->summary_header_to_db = summary_header_to_db;
+ folder_summary_class->message_info_from_db = message_info_from_db;
+ folder_summary_class->message_info_to_db = message_info_to_db;
+ folder_summary_class->message_info_new_from_header = message_info_new_from_header;
+ folder_summary_class->message_info_new_from_parser = message_info_new_from_parser;
+ folder_summary_class->info_set_user_flag = mbox_info_set_user_flag;
+ folder_summary_class->info_set_user_tag = mbox_info_set_user_tag;
+#ifdef STATUS_PINE
+ folder_summary_class->info_set_flags = mbox_info_set_flags;
+#endif
+
+ local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (class);
+ local_summary_class->encode_x_evolution = mbox_summary_encode_x_evolution;
+ local_summary_class->check = mbox_summary_check;
+ local_summary_class->sync = mbox_summary_sync;
+#ifdef STATUS_PINE
+ local_summary_class->add = mbox_summary_add;
+#endif
+
+ class->sync_quick = mbox_summary_sync_quick;
+ class->sync_full = mbox_summary_sync_full;
+}
+
+static void
+camel_mbox_summary_init (CamelMboxSummary *mbox_summary)
+{
+ CamelFolderSummary *folder_summary;
+
+ folder_summary = CAMEL_FOLDER_SUMMARY (mbox_summary);
+
+ /* and a unique file version */
+ folder_summary->version += CAMEL_MBOX_SUMMARY_VERSION;
+}
+
+/**
+ * camel_mbox_summary_new:
+ *
+ * Create a new CamelMboxSummary object.
+ *
+ * Returns: A new CamelMboxSummary widget.
+ **/
+CamelMboxSummary *
+camel_mbox_summary_new (CamelFolder *folder,
+ const gchar *mbox_name,
+ CamelIndex *index)
+{
+ CamelMboxSummary *new;
+
+ new = g_object_new (CAMEL_TYPE_MBOX_SUMMARY, "folder", folder, NULL);
+ if (folder) {
+ CamelFolderSummary *summary = (CamelFolderSummary *) new;
+ CamelStore *parent_store;
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ /* Set the functions for db sorting */
+ camel_db_set_collate (parent_store->cdb_r, "bdata", "mbox_frompos_sort", (CamelDBCollate) camel_local_frompos_sort);
+ summary->sort_by = "bdata";
+ summary->collate = "mbox_frompos_sort";
+
+ }
+ camel_local_summary_construct ((CamelLocalSummary *) new, mbox_name, index);
+ return new;
+}
+
+void camel_mbox_summary_xstatus (CamelMboxSummary *mbs, gint state)
+{
+ mbs->xstatus = state;
+}
+
+static gchar *
+mbox_summary_encode_x_evolution (CamelLocalSummary *cls,
+ const CamelLocalMessageInfo *mi)
+{
+ const gchar *p, *uidstr;
+ guint32 uid;
+
+ /* This is busted, it is supposed to encode ALL DATA */
+ p = uidstr = camel_message_info_get_uid (mi);
+ while (*p && isdigit (*p))
+ p++;
+
+ if (*p == 0 && sscanf (uidstr, "%u", &uid) == 1) {
+ return g_strdup_printf ("%08x-%04x", uid, mi->info.flags & 0xffff);
+ } else {
+ return g_strdup_printf ("%s-%04x", uidstr, mi->info.flags & 0xffff);
+ }
+}
+
+static gboolean
+summary_header_from_db (CamelFolderSummary *s,
+ struct _CamelFIRecord *fir)
+{
+ CamelMboxSummary *mbs = CAMEL_MBOX_SUMMARY (s);
+ gchar *part;
+
+ if (!CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_parent_class)->summary_header_from_db (s, fir))
+ return FALSE;
+
+ part = fir->bdata;
+ if (part) {
+ mbs->version = bdata_extract_digit (&part);
+ mbs->folder_size = bdata_extract_digit (&part);
+ }
+
+ return TRUE;
+}
+
+static CamelFIRecord *
+summary_header_to_db (CamelFolderSummary *s,
+ GError **error)
+{
+ CamelFolderSummaryClass *folder_summary_class;
+ CamelMboxSummary *mbs = CAMEL_MBOX_SUMMARY (s);
+ struct _CamelFIRecord *fir;
+ gchar *tmp;
+
+ /* Chain up to parent's summary_header_to_db() method. */
+ folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_parent_class);
+ fir = folder_summary_class->summary_header_to_db (s, error);
+ if (fir) {
+ tmp = fir->bdata;
+ fir->bdata = g_strdup_printf ("%s %d %d", tmp ? tmp : "", CAMEL_MBOX_SUMMARY_VERSION, (gint) mbs->folder_size);
+ g_free (tmp);
+ }
+
+ return fir;
+}
+
+static CamelMessageInfo *
+message_info_new_from_header (CamelFolderSummary *s,
+ struct _camel_header_raw *h)
+{
+ CamelMboxMessageInfo *mi;
+ CamelMboxSummary *mbs = (CamelMboxSummary *) s;
+
+ mi = (CamelMboxMessageInfo *) CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_parent_class)->message_info_new_from_header (s, h);
+ if (mi) {
+ const gchar *xev, *uid;
+ CamelMboxMessageInfo *info = NULL;
+ gint add = 0; /* bitmask of things to add, 1 assign uid, 2, just add as new, 4 = recent */
+#ifdef STATUS_PINE
+ const gchar *status = NULL, *xstatus = NULL;
+ guint32 flags = 0;
+
+ if (mbs->xstatus) {
+ /* check for existance of status & x-status headers */
+ status = camel_header_raw_find (&h, "Status", NULL);
+ if (status)
+ flags = decode_status (status);
+ xstatus = camel_header_raw_find (&h, "X-Status", NULL);
+ if (xstatus)
+ flags |= decode_status (xstatus);
+ }
+#endif
+ /* if we have an xev header, use it, else assign a new one */
+ xev = camel_header_raw_find (&h, "X-Evolution", NULL);
+ if (xev != NULL
+ && camel_local_summary_decode_x_evolution ((CamelLocalSummary *) s, xev, &mi->info) == 0) {
+ uid = camel_message_info_get_uid (mi);
+ d (printf ("found valid x-evolution: %s\n", uid));
+ /* If one is there, it should be there already */
+ info = (CamelMboxMessageInfo *) camel_folder_summary_peek_loaded (s, uid);
+ if (info) {
+ if ((info->info.info.flags & CAMEL_MESSAGE_FOLDER_NOTSEEN)) {
+ info->info.info.flags &= ~CAMEL_MESSAGE_FOLDER_NOTSEEN;
+ camel_message_info_unref (mi);
+ mi = info;
+ } else {
+ add = 7;
+ d (printf ("seen '%s' before, adding anew\n", uid));
+ camel_message_info_unref (info);
+ }
+ } else {
+ add = 2;
+ d (printf ("but isn't present in summary\n"));
+ }
+ } else {
+ d (printf ("didn't find x-evolution\n"));
+ add = 7;
+ }
+
+ if (add&1) {
+ mi->info.info.flags |= CAMEL_MESSAGE_FOLDER_FLAGGED | CAMEL_MESSAGE_FOLDER_NOXEV;
+ camel_pstring_free (mi->info.info.uid);
+ mi->info.info.uid = camel_pstring_add (camel_folder_summary_next_uid_string (s), TRUE);
+ } else {
+ camel_folder_summary_set_next_uid (s, strtoul (camel_message_info_get_uid (mi), NULL, 10));
+ }
+#ifdef STATUS_PINE
+ if (mbs->xstatus && add&2) {
+ /* use the status as the flags when we read it the first time */
+ if (status)
+ mi->info.info.flags = (mi->info.info.flags & ~(STATUS_STATUS)) | (flags & STATUS_STATUS);
+ if (xstatus)
+ mi->info.info.flags = (mi->info.info.flags & ~(STATUS_XSTATUS)) | (flags & STATUS_XSTATUS);
+ }
+#endif
+ if (mbs->changes) {
+ if (add&2)
+ camel_folder_change_info_add_uid (mbs->changes, camel_message_info_get_uid (mi));
+ if ((add&4) && status == NULL)
+ camel_folder_change_info_recent_uid (mbs->changes, camel_message_info_get_uid (mi));
+ }
+
+ mi->frompos = -1;
+ }
+
+ return (CamelMessageInfo *) mi;
+}
+
+static CamelMessageInfo *
+message_info_new_from_parser (CamelFolderSummary *s,
+ CamelMimeParser *mp)
+{
+ CamelMessageInfo *mi;
+
+ mi = CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_parent_class)->message_info_new_from_parser (s, mp);
+ if (mi) {
+ CamelMboxMessageInfo *mbi = (CamelMboxMessageInfo *) mi;
+
+ mbi->frompos = camel_mime_parser_tell_start_from (mp);
+ }
+
+ return mi;
+}
+
+static CamelMessageInfo *
+message_info_from_db (CamelFolderSummary *s,
+ struct _CamelMIRecord *mir)
+{
+ CamelMessageInfo *mi;
+
+ mi = CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_parent_class)->message_info_from_db (s, mir);
+
+ if (mi) {
+ CamelMboxMessageInfo *mbi = (CamelMboxMessageInfo *) mi;
+ gchar *part = mir->bdata;
+ if (part) {
+ mbi->frompos = bdata_extract_digit (&part);
+ }
+ }
+
+ return mi;
+}
+
+static struct _CamelMIRecord *
+message_info_to_db (CamelFolderSummary *s,
+ CamelMessageInfo *info)
+{
+ CamelMboxMessageInfo *mbi = (CamelMboxMessageInfo *) info;
+ struct _CamelMIRecord *mir;
+
+ mir = CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_parent_class)->message_info_to_db (s, info);
+ mir->bdata = g_strdup_printf ("%" G_GOFFSET_FORMAT, mbi->frompos);
+
+ return mir;
+}
+
+/* like summary_rebuild, but also do changeinfo stuff (if supplied) */
+static gint
+summary_update (CamelLocalSummary *cls,
+ goffset offset,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint i;
+ CamelFolderSummary *s = (CamelFolderSummary *) cls;
+ CamelMboxSummary *mbs = (CamelMboxSummary *) cls;
+ CamelMimeParser *mp;
+ CamelMboxMessageInfo *mi;
+ CamelStore *parent_store;
+ const gchar *full_name;
+ gint fd;
+ gint ok = 0;
+ struct stat st;
+ goffset size = 0;
+ GList *del = NULL;
+ GPtrArray *known_uids;
+
+ d (printf ("Calling summary update, from pos %d\n", (gint) offset));
+
+ cls->index_force = FALSE;
+
+ camel_operation_push_message (cancellable, _("Storing folder"));
+
+ camel_folder_summary_lock (s);
+ fd = g_open (cls->folder_path, O_LARGEFILE | O_RDONLY | O_BINARY, 0);
+ if (fd == -1) {
+ d (printf ("%s failed to open: %s\n", cls->folder_path, g_strerror (errno)));
+ camel_folder_summary_unlock (s);
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not open folder: %s: %s"),
+ cls->folder_path, g_strerror (errno));
+ camel_operation_pop_message (cancellable);
+ return -1;
+ }
+
+ if (fstat (fd, &st) == 0)
+ size = st.st_size;
+
+ mp = camel_mime_parser_new ();
+ camel_mime_parser_init_with_fd (mp, fd);
+ camel_mime_parser_scan_from (mp, TRUE);
+ camel_mime_parser_seek (mp, offset, SEEK_SET);
+
+ if (offset > 0) {
+ if (camel_mime_parser_step (mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM
+ && camel_mime_parser_tell_start_from (mp) == offset) {
+ camel_mime_parser_unstep (mp);
+ } else {
+ g_warning ("The next message didn't start where I expected, building summary from start");
+ camel_mime_parser_drop_step (mp);
+ offset = 0;
+ camel_mime_parser_seek (mp, offset, SEEK_SET);
+ }
+ }
+
+ /* we mark messages as to whether we've seen them or not.
+ * If we're not starting from the start, we must be starting
+ * from the old end, so everything must be treated as new */
+ camel_folder_summary_prepare_fetch_all (s, NULL);
+ known_uids = camel_folder_summary_get_array (s);
+ for (i = 0; known_uids && i < known_uids->len; i++) {
+ mi = (CamelMboxMessageInfo *) camel_folder_summary_get (s, g_ptr_array_index (known_uids, i));
+ if (offset == 0)
+ mi->info.info.flags |= CAMEL_MESSAGE_FOLDER_NOTSEEN;
+ else
+ mi->info.info.flags &= ~CAMEL_MESSAGE_FOLDER_NOTSEEN;
+ camel_message_info_unref (mi);
+ }
+ camel_folder_summary_free_array (known_uids);
+ mbs->changes = changeinfo;
+
+ while (camel_mime_parser_step (mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM) {
+ CamelMessageInfo *info;
+ goffset pc = camel_mime_parser_tell_start_from (mp) + 1;
+ gint progress;
+
+ /* To avoid a division by zero if the fstat()
+ * call above failed. */
+ size = MAX (size, pc);
+ progress = (size > 0) ? (gint) (((gfloat) pc / size) * 100) : 0;
+
+ camel_operation_progress (cancellable, progress);
+
+ info = camel_folder_summary_info_new_from_parser (s, mp);
+ camel_folder_summary_add (s, info);
+
+ g_warn_if_fail (camel_mime_parser_step (mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM_END);
+ }
+
+ g_object_unref (mp);
+
+ known_uids = camel_folder_summary_get_array (s);
+ for (i = 0; known_uids && i < known_uids->len; i++) {
+ const gchar *uid;
+
+ uid = g_ptr_array_index (known_uids, i);
+ if (!uid)
+ continue;
+
+ mi = (CamelMboxMessageInfo *) camel_folder_summary_get (s, uid);
+ /* must've dissapeared from the file? */
+ if (!mi || mi->info.info.flags & CAMEL_MESSAGE_FOLDER_NOTSEEN) {
+ d (printf ("uid '%s' vanished, removing", uid));
+ if (changeinfo)
+ camel_folder_change_info_remove_uid (changeinfo, uid);
+ del = g_list_prepend (del, (gpointer) camel_pstring_strdup (uid));
+ if (mi)
+ camel_folder_summary_remove (s, (CamelMessageInfo *) mi);
+ }
+
+ if (mi)
+ camel_message_info_unref (mi);
+ }
+
+ if (known_uids)
+ camel_folder_summary_free_array (known_uids);
+
+ /* Delete all in one transaction */
+ full_name = camel_folder_get_full_name (camel_folder_summary_get_folder (s));
+ parent_store = camel_folder_get_parent_store (camel_folder_summary_get_folder (s));
+ camel_db_delete_uids (parent_store->cdb_w, full_name, del, NULL);
+ g_list_foreach (del, (GFunc) camel_pstring_free, NULL);
+ g_list_free (del);
+
+ mbs->changes = NULL;
+
+ /* update the file size/mtime in the summary */
+ if (ok != -1) {
+ if (g_stat (cls->folder_path, &st) == 0) {
+ camel_folder_summary_touch (s);
+ mbs->folder_size = st.st_size;
+ s->time = st.st_mtime;
+ }
+ }
+
+ camel_operation_pop_message (cancellable);
+ camel_folder_summary_unlock (s);
+
+ return ok;
+}
+
+static gint
+mbox_summary_check (CamelLocalSummary *cls,
+ CamelFolderChangeInfo *changes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMboxSummary *mbs = (CamelMboxSummary *) cls;
+ CamelFolderSummary *s = (CamelFolderSummary *) cls;
+ struct stat st;
+ gint ret = 0;
+ gint i;
+
+ d (printf ("Checking summary\n"));
+
+ camel_folder_summary_lock (s);
+
+ /* check if the summary is up-to-date */
+ if (g_stat (cls->folder_path, &st) == -1) {
+ camel_folder_summary_clear (s, NULL);
+ camel_folder_summary_unlock (s);
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot check folder: %s: %s"),
+ cls->folder_path, g_strerror (errno));
+ return -1;
+ }
+
+ if (cls->check_force)
+ mbs->folder_size = 0;
+ cls->check_force = 0;
+
+ if (st.st_size == 0) {
+ GPtrArray *known_uids;
+
+ /* empty? No need to scan at all */
+ d (printf ("Empty mbox, clearing summary\n"));
+ camel_folder_summary_prepare_fetch_all (s, NULL);
+ known_uids = camel_folder_summary_get_array (s);
+ for (i = 0; known_uids && i < known_uids->len; i++) {
+ CamelMessageInfo *info = camel_folder_summary_get (s, g_ptr_array_index (known_uids, i));
+
+ if (info) {
+ camel_folder_change_info_remove_uid (changes, camel_message_info_get_uid (info));
+ camel_message_info_unref (info);
+ }
+ }
+ camel_folder_summary_free_array (known_uids);
+ camel_folder_summary_clear (s, NULL);
+ ret = 0;
+ } else {
+ /* is the summary uptodate? */
+ if (st.st_size != mbs->folder_size || st.st_mtime != s->time) {
+ if (mbs->folder_size < st.st_size) {
+ /* this will automatically rescan from 0 if there is a problem */
+ d (printf ("folder grew, attempting to rebuild from %d\n", mbs->folder_size));
+ ret = summary_update (cls, mbs->folder_size, changes, cancellable, error);
+ } else {
+ d (printf ("folder shrank! rebuilding from start\n"));
+ ret = summary_update (cls, 0, changes, cancellable, error);
+ }
+ } else {
+ d (printf ("Folder unchanged, do nothing\n"));
+ }
+ }
+
+ /* FIXME: move upstream? */
+
+ if (ret != -1) {
+ if (mbs->folder_size != st.st_size || s->time != st.st_mtime) {
+ mbs->folder_size = st.st_size;
+ s->time = st.st_mtime;
+ camel_folder_summary_touch (s);
+ }
+ }
+
+ camel_folder_summary_unlock (s);
+
+ return ret;
+}
+
+/* perform a full sync */
+static gint
+mbox_summary_sync_full (CamelMboxSummary *mbs,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSummary *cls = (CamelLocalSummary *) mbs;
+ CamelFolderSummary *s = CAMEL_FOLDER_SUMMARY (mbs);
+ gint fd = -1, fdout = -1;
+ gchar *tmpname = NULL;
+ gsize tmpname_len = 0;
+ guint32 flags = (expunge ? 1 : 0), filemode = 0600;
+ struct stat st;
+
+ d (printf ("performing full summary/sync\n"));
+
+ camel_operation_push_message (cancellable, _("Storing folder"));
+ camel_folder_summary_lock (s);
+
+ fd = g_open (cls->folder_path, O_LARGEFILE | O_RDONLY | O_BINARY, 0);
+ if (fd == -1) {
+ camel_folder_summary_unlock (s);
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not open file: %s: %s"),
+ cls->folder_path, g_strerror (errno));
+ camel_operation_pop_message (cancellable);
+ return -1;
+ }
+
+ /* preserve attributes as set on the file previously */
+ if (fstat (fd, &st) == 0)
+ filemode = 07777 & st.st_mode;
+
+ tmpname_len = strlen (cls->folder_path) + 5;
+ tmpname = g_alloca (tmpname_len);
+ g_snprintf (tmpname, tmpname_len, "%s.tmp", cls->folder_path);
+ d (printf ("Writing temporary file to %s\n", tmpname));
+ fdout = g_open (tmpname, O_LARGEFILE | O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, filemode);
+ if (fdout == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot open temporary mailbox: %s"),
+ g_strerror (errno));
+ goto error;
+ }
+
+ if (camel_mbox_summary_sync_mbox (
+ (CamelMboxSummary *) cls, flags, changeinfo,
+ fd, fdout, cancellable, error) == -1)
+ goto error;
+
+ d (printf ("Closing folders\n"));
+
+ if (close (fd) == -1) {
+ g_warning ("Cannot close source folder: %s", g_strerror (errno));
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not close source folder %s: %s"),
+ cls->folder_path, g_strerror (errno));
+ fd = -1;
+ goto error;
+ }
+
+ fd = -1;
+
+ if (close (fdout) == -1) {
+ g_warning ("Cannot close temporary folder: %s", g_strerror (errno));
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not close temporary folder: %s"),
+ g_strerror (errno));
+ fdout = -1;
+ goto error;
+ }
+
+ fdout = -1;
+
+ /* this should probably either use unlink/link/unlink, or recopy over
+ * the original mailbox, for various locking reasons/etc */
+#ifdef G_OS_WIN32
+ if (g_file_test (cls->folder_path,G_FILE_TEST_IS_REGULAR) && g_remove (cls->folder_path) == -1)
+ g_warning ("Cannot remove %s: %s", cls->folder_path, g_strerror (errno));
+#endif
+ if (g_rename (tmpname, cls->folder_path) == -1) {
+ g_warning ("Cannot rename folder: %s", g_strerror (errno));
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not rename folder: %s"),
+ g_strerror (errno));
+ goto error;
+ }
+
+ camel_operation_pop_message (cancellable);
+ camel_folder_summary_unlock (s);
+
+ return 0;
+ error:
+ if (fd != -1)
+ close (fd);
+
+ if (fdout != -1)
+ close (fdout);
+
+ g_unlink (tmpname);
+
+ camel_operation_pop_message (cancellable);
+ camel_folder_summary_unlock (s);
+
+ return -1;
+}
+
+static gint
+cms_sort_frompos (gconstpointer a,
+ gconstpointer b,
+ gpointer data)
+{
+ CamelFolderSummary *summary = (CamelFolderSummary *) data;
+ CamelMboxMessageInfo *info1, *info2;
+ gint ret = 0;
+
+ /* Things are in memory already. Sorting speeds up syncing, if things are sorted by from pos. */
+ info1 = (CamelMboxMessageInfo *) camel_folder_summary_get (summary, *(gchar **) a);
+ info2 = (CamelMboxMessageInfo *) camel_folder_summary_get (summary, *(gchar **) b);
+
+ if (info1->frompos > info2->frompos)
+ ret = 1;
+ else if (info1->frompos < info2->frompos)
+ ret = -1;
+ else
+ ret = 0;
+ camel_message_info_unref (info1);
+ camel_message_info_unref (info2);
+
+ return ret;
+
+}
+
+/* perform a quick sync - only system flags have changed */
+static gint
+mbox_summary_sync_quick (CamelMboxSummary *mbs,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSummary *cls = (CamelLocalSummary *) mbs;
+ CamelFolderSummary *s = (CamelFolderSummary *) mbs;
+ CamelMimeParser *mp = NULL;
+ gint i;
+ CamelMboxMessageInfo *info = NULL;
+ gint fd = -1, pfd;
+ gchar *xevnew, *xevtmp;
+ const gchar *xev;
+ gint len;
+ goffset lastpos;
+ GPtrArray *summary = NULL;
+
+ d (printf ("Performing quick summary sync\n"));
+
+ camel_operation_push_message (cancellable, _("Storing folder"));
+ camel_folder_summary_lock (s);
+
+ fd = g_open (cls->folder_path, O_LARGEFILE | O_RDWR | O_BINARY, 0);
+ if (fd == -1) {
+ camel_folder_summary_unlock (s);
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not open file: %s: %s"),
+ cls->folder_path, g_strerror (errno));
+
+ camel_operation_pop_message (cancellable);
+ return -1;
+ }
+
+ /* need to dup since mime parser closes its fd once it is finalized */
+ pfd = dup (fd);
+ if (pfd == -1) {
+ camel_folder_summary_unlock (s);
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not store folder: %s"),
+ g_strerror (errno));
+ close (fd);
+ return -1;
+ }
+
+ mp = camel_mime_parser_new ();
+ camel_mime_parser_scan_from (mp, TRUE);
+ camel_mime_parser_scan_pre_from (mp, TRUE);
+ camel_mime_parser_init_with_fd (mp, pfd);
+
+ /* Sync only the changes */
+ summary = camel_folder_summary_get_changed ((CamelFolderSummary *) mbs);
+ if (summary->len)
+ g_ptr_array_sort_with_data (summary, cms_sort_frompos, mbs);
+
+ for (i = 0; i < summary->len; i++) {
+ gint xevoffset;
+ gint pc = (i + 1) * 100 / summary->len;
+
+ camel_operation_progress (cancellable, pc);
+
+ info = (CamelMboxMessageInfo *) camel_folder_summary_get (s, summary->pdata[i]);
+
+ d (printf ("Checking message %s %08x\n", camel_message_info_get_uid (info), ((CamelMessageInfoBase *) info)->flags));
+
+ if ((info->info.info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED) == 0) {
+ camel_message_info_unref (info);
+ info = NULL;
+ continue;
+ }
+
+ d (printf ("Updating message %s: %d\n", camel_message_info_get_uid (info), (gint) info->frompos));
+
+ camel_mime_parser_seek (mp, info->frompos, SEEK_SET);
+
+ if (camel_mime_parser_step (mp, NULL, NULL) != CAMEL_MIME_PARSER_STATE_FROM) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("MBOX file is corrupted, please fix it. (Expected a From line, but didn't get it.)"));
+ goto error;
+ }
+
+ if (camel_mime_parser_tell_start_from (mp) != info->frompos) {
+ g_warning (
+ "Didn't get the next message where I expected (%d) got %d instead",
+ (gint) info->frompos, (gint) camel_mime_parser_tell_start_from (mp));
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Summary and folder mismatch, even after a sync"));
+ goto error;
+ }
+
+ if (camel_mime_parser_step (mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM_END) {
+ g_warning ("camel_mime_parser_step failed (2)");
+ goto error;
+ }
+
+ xev = camel_mime_parser_header (mp, "X-Evolution", &xevoffset);
+ if (xev == NULL || camel_local_summary_decode_x_evolution (cls, xev, NULL) == -1) {
+ g_warning ("We're supposed to have a valid x-ev header, but we dont");
+ goto error;
+ }
+ xevnew = camel_local_summary_encode_x_evolution (cls, &info->info);
+ /* SIGH: encode_param_list is about the only function which folds headers by itself.
+ * This should be fixed somehow differently (either parser doesn't fold headers,
+ * or param_list doesn't, or something */
+ xevtmp = camel_header_unfold (xevnew);
+ /* the raw header contains a leading ' ', so (dis)count that too */
+ if (strlen (xev) - 1 != strlen (xevtmp)) {
+ g_free (xevnew);
+ g_free (xevtmp);
+ g_warning ("Hmm, the xev headers shouldn't have changed size, but they did");
+ goto error;
+ }
+ g_free (xevtmp);
+
+ /* we write out the xevnew string, assuming its been folded identically to the original too! */
+
+ lastpos = lseek (fd, 0, SEEK_CUR);
+ CHECK_CALL (lseek (fd, xevoffset + strlen ("X-Evolution: "), SEEK_SET));
+ do {
+ len = write (fd, xevnew, strlen (xevnew));
+ } while (len == -1 && errno == EINTR);
+
+ if (lastpos != -1 && lseek (fd, lastpos, SEEK_SET) == (off_t) -1) {
+ g_warning (
+ "%s: Failed to rewind file to last position: %s",
+ G_STRFUNC, g_strerror (errno));
+ }
+ g_free (xevnew);
+
+ camel_mime_parser_drop_step (mp);
+ camel_mime_parser_drop_step (mp);
+
+ info->info.info.flags &= 0xffff;
+ info->info.info.dirty = TRUE;
+ camel_message_info_unref (info);
+ info = NULL;
+ }
+
+ d (printf ("Closing folders\n"));
+
+ if (close (fd) == -1) {
+ g_warning ("Cannot close source folder: %s", g_strerror (errno));
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not close source folder %s: %s"),
+ cls->folder_path, g_strerror (errno));
+ fd = -1;
+ goto error;
+ }
+
+ g_ptr_array_foreach (summary, (GFunc) camel_pstring_free, NULL);
+ g_ptr_array_free (summary, TRUE);
+ g_object_unref (mp);
+
+ camel_operation_pop_message (cancellable);
+ camel_folder_summary_unlock (s);
+
+ return 0;
+ error:
+ g_ptr_array_foreach (summary, (GFunc) camel_pstring_free, NULL);
+ g_ptr_array_free (summary, TRUE);
+ g_object_unref (mp);
+ if (fd != -1)
+ close (fd);
+ if (info)
+ camel_message_info_unref (info);
+
+ camel_operation_pop_message (cancellable);
+ camel_folder_summary_unlock (s);
+
+ return -1;
+}
+
+static gint
+mbox_summary_sync (CamelLocalSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct stat st;
+ CamelMboxSummary *mbs = (CamelMboxSummary *) cls;
+ CamelFolderSummary *s = (CamelFolderSummary *) cls;
+ CamelStore *parent_store;
+ const gchar *full_name;
+ gint i;
+ gint quick = TRUE, work = FALSE;
+ gint ret;
+ GPtrArray *summary = NULL;
+
+ camel_folder_summary_lock (s);
+
+ /* first, sync ourselves up, just to make sure */
+ if (camel_local_summary_check (cls, changeinfo, cancellable, error) == -1) {
+ camel_folder_summary_unlock (s);
+ return -1;
+ }
+
+ full_name = camel_folder_get_full_name (camel_folder_summary_get_folder (s));
+ parent_store = camel_folder_get_parent_store (camel_folder_summary_get_folder (s));
+
+ /* Sync only the changes */
+
+ summary = camel_folder_summary_get_changed ((CamelFolderSummary *) mbs);
+ for (i = 0; i < summary->len; i++) {
+ CamelMboxMessageInfo *info = (CamelMboxMessageInfo *) camel_folder_summary_get (s, summary->pdata[i]);
+
+ if ((expunge && (info->info.info.flags & CAMEL_MESSAGE_DELETED)) ||
+ (info->info.info.flags & (CAMEL_MESSAGE_FOLDER_NOXEV | CAMEL_MESSAGE_FOLDER_XEVCHANGE)))
+ quick = FALSE;
+ else
+ work |= (info->info.info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0;
+ camel_message_info_unref (info);
+ }
+
+ g_ptr_array_foreach (summary, (GFunc) camel_pstring_free, NULL);
+ g_ptr_array_free (summary, TRUE);
+
+ if (quick && expunge) {
+ guint32 dcount =0;
+
+ if (camel_db_count_deleted_message_info (parent_store->cdb_w, full_name, &dcount, error) == -1) {
+ camel_folder_summary_unlock (s);
+ return -1;
+ }
+ if (dcount)
+ quick = FALSE;
+ }
+
+ /* yuck i hate this logic, but its to simplify the 'all ok, update summary' and failover cases */
+ ret = -1;
+ if (quick) {
+ if (work) {
+ ret = CAMEL_MBOX_SUMMARY_GET_CLASS (cls)->sync_quick (
+ mbs, expunge, changeinfo, cancellable, NULL);
+ if (ret == -1)
+ g_warning ("failed a quick-sync, trying a full sync");
+ } else {
+ ret = 0;
+ }
+ }
+
+ if (ret == -1)
+ ret = CAMEL_MBOX_SUMMARY_GET_CLASS (cls)->sync_full (
+ mbs, expunge, changeinfo, cancellable, error);
+ if (ret == -1) {
+ camel_folder_summary_unlock (s);
+ return -1;
+ }
+
+ if (g_stat (cls->folder_path, &st) == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Unknown error: %s"), g_strerror (errno));
+ camel_folder_summary_unlock (s);
+ return -1;
+ }
+
+ if (mbs->folder_size != st.st_size || s->time != st.st_mtime) {
+ s->time = st.st_mtime;
+ mbs->folder_size = st.st_size;
+ camel_folder_summary_touch (s);
+ }
+
+ ret = CAMEL_LOCAL_SUMMARY_CLASS (camel_mbox_summary_parent_class)->sync (cls, expunge, changeinfo, cancellable, error);
+ camel_folder_summary_unlock (s);
+
+ return ret;
+}
+
+gint
+camel_mbox_summary_sync_mbox (CamelMboxSummary *cls,
+ guint32 flags,
+ CamelFolderChangeInfo *changeinfo,
+ gint fd,
+ gint fdout,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMboxSummary *mbs = (CamelMboxSummary *) cls;
+ CamelFolderSummary *s = (CamelFolderSummary *) mbs;
+ CamelMimeParser *mp = NULL;
+ CamelStore *parent_store;
+ const gchar *full_name;
+ gint i;
+ CamelMboxMessageInfo *info = NULL;
+ gchar *buffer, *xevnew = NULL;
+ gsize len;
+ const gchar *fromline;
+ gint lastdel = FALSE;
+ gboolean touched = FALSE;
+ GList *del = NULL;
+ GPtrArray *known_uids = NULL;
+#ifdef STATUS_PINE
+ gchar statnew[8], xstatnew[8];
+#endif
+
+ d (printf ("performing full summary/sync\n"));
+
+ camel_folder_summary_lock (s);
+
+ /* need to dup this because the mime-parser owns the fd after we give it to it */
+ fd = dup (fd);
+ if (fd == -1) {
+ camel_folder_summary_unlock (s);
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not store folder: %s"),
+ g_strerror (errno));
+ return -1;
+ }
+
+ mp = camel_mime_parser_new ();
+ camel_mime_parser_scan_from (mp, TRUE);
+ camel_mime_parser_scan_pre_from (mp, TRUE);
+ camel_mime_parser_init_with_fd (mp, fd);
+
+ camel_folder_summary_prepare_fetch_all (s, NULL);
+ known_uids = camel_folder_summary_get_array (s);
+ /* walk them in the same order as stored in the file */
+ if (known_uids && known_uids->len)
+ g_ptr_array_sort_with_data (known_uids, cms_sort_frompos, mbs);
+ for (i = 0; known_uids && i < known_uids->len; i++) {
+ gint pc = (i + 1) * 100 / known_uids->len;
+
+ camel_operation_progress (cancellable, pc);
+
+ info = (CamelMboxMessageInfo *) camel_folder_summary_get (s, g_ptr_array_index (known_uids, i));
+
+ if (!info)
+ continue;
+
+ d (printf (
+ "Looking at message %s\n",
+ camel_message_info_get_uid (info)));
+
+ d (printf (
+ "seeking (%s) to %d\n",
+ ((CamelMessageInfo *) info)->uid,
+ (gint) info->frompos));
+
+ if (lastdel)
+ camel_mime_parser_seek (mp, info->frompos, SEEK_SET);
+
+ if (camel_mime_parser_step (mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_FROM) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("MBOX file is corrupted, please fix it. "
+ "(Expected a From line, but didn't get it.)"));
+ goto error;
+ }
+
+ if (camel_mime_parser_tell_start_from (mp) != info->frompos) {
+ g_warning (
+ "Didn't get the next message where I expected (%d) got %d instead",
+ (gint) info->frompos,
+ (gint) camel_mime_parser_tell_start_from (mp));
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Summary and folder mismatch, even after a sync"));
+ goto error;
+ }
+
+ lastdel = FALSE;
+ if ((flags&1) && info->info.info.flags & CAMEL_MESSAGE_DELETED) {
+ const gchar *uid = camel_message_info_get_uid (info);
+ d (printf ("Deleting %s\n", uid));
+
+ if (((CamelLocalSummary *) cls)->index)
+ camel_index_delete_name (((CamelLocalSummary *) cls)->index, uid);
+
+ /* remove it from the change list */
+ camel_folder_change_info_remove_uid (changeinfo, uid);
+ camel_folder_summary_remove (s, (CamelMessageInfo *) info);
+ del = g_list_prepend (del, (gpointer) camel_pstring_strdup (uid));
+ camel_message_info_unref (info);
+ info = NULL;
+ lastdel = TRUE;
+ touched = TRUE;
+ } else {
+ /* otherwise, the message is staying, copy its From_ line across */
+#if 0
+ if (i > 0)
+ write (fdout, "\n", 1);
+#endif
+ info->frompos = lseek (fdout, 0, SEEK_CUR);
+ ((CamelMessageInfo *) info)->dirty = TRUE;
+ fromline = camel_mime_parser_from_line (mp);
+ d (printf ("Saving %s:%d\n", camel_message_info_get_uid (info), info->frompos));
+ g_warn_if_fail (write (fdout, fromline, strlen (fromline)) != -1);
+ }
+
+ if (info && info->info.info.flags & (CAMEL_MESSAGE_FOLDER_NOXEV | CAMEL_MESSAGE_FOLDER_FLAGGED)) {
+ d (printf ("Updating header for %s flags = %08x\n", camel_message_info_get_uid (info), info->info.flags));
+
+ if (camel_mime_parser_step (mp, &buffer, &len) == CAMEL_MIME_PARSER_STATE_FROM_END) {
+ g_warning ("camel_mime_parser_step failed (2)");
+ goto error;
+ }
+
+ xevnew = camel_local_summary_encode_x_evolution ((CamelLocalSummary *) cls, &info->info);
+#ifdef STATUS_PINE
+ if (mbs->xstatus) {
+ encode_status (info->info.info.flags & STATUS_STATUS, statnew);
+ encode_status (info->info.info.flags & STATUS_XSTATUS, xstatnew);
+ len = camel_local_summary_write_headers (fdout, camel_mime_parser_headers_raw (mp), xevnew, statnew, xstatnew);
+ } else {
+#endif
+ len = camel_local_summary_write_headers (fdout, camel_mime_parser_headers_raw (mp), xevnew, NULL, NULL);
+#ifdef STATUS_PINE
+ }
+#endif
+ if (len == -1) {
+ d (printf ("Error writing to temporary mailbox\n"));
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Writing to temporary mailbox failed: %s"),
+ g_strerror (errno));
+ goto error;
+ }
+ info->info.info.flags &= 0xffff;
+ g_free (xevnew);
+ xevnew = NULL;
+ camel_mime_parser_drop_step (mp);
+ }
+
+ camel_mime_parser_drop_step (mp);
+ if (info) {
+ d (printf ("looking for message content to copy across from %d\n", (gint) camel_mime_parser_tell (mp)));
+ while (camel_mime_parser_step (mp, &buffer, &len) == CAMEL_MIME_PARSER_STATE_PRE_FROM) {
+ /*d(printf("copying mbox contents to temporary: '%.*s'\n", len, buffer));*/
+ if (write (fdout, buffer, len) != len) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Writing to temporary mailbox failed: %s: %s"),
+ ((CamelLocalSummary *) cls)->folder_path,
+ g_strerror (errno));
+ goto error;
+ }
+ }
+
+ if (write (fdout, "\n", 1) != 1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Writing to temporary mailbox failed: %s"),
+ g_strerror (errno));
+ goto error;
+ }
+
+ d (printf (
+ "we are now at %d, from = %d\n",
+ (gint) camel_mime_parser_tell (mp),
+ (gint) camel_mime_parser_tell_start_from (mp)));
+ camel_mime_parser_unstep (mp);
+ camel_message_info_unref (info);
+ info = NULL;
+ }
+ }
+
+ full_name = camel_folder_get_full_name (camel_folder_summary_get_folder (s));
+ parent_store = camel_folder_get_parent_store (camel_folder_summary_get_folder (s));
+ camel_db_delete_uids (parent_store->cdb_w, full_name, del, NULL);
+ g_list_foreach (del, (GFunc) camel_pstring_free, NULL);
+ g_list_free (del);
+
+#if 0
+ /* if last was deleted, append the \n we removed */
+ if (lastdel && count > 0)
+ write (fdout, "\n", 1);
+#endif
+
+ g_object_unref (mp);
+
+ /* clear working flags */
+ for (i = 0; known_uids && i < known_uids->len; i++) {
+ info = (CamelMboxMessageInfo *) camel_folder_summary_get (s, g_ptr_array_index (known_uids, i));
+ if (info) {
+ if (info->info.info.flags & (CAMEL_MESSAGE_FOLDER_NOXEV | CAMEL_MESSAGE_FOLDER_FLAGGED | CAMEL_MESSAGE_FOLDER_XEVCHANGE)) {
+ info->info.info.flags &= ~(CAMEL_MESSAGE_FOLDER_NOXEV
+ |CAMEL_MESSAGE_FOLDER_FLAGGED
+ |CAMEL_MESSAGE_FOLDER_XEVCHANGE);
+ ((CamelMessageInfo *) info)->dirty = TRUE;
+ camel_folder_summary_touch (s);
+ }
+ camel_message_info_unref (info);
+ info = NULL;
+ }
+ }
+
+ camel_folder_summary_free_array (known_uids);
+
+ if (touched)
+ camel_folder_summary_header_save_to_db (s, NULL);
+
+ camel_folder_summary_unlock (s);
+
+ return 0;
+ error:
+ g_free (xevnew);
+ g_object_unref (mp);
+
+ if (info)
+ camel_message_info_unref (info);
+
+ camel_folder_summary_free_array (known_uids);
+ camel_folder_summary_unlock (s);
+
+ return -1;
+}
+
+#ifdef STATUS_PINE
+static CamelMessageInfo *
+mbox_summary_add (CamelLocalSummary *cls,
+ CamelMimeMessage *msg,
+ const CamelMessageInfo *info,
+ CamelFolderChangeInfo *ci,
+ GError **error)
+{
+ CamelLocalSummaryClass *local_summary_class;
+ CamelMboxMessageInfo *mi;
+
+ /* Chain up to parent's add() method. */
+ local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (camel_mbox_summary_parent_class);
+ mi = (CamelMboxMessageInfo *) local_summary_class->add (
+ cls, msg, info, ci, error);
+ if (mi && ((CamelMboxSummary *) cls)->xstatus) {
+ gchar status[8];
+
+ /* we snoop and add status/x-status headers to suit */
+ encode_status (mi->info.info.flags & STATUS_STATUS, status);
+ camel_medium_set_header ((CamelMedium *) msg, "Status", status);
+ encode_status (mi->info.info.flags & STATUS_XSTATUS, status);
+ camel_medium_set_header ((CamelMedium *) msg, "X-Status", status);
+ }
+
+ return (CamelMessageInfo *) mi;
+}
+
+static struct {
+ gchar tag;
+ guint32 flag;
+} status_flags[] = {
+ { 'F', CAMEL_MESSAGE_FLAGGED },
+ { 'A', CAMEL_MESSAGE_ANSWERED },
+ { 'D', CAMEL_MESSAGE_DELETED },
+ { 'R', CAMEL_MESSAGE_SEEN },
+};
+
+static void
+encode_status (guint32 flags,
+ gchar status[8])
+{
+ gsize i;
+ gchar *p;
+
+ p = status;
+ for (i = 0; i < G_N_ELEMENTS (status_flags); i++)
+ if (status_flags[i].flag & flags)
+ *p++ = status_flags[i].tag;
+ *p++ = 'O';
+ *p = '\0';
+}
+
+static guint32
+decode_status (const gchar *status)
+{
+ const gchar *p;
+ guint32 flags = 0;
+ gsize i;
+ gchar c;
+
+ p = status;
+ while ((c = *p++)) {
+ for (i = 0; i < G_N_ELEMENTS (status_flags); i++)
+ if (status_flags[i].tag == c)
+ flags |= status_flags[i].flag;
+ }
+
+ return flags;
+}
+
+#endif /* STATUS_PINE */
diff --git a/src/camel/providers/local/camel-mbox-summary.h b/src/camel/providers/local/camel-mbox-summary.h
new file mode 100644
index 000000000..37f6bad95
--- /dev/null
+++ b/src/camel/providers/local/camel-mbox-summary.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_MBOX_SUMMARY_H
+#define CAMEL_MBOX_SUMMARY_H
+
+#include "camel-local-summary.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MBOX_SUMMARY \
+ (camel_mbox_summary_get_type ())
+#define CAMEL_MBOX_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MBOX_SUMMARY, CamelMboxSummary))
+#define CAMEL_MBOX_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MBOX_SUMMARY, CamelMboxSummaryClass))
+#define CAMEL_IS_MBOX_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MBOX_SUMMARY))
+#define CAMEL_IS_MBOX_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MBOX_SUMMARY))
+#define CAMEL_MBOX_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MBOX_SUMMARY, CamelMboxSummaryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMboxSummary CamelMboxSummary;
+typedef struct _CamelMboxSummaryClass CamelMboxSummaryClass;
+
+typedef struct _CamelMboxMessageInfo {
+ CamelLocalMessageInfo info;
+
+ goffset frompos;
+} CamelMboxMessageInfo;
+
+struct _CamelMboxSummary {
+ CamelLocalSummary parent;
+
+ CamelFolderChangeInfo *changes; /* used to build change sets */
+
+ guint32 version;
+ gsize folder_size; /* size of the mbox file, last sync */
+
+ guint xstatus:1; /* do we store/honour xstatus/status headers */
+};
+
+struct _CamelMboxSummaryClass {
+ CamelLocalSummaryClass parent_class;
+
+ /* sync in-place */
+ gint (*sync_quick) (CamelMboxSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error);
+
+ /* sync requires copy */
+ gint (*sync_full) (CamelMboxSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error);
+};
+
+GType camel_mbox_summary_get_type (void);
+CamelMboxSummary *
+ camel_mbox_summary_new (CamelFolder *folder,
+ const gchar *mbox_name,
+ CamelIndex *index);
+
+/* do we honor/use xstatus headers, etc */
+void camel_mbox_summary_xstatus (CamelMboxSummary *mbs,
+ gint state);
+
+/* build a new mbox from an existing mbox storing summary information */
+gint camel_mbox_summary_sync_mbox (CamelMboxSummary *cls,
+ guint32 flags,
+ CamelFolderChangeInfo *changeinfo,
+ gint fd,
+ gint fdout,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_MBOX_SUMMARY_H */
diff --git a/src/camel/providers/local/camel-mh-folder.c b/src/camel/providers/local/camel-mh-folder.c
new file mode 100644
index 000000000..7eaafe9ab
--- /dev/null
+++ b/src/camel/providers/local/camel-mh-folder.c
@@ -0,0 +1,252 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-mh-folder.h"
+#include "camel-mh-store.h"
+#include "camel-mh-summary.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+G_DEFINE_TYPE (CamelMhFolder, camel_mh_folder, CAMEL_TYPE_LOCAL_FOLDER)
+
+static gchar *
+mh_folder_get_filename (CamelFolder *folder,
+ const gchar *uid,
+ GError **error)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *) folder;
+
+ return g_strdup_printf ("%s/%s", lf->folder_path, uid);
+}
+
+static gboolean
+mh_folder_append_message_sync (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *) folder;
+ CamelStream *output_stream;
+ CamelMessageInfo *mi;
+ gchar *name;
+ gboolean has_attachment;
+
+ /* FIXME: probably needs additional locking (although mh doesn't appear do do it) */
+
+ d (printf ("Appending message\n"));
+
+ /* If we can't lock, don't do anything */
+ if (!lf || camel_local_folder_lock (lf, CAMEL_LOCK_WRITE, error) == -1)
+ return FALSE;
+
+ /* add it to the summary/assign the uid, etc */
+ mi = camel_local_summary_add ((CamelLocalSummary *) folder->summary, message, info, lf->changes, error);
+ if (mi == NULL)
+ goto check_changed;
+
+ has_attachment = camel_mime_message_has_attachment (message);
+ if (((camel_message_info_get_flags (mi) & CAMEL_MESSAGE_ATTACHMENTS) && !has_attachment) ||
+ ((camel_message_info_get_flags (mi) & CAMEL_MESSAGE_ATTACHMENTS) == 0 && has_attachment)) {
+ camel_message_info_set_flags (mi, CAMEL_MESSAGE_ATTACHMENTS, has_attachment ? CAMEL_MESSAGE_ATTACHMENTS : 0);
+ }
+
+ d (printf ("Appending message: uid is %s\n", camel_message_info_get_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_get_uid (mi));
+ output_stream = camel_stream_fs_new_with_name (
+ name, O_WRONLY | O_CREAT, 0600, error);
+ if (output_stream == NULL)
+ goto fail_write;
+
+ if (camel_data_wrapper_write_to_stream_sync (
+ (CamelDataWrapper *) message, output_stream, cancellable, error) == -1
+ || camel_stream_close (output_stream, cancellable, error) == -1)
+ goto fail_write;
+
+ /* close this? */
+ g_object_unref (output_stream);
+
+ g_free (name);
+
+ if (appended_uid)
+ *appended_uid = g_strdup(camel_message_info_get_uid(mi));
+
+ goto check_changed;
+
+ fail_write:
+
+ /* remove the summary info so we are not out-of-sync with the mh folder */
+ camel_folder_summary_remove (CAMEL_FOLDER_SUMMARY (folder->summary), mi);
+
+ g_prefix_error (
+ error, _("Cannot append message to mh folder: %s: "), name);
+
+ if (output_stream) {
+ g_object_unref (output_stream);
+ unlink (name);
+ }
+
+ g_free (name);
+
+ check_changed:
+ camel_local_folder_unlock (lf);
+
+ if (camel_folder_change_info_changed (lf->changes)) {
+ camel_folder_changed (folder, lf->changes);
+ camel_folder_change_info_clear (lf->changes);
+ }
+
+ return TRUE;
+}
+
+static CamelMimeMessage *
+mh_folder_get_message_sync (CamelFolder *folder,
+ const gchar *uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *) folder;
+ CamelStream *message_stream = NULL;
+ CamelMimeMessage *message = NULL;
+ CamelMessageInfo *info;
+ gchar *name = NULL;
+
+ d (printf ("getting message: %s\n", uid));
+
+ if (!lf || camel_local_folder_lock (lf, CAMEL_LOCK_WRITE, error) == -1)
+ return NULL;
+
+ /* get the message summary info */
+ if ((info = camel_folder_summary_get (folder->summary, uid)) == NULL) {
+ set_cannot_get_message_ex (
+ error, CAMEL_FOLDER_ERROR_INVALID_UID,
+ uid, lf->folder_path, _("No such message"));
+ goto fail;
+ }
+
+ /* we only need it to check the message exists */
+ camel_message_info_unref (info);
+
+ name = g_strdup_printf ("%s/%s", lf->folder_path, uid);
+ message_stream = camel_stream_fs_new_with_name (
+ name, O_RDONLY, 0, error);
+ if (message_stream == NULL) {
+ g_prefix_error (
+ error, _("Cannot get message %s from folder %s: "),
+ name, lf->folder_path);
+ goto fail;
+ }
+
+ message = camel_mime_message_new ();
+ if (!camel_data_wrapper_construct_from_stream_sync (
+ (CamelDataWrapper *) message,
+ message_stream, cancellable, error)) {
+ g_prefix_error (
+ error, _("Cannot get message %s from folder %s: "),
+ name, lf->folder_path);
+ g_object_unref (message);
+ message = NULL;
+
+ }
+ g_object_unref (message_stream);
+
+ fail:
+ g_free (name);
+
+ camel_local_folder_unlock (lf);
+
+ if (camel_folder_change_info_changed (lf->changes)) {
+ camel_folder_changed (folder, lf->changes);
+ camel_folder_change_info_clear (lf->changes);
+ }
+
+ return message;
+}
+
+static CamelLocalSummary *
+mh_folder_create_summary (CamelLocalFolder *lf,
+ const gchar *folder,
+ CamelIndex *index)
+{
+ return (CamelLocalSummary *) camel_mh_summary_new (
+ CAMEL_FOLDER (lf), folder, index);
+}
+
+static void
+camel_mh_folder_class_init (CamelMhFolderClass *class)
+{
+ CamelFolderClass *folder_class;
+ CamelLocalFolderClass *local_folder_class;
+
+ folder_class = CAMEL_FOLDER_CLASS (class);
+ folder_class->get_filename = mh_folder_get_filename;
+ folder_class->append_message_sync = mh_folder_append_message_sync;
+ folder_class->get_message_sync = mh_folder_get_message_sync;
+
+ local_folder_class = CAMEL_LOCAL_FOLDER_CLASS (class);
+ local_folder_class->create_summary = mh_folder_create_summary;
+}
+
+static void
+camel_mh_folder_init (CamelMhFolder *mh_folder)
+{
+}
+
+CamelFolder *
+camel_mh_folder_new (CamelStore *parent_store,
+ const gchar *full_name,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ gchar *basename;
+
+ d (printf ("Creating mh folder: %s\n", full_name));
+
+ basename = g_path_get_basename (full_name);
+
+ folder = g_object_new (
+ CAMEL_TYPE_MH_FOLDER,
+ "display-name", basename, "full-name", full_name,
+ "parent-store", parent_store, NULL);
+ folder = (CamelFolder *) camel_local_folder_construct (
+ CAMEL_LOCAL_FOLDER (folder), flags, cancellable, error);
+
+ g_free (basename);
+
+ return folder;
+}
+
diff --git a/src/camel/providers/local/camel-mh-folder.h b/src/camel/providers/local/camel-mh-folder.h
new file mode 100644
index 000000000..c905c2e78
--- /dev/null
+++ b/src/camel/providers/local/camel-mh-folder.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_MH_FOLDER_H
+#define CAMEL_MH_FOLDER_H
+
+#include "camel-local-folder.h"
+
+#define CAMEL_TYPE_MH_FOLDER \
+ (camel_mh_folder_get_type ())
+#define CAMEL_MH_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MH_FOLDER, CamelMhFolder))
+#define CAMEL_MH_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MH_FOLDER, CamelMhFolderClass))
+#define CAMEL_IS_MH_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MH_FOLDER))
+#define CAMEL_IS_MH_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MH_FOLDER))
+#define CAMEL_MH_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MH_FOLDER, CamelMhFolderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMhFolder CamelMhFolder;
+typedef struct _CamelMhFolderClass CamelMhFolderClass;
+
+struct _CamelMhFolder {
+ CamelLocalFolder parent;
+};
+
+struct _CamelMhFolderClass {
+ CamelLocalFolderClass parent_class;
+};
+
+GType camel_mh_folder_get_type (void);
+CamelFolder * camel_mh_folder_new (CamelStore *parent_store,
+ const gchar *full_name,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_MH_FOLDER_H */
diff --git a/src/camel/providers/local/camel-mh-settings.c b/src/camel/providers/local/camel-mh-settings.c
new file mode 100644
index 000000000..dbaa118f9
--- /dev/null
+++ b/src/camel/providers/local/camel-mh-settings.c
@@ -0,0 +1,146 @@
+/*
+ * camel-mh-settings.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-mh-settings.h"
+
+#define CAMEL_MH_SETTINGS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MH_SETTINGS, CamelMhSettingsPrivate))
+
+struct _CamelMhSettingsPrivate {
+ gboolean use_dot_folders;
+};
+
+enum {
+ PROP_0,
+ PROP_USE_DOT_FOLDERS
+};
+
+G_DEFINE_TYPE (
+ CamelMhSettings,
+ camel_mh_settings,
+ CAMEL_TYPE_LOCAL_SETTINGS)
+
+static void
+mh_settings_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_USE_DOT_FOLDERS:
+ camel_mh_settings_set_use_dot_folders (
+ CAMEL_MH_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mh_settings_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_USE_DOT_FOLDERS:
+ g_value_set_boolean (
+ value,
+ camel_mh_settings_get_use_dot_folders (
+ CAMEL_MH_SETTINGS (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+camel_mh_settings_class_init (CamelMhSettingsClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelMhSettingsPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = mh_settings_set_property;
+ object_class->get_property = mh_settings_get_property;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_DOT_FOLDERS,
+ g_param_spec_boolean (
+ "use-dot-folders",
+ "Use Dot Folders",
+ "Update the exmh .folders file",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_mh_settings_init (CamelMhSettings *settings)
+{
+ settings->priv = CAMEL_MH_SETTINGS_GET_PRIVATE (settings);
+}
+
+/**
+ * camel_mh_settings_get_use_dot_folders:
+ * @settings: a #CamelMhSettings
+ *
+ * Returns whether @settings should keep the .folders summary file used by
+ * the exmh (http://www.beedub.com/exmh/) mail client updated as it makes
+ * changes to the MH folders.
+ *
+ * Returns: whether to use exmh's .folders file
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_mh_settings_get_use_dot_folders (CamelMhSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_MH_SETTINGS (settings), FALSE);
+
+ return settings->priv->use_dot_folders;
+}
+
+/**
+ * camel_mh_settings_set_use_dot_folders:
+ * @settings: a #CamelMhSettings
+ * @use_dot_folders: whether to use exmh's .folders file
+ *
+ * Sets whether @settings should keep the .folders summary file used by
+ * the exmh (http://www.beedub.com/exmh/) mail client updated as it makes
+ * changes to the MH folders.
+ *
+ * Since: 3.2
+ **/
+void
+camel_mh_settings_set_use_dot_folders (CamelMhSettings *settings,
+ gboolean use_dot_folders)
+{
+ g_return_if_fail (CAMEL_IS_MH_SETTINGS (settings));
+
+ if (settings->priv->use_dot_folders == use_dot_folders)
+ return;
+
+ settings->priv->use_dot_folders = use_dot_folders;
+
+ g_object_notify (G_OBJECT (settings), "use-dot-folders");
+}
diff --git a/src/camel/providers/local/camel-mh-settings.h b/src/camel/providers/local/camel-mh-settings.h
new file mode 100644
index 000000000..0ec856174
--- /dev/null
+++ b/src/camel/providers/local/camel-mh-settings.h
@@ -0,0 +1,66 @@
+/*
+ * camel-mh-settings.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_MH_SETTINGS_H
+#define CAMEL_MH_SETTINGS_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MH_SETTINGS \
+ (camel_mh_settings_get_type ())
+#define CAMEL_MH_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MH_SETTINGS, CamelMhSettings))
+#define CAMEL_MH_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MH_SETTINGS, CamelMhSettingsClass))
+#define CAMEL_IS_MH_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MH_SETTINGS))
+#define CAMEL_IS_MH_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MH_SETTINGS))
+#define CAMEL_MH_SETTINGS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MH_SETTINGS, CamelMhSettingsClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMhSettings CamelMhSettings;
+typedef struct _CamelMhSettingsClass CamelMhSettingsClass;
+typedef struct _CamelMhSettingsPrivate CamelMhSettingsPrivate;
+
+struct _CamelMhSettings {
+ CamelLocalSettings parent;
+ CamelMhSettingsPrivate *priv;
+};
+
+struct _CamelMhSettingsClass {
+ CamelLocalSettingsClass parent_class;
+};
+
+GType camel_mh_settings_get_type (void) G_GNUC_CONST;
+gboolean camel_mh_settings_get_use_dot_folders
+ (CamelMhSettings *settings);
+void camel_mh_settings_set_use_dot_folders
+ (CamelMhSettings *settings,
+ gboolean use_dot_folders);
+
+G_END_DECLS
+
+#endif /* CAMEL_MH_SETTINGS_H */
diff --git a/src/camel/providers/local/camel-mh-store.c b/src/camel/providers/local/camel-mh-store.c
new file mode 100644
index 000000000..7f771ec64
--- /dev/null
+++ b/src/camel/providers/local/camel-mh-store.c
@@ -0,0 +1,760 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-mh-folder.h"
+#include "camel-mh-settings.h"
+#include "camel-mh-store.h"
+#include "camel-mh-summary.h"
+
+#define d(x)
+
+G_DEFINE_TYPE (CamelMhStore, camel_mh_store, CAMEL_TYPE_LOCAL_STORE)
+
+enum {
+ UPDATE_NONE,
+ UPDATE_ADD,
+ UPDATE_REMOVE,
+ UPDATE_RENAME
+};
+
+/* update the .folders file if it exists, or create it if it doesn't */
+static void
+folders_update (const gchar *root,
+ gint mode,
+ const gchar *folder,
+ const gchar *new,
+ GCancellable *cancellable)
+{
+ gchar *tmp, *tmpnew, *line = NULL;
+ gsize tmpnew_len = 0;
+ CamelStream *stream, *in = NULL, *out = NULL;
+ gchar *folder_newline;
+ gint flen = strlen (folder);
+
+ folder_newline = g_strdup_printf ("%s\n", folder);
+
+ tmpnew_len = strlen (root) + 16;
+ tmpnew = g_alloca (tmpnew_len);
+ g_snprintf (
+ tmpnew, tmpnew_len,
+ "%s" G_DIR_SEPARATOR_S ".folders~", root);
+
+ out = camel_stream_fs_new_with_name (
+ tmpnew, O_WRONLY | O_CREAT | O_TRUNC, 0666, NULL);
+ if (out == NULL)
+ goto fail;
+
+ tmp = g_alloca (tmpnew_len);
+ g_snprintf (
+ tmp, tmpnew_len,
+ "%s" G_DIR_SEPARATOR_S ".folders", root);
+ stream = camel_stream_fs_new_with_name (tmp, O_RDONLY, 0, NULL);
+ if (stream) {
+ in = camel_stream_buffer_new (stream, CAMEL_STREAM_BUFFER_READ);
+ g_object_unref (stream);
+ }
+ if (in == NULL || stream == NULL) {
+ if (mode == UPDATE_ADD) {
+ gint ret;
+
+ ret = camel_stream_write_string (
+ out, folder_newline, cancellable, NULL);
+
+ if (ret == -1)
+ goto fail;
+ }
+ goto done;
+ }
+
+ while ((line = camel_stream_buffer_read_line ((CamelStreamBuffer *) in, cancellable, NULL))) {
+ gint copy = TRUE;
+
+ switch (mode) {
+ case UPDATE_REMOVE:
+ if (strcmp (line, folder) == 0)
+ copy = FALSE;
+ break;
+ case UPDATE_RENAME:
+ if (strncmp (line, folder, flen) == 0
+ && (line[flen] == 0 || line[flen] == '/')) {
+ if (camel_stream_write (out, new, strlen (new), cancellable, NULL) == -1
+ || camel_stream_write (out, line + flen, strlen (line) - flen, cancellable, NULL) == -1
+ || camel_stream_write (out, "\n", 1, cancellable, NULL) == -1)
+ goto fail;
+ copy = FALSE;
+ }
+ break;
+ case UPDATE_ADD: {
+ gint cmp = strcmp (line, folder);
+
+ if (cmp > 0) {
+ gint ret;
+
+ /* found insertion point */
+ ret = camel_stream_write_string (
+ out, folder_newline, cancellable, NULL);
+
+ if (ret == -1)
+ goto fail;
+ mode = UPDATE_NONE;
+ } else if (cmp == 0) {
+ /* already there */
+ mode = UPDATE_NONE;
+ }
+ break; }
+ case UPDATE_NONE:
+ break;
+ }
+
+ if (copy) {
+ gchar *string;
+ gint ret;
+
+ string = g_strdup_printf ("%s\n", line);
+ ret = camel_stream_write_string (
+ out, string, cancellable, NULL);
+ g_free (string);
+
+ if (ret == -1)
+ goto fail;
+ }
+
+ g_free (line);
+ line = NULL;
+ }
+
+ /* add to end? */
+ if (mode == UPDATE_ADD) {
+ gint ret;
+
+ ret = camel_stream_write_string (
+ out, folder_newline, cancellable, NULL);
+
+ if (ret == -1)
+ goto fail;
+ }
+
+ if (camel_stream_close (out, cancellable, NULL) == -1)
+ goto fail;
+
+done:
+ if (g_rename (tmpnew, tmp) == -1) {
+ g_warning ("%s: Failed to rename '%s' to '%s': %s", G_STRFUNC, tmpnew, tmp, g_strerror (errno));
+ }
+fail:
+ unlink (tmpnew); /* remove it if its there */
+ g_free (line);
+ if (in)
+ g_object_unref (in);
+ if (out)
+ g_object_unref (out);
+
+ g_free (folder_newline);
+}
+
+static void
+fill_fi (CamelStore *store,
+ CamelFolderInfo *fi,
+ guint32 flags,
+ GCancellable *cancellable)
+{
+ CamelLocalStore *local_store;
+ CamelFolder *folder;
+
+ local_store = CAMEL_LOCAL_STORE (store);
+ folder = camel_object_bag_peek (store->folders, fi->full_name);
+
+ if (folder != NULL) {
+ fi->unread = camel_folder_get_unread_message_count (folder);
+ fi->total = camel_folder_get_message_count (folder);
+ g_object_unref (folder);
+ } else {
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelFolderSummary *s;
+ gchar *folderpath;
+ gchar *path;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ /* This should be fast enough not to have to test for INFO_FAST */
+
+ /* We could: if we have no folder, and FAST isn't specified,
+ * perform a full scan of all messages for their status flags.
+ * But its probably not worth it as we need to read the top of
+ * every file, i.e. very very slow */
+
+ folderpath = g_strdup_printf ("%s/%s", path, fi->full_name);
+ s = (CamelFolderSummary *) camel_mh_summary_new (
+ NULL, folderpath, NULL);
+ if (camel_folder_summary_header_load_from_db (
+ s, store, fi->full_name, NULL)) {
+ fi->unread = camel_folder_summary_get_unread_count (s);
+ fi->total = camel_folder_summary_get_saved_count (s);
+ }
+ g_object_unref (s);
+ g_free (folderpath);
+
+ g_free (path);
+ }
+
+ if (camel_local_store_is_main_store (local_store) && fi->full_name
+ && (fi->flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_NORMAL)
+ fi->flags =
+ (fi->flags & ~CAMEL_FOLDER_TYPE_MASK) |
+ camel_local_store_get_folder_type_by_full_name (
+ local_store, fi->full_name);
+}
+
+static CamelFolderInfo *
+folder_info_new (CamelStore *store,
+ const gchar *root,
+ const gchar *path,
+ guint32 flags,
+ GCancellable *cancellable)
+{
+ /* FIXME Need to set fi->flags = CAMEL_FOLDER_NOSELECT
+ * (and possibly others) when appropriate. */
+ CamelFolderInfo *fi;
+ gchar *base;
+
+ base = strrchr (path, '/');
+
+ /* Build the folder info structure. */
+ fi = camel_folder_info_new ();
+ fi->full_name = g_strdup (path);
+ fi->display_name = g_strdup (base ? base + 1 : path);
+ fill_fi (store, fi, flags, cancellable);
+
+ return fi;
+}
+
+/* used to find out where we've visited already */
+struct _inode {
+ dev_t dnode;
+ ino_t inode;
+};
+
+/* Scan path, under root, for directories to add folders for. Both
+ * root and path should have a trailing "/" if they aren't empty. */
+static void
+recursive_scan (CamelStore *store,
+ CamelFolderInfo **fip,
+ CamelFolderInfo *parent,
+ GHashTable *visited,
+ const gchar *root,
+ const gchar *path,
+ guint32 flags,
+ GCancellable *cancellable)
+{
+ gchar *fullpath, *tmp;
+ gsize fullpath_len;
+ DIR *dp;
+ struct dirent *d;
+ struct stat st;
+ CamelFolderInfo *fi;
+ struct _inode in, *inew;
+
+ /* Open the specified directory. */
+ if (path[0]) {
+ fullpath_len = strlen (root) + strlen (path) + 2;
+ fullpath = alloca (fullpath_len);
+ g_snprintf (fullpath, fullpath_len, "%s/%s", root, path);
+ } else
+ fullpath = (gchar *) root;
+
+ if (g_stat (fullpath, &st) == -1 || !S_ISDIR (st.st_mode))
+ return;
+
+ in.dnode = st.st_dev;
+ in.inode = st.st_ino;
+
+ /* see if we've visited already */
+ if (g_hash_table_lookup (visited, &in) != NULL)
+ return;
+
+ inew = g_malloc (sizeof (*inew));
+ *inew = in;
+ g_hash_table_insert (visited, inew, inew);
+
+ /* link in ... */
+ fi = folder_info_new (store, root, path, flags, cancellable);
+ fi->parent = parent;
+ fi->next = *fip;
+ *fip = fi;
+
+ if (((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) || parent == NULL)) {
+ /* now check content for possible other directories */
+ dp = opendir (fullpath);
+ if (dp == NULL)
+ return;
+
+ /* Look for subdirectories to add and scan. */
+ while ((d = readdir (dp)) != NULL) {
+ /* Skip current and parent directory. */
+ if (strcmp (d->d_name, ".") == 0
+ || strcmp (d->d_name, "..") == 0)
+ continue;
+
+ /* skip fully-numerical entries (i.e. mh messages) */
+ strtoul (d->d_name, &tmp, 10);
+ if (*tmp == 0)
+ continue;
+
+ /* Otherwise, treat at potential node, and recurse,
+ * a bit more expensive than needed, but tough! */
+ if (path[0]) {
+ tmp = g_strdup_printf ("%s/%s", path, d->d_name);
+ recursive_scan (
+ store, &fi->child, fi, visited,
+ root, tmp, flags, cancellable);
+ g_free (tmp);
+ } else {
+ recursive_scan (
+ store, &fi->child, fi, visited,
+ root, d->d_name, flags, cancellable);
+ }
+ }
+
+ closedir (dp);
+ }
+}
+
+/* scan a .folders file */
+static void
+folders_scan (CamelStore *store,
+ const gchar *root,
+ const gchar *top,
+ CamelFolderInfo **fip,
+ guint32 flags,
+ GCancellable *cancellable)
+{
+ CamelFolderInfo *fi;
+ gchar line[512], *path, *tmp;
+ gsize tmp_len;
+ CamelStream *stream, *in;
+ struct stat st;
+ GPtrArray *folders;
+ GHashTable *visited;
+ gint len;
+
+ tmp_len = strlen (root) + 16;
+ tmp = g_alloca (tmp_len);
+ g_snprintf (tmp, tmp_len, "%s/.folders", root);
+ stream = camel_stream_fs_new_with_name (tmp, 0, O_RDONLY, NULL);
+ if (stream == NULL)
+ return;
+
+ in = camel_stream_buffer_new (stream, CAMEL_STREAM_BUFFER_READ);
+ g_object_unref (stream);
+ if (in == NULL)
+ return;
+
+ visited = g_hash_table_new (g_str_hash, g_str_equal);
+ folders = g_ptr_array_new ();
+
+ while ((len = camel_stream_buffer_gets (
+ (CamelStreamBuffer *) in, line,
+ sizeof (line), cancellable, NULL)) > 0) {
+
+ /* ignore blank lines */
+ if (len <= 1)
+ continue;
+
+ /* Check for invalidly long lines,
+ * we abort everything and fallback. */
+ if (line[len - 1] != '\n') {
+ gint i;
+
+ for (i = 0; i < folders->len; i++)
+ camel_folder_info_free (folders->pdata[i]);
+ g_ptr_array_set_size (folders, 0);
+ break;
+ }
+ line[len - 1] = 0;
+
+ /* check for \r ? */
+
+ if (top && top[0]) {
+ gint toplen = strlen (top);
+
+ /* check is dir or subdir */
+ if (strncmp (top, line, toplen) != 0
+ || (line[toplen] != 0 && line[toplen] != '/'))
+ continue;
+
+ /* check is not sub-subdir if not recursive */
+ if ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) == 0
+ && (tmp = strrchr (line, '/'))
+ && tmp > line + toplen)
+ continue;
+ }
+
+ if (g_hash_table_lookup (visited, line) != NULL)
+ continue;
+
+ tmp = g_strdup (line);
+ g_hash_table_insert (visited, tmp, tmp);
+
+ path = g_strdup_printf ("%s/%s", root, line);
+ if (g_stat (path, &st) == 0 && S_ISDIR (st.st_mode)) {
+ fi = folder_info_new (
+ store, root, line, flags, cancellable);
+ g_ptr_array_add (folders, fi);
+ }
+ g_free (path);
+ }
+
+ if (folders->len)
+ *fip = camel_folder_info_build(folders, top, '/', TRUE);
+ g_ptr_array_free (folders, TRUE);
+
+ g_hash_table_foreach (visited, (GHFunc) g_free, NULL);
+ g_hash_table_destroy (visited);
+
+ g_object_unref (in);
+}
+
+/* FIXME: move to camel-local, this is shared with maildir code */
+static guint
+inode_hash (gconstpointer d)
+{
+ const struct _inode *v = d;
+
+ return v->inode ^ v->dnode;
+}
+
+static gboolean
+inode_equal (gconstpointer a,
+ gconstpointer b)
+{
+ const struct _inode *v1 = a, *v2 = b;
+
+ return v1->inode == v2->inode && v1->dnode == v2->dnode;
+}
+
+static void
+inode_free (gpointer k,
+ gpointer v,
+ gpointer d)
+{
+ g_free (k);
+}
+
+static CamelFolder *
+mh_store_get_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStoreClass *store_class;
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelFolder *folder = NULL;
+ gboolean use_dot_folders;
+ struct stat st;
+ gchar *name;
+ gchar *path;
+
+ /* Chain up to parent's get_folder() method. */
+ store_class = CAMEL_STORE_CLASS (camel_mh_store_parent_class);
+ if (store_class->get_folder_sync (
+ store, folder_name, flags, cancellable, error) == NULL)
+ return NULL;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ use_dot_folders = camel_mh_settings_get_use_dot_folders (
+ CAMEL_MH_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ name = g_build_filename (path, folder_name, NULL);
+
+ if (g_stat (name, &st) == -1) {
+ if (errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot get folder '%s': %s"),
+ folder_name, g_strerror (errno));
+ goto exit;
+ }
+
+ if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Cannot get folder '%s': "
+ "folder does not exist."),
+ folder_name);
+ goto exit;
+ }
+
+ if (g_mkdir (name, 0777) != 0) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not create folder '%s': %s"),
+ folder_name, g_strerror (errno));
+ goto exit;
+ }
+
+ /* add to .folders if we are supposed to */
+ /* FIXME: throw exception on error */
+ if (use_dot_folders)
+ folders_update (
+ path, UPDATE_ADD, folder_name,
+ NULL, cancellable);
+
+ } else if (!S_ISDIR (st.st_mode)) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Cannot get folder '%s': not a directory."),
+ folder_name);
+ goto exit;
+ }
+
+ folder = camel_mh_folder_new (
+ store, folder_name, flags, cancellable, error);
+
+exit:
+ g_free (name);
+ g_free (path);
+
+ return folder;
+}
+
+static CamelFolderInfo *
+mh_store_get_folder_info_sync (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSettings *local_settings;
+ CamelService *service;
+ CamelSettings *settings;
+ CamelFolderInfo *fi = NULL;
+ gboolean use_dot_folders;
+ gchar *path;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ use_dot_folders = camel_mh_settings_get_use_dot_folders (
+ CAMEL_MH_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ /* use .folders if we are supposed to */
+ if (use_dot_folders) {
+ folders_scan (
+ store, path, top, &fi, flags, cancellable);
+ } else {
+ GHashTable *visited;
+
+ visited = g_hash_table_new (inode_hash, inode_equal);
+
+ if (top == NULL)
+ top = "";
+
+ recursive_scan (
+ store, &fi, NULL, visited,
+ path, top, flags, cancellable);
+
+ /* If we actually scanned from root,
+ * we have a "" root node we dont want. */
+ if (fi != NULL && top[0] == 0) {
+ CamelFolderInfo *rfi;
+
+ rfi = fi;
+ fi = rfi->child;
+ rfi->child = NULL;
+ camel_folder_info_free (rfi);
+ }
+
+ g_hash_table_foreach (visited, inode_free, NULL);
+ g_hash_table_destroy (visited);
+ }
+
+ g_free (path);
+
+ return fi;
+}
+
+static CamelFolder *
+mh_store_get_inbox_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return mh_store_get_folder_sync (
+ store, "inbox", 0, cancellable, error);
+}
+
+static gboolean
+mh_store_delete_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStoreClass *store_class;
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ gboolean use_dot_folders;
+ gchar *name;
+ gchar *path;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ use_dot_folders = camel_mh_settings_get_use_dot_folders (
+ CAMEL_MH_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ /* remove folder directory - will fail if not empty */
+ name = g_build_filename (path, folder_name, NULL);
+ if (rmdir (name) == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not delete folder '%s': %s"),
+ folder_name, g_strerror (errno));
+ g_free (name);
+ g_free (path);
+ return FALSE;
+ }
+ g_free (name);
+
+ /* remove from .folders if we are supposed to */
+ if (use_dot_folders)
+ folders_update (
+ path, UPDATE_REMOVE, folder_name,
+ NULL, cancellable);
+
+ g_free (path);
+
+ /* Chain up to parent's delete_folder() method. */
+ store_class = CAMEL_STORE_CLASS (camel_mh_store_parent_class);
+ return store_class->delete_folder_sync (
+ store, folder_name, cancellable, error);
+}
+
+static gboolean
+mh_store_rename_folder_sync (CamelStore *store,
+ const gchar *old,
+ const gchar *new,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStoreClass *store_class;
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ gboolean use_dot_folders;
+ gboolean success;
+ gchar *path;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ use_dot_folders = camel_mh_settings_get_use_dot_folders (
+ CAMEL_MH_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ /* Chain up to parent's rename_folder() method. */
+ store_class = CAMEL_STORE_CLASS (camel_mh_store_parent_class);
+ success = store_class->rename_folder_sync (
+ store, old, new, cancellable, error);
+
+ if (success && use_dot_folders) {
+ /* yeah this is messy, but so is mh! */
+ folders_update (
+ path, UPDATE_RENAME, old, new, cancellable);
+ }
+
+ g_free (path);
+
+ return success;
+}
+
+static void
+camel_mh_store_class_init (CamelMhStoreClass *class)
+{
+ CamelServiceClass *service_class;
+ CamelStoreClass *store_class;
+
+ service_class = CAMEL_SERVICE_CLASS (class);
+ service_class->settings_type = CAMEL_TYPE_MH_SETTINGS;
+
+ store_class = CAMEL_STORE_CLASS (class);
+ store_class->get_folder_sync = mh_store_get_folder_sync;
+ store_class->get_folder_info_sync = mh_store_get_folder_info_sync;
+ store_class->get_inbox_folder_sync = mh_store_get_inbox_sync;
+ store_class->delete_folder_sync = mh_store_delete_folder_sync;
+ store_class->rename_folder_sync = mh_store_rename_folder_sync;
+}
+
+static void
+camel_mh_store_init (CamelMhStore *mh_store)
+{
+}
+
diff --git a/src/camel/providers/local/camel-mh-store.h b/src/camel/providers/local/camel-mh-store.h
new file mode 100644
index 000000000..c478d4815
--- /dev/null
+++ b/src/camel/providers/local/camel-mh-store.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_MH_STORE_H
+#define CAMEL_MH_STORE_H
+
+#include "camel-local-store.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MH_STORE \
+ (camel_mh_store_get_type ())
+#define CAMEL_MH_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MH_STORE, CamelMhStore))
+#define CAMEL_MH_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MH_STORE, CamelMhStoreClass))
+#define CAMEL_IS_MH_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MH_STORE))
+#define CAMEL_IS_MH_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MH_STORE))
+#define CAMEL_MH_STORE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MH_STORE, CamelMhStoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMhStore CamelMhStore;
+typedef struct _CamelMhStoreClass CamelMhStoreClass;
+typedef struct _CamelMhStorePrivate CamelMhStorePrivate;
+
+struct _CamelMhStore {
+ CamelLocalStore parent;
+ CamelMhStorePrivate *priv;
+};
+
+struct _CamelMhStoreClass {
+ CamelLocalStoreClass parent_class;
+};
+
+GType camel_mh_store_get_type (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_MH_STORE_H */
diff --git a/src/camel/providers/local/camel-mh-summary.c b/src/camel/providers/local/camel-mh-summary.c
new file mode 100644
index 000000000..b65319248
--- /dev/null
+++ b/src/camel/providers/local/camel-mh-summary.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Not Zed <notzed@lostzed.mmc.com.au>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-mh-summary.h"
+#include "camel-local-private.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+#define CAMEL_MH_SUMMARY_VERSION (0x2000)
+
+#define CAMEL_MH_SUMMARY_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MH_SUMMARY, CamelMhSummaryPrivate))
+
+static gint mh_summary_check (CamelLocalSummary *cls, CamelFolderChangeInfo *changeinfo, GCancellable *cancellable, GError **error);
+static gint mh_summary_sync (CamelLocalSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, GCancellable *cancellable, GError **error);
+static gint mh_summary_decode_x_evolution (CamelLocalSummary *cls, const gchar *xev, CamelLocalMessageInfo *info);
+/*static gint mh_summary_add(CamelLocalSummary *cls, CamelMimeMessage *msg, CamelMessageInfo *info, CamelFolderChangeInfo *, GError **error);*/
+
+static gchar *mh_summary_next_uid_string (CamelFolderSummary *s);
+
+struct _CamelMhSummaryPrivate {
+ gchar *current_uid;
+};
+
+G_DEFINE_TYPE (CamelMhSummary, camel_mh_summary, CAMEL_TYPE_LOCAL_SUMMARY)
+
+static void
+camel_mh_summary_class_init (CamelMhSummaryClass *class)
+{
+ CamelFolderSummaryClass *folder_summary_class;
+ CamelLocalSummaryClass *local_summary_class;
+
+ g_type_class_add_private (class, sizeof (CamelMhSummaryPrivate));
+
+ folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (class);
+ folder_summary_class->next_uid_string = mh_summary_next_uid_string;
+
+ local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (class);
+ local_summary_class->check = mh_summary_check;
+ local_summary_class->sync = mh_summary_sync;
+ local_summary_class->decode_x_evolution = mh_summary_decode_x_evolution;
+}
+
+static void
+camel_mh_summary_init (CamelMhSummary *mh_summary)
+{
+ CamelFolderSummary *folder_summary;
+
+ mh_summary->priv = CAMEL_MH_SUMMARY_GET_PRIVATE (mh_summary);
+
+ folder_summary = CAMEL_FOLDER_SUMMARY (mh_summary);
+
+ /* set unique file version */
+ folder_summary->version += CAMEL_MH_SUMMARY_VERSION;
+}
+
+/**
+ * camel_mh_summary_new:
+ *
+ * Create a new CamelMhSummary object.
+ *
+ * Returns: A new #CamelMhSummary object.
+ **/
+CamelMhSummary *
+camel_mh_summary_new (CamelFolder *folder,
+ const gchar *mhdir,
+ CamelIndex *index)
+{
+ CamelMhSummary *o;
+
+ o = g_object_new (CAMEL_TYPE_MH_SUMMARY, "folder", folder, NULL);
+ if (folder) {
+ CamelStore *parent_store;
+
+ parent_store = camel_folder_get_parent_store (folder);
+ camel_db_set_collate (parent_store->cdb_r, "uid", "mh_uid_sort", (CamelDBCollate) camel_local_frompos_sort);
+ ((CamelFolderSummary *) o)->sort_by = "uid";
+ ((CamelFolderSummary *) o)->collate = "mh_uid_sort";
+ }
+
+ camel_local_summary_construct ((CamelLocalSummary *) o, mhdir, index);
+ return o;
+}
+
+static gchar *
+mh_summary_next_uid_string (CamelFolderSummary *s)
+{
+ CamelMhSummary *mhs = (CamelMhSummary *) s;
+ CamelLocalSummary *cls = (CamelLocalSummary *) s;
+ gint fd = -1;
+ guint32 uid;
+ gchar *name;
+ gchar *uidstr;
+
+ /* if we are working to add an existing file, then use current_uid */
+ if (mhs->priv->current_uid) {
+ uidstr = g_strdup (mhs->priv->current_uid);
+ /* tell the summary of this, so we always append numbers to the end */
+ camel_folder_summary_set_next_uid (s, strtoul (uidstr, NULL, 10) + 1);
+ } else {
+ /* else scan for one - and create it too, to make sure */
+ do {
+ uid = camel_folder_summary_next_uid (s);
+ name = g_strdup_printf ("%s/%u", cls->folder_path, uid);
+ /* O_EXCL isn't guaranteed, sigh. Oh well, bad luck, mh has problems anyway */
+ fd = open (name, O_WRONLY | O_CREAT | O_EXCL | O_LARGEFILE, 0600);
+ g_free (name);
+ } while (fd == -1 && errno == EEXIST);
+
+ if (fd != -1)
+ close (fd);
+
+ uidstr = g_strdup_printf ("%u", uid);
+ }
+
+ return uidstr;
+}
+
+static gint
+camel_mh_summary_add (CamelLocalSummary *cls,
+ const gchar *name,
+ gint forceindex,
+ GCancellable *cancellable)
+{
+ CamelMessageInfo *info;
+ CamelFolderSummary *summary;
+ CamelMhSummary *mhs = (CamelMhSummary *) cls;
+ gchar *filename = g_strdup_printf ("%s/%s", cls->folder_path, name);
+ gint fd;
+ CamelMimeParser *mp;
+
+ summary = CAMEL_FOLDER_SUMMARY (cls);
+
+ d (printf ("summarising: %s\n", name));
+
+ fd = open (filename, O_RDONLY | O_LARGEFILE);
+ if (fd == -1) {
+ g_warning ("Cannot summarise/index: %s: %s", filename, g_strerror (errno));
+ g_free (filename);
+ return -1;
+ }
+ mp = camel_mime_parser_new ();
+ camel_mime_parser_scan_from (mp, FALSE);
+ camel_mime_parser_init_with_fd (mp, fd);
+ if (cls->index && (forceindex || !camel_index_has_name (cls->index, name))) {
+ d (printf ("forcing indexing of message content\n"));
+ cls->index_force = TRUE;
+ camel_folder_summary_set_index (summary, cls->index);
+ } else {
+ cls->index_force = FALSE;
+ camel_folder_summary_set_index (summary, NULL);
+ }
+ mhs->priv->current_uid = (gchar *) name;
+
+ info = camel_folder_summary_info_new_from_parser (summary, mp);
+ camel_folder_summary_add (summary, info);
+
+ g_object_unref (mp);
+ mhs->priv->current_uid = NULL;
+ camel_folder_summary_set_index (summary, NULL);
+ cls->index_force = FALSE;
+ g_free (filename);
+ return 0;
+}
+
+static void
+remove_summary (gchar *key,
+ CamelMessageInfo *info,
+ CamelLocalSummary *cls)
+{
+ d (printf ("removing message %s from summary\n", key));
+ if (cls->index)
+ camel_index_delete_name (cls->index, camel_message_info_get_uid (info));
+ camel_folder_summary_remove ((CamelFolderSummary *) cls, info);
+ camel_message_info_unref (info);
+}
+
+static gint
+mh_summary_check (CamelLocalSummary *cls,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ DIR *dir;
+ struct dirent *d;
+ gchar *p, c;
+ CamelMessageInfo *info;
+ GHashTable *left;
+ gint i;
+ gboolean forceindex;
+ GPtrArray *known_uids;
+
+ /* FIXME: Handle changeinfo */
+
+ d (printf ("checking summary ...\n"));
+
+ /* scan the directory, check for mail files not in the index, or index entries that
+ * no longer exist */
+ dir = opendir (cls->folder_path);
+ if (dir == NULL) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot open MH directory path: %s: %s"),
+ cls->folder_path, g_strerror (errno));
+ return -1;
+ }
+
+ /* keeps track of all uid's that have not been processed */
+ left = g_hash_table_new (g_str_hash, g_str_equal);
+ camel_folder_summary_prepare_fetch_all ((CamelFolderSummary *) cls, error);
+ known_uids = camel_folder_summary_get_array ((CamelFolderSummary *) cls);
+ forceindex = !known_uids || known_uids->len == 0;
+ for (i = 0; known_uids && i < known_uids->len; i++) {
+ info = camel_folder_summary_get ((CamelFolderSummary *) cls, g_ptr_array_index (known_uids, i));
+ if (info) {
+ g_hash_table_insert (left, (gchar *) camel_message_info_get_uid (info), info);
+ }
+ }
+ camel_folder_summary_free_array (known_uids);
+
+ while ((d = readdir (dir))) {
+ /* FIXME: also run stat to check for regular file */
+ p = d->d_name;
+ while ((c = *p++)) {
+ if (!isdigit (c))
+ break;
+ }
+ if (c == 0) {
+ info = camel_folder_summary_get ((CamelFolderSummary *) cls, d->d_name);
+ if (info == NULL || (cls->index && (!camel_index_has_name (cls->index, d->d_name)))) {
+ /* need to add this file to the summary */
+ if (info != NULL) {
+ CamelMessageInfo *old = g_hash_table_lookup (left, camel_message_info_get_uid (info));
+
+ if (old) {
+ g_hash_table_remove (left, camel_message_info_get_uid (info));
+ camel_message_info_unref (old);
+ }
+
+ camel_folder_summary_remove ((CamelFolderSummary *) cls, info);
+ camel_message_info_unref (info);
+ }
+ camel_mh_summary_add (cls, d->d_name, forceindex, cancellable);
+ } else {
+ const gchar *uid = camel_message_info_get_uid (info);
+ CamelMessageInfo *old = g_hash_table_lookup (left, uid);
+
+ if (old) {
+ g_hash_table_remove (left, uid);
+ camel_message_info_unref (old);
+ }
+ camel_message_info_unref (info);
+ }
+ }
+ }
+ closedir (dir);
+ g_hash_table_foreach (left, (GHFunc) remove_summary, cls);
+ g_hash_table_destroy (left);
+
+ return 0;
+}
+
+/* sync the summary file with the ondisk files */
+static gint
+mh_summary_sync (CamelLocalSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSummaryClass *local_summary_class;
+ gint i;
+ GPtrArray *known_uids;
+ CamelLocalMessageInfo *info;
+ gchar *name;
+ const gchar *uid;
+
+ d (printf ("summary_sync(expunge=%s)\n", expunge?"true":"false"));
+
+ /* we could probably get away without this ... but why not use it, esp if we're going to
+ * be doing any significant io already */
+ if (camel_local_summary_check (cls, changes, cancellable, error) == -1)
+ return -1;
+
+ /* FIXME: need to update/honour .mh_sequences or whatever it is */
+
+ camel_folder_summary_prepare_fetch_all ((CamelFolderSummary *) cls, error);
+ known_uids = camel_folder_summary_get_array ((CamelFolderSummary *) cls);
+ for (i = (known_uids ? known_uids->len : 0) - 1; i >= 0; i--) {
+ info = (CamelLocalMessageInfo *) camel_folder_summary_get ((CamelFolderSummary *) cls, g_ptr_array_index (known_uids, i));
+ g_return_val_if_fail (info, -1);
+ if (expunge && (info->info.flags & CAMEL_MESSAGE_DELETED)) {
+ uid = camel_message_info_get_uid (info);
+ name = g_strdup_printf ("%s/%s", cls->folder_path, uid);
+ d (printf ("deleting %s\n", name));
+ if (unlink (name) == 0 || errno == ENOENT) {
+
+ /* FIXME: put this in folder_summary::remove()? */
+ if (cls->index)
+ camel_index_delete_name (cls->index, (gchar *) uid);
+
+ camel_folder_change_info_remove_uid (changes, uid);
+ camel_folder_summary_remove ((CamelFolderSummary *) cls, (CamelMessageInfo *) info);
+ }
+ g_free (name);
+ } else if (info->info.flags & (CAMEL_MESSAGE_FOLDER_NOXEV | CAMEL_MESSAGE_FOLDER_FLAGGED)) {
+ info->info.flags &= 0xffff;
+ }
+ camel_message_info_unref (info);
+ }
+
+ camel_folder_summary_free_array (known_uids);
+
+ /* Chain up to parent's sync() method. */
+ local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (camel_mh_summary_parent_class);
+ return local_summary_class->sync (cls, expunge, changes, cancellable, error);
+}
+
+static gint
+mh_summary_decode_x_evolution (CamelLocalSummary *cls,
+ const gchar *xev,
+ CamelLocalMessageInfo *info)
+{
+ CamelLocalSummaryClass *local_summary_class;
+ CamelMhSummary *mh_summary;
+ gint ret;
+
+ local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (camel_mh_summary_parent_class);
+ ret = local_summary_class->decode_x_evolution (cls, xev, info);
+
+ if (ret == -1)
+ return ret;
+
+ /* do not use UID from the header, rather use the one provided, if any */
+ mh_summary = CAMEL_MH_SUMMARY (cls);
+ if (mh_summary->priv->current_uid) {
+ camel_pstring_free (info->info.uid);
+ info->info.uid = camel_pstring_strdup (mh_summary->priv->current_uid);
+ }
+
+ return ret;
+}
diff --git a/src/camel/providers/local/camel-mh-summary.h b/src/camel/providers/local/camel-mh-summary.h
new file mode 100644
index 000000000..3c3ef2059
--- /dev/null
+++ b/src/camel/providers/local/camel-mh-summary.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Not Zed <notzed@lostzed.mmc.com.au>
+ */
+
+#ifndef CAMEL_MH_SUMMARY_H
+#define CAMEL_MH_SUMMARY_H
+
+#include "camel-local-summary.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_MH_SUMMARY \
+ (camel_mh_summary_get_type ())
+#define CAMEL_MH_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_MH_SUMMARY, CamelMhSummary))
+#define CAMEL_MH_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_MH_SUMMARY, CamelMhSummaryClass))
+#define CAMEL_IS_MH_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_MH_SUMMARY))
+#define CAMEL_IS_MH_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_MH_SUMMARY))
+#define CAMEL_MH_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_MH_SUMMARY, CamelMhSummaryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelMhSummary CamelMhSummary;
+typedef struct _CamelMhSummaryClass CamelMhSummaryClass;
+typedef struct _CamelMhSummaryPrivate CamelMhSummaryPrivate;
+
+struct _CamelMhSummary {
+ CamelLocalSummary parent;
+ CamelMhSummaryPrivate *priv;
+};
+
+struct _CamelMhSummaryClass {
+ CamelLocalSummaryClass parent_class;
+};
+
+GType camel_mh_summary_get_type (void);
+CamelMhSummary *
+ camel_mh_summary_new (CamelFolder *folder,
+ const gchar *mhdir,
+ CamelIndex *index);
+
+G_END_DECLS
+
+#endif /* CAMEL_MH_SUMMARY_H */
diff --git a/src/camel/providers/local/camel-spool-folder.c b/src/camel/providers/local/camel-spool-folder.c
new file mode 100644
index 000000000..af8c06410
--- /dev/null
+++ b/src/camel/providers/local/camel-spool-folder.c
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-spool-folder.h"
+#include "camel-spool-settings.h"
+#include "camel-spool-store.h"
+#include "camel-spool-summary.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+G_DEFINE_TYPE (CamelSpoolFolder, camel_spool_folder, CAMEL_TYPE_MBOX_FOLDER)
+
+static CamelLocalSummary *
+spool_folder_create_summary (CamelLocalFolder *lf,
+ const gchar *folder,
+ CamelIndex *index)
+{
+ return (CamelLocalSummary *) camel_spool_summary_new (
+ CAMEL_FOLDER (lf), folder);
+}
+
+static gint
+spool_folder_lock (CamelLocalFolder *lf,
+ CamelLockType type,
+ GError **error)
+{
+ gint retry = 0;
+ CamelMboxFolder *mf = (CamelMboxFolder *) lf;
+ CamelSpoolFolder *sf = (CamelSpoolFolder *) lf;
+ GError *local_error = NULL;
+
+ mf->lockfd = open (lf->folder_path, O_RDWR | O_LARGEFILE, 0);
+ if (mf->lockfd == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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);
+
+ g_clear_error (&local_error);
+
+ if (camel_lock_fcntl (mf->lockfd, type, &local_error) == 0) {
+ if (camel_lock_flock (mf->lockfd, type, &local_error) == 0) {
+ if ((sf->lockid = camel_lock_helper_lock (lf->folder_path, &local_error)) != -1)
+ return 0;
+ camel_unlock_flock (mf->lockfd);
+ }
+ camel_unlock_fcntl (mf->lockfd);
+ }
+ retry++;
+ }
+
+ close (mf->lockfd);
+ mf->lockfd = -1;
+
+ if (local_error != NULL)
+ g_propagate_error (error, local_error);
+
+ return -1;
+}
+
+static void
+spool_folder_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;
+}
+
+static void
+camel_spool_folder_class_init (CamelSpoolFolderClass *class)
+{
+ CamelLocalFolderClass *local_folder_class;
+
+ local_folder_class = CAMEL_LOCAL_FOLDER_CLASS (class);
+ local_folder_class->create_summary = spool_folder_create_summary;
+ local_folder_class->lock = spool_folder_lock;
+ local_folder_class->unlock = spool_folder_unlock;
+}
+
+static void
+camel_spool_folder_init (CamelSpoolFolder *spool_folder)
+{
+ spool_folder->lockid = -1;
+}
+
+CamelFolder *
+camel_spool_folder_new (CamelStore *parent_store,
+ const gchar *full_name,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelService *service;
+ CamelSettings *settings;
+ gboolean filter_inbox;
+ gboolean use_xstatus_headers;
+ gchar *basename;
+
+ service = CAMEL_SERVICE (parent_store);
+
+ settings = camel_service_ref_settings (service);
+
+ filter_inbox = camel_store_settings_get_filter_inbox (
+ CAMEL_STORE_SETTINGS (settings));
+
+ use_xstatus_headers = camel_spool_settings_get_use_xstatus_headers (
+ CAMEL_SPOOL_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ basename = g_path_get_basename (full_name);
+
+ folder = g_object_new (
+ CAMEL_TYPE_SPOOL_FOLDER,
+ "display-name", basename, "full-name", full_name,
+ "parent-store", parent_store, NULL);
+
+ if (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, flags, cancellable, error);
+
+ if (folder != NULL && use_xstatus_headers)
+ camel_mbox_summary_xstatus (
+ CAMEL_MBOX_SUMMARY (folder->summary), TRUE);
+
+ g_free (basename);
+
+ return folder;
+}
+
diff --git a/src/camel/providers/local/camel-spool-folder.h b/src/camel/providers/local/camel-spool-folder.h
new file mode 100644
index 000000000..1de8e2dba
--- /dev/null
+++ b/src/camel/providers/local/camel-spool-folder.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_SPOOL_FOLDER_H
+#define CAMEL_SPOOL_FOLDER_H
+
+#include "camel-mbox-folder.h"
+#include "camel-spool-summary.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SPOOL_FOLDER \
+ (camel_spool_folder_get_type ())
+#define CAMEL_SPOOL_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SPOOL_FOLDER, CamelSpoolFolder))
+#define CAMEL_SPOOL_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SPOOL_FOLDER, CamelSpoolFolderClass))
+#define CAMEL_IS_SPOOL_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SPOOL_FOLDER))
+#define CAMEL_IS_SPOOL_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SPOOL_FOLDER))
+#define CAMEL_SPOOL_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SPOOL_FOLDER, CamelSpoolFolderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSpoolFolder CamelSpoolFolder;
+typedef struct _CamelSpoolFolderClass CamelSpoolFolderClass;
+typedef struct _CamelSpoolFolderPrivate CamelSpoolFolderPrivate;
+
+struct _CamelSpoolFolder {
+ CamelMboxFolder parent;
+ CamelSpoolFolderPrivate *priv;
+
+ gint lockid; /* lock id for dot locking */
+};
+
+struct _CamelSpoolFolderClass {
+ CamelMboxFolderClass parent_class;
+};
+
+GType camel_spool_folder_get_type (void);
+CamelFolder * camel_spool_folder_new (CamelStore *parent_store,
+ const gchar *full_name,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_SPOOL_FOLDER_H */
diff --git a/src/camel/providers/local/camel-spool-settings.c b/src/camel/providers/local/camel-spool-settings.c
new file mode 100644
index 000000000..e0ac75c8a
--- /dev/null
+++ b/src/camel/providers/local/camel-spool-settings.c
@@ -0,0 +1,144 @@
+/*
+ * camel-spool-settings.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-spool-settings.h"
+
+#define CAMEL_SPOOL_SETTINGS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SPOOL_SETTINGS, CamelSpoolSettingsPrivate))
+
+struct _CamelSpoolSettingsPrivate {
+ gboolean use_xstatus_headers;
+};
+
+enum {
+ PROP_0,
+ PROP_USE_XSTATUS_HEADERS
+};
+
+G_DEFINE_TYPE (
+ CamelSpoolSettings,
+ camel_spool_settings,
+ CAMEL_TYPE_LOCAL_SETTINGS)
+
+static void
+spool_settings_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_USE_XSTATUS_HEADERS:
+ camel_spool_settings_set_use_xstatus_headers (
+ CAMEL_SPOOL_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+spool_settings_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_USE_XSTATUS_HEADERS:
+ g_value_set_boolean (
+ value,
+ camel_spool_settings_get_use_xstatus_headers (
+ CAMEL_SPOOL_SETTINGS (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+camel_spool_settings_class_init (CamelSpoolSettingsClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelSpoolSettingsPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = spool_settings_set_property;
+ object_class->get_property = spool_settings_get_property;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_XSTATUS_HEADERS,
+ g_param_spec_boolean (
+ "use-xstatus-headers",
+ "Use X-Status Headers",
+ "Whether to use X-Status headers",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_spool_settings_init (CamelSpoolSettings *settings)
+{
+ settings->priv = CAMEL_SPOOL_SETTINGS_GET_PRIVATE (settings);
+}
+
+/**
+ * camel_spool_settings_get_use_xstatus_headers:
+ * @settings: a #CamelSpoolSettings
+ *
+ * Returns whether to utilize both "Status" and "X-Status" headers for
+ * interoperability with mbox-based mail clients like Elm, Pine and Mutt.
+ *
+ * Returns: whether to use "X-Status" headers
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_spool_settings_get_use_xstatus_headers (CamelSpoolSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_SPOOL_SETTINGS (settings), FALSE);
+
+ return settings->priv->use_xstatus_headers;
+}
+
+/**
+ * camel_spool_settings_set_use_xstatus_headers:
+ * @settings: a #CamelSpoolSettings
+ * @use_xstatus_headers: whether to use "X-Status" headers
+ *
+ * Sets whether to utilize both "Status" and "X-Status" headers for
+ * interoperability with mbox-based mail clients like Elm, Pine and Mutt.
+ *
+ * Since: 3.2
+ **/
+void
+camel_spool_settings_set_use_xstatus_headers (CamelSpoolSettings *settings,
+ gboolean use_xstatus_headers)
+{
+ g_return_if_fail (CAMEL_IS_SPOOL_SETTINGS (settings));
+
+ if (settings->priv->use_xstatus_headers == use_xstatus_headers)
+ return;
+
+ settings->priv->use_xstatus_headers = use_xstatus_headers;
+
+ g_object_notify (G_OBJECT (settings), "use-xstatus-headers");
+}
diff --git a/src/camel/providers/local/camel-spool-settings.h b/src/camel/providers/local/camel-spool-settings.h
new file mode 100644
index 000000000..5ee0ecc33
--- /dev/null
+++ b/src/camel/providers/local/camel-spool-settings.h
@@ -0,0 +1,66 @@
+/*
+ * camel-spool-settings.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_SPOOL_SETTINGS_H
+#define CAMEL_SPOOL_SETTINGS_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SPOOL_SETTINGS \
+ (camel_spool_settings_get_type ())
+#define CAMEL_SPOOL_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SPOOL_SETTINGS, CamelSpoolSettings))
+#define CAMEL_SPOOL_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SPOOL_SETTINGS, CamelSpoolSettingsClass))
+#define CAMEL_IS_SPOOL_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SPOOL_SETTINGS))
+#define CAMEL_IS_SPOOL_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SPOOL_SETTINGS))
+#define CAMEL_SPOOL_SETTINGS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SPOOL_SETTINGS, CamelSpoolSettingsClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSpoolSettings CamelSpoolSettings;
+typedef struct _CamelSpoolSettingsClass CamelSpoolSettingsClass;
+typedef struct _CamelSpoolSettingsPrivate CamelSpoolSettingsPrivate;
+
+struct _CamelSpoolSettings {
+ CamelLocalSettings parent;
+ CamelSpoolSettingsPrivate *priv;
+};
+
+struct _CamelSpoolSettingsClass {
+ CamelLocalSettingsClass parent_class;
+};
+
+GType camel_spool_settings_get_type (void) G_GNUC_CONST;
+gboolean camel_spool_settings_get_use_xstatus_headers
+ (CamelSpoolSettings *settings);
+void camel_spool_settings_set_use_xstatus_headers
+ (CamelSpoolSettings *settings,
+ gboolean use_xstatus_headers);
+
+G_END_DECLS
+
+#endif /* CAMEL_SPOOL_SETTINGS_H */
diff --git a/src/camel/providers/local/camel-spool-store.c b/src/camel/providers/local/camel-spool-store.c
new file mode 100644
index 000000000..b97810d2a
--- /dev/null
+++ b/src/camel/providers/local/camel-spool-store.c
@@ -0,0 +1,711 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-spool-folder.h"
+#include "camel-spool-settings.h"
+#include "camel-spool-store.h"
+
+#define CAMEL_SPOOL_STORE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SPOOL_STORE, CamelSpoolStorePrivate))
+
+#define d(x)
+
+typedef enum _camel_spool_store_t {
+ CAMEL_SPOOL_STORE_INVALID,
+ CAMEL_SPOOL_STORE_MBOX, /* a single mbox */
+ CAMEL_SPOOL_STORE_ELM /* elm/pine/etc tree of mbox files in folders */
+} camel_spool_store_t;
+
+struct _CamelSpoolStorePrivate {
+ gint placeholder; /* for future expansion */
+};
+
+G_DEFINE_TYPE (
+ CamelSpoolStore,
+ camel_spool_store,
+ CAMEL_TYPE_MBOX_STORE)
+
+static camel_spool_store_t
+spool_store_get_type (CamelSpoolStore *spool_store,
+ GError **error)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ camel_spool_store_t type;
+ struct stat st;
+ gchar *path;
+
+ service = CAMEL_SERVICE (spool_store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ /* Check the path for validity while we have the opportunity. */
+
+ if (path == NULL || *path != '/') {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Store root %s is not an absolute path"),
+ (path != NULL) ? path : "(null)");
+ type = CAMEL_SPOOL_STORE_INVALID;
+
+ } else if (g_stat (path, &st) == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Spool '%s' cannot be opened: %s"),
+ path, g_strerror (errno));
+ type = CAMEL_SPOOL_STORE_INVALID;
+
+ } else if (S_ISREG (st.st_mode)) {
+ type = CAMEL_SPOOL_STORE_MBOX;
+
+ } else if (S_ISDIR (st.st_mode)) {
+ type = CAMEL_SPOOL_STORE_ELM;
+
+ } else {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Spool '%s' is not a regular file or directory"),
+ path);
+ type = CAMEL_SPOOL_STORE_INVALID;
+ }
+
+ g_free (path);
+
+ return type;
+}
+
+/* partially copied from mbox */
+static void
+spool_fill_fi (CamelStore *store,
+ CamelFolderInfo *fi,
+ guint32 flags,
+ GCancellable *cancellable)
+{
+ 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)
+ camel_folder_refresh_info_sync (folder, cancellable, NULL);
+ fi->unread = camel_folder_get_unread_message_count (folder);
+ fi->total = camel_folder_get_message_count (folder);
+ g_object_unref (folder);
+ }
+}
+
+static CamelFolderInfo *
+spool_new_fi (CamelStore *store,
+ CamelFolderInfo *parent,
+ CamelFolderInfo **fip,
+ const gchar *full,
+ guint32 flags)
+{
+ CamelFolderInfo *fi;
+ const gchar *name;
+
+ name = strrchr (full, '/');
+ if (name)
+ name++;
+ else
+ name = full;
+
+ fi = camel_folder_info_new ();
+ fi->full_name = g_strdup (full);
+ fi->display_name = g_strdup (name);
+ fi->unread = -1;
+ fi->total = -1;
+ fi->flags = flags;
+
+ fi->parent = parent;
+ fi->next = *fip;
+ *fip = fi;
+
+ 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 gint
+scan_dir (CamelStore *store,
+ GHashTable *visited,
+ const gchar *root,
+ const gchar *path,
+ guint32 flags,
+ CamelFolderInfo *parent,
+ CamelFolderInfo **fip,
+ GCancellable *cancellable,
+ GError **error)
+{
+ DIR *dir;
+ struct dirent *d;
+ gchar *name, *tmp, *fname;
+ gsize name_len;
+ CamelFolderInfo *fi = NULL;
+ struct stat st;
+ CamelFolder *folder;
+ gchar 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_len = strlen (root) + strlen (path) + 2;
+ name = alloca (name_len);
+ g_snprintf (name, name_len, "%s/%s", root, path);
+ } else
+ name = (gchar *) root; /* XXX casting away const */
+
+ if (g_stat (name, &st) == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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, cancellable);
+ }
+ return 0;
+ }
+
+ dir = opendir (name);
+ if (dir == NULL) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("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 (g_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)) {
+ gint isfolder = FALSE;
+
+ /* first, see if we already have it open */
+ folder = camel_object_bag_peek (
+ 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, cancellable);
+ }
+ if (folder)
+ g_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, cancellable, error) == -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 (gconstpointer d)
+{
+ const struct _inode *v = d;
+
+ return v->inode ^ v->dnode;
+}
+
+static gboolean
+inode_equal (gconstpointer a,
+ gconstpointer b)
+{
+ const struct _inode *v1 = a, *v2 = b;
+
+ return v1->inode == v2->inode && v1->dnode == v2->dnode;
+}
+
+static void
+inode_free (gpointer k,
+ gpointer v,
+ gpointer d)
+{
+ g_free (k);
+}
+
+static CamelFolderInfo *
+get_folder_info_elm (CamelStore *store,
+ const gchar *top,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSettings *local_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelFolderInfo *fi = NULL;
+ GHashTable *visited;
+ gchar *path;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ visited = g_hash_table_new (inode_hash, inode_equal);
+
+ if (scan_dir (
+ store, visited, path, top, flags,
+ NULL, &fi, cancellable, error) == -1 && fi != NULL) {
+ camel_folder_info_free (fi);
+ fi = NULL;
+ }
+
+ g_hash_table_foreach (visited, inode_free, NULL);
+ g_hash_table_destroy (visited);
+
+ g_free (path);
+
+ return fi;
+}
+
+static CamelFolderInfo *
+get_folder_info_mbox (CamelStore *store,
+ const gchar *top,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ 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->display_name);
+ fi->display_name = g_strdup (_("Inbox"));
+ spool_fill_fi (store, fi, flags, cancellable);
+ }
+
+ return fi;
+}
+
+static gchar *
+spool_store_get_name (CamelService *service,
+ gboolean brief)
+{
+ CamelLocalSettings *local_settings;
+ CamelSpoolStore *spool_store;
+ CamelSettings *settings;
+ gchar *name;
+ gchar *path;
+
+ spool_store = CAMEL_SPOOL_STORE (service);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ if (brief)
+ return path;
+
+ switch (spool_store_get_type (spool_store, NULL)) {
+ case CAMEL_SPOOL_STORE_MBOX:
+ name = g_strdup_printf (
+ _("Spool mail file %s"), path);
+ break;
+ case CAMEL_SPOOL_STORE_ELM:
+ name = g_strdup_printf (
+ _("Spool folder tree %s"), path);
+ break;
+ default:
+ name = g_strdup (_("Invalid spool"));
+ break;
+ }
+
+ g_free (path);
+
+ return name;
+}
+
+static CamelFolder *
+spool_store_get_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelLocalSettings *local_settings;
+ CamelSpoolStore *spool_store;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelFolder *folder = NULL;
+ camel_spool_store_t type;
+ struct stat st;
+ gchar *name;
+ gchar *path;
+
+ d (printf ("opening folder %s on path %s\n", folder_name, path));
+
+ spool_store = CAMEL_SPOOL_STORE (store);
+ type = spool_store_get_type (spool_store, error);
+
+ if (type == CAMEL_SPOOL_STORE_INVALID)
+ return NULL;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ /* we only support an 'INBOX' in mbox mode */
+ if (type == CAMEL_SPOOL_STORE_MBOX) {
+ if (strcmp (folder_name, "INBOX") != 0) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Folder '%s/%s' does not exist."),
+ path, folder_name);
+ } else {
+ folder = camel_spool_folder_new (
+ store, folder_name, flags, cancellable, error);
+ }
+ } else {
+ name = g_build_filename (path, folder_name, NULL);
+ if (g_stat (name, &st) == -1) {
+ if (errno != ENOENT) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not open folder '%s':\n%s"),
+ folder_name, g_strerror (errno));
+ } else if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Folder '%s' does not exist."),
+ folder_name);
+ } else {
+ gint fd = creat (name, 0600);
+ if (fd == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not create folder '%s':\n%s"),
+ folder_name, g_strerror (errno));
+ } else {
+ close (fd);
+ folder = camel_spool_folder_new (
+ store, folder_name, flags,
+ cancellable, error);
+ }
+ }
+ } else if (!S_ISREG (st.st_mode)) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("'%s' is not a mailbox file."), name);
+ } else {
+ folder = camel_spool_folder_new (
+ store, folder_name, flags, cancellable, error);
+ }
+ g_free (name);
+ }
+
+ g_free (path);
+
+ return folder;
+}
+
+static CamelFolderInfo *
+spool_store_get_folder_info_sync (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSpoolStore *spool_store;
+ CamelFolderInfo *folder_info = NULL;
+
+ spool_store = CAMEL_SPOOL_STORE (store);
+
+ switch (spool_store_get_type (spool_store, error)) {
+ case CAMEL_SPOOL_STORE_MBOX:
+ folder_info = get_folder_info_mbox (
+ store, top, flags, cancellable, error);
+ break;
+
+ case CAMEL_SPOOL_STORE_ELM:
+ folder_info = get_folder_info_elm (
+ store, top, flags, cancellable, error);
+ break;
+
+ default:
+ break;
+ }
+
+ return folder_info;
+}
+
+static CamelFolder *
+spool_store_get_inbox_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSpoolStore *spool_store;
+ CamelFolder *folder = NULL;
+
+ spool_store = CAMEL_SPOOL_STORE (store);
+
+ switch (spool_store_get_type (spool_store, error)) {
+ case CAMEL_SPOOL_STORE_MBOX:
+ folder = spool_store_get_folder_sync (
+ store, "INBOX", CAMEL_STORE_FOLDER_CREATE,
+ cancellable, error);
+ break;
+
+ case CAMEL_SPOOL_STORE_ELM:
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Store does not support an INBOX"));
+ break;
+
+ default:
+ break;
+ }
+
+ return folder;
+}
+
+/* default implementation, only delete metadata */
+static gboolean
+spool_store_delete_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Spool folders cannot be deleted"));
+
+ return FALSE;
+}
+
+/* default implementation, rename all */
+static gboolean
+spool_store_rename_folder_sync (CamelStore *store,
+ const gchar *old,
+ const gchar *new,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Spool folders cannot be renamed"));
+
+ return FALSE;
+}
+
+static gchar *
+spool_store_get_full_path (CamelLocalStore *local_store,
+ const gchar *full_name)
+{
+ CamelLocalSettings *local_settings;
+ CamelSpoolStore *spool_store;
+ CamelSettings *settings;
+ CamelService *service;
+ gchar *full_path;
+ gchar *path;
+
+ service = CAMEL_SERVICE (local_store);
+
+ settings = camel_service_ref_settings (service);
+
+ local_settings = CAMEL_LOCAL_SETTINGS (settings);
+ path = camel_local_settings_dup_path (local_settings);
+
+ g_object_unref (settings);
+
+ spool_store = CAMEL_SPOOL_STORE (local_store);
+
+ switch (spool_store_get_type (spool_store, NULL)) {
+ case CAMEL_SPOOL_STORE_MBOX:
+ full_path = g_strdup (path);
+ break;
+
+ case CAMEL_SPOOL_STORE_ELM:
+ full_path = g_build_filename (path, full_name, NULL);
+ break;
+
+ default:
+ full_path = NULL;
+ break;
+ }
+
+ g_free (path);
+
+ return full_path;
+}
+
+static gchar *
+spool_store_get_meta_path (CamelLocalStore *ls,
+ const gchar *full_name,
+ const gchar *ext)
+{
+ CamelService *service;
+ const gchar *user_data_dir;
+ gchar *path, *key;
+
+ service = CAMEL_SERVICE (ls);
+ user_data_dir = camel_service_get_user_data_dir (service);
+
+ key = camel_file_util_safe_filename (full_name);
+ path = g_strdup_printf ("%s/%s%s", user_data_dir, key, ext);
+ g_free (key);
+
+ return path;
+}
+
+static void
+camel_spool_store_class_init (CamelSpoolStoreClass *class)
+{
+ CamelServiceClass *service_class;
+ CamelStoreClass *store_class;
+ CamelLocalStoreClass *local_store_class;
+
+ g_type_class_add_private (class, sizeof (CamelSpoolStorePrivate));
+
+ service_class = CAMEL_SERVICE_CLASS (class);
+ service_class->settings_type = CAMEL_TYPE_SPOOL_SETTINGS;
+ service_class->get_name = spool_store_get_name;
+
+ store_class = CAMEL_STORE_CLASS (class);
+ store_class->get_folder_sync = spool_store_get_folder_sync;
+ store_class->get_folder_info_sync = spool_store_get_folder_info_sync;
+ store_class->get_inbox_folder_sync = spool_store_get_inbox_folder_sync;
+ store_class->delete_folder_sync = spool_store_delete_folder_sync;
+ store_class->rename_folder_sync = spool_store_rename_folder_sync;
+
+ local_store_class = CAMEL_LOCAL_STORE_CLASS (class);
+ local_store_class->get_full_path = spool_store_get_full_path;
+ local_store_class->get_meta_path = spool_store_get_meta_path;
+}
+
+static void
+camel_spool_store_init (CamelSpoolStore *spool_store)
+{
+ spool_store->priv = CAMEL_SPOOL_STORE_GET_PRIVATE (spool_store);
+}
diff --git a/src/camel/providers/local/camel-spool-store.h b/src/camel/providers/local/camel-spool-store.h
new file mode 100644
index 000000000..d6974d101
--- /dev/null
+++ b/src/camel/providers/local/camel-spool-store.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_SPOOL_STORE_H
+#define CAMEL_SPOOL_STORE_H
+
+#include "camel-mbox-store.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SPOOL_STORE \
+ (camel_spool_store_get_type ())
+#define CAMEL_SPOOL_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SPOOL_STORE, CamelSpoolStore))
+#define CAMEL_SPOOL_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SPOOL_STORE, CamelSpoolStoreClass))
+#define CAMEL_IS_SPOOL_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SPOOL_STORE))
+#define CAMEL_IS_SPOOL_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SPOOL_STORE))
+#define CAMEL_SPOOL_STORE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SPOOL_STORE, CamelSpoolStoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSpoolStore CamelSpoolStore;
+typedef struct _CamelSpoolStoreClass CamelSpoolStoreClass;
+typedef struct _CamelSpoolStorePrivate CamelSpoolStorePrivate;
+
+struct _CamelSpoolStore {
+ CamelMboxStore parent;
+ CamelSpoolStorePrivate *priv;
+};
+
+struct _CamelSpoolStoreClass {
+ CamelMboxStoreClass parent_class;
+};
+
+GType camel_spool_store_get_type (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_SPOOL_STORE_H */
+
diff --git a/src/camel/providers/local/camel-spool-summary.c b/src/camel/providers/local/camel-spool-summary.c
new file mode 100644
index 000000000..44e37199a
--- /dev/null
+++ b/src/camel/providers/local/camel-spool-summary.c
@@ -0,0 +1,370 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-spool-summary.h"
+#include "camel-local-private.h"
+#include "camel-win32.h"
+
+#define io(x)
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+#define CAMEL_SPOOL_SUMMARY_VERSION (0x400)
+
+static gint spool_summary_load (CamelLocalSummary *cls,
+ gint forceindex,
+ GError **error);
+static gint spool_summary_check (CamelLocalSummary *cls,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error);
+
+static gint spool_summary_sync_full (CamelMboxSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error);
+static gint spool_summary_need_index (void);
+
+G_DEFINE_TYPE (CamelSpoolSummary, camel_spool_summary, CAMEL_TYPE_MBOX_SUMMARY)
+
+static void
+camel_spool_summary_class_init (CamelSpoolSummaryClass *class)
+{
+ CamelLocalSummaryClass *local_summary_class;
+ CamelMboxSummaryClass *mbox_summary_class;
+
+ local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (class);
+ local_summary_class->load = spool_summary_load;
+ local_summary_class->check = spool_summary_check;
+ local_summary_class->need_index = spool_summary_need_index;
+
+ mbox_summary_class = CAMEL_MBOX_SUMMARY_CLASS (class);
+ mbox_summary_class->sync_full = spool_summary_sync_full;
+}
+
+static void
+camel_spool_summary_init (CamelSpoolSummary *spool_summary)
+{
+ CamelFolderSummary *folder_summary;
+
+ folder_summary = CAMEL_FOLDER_SUMMARY (spool_summary);
+
+ /* message info size is from mbox parent */
+
+ /* and a unique file version */
+ folder_summary->version += CAMEL_SPOOL_SUMMARY_VERSION;
+}
+
+CamelSpoolSummary *
+camel_spool_summary_new (CamelFolder *folder,
+ const gchar *mbox_name)
+{
+ CamelSpoolSummary *new;
+
+ new = g_object_new (CAMEL_TYPE_SPOOL_SUMMARY, "folder", folder, NULL);
+ if (folder) {
+ CamelStore *parent_store;
+
+ parent_store = camel_folder_get_parent_store (folder);
+ camel_db_set_collate (parent_store->cdb_r, "bdata", "spool_frompos_sort", (CamelDBCollate) camel_local_frompos_sort);
+ ((CamelFolderSummary *) new)->sort_by = "bdata";
+ ((CamelFolderSummary *) new)->collate = "spool_frompos_sort";
+ }
+ camel_local_summary_construct ((CamelLocalSummary *) new, mbox_name, NULL);
+ camel_folder_summary_load_from_db ((CamelFolderSummary *) new, NULL);
+ return new;
+}
+
+static gint
+spool_summary_load (CamelLocalSummary *cls,
+ gint forceindex,
+ GError **error)
+{
+ /* if not loading, then rescan mbox file content */
+ camel_local_summary_check_force (cls);
+
+ return 0;
+}
+
+/* perform a full sync */
+static gint
+spool_summary_sync_full (CamelMboxSummary *cls,
+ gboolean expunge,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint fd = -1, fdout = -1;
+ gchar tmpname[64] = { '\0' };
+ gchar *buffer, *p;
+ goffset spoollen, outlen;
+ gint size, sizeout;
+ struct stat st;
+ guint32 flags = (expunge ? 1 : 0);
+
+ d (printf ("performing full summary/sync\n"));
+
+ camel_operation_push_message (cancellable, _("Storing folder"));
+
+ fd = open (((CamelLocalSummary *) cls)->folder_path, O_RDWR | O_LARGEFILE);
+ if (fd == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not open file: %s: %s"),
+ ((CamelLocalSummary *) cls)->folder_path,
+ g_strerror (errno));
+ camel_operation_pop_message (cancellable);
+ return -1;
+ }
+
+ g_snprintf (tmpname, sizeof (tmpname), "/tmp/spool.camel.XXXXXX");
+ fdout = g_mkstemp (tmpname);
+
+ d (printf ("Writing tmp file to %s\n", tmpname));
+ if (fdout == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Cannot open temporary mailbox: %s"),
+ g_strerror (errno));
+ goto error;
+ }
+
+ if (camel_mbox_summary_sync_mbox (
+ (CamelMboxSummary *) cls, flags, changeinfo,
+ fd, fdout, cancellable, error) == -1)
+ goto error;
+
+ /* sync out content */
+ if (fsync (fdout) == -1) {
+ g_warning ("Cannot synchronize temporary folder: %s", g_strerror (errno));
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not synchronize temporary folder %s: %s"),
+ ((CamelLocalSummary *) cls)->folder_path,
+ g_strerror (errno));
+ goto error;
+ }
+
+ /* see if we can write this much to the spool file */
+ if (fstat (fd, &st) == -1) {
+ g_warning ("Cannot synchronize temporary folder: %s", g_strerror (errno));
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not synchronize temporary folder %s: %s"),
+ ((CamelLocalSummary *) cls)->folder_path,
+ g_strerror (errno));
+ goto error;
+ }
+ spoollen = st.st_size;
+
+ if (fstat (fdout, &st) == -1) {
+ g_warning ("Cannot synchronize temporary folder: %s", g_strerror (errno));
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not synchronize temporary folder %s: %s"),
+ ((CamelLocalSummary *) cls)->folder_path,
+ g_strerror (errno));
+ goto error;
+ }
+ outlen = st.st_size;
+
+ /* I think this is the right way to do this - checking that the file will fit the new data */
+ if (outlen > 0
+ && (lseek (fd, outlen - 1, SEEK_SET) == -1
+ || write (fd, "", 1) != 1
+ || fsync (fd) == -1
+ || lseek (fd, 0, SEEK_SET) == -1
+ || lseek (fdout, 0, SEEK_SET) == -1)) {
+ g_warning ("Cannot synchronize spool folder: %s", g_strerror (errno));
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not synchronize spool folder %s: %s"),
+ ((CamelLocalSummary *) cls)->folder_path,
+ g_strerror (errno));
+ /* incase we ran out of room, remove any trailing space first */
+ if (ftruncate (fd, spoollen) == -1) {
+ g_debug ("%s: Failed to call ftruncate: %s", G_STRFUNC, g_strerror (errno));
+ }
+ goto error;
+ }
+
+ /* now copy content back */
+ buffer = g_malloc (8192);
+ size = 1;
+ while (size > 0) {
+ do {
+ size = read (fdout, buffer, 8192);
+ } while (size == -1 && errno == EINTR);
+
+ if (size > 0) {
+ p = buffer;
+ do {
+ sizeout = write (fd, p, size);
+ if (sizeout > 0) {
+ p+= sizeout;
+ size -= sizeout;
+ }
+ } while ((sizeout == -1 && errno == EINTR) && size > 0);
+ size = sizeout;
+ }
+
+ if (size == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not synchronize spool folder %s: %s\n"
+ "Folder may be corrupt, copy saved in '%s'"),
+ ((CamelLocalSummary *) cls)->folder_path,
+ g_strerror (errno), tmpname);
+ /* so we dont delete it */
+ tmpname[0] = '\0';
+ g_free (buffer);
+ goto error;
+ }
+ }
+
+ g_free (buffer);
+
+ d (printf ("Closing folders\n"));
+
+ if (ftruncate (fd, outlen) == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not synchronize spool folder %s: %s\n"
+ "Folder may be corrupt, copy saved in '%s'"),
+ ((CamelLocalSummary *) cls)->folder_path,
+ g_strerror (errno), tmpname);
+ tmpname[0] = '\0';
+ goto error;
+ }
+
+ if (close (fd) == -1) {
+ g_warning ("Cannot close source folder: %s", g_strerror (errno));
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not synchronize spool folder %s: %s\n"
+ "Folder may be corrupt, copy saved in '%s'"),
+ ((CamelLocalSummary *) cls)->folder_path,
+ g_strerror (errno), tmpname);
+ tmpname[0] = '\0';
+ fd = -1;
+ goto error;
+ }
+
+ close (fdout);
+
+ if (tmpname[0] != '\0')
+ unlink (tmpname);
+
+ camel_operation_pop_message (cancellable);
+
+ return 0;
+ error:
+ if (fd != -1)
+ close (fd);
+
+ if (fdout != -1)
+ close (fdout);
+
+ if (tmpname[0] != '\0')
+ unlink (tmpname);
+
+ camel_operation_pop_message (cancellable);
+
+ return -1;
+}
+
+static gint
+spool_summary_check (CamelLocalSummary *cls,
+ CamelFolderChangeInfo *changeinfo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint i;
+ gboolean work;
+ struct stat st;
+ CamelFolderSummary *s = (CamelFolderSummary *) cls;
+ GPtrArray *known_uids;
+
+ if (CAMEL_LOCAL_SUMMARY_CLASS (camel_spool_summary_parent_class)->check (cls, changeinfo, cancellable, error) == -1)
+ return -1;
+
+ /* check to see if we need to copy/update the file; missing xev headers prompt this */
+ work = FALSE;
+ camel_folder_summary_prepare_fetch_all (s, error);
+ known_uids = camel_folder_summary_get_array (s);
+ for (i = 0; !work && known_uids && i < known_uids->len; i++) {
+ CamelMboxMessageInfo *info = (CamelMboxMessageInfo *) camel_folder_summary_get (s, g_ptr_array_index (known_uids, i));
+ g_return_val_if_fail (info, -1);
+ work = (info->info.info.flags & (CAMEL_MESSAGE_FOLDER_NOXEV)) != 0;
+ camel_message_info_unref (info);
+ }
+ camel_folder_summary_free_array (known_uids);
+
+ /* if we do, then write out the headers using sync_full, etc */
+ if (work) {
+ d (printf ("Have to add new headers, re-syncing from the start to accomplish this\n"));
+ if (CAMEL_MBOX_SUMMARY_GET_CLASS (cls)->sync_full (
+ CAMEL_MBOX_SUMMARY (cls), FALSE,
+ changeinfo, cancellable, error) == -1)
+ return -1;
+
+ if (g_stat (cls->folder_path, &st) == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Unknown error: %s"),
+ g_strerror (errno));
+ return -1;
+ }
+
+ ((CamelMboxSummary *) cls)->folder_size = st.st_size;
+ ((CamelFolderSummary *) cls)->time = st.st_mtime;
+ }
+
+ return 0;
+}
+
+static gint
+spool_summary_need_index (void)
+{
+ return 0;
+}
diff --git a/src/camel/providers/local/camel-spool-summary.h b/src/camel/providers/local/camel-spool-summary.h
new file mode 100644
index 000000000..b08adfbf9
--- /dev/null
+++ b/src/camel/providers/local/camel-spool-summary.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_SPOOL_SUMMARY_H
+#define CAMEL_SPOOL_SUMMARY_H
+
+#include <camel/camel.h>
+
+#include "camel-mbox-summary.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SPOOL_SUMMARY \
+ (camel_spool_summary_get_type ())
+#define CAMEL_SPOOL_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SPOOL_SUMMARY, CamelSpoolSummary))
+#define CAMEL_SPOOL_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SPOOL_SUMMARY, CamelSpoolSummaryClass))
+#define CAMEL_IS_SPOOL_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SPOOL_SUMMARY))
+#define CAMEL_IS_SPOOL_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SPOOL_SUMMARY))
+#define CAMEL_SPOOL_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SPOOL_SUMMARY, CamelSpoolSummaryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSpoolSummary CamelSpoolSummary;
+typedef struct _CamelSpoolSummaryClass CamelSpoolSummaryClass;
+
+struct _CamelSpoolSummary {
+ CamelMboxSummary parent;
+
+};
+
+struct _CamelSpoolSummaryClass {
+ CamelMboxSummaryClass parent_class;
+};
+
+GType camel_spool_summary_get_type (void);
+void camel_spool_summary_construct (CamelSpoolSummary *new, const gchar *filename, const gchar *spool_name, CamelIndex *index);
+
+/* create the summary, in-memory only */
+CamelSpoolSummary *camel_spool_summary_new (struct _CamelFolder *, const gchar *filename);
+
+/* load/check the summary */
+gint camel_spool_summary_load (CamelSpoolSummary *cls, gint forceindex, GError **error);
+/* check for new/removed messages */
+gint camel_spool_summary_check (CamelSpoolSummary *cls, CamelFolderChangeInfo *, GError **error);
+/* perform a folder sync or expunge, if needed */
+gint camel_spool_summary_sync (CamelSpoolSummary *cls, gboolean expunge, CamelFolderChangeInfo *, GError **error);
+/* add a new message to the summary */
+CamelMessageInfo *camel_spool_summary_add (CamelSpoolSummary *cls, CamelMimeMessage *msg, const CamelMessageInfo *info, CamelFolderChangeInfo *, GError **error);
+
+/* generate an X-Evolution header line */
+gchar *camel_spool_summary_encode_x_evolution (CamelSpoolSummary *cls, const CamelMessageInfo *info);
+gint camel_spool_summary_decode_x_evolution (CamelSpoolSummary *cls, const gchar *xev, CamelMessageInfo *info);
+
+/* utility functions - write headers to a file with optional X-Evolution header */
+gint camel_spool_summary_write_headers (gint fd, struct _camel_header_raw *header, gchar *xevline);
+
+G_END_DECLS
+
+#endif /* CAMEL_SPOOL_SUMMARY_H */
diff --git a/src/camel/providers/local/libcamellocal.urls b/src/camel/providers/local/libcamellocal.urls
new file mode 100644
index 000000000..207c19a98
--- /dev/null
+++ b/src/camel/providers/local/libcamellocal.urls
@@ -0,0 +1,4 @@
+mh
+mbox
+maildir
+spool
diff --git a/src/camel/providers/nntp/CMakeLists.txt b/src/camel/providers/nntp/CMakeLists.txt
new file mode 100644
index 000000000..e6fdc743d
--- /dev/null
+++ b/src/camel/providers/nntp/CMakeLists.txt
@@ -0,0 +1,57 @@
+set(SOURCES
+ camel-nntp-folder.c
+ camel-nntp-folder.h
+ camel-nntp-private.h
+ camel-nntp-provider.c
+ camel-nntp-resp-codes.h
+ camel-nntp-settings.c
+ camel-nntp-settings.h
+ camel-nntp-store-summary.c
+ camel-nntp-store-summary.h
+ camel-nntp-store.c
+ camel-nntp-store.h
+ camel-nntp-stream.c
+ camel-nntp-stream.h
+ camel-nntp-summary.c
+ camel-nntp-summary.h
+)
+
+set(DEPENDENCIES
+ camel
+)
+
+add_library(camelnntp MODULE ${SOURCES})
+
+add_dependencies(camelnntp
+ ${DEPENDENCIES}
+)
+
+target_compile_definitions(camelnntp PRIVATE
+ -DG_LOG_DOMAIN=\"camel-nntp-provider\"
+)
+
+target_compile_options(camelnntp PUBLIC
+ ${CAMEL_CFLAGS}
+)
+
+target_include_directories(camelnntp PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/camel
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_SOURCE_DIR}/src/camel
+ ${CAMEL_INCLUDE_DIRS}
+)
+
+target_link_libraries(camelnntp
+ ${DEPENDENCIES}
+ ${CAMEL_LDFLAGS}
+)
+
+install(TARGETS camelnntp
+ DESTINATION ${camel_providerdir}
+)
+
+install(FILES libcamelnntp.urls
+ DESTINATION ${camel_providerdir}
+)
diff --git a/src/camel/providers/nntp/camel-nntp-folder.c b/src/camel/providers/nntp/camel-nntp-folder.c
new file mode 100644
index 000000000..8e45b11ea
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-folder.c
@@ -0,0 +1,857 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-nntp-folder.c : Class for a news folder
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors : Chris Toshok <toshok@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-nntp-folder.h"
+#include "camel-nntp-private.h"
+#include "camel-nntp-store.h"
+#include "camel-nntp-summary.h"
+
+#define CAMEL_NNTP_FOLDER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_NNTP_FOLDER, CamelNNTPFolderPrivate))
+
+/* The custom property ID is a CamelArg artifact.
+ * It still identifies the property in state files. */
+enum {
+ PROP_0,
+ PROP_APPLY_FILTERS = 0x2501
+};
+
+G_DEFINE_TYPE (
+ CamelNNTPFolder,
+ camel_nntp_folder,
+ CAMEL_TYPE_OFFLINE_FOLDER)
+
+static gboolean
+nntp_folder_get_apply_filters (CamelNNTPFolder *folder)
+{
+ g_return_val_if_fail (folder != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_NNTP_FOLDER (folder), FALSE);
+
+ return folder->priv->apply_filters;
+}
+
+static void
+nntp_folder_set_apply_filters (CamelNNTPFolder *folder,
+ gboolean apply_filters)
+{
+ g_return_if_fail (folder != NULL);
+ g_return_if_fail (CAMEL_IS_NNTP_FOLDER (folder));
+
+ if (folder->priv->apply_filters == apply_filters)
+ return;
+
+ folder->priv->apply_filters = apply_filters;
+
+ g_object_notify (G_OBJECT (folder), "apply-filters");
+}
+
+static void
+nntp_folder_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_APPLY_FILTERS:
+ nntp_folder_set_apply_filters (
+ CAMEL_NNTP_FOLDER (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+nntp_folder_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_APPLY_FILTERS:
+ g_value_set_boolean (
+ value, nntp_folder_get_apply_filters (
+ CAMEL_NNTP_FOLDER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+nntp_folder_dispose (GObject *object)
+{
+ CamelFolder *folder;
+ CamelStore *store;
+
+ folder = CAMEL_FOLDER (object);
+ camel_folder_summary_save_to_db (folder->summary, NULL);
+
+ store = camel_folder_get_parent_store (folder);
+ if (store != NULL) {
+ CamelNNTPStoreSummary *nntp_store_summary;
+
+ nntp_store_summary = camel_nntp_store_ref_summary (
+ CAMEL_NNTP_STORE (store));
+ camel_store_summary_disconnect_folder_summary (
+ CAMEL_STORE_SUMMARY (nntp_store_summary),
+ folder->summary);
+ g_clear_object (&nntp_store_summary);
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_nntp_folder_parent_class)->dispose (object);
+}
+
+static void
+nntp_folder_finalize (GObject *object)
+{
+ CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (object);
+
+ if (nntp_folder->changes) {
+ camel_folder_change_info_free (nntp_folder->changes);
+ nntp_folder->changes = NULL;
+ }
+
+ g_mutex_clear (&nntp_folder->priv->search_lock);
+ g_mutex_clear (&nntp_folder->priv->cache_lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_nntp_folder_parent_class)->finalize (object);
+}
+
+gboolean
+camel_nntp_folder_selected (CamelNNTPFolder *nntp_folder,
+ gchar *line,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelStore *parent_store;
+ gboolean res;
+
+ folder = CAMEL_FOLDER (nntp_folder);
+ parent_store = camel_folder_get_parent_store (folder);
+
+ res = camel_nntp_summary_check (
+ CAMEL_NNTP_SUMMARY (folder->summary),
+ CAMEL_NNTP_STORE (parent_store),
+ line, nntp_folder->changes,
+ cancellable, error);
+
+ if (camel_folder_change_info_changed (nntp_folder->changes)) {
+ CamelFolderChangeInfo *changes;
+
+ changes = nntp_folder->changes;
+ nntp_folder->changes = camel_folder_change_info_new ();
+
+ camel_folder_changed (CAMEL_FOLDER (nntp_folder), changes);
+ camel_folder_change_info_free (changes);
+ }
+
+ return res;
+}
+
+static void
+unset_flagged_flag (const gchar *uid,
+ CamelFolderSummary *summary)
+{
+ CamelMessageInfo *info;
+
+ info = camel_folder_summary_get (summary, uid);
+ if (info) {
+ CamelMessageInfoBase *base = (CamelMessageInfoBase *) info;
+
+ if ((base->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0) {
+ base->flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
+ base->dirty = TRUE;
+ }
+
+ camel_message_info_unref (info);
+ }
+}
+
+static gchar *
+nntp_get_filename (CamelFolder *folder,
+ const gchar *uid,
+ GError **error)
+{
+ CamelStore *parent_store;
+ CamelDataCache *nntp_cache;
+ CamelNNTPStore *nntp_store;
+ gchar *article, *msgid;
+ gsize article_len;
+ gchar *filename;
+
+ parent_store = camel_folder_get_parent_store (folder);
+ nntp_store = CAMEL_NNTP_STORE (parent_store);
+
+ article_len = strlen (uid) + 1;
+ article = alloca (article_len);
+ g_strlcpy (article, uid, article_len);
+ msgid = strchr (article, ',');
+ if (msgid == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Internal error: UID in invalid format: %s"), uid);
+ return NULL;
+ }
+ *msgid++ = 0;
+
+ nntp_cache = camel_nntp_store_ref_cache (nntp_store);
+ filename = camel_data_cache_get_filename (nntp_cache, "cache", msgid);
+ g_clear_object (&nntp_cache);
+
+ return filename;
+}
+
+static CamelStream *
+nntp_folder_download_message (CamelNNTPFolder *nntp_folder,
+ const gchar *id,
+ const gchar *msgid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelStore *parent_store;
+ CamelDataCache *nntp_cache;
+ CamelNNTPStore *nntp_store;
+ CamelNNTPStream *nntp_stream = NULL;
+ CamelStream *stream = NULL;
+ gint ret;
+ gchar *line;
+
+ folder = CAMEL_FOLDER (nntp_folder);
+ parent_store = camel_folder_get_parent_store (folder);
+
+ nntp_store = CAMEL_NNTP_STORE (parent_store);
+ nntp_cache = camel_nntp_store_ref_cache (nntp_store);
+
+ ret = camel_nntp_command (
+ nntp_store, cancellable, error,
+ nntp_folder, &line, "article %s", id);
+
+ if (ret == 220) {
+ GIOStream *base_stream;
+
+ nntp_stream = camel_nntp_store_ref_stream (nntp_store);
+
+ base_stream = camel_data_cache_add (
+ nntp_cache, "cache", msgid, NULL);
+ if (base_stream != NULL) {
+ gboolean success;
+
+ stream = camel_stream_new (base_stream);
+ g_object_unref (base_stream);
+
+ success = (camel_stream_write_to_stream (
+ CAMEL_STREAM (nntp_stream),
+ stream, cancellable, error) != -1);
+ if (!success)
+ goto fail;
+
+ success = g_seekable_seek (
+ G_SEEKABLE (stream), 0,
+ G_SEEK_SET, cancellable, error);
+ if (!success)
+ goto fail;
+ } else {
+ stream = g_object_ref (nntp_stream);
+ }
+
+ } else if (ret == 423 || ret == 430) {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID_UID,
+ _("Cannot get message %s: %s"), msgid, line);
+
+ } else if (ret != -1) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot get message %s: %s"), msgid, line);
+ }
+
+ goto exit;
+
+fail:
+ camel_data_cache_remove (nntp_cache, "cache", msgid, NULL);
+ g_prefix_error (error, _("Cannot get message %s: "), msgid);
+
+ g_clear_object (&stream);
+
+exit:
+ g_clear_object (&nntp_cache);
+ g_clear_object (&nntp_stream);
+
+ return stream;
+}
+
+static GPtrArray *
+nntp_folder_search_by_expression (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ 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, cancellable, error);
+
+ CAMEL_NNTP_FOLDER_UNLOCK (nntp_folder, search_lock);
+
+ return matches;
+}
+
+static guint32
+nntp_folder_count_by_expression (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
+ guint32 count;
+
+ 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);
+ count = camel_folder_search_count (nntp_folder->search, expression, cancellable, error);
+
+ CAMEL_NNTP_FOLDER_UNLOCK (nntp_folder, search_lock);
+
+ return count;
+}
+
+static GPtrArray *
+nntp_folder_search_by_uids (CamelFolder *folder,
+ const gchar *expression,
+ GPtrArray *uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ 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, cancellable, error);
+
+ 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_NNTP_FOLDER_LOCK (nntp_folder, search_lock);
+ camel_folder_search_free_result (nntp_folder->search, result);
+ CAMEL_NNTP_FOLDER_UNLOCK (nntp_folder, search_lock);
+}
+
+static gboolean
+nntp_folder_append_message_sync (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *parent_store;
+ CamelNNTPStore *nntp_store;
+ CamelNNTPStream *nntp_stream = NULL;
+ CamelStream *filtered_stream;
+ CamelMimeFilter *crlffilter;
+ gint ret;
+ guint u;
+ struct _camel_header_raw *header, *savedhdrs, *n, *tail;
+ const gchar *full_name;
+ gchar *group, *line;
+ gboolean success = TRUE;
+ GError *local_error = NULL;
+
+ full_name = camel_folder_get_full_name (folder);
+ parent_store = camel_folder_get_parent_store (folder);
+
+ nntp_store = CAMEL_NNTP_STORE (parent_store);
+
+ /* send 'POST' command */
+ ret = camel_nntp_command (
+ nntp_store, cancellable, error, NULL, &line, "post");
+ if (ret != 340) {
+ if (ret == 440) {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INSUFFICIENT_PERMISSION,
+ _("Posting failed: %s"), line);
+ success = FALSE;
+ } else if (ret != -1) {
+ g_set_error (
+ error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC,
+ _("Posting failed: %s"), line);
+ success = FALSE;
+ }
+ goto exit;
+ }
+
+ /* the 'Newsgroups: ' header */
+ group = g_strdup_printf ("Newsgroups: %s\r\n", full_name);
+
+ /* remove mail 'To', 'CC', and 'BCC' headers */
+ savedhdrs = NULL;
+ tail = (struct _camel_header_raw *) &savedhdrs;
+
+ header = (struct _camel_header_raw *) &CAMEL_MIME_PART (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;
+ }
+
+ nntp_stream = camel_nntp_store_ref_stream (nntp_store);
+
+ /* setup stream filtering */
+ filtered_stream = camel_stream_filter_new (CAMEL_STREAM (nntp_stream));
+ crlffilter = camel_mime_filter_crlf_new (
+ CAMEL_MIME_FILTER_CRLF_ENCODE,
+ CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
+ camel_stream_filter_add (
+ CAMEL_STREAM_FILTER (filtered_stream), crlffilter);
+ g_object_unref (crlffilter);
+
+ /* write the message */
+ if (local_error == NULL)
+ camel_stream_write (
+ CAMEL_STREAM (nntp_stream),
+ group, strlen (group),
+ cancellable, &local_error);
+ if (local_error == NULL)
+ camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (message),
+ filtered_stream, cancellable, &local_error);
+ if (local_error == NULL)
+ camel_stream_flush (
+ filtered_stream, cancellable, &local_error);
+ if (local_error == NULL)
+ camel_stream_write (
+ CAMEL_STREAM (nntp_stream),
+ "\r\n.\r\n", 5,
+ cancellable, &local_error);
+ if (local_error == NULL)
+ camel_nntp_stream_line (
+ nntp_stream, (guchar **) &line,
+ &u, cancellable, &local_error);
+ if (local_error == NULL && atoi (line) != 240)
+ local_error = g_error_new_literal (
+ CAMEL_ERROR, CAMEL_ERROR_GENERIC, line);
+
+ if (local_error != NULL) {
+ g_propagate_prefixed_error (
+ error, local_error, _("Posting failed: "));
+ success = FALSE;
+ }
+
+ g_object_unref (filtered_stream);
+ g_free (group);
+ header->next = savedhdrs;
+
+exit:
+ g_clear_object (&nntp_stream);
+
+ return success;
+}
+
+static gboolean
+nntp_folder_expunge_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderSummary *summary;
+ CamelFolderChangeInfo *changes;
+ GPtrArray *known_uids;
+ guint ii;
+
+ summary = folder->summary;
+
+ camel_folder_summary_prepare_fetch_all (summary, NULL);
+ known_uids = camel_folder_summary_get_array (summary);
+
+ if (known_uids == NULL)
+ return TRUE;
+
+ changes = camel_folder_change_info_new ();
+
+ for (ii = 0; ii < known_uids->len; ii++) {
+ CamelMessageInfo *info;
+ const gchar *uid;
+
+ uid = g_ptr_array_index (known_uids, ii);
+ info = camel_folder_summary_get (summary, uid);
+
+ if (camel_message_info_get_flags (info) & CAMEL_MESSAGE_DELETED) {
+ camel_folder_change_info_remove_uid (changes, uid);
+ camel_folder_summary_remove (summary, info);
+ }
+
+ camel_message_info_unref (info);
+ }
+
+ camel_folder_summary_save_to_db (summary, NULL);
+ camel_folder_changed (folder, changes);
+
+ camel_folder_change_info_free (changes);
+ camel_folder_summary_free_array (known_uids);
+
+ return TRUE;
+}
+
+static CamelMimeMessage *
+nntp_folder_get_message_sync (CamelFolder *folder,
+ const gchar *uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *parent_store;
+ CamelMimeMessage *message = NULL;
+ CamelDataCache *nntp_cache;
+ CamelNNTPStore *nntp_store;
+ CamelFolderChangeInfo *changes;
+ CamelNNTPFolder *nntp_folder;
+ CamelStream *stream = NULL;
+ GIOStream *base_stream;
+ gchar *article, *msgid;
+ gsize article_len;
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ nntp_folder = CAMEL_NNTP_FOLDER (folder);
+ nntp_store = CAMEL_NNTP_STORE (parent_store);
+
+ article_len = strlen (uid) + 1;
+ article = alloca (article_len);
+ g_strlcpy (article, uid, article_len);
+ msgid = strchr (article, ',');
+ if (msgid == NULL) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Internal error: UID in invalid format: %s"), uid);
+ return NULL;
+ }
+ *msgid++ = 0;
+
+ /* Lookup in cache, NEWS is global messageid's so use a global cache path */
+ nntp_cache = camel_nntp_store_ref_cache (nntp_store);
+ base_stream = camel_data_cache_get (nntp_cache, "cache", msgid, NULL);
+ g_clear_object (&nntp_cache);
+
+ if (base_stream != NULL) {
+ stream = camel_stream_new (base_stream);
+ g_object_unref (base_stream);
+ } else {
+ CamelServiceConnectionStatus connection_status;
+
+ connection_status = camel_service_get_connection_status (
+ CAMEL_SERVICE (parent_store));
+
+ if (connection_status != CAMEL_SERVICE_CONNECTED) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("This message is not currently available"));
+ goto fail;
+ }
+
+ stream = nntp_folder_download_message (nntp_folder, article, msgid, cancellable, error);
+ if (stream == NULL)
+ goto fail;
+ }
+
+ message = camel_mime_message_new ();
+ if (!camel_data_wrapper_construct_from_stream_sync ((CamelDataWrapper *) message, stream, cancellable, error)) {
+ g_prefix_error (error, _("Cannot get message %s: "), uid);
+ g_object_unref (message);
+ message = NULL;
+ }
+
+ g_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;
+ }
+
+ if (changes) {
+ camel_folder_changed (folder, changes);
+ camel_folder_change_info_free (changes);
+ }
+
+ return message;
+}
+
+static gboolean
+nntp_folder_refresh_info_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *parent_store;
+ CamelNNTPStore *nntp_store;
+ CamelFolderChangeInfo *changes = NULL;
+ CamelNNTPFolder *nntp_folder;
+ gchar *line;
+ gboolean success;
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ nntp_folder = CAMEL_NNTP_FOLDER (folder);
+ nntp_store = CAMEL_NNTP_STORE (parent_store);
+
+ /* When invoked with no fmt, camel_nntp_command() just selects the folder
+ * and should return zero. */
+ success = !camel_nntp_command (
+ nntp_store, cancellable, error, 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 ();
+ }
+
+ if (changes) {
+ camel_folder_changed (folder, changes);
+ camel_folder_change_info_free (changes);
+ }
+
+ return success;
+}
+
+static gboolean
+nntp_folder_synchronize_sync (CamelFolder *folder,
+ gboolean expunge,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolderSummary *summary;
+ GPtrArray *changed;
+
+ if (expunge) {
+ if (!camel_folder_expunge_sync (folder, cancellable, error))
+ return FALSE;
+ }
+
+ summary = folder->summary;
+
+ changed = camel_folder_summary_get_changed (summary);
+ if (changed != NULL) {
+ g_ptr_array_foreach (
+ changed, (GFunc) unset_flagged_flag, summary);
+ g_ptr_array_foreach (
+ changed, (GFunc) camel_pstring_free, NULL);
+ camel_folder_summary_touch (summary);
+ g_ptr_array_free (changed, TRUE);
+ }
+
+ return camel_folder_summary_save_to_db (summary, error);
+}
+
+static gboolean
+nntp_folder_transfer_messages_to_sync (CamelFolder *source,
+ GPtrArray *uids,
+ CamelFolder *dest,
+ gboolean delete_originals,
+ GPtrArray **transferred_uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You cannot copy messages from a NNTP folder"));
+
+ return FALSE;
+}
+
+static void
+camel_nntp_folder_class_init (CamelNNTPFolderClass *class)
+{
+ GObjectClass *object_class;
+ CamelFolderClass *folder_class;
+
+ g_type_class_add_private (class, sizeof (CamelNNTPFolderPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = nntp_folder_set_property;
+ object_class->get_property = nntp_folder_get_property;
+ object_class->dispose = nntp_folder_dispose;
+ object_class->finalize = nntp_folder_finalize;
+
+ folder_class = CAMEL_FOLDER_CLASS (class);
+ folder_class->search_by_expression = nntp_folder_search_by_expression;
+ folder_class->count_by_expression = nntp_folder_count_by_expression;
+ folder_class->search_by_uids = nntp_folder_search_by_uids;
+ folder_class->search_free = nntp_folder_search_free;
+ folder_class->get_filename = nntp_get_filename;
+ folder_class->append_message_sync = nntp_folder_append_message_sync;
+ folder_class->expunge_sync = nntp_folder_expunge_sync;
+ folder_class->get_message_sync = nntp_folder_get_message_sync;
+ folder_class->refresh_info_sync = nntp_folder_refresh_info_sync;
+ folder_class->synchronize_sync = nntp_folder_synchronize_sync;
+ folder_class->transfer_messages_to_sync = nntp_folder_transfer_messages_to_sync;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_APPLY_FILTERS,
+ g_param_spec_boolean (
+ "apply-filters",
+ "Apply Filters",
+ _("Apply message _filters to this folder"),
+ FALSE,
+ G_PARAM_READWRITE |
+ CAMEL_PARAM_PERSISTENT));
+}
+
+static void
+camel_nntp_folder_init (CamelNNTPFolder *nntp_folder)
+{
+ nntp_folder->priv = CAMEL_NNTP_FOLDER_GET_PRIVATE (nntp_folder);
+
+ nntp_folder->changes = camel_folder_change_info_new ();
+ g_mutex_init (&nntp_folder->priv->search_lock);
+ g_mutex_init (&nntp_folder->priv->cache_lock);
+}
+
+CamelFolder *
+camel_nntp_folder_new (CamelStore *parent,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelNNTPFolder *nntp_folder;
+ CamelNNTPStore *nntp_store;
+ CamelNNTPStoreSummary *nntp_store_summary;
+ gchar *storage_path, *root;
+ CamelService *service;
+ CamelSettings *settings;
+ CamelStoreInfo *si;
+ const gchar *user_cache_dir;
+ gboolean subscribed = TRUE;
+ gboolean filter_all;
+
+ service = CAMEL_SERVICE (parent);
+ user_cache_dir = camel_service_get_user_cache_dir (service);
+
+ settings = camel_service_ref_settings (service);
+
+ g_object_get (
+ settings,
+ "filter-all", &filter_all,
+ NULL);
+
+ g_object_unref (settings);
+
+ folder = g_object_new (
+ CAMEL_TYPE_NNTP_FOLDER,
+ "display-name", folder_name,
+ "full-name", folder_name,
+ "parent-store", parent, NULL);
+ nntp_folder = (CamelNNTPFolder *) folder;
+
+ folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY;
+
+ storage_path = g_build_filename (user_cache_dir, folder_name, NULL);
+ root = g_strdup_printf ("%s.cmeta", storage_path);
+ camel_object_set_state_filename (CAMEL_OBJECT (nntp_folder), root);
+ camel_object_state_read (CAMEL_OBJECT (nntp_folder));
+ g_free (root);
+ g_free (storage_path);
+
+ folder->summary = (CamelFolderSummary *) camel_nntp_summary_new (folder);
+
+ if (filter_all || nntp_folder_get_apply_filters (nntp_folder))
+ folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
+
+ camel_folder_summary_load_from_db (folder->summary, NULL);
+
+ nntp_store = CAMEL_NNTP_STORE (parent);
+ nntp_store_summary = camel_nntp_store_ref_summary (nntp_store);
+
+ si = camel_store_summary_path (
+ CAMEL_STORE_SUMMARY (nntp_store_summary), folder_name);
+ if (si != NULL) {
+ subscribed =
+ (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
+ camel_store_summary_info_unref (
+ CAMEL_STORE_SUMMARY (nntp_store_summary), si);
+ }
+
+ camel_store_summary_connect_folder_summary (
+ CAMEL_STORE_SUMMARY (nntp_store_summary),
+ folder_name, folder->summary);
+
+ g_clear_object (&nntp_store_summary);
+
+ if (subscribed && !camel_folder_refresh_info_sync (
+ folder, cancellable, error)) {
+ g_object_unref (folder);
+ folder = NULL;
+ }
+
+ return folder;
+}
diff --git a/src/camel/providers/nntp/camel-nntp-folder.h b/src/camel/providers/nntp/camel-nntp-folder.h
new file mode 100644
index 000000000..7f8308dc1
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-folder.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-nntp-folder.h : NNTP group (folder) support.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Chris Toshok <toshok@ximian.com>
+ */
+
+#ifndef CAMEL_NNTP_FOLDER_H
+#define CAMEL_NNTP_FOLDER_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_NNTP_FOLDER \
+ (camel_nntp_folder_get_type ())
+#define CAMEL_NNTP_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_NNTP_FOLDER, CamelNNTPFolder))
+#define CAMEL_NNTP_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_NNTP_FOLDER, CamelNNTPFolderClass))
+#define CAMEL_IS_NNTP_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_NNTP_FOLDER))
+#define CAMEL_IS_NNTP_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_NNTP_FOLDER))
+#define CAMEL_NNTP_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_NNTP_FOLDER, CamelNNTPFolderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelNNTPFolder CamelNNTPFolder;
+typedef struct _CamelNNTPFolderClass CamelNNTPFolderClass;
+typedef struct _CamelNNTPFolderPrivate CamelNNTPFolderPrivate;
+
+struct _CamelNNTPFolder {
+ CamelOfflineFolder parent;
+ CamelNNTPFolderPrivate *priv;
+
+ struct _CamelFolderChangeInfo *changes;
+ CamelFolderSearch *search;
+};
+
+struct _CamelNNTPFolderClass {
+ CamelOfflineFolderClass parent;
+};
+
+GType camel_nntp_folder_get_type (void);
+CamelFolder * camel_nntp_folder_new (CamelStore *parent,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_nntp_folder_selected (CamelNNTPFolder *folder,
+ gchar *line,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_NNTP_FOLDER_H */
diff --git a/src/camel/providers/nntp/camel-nntp-private.h b/src/camel/providers/nntp/camel-nntp-private.h
new file mode 100644
index 000000000..e1df345e0
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-private.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-nntp-private.h: Private info for nntp.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_NNTP_PRIVATE_H
+#define CAMEL_NNTP_PRIVATE_H
+
+/* need a way to configure and save this data, if this header is to
+ * be installed. For now, dont install it */
+
+#include "evolution-data-server-config.h"
+
+G_BEGIN_DECLS
+
+struct _CamelNNTPFolderPrivate {
+ GMutex search_lock; /* for locking the search object */
+ GMutex cache_lock; /* for locking the cache object */
+
+ gboolean apply_filters; /* persistent property */
+};
+
+#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))
+
+G_END_DECLS
+
+#endif /* CAMEL_NNTP_PRIVATE_H */
diff --git a/src/camel/providers/nntp/camel-nntp-provider.c b/src/camel/providers/nntp/camel-nntp-provider.c
new file mode 100644
index 000000000..a2382a1ea
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-provider.c
@@ -0,0 +1,165 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-nntp-provider.c: nntp provider registration code
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors :
+ * Chris Toshok <toshok@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-nntp-store.h"
+
+static void add_hash (guint *hash, gchar *s);
+static guint nntp_url_hash (gconstpointer key);
+static gint check_equal (gchar *s1, gchar *s2);
+static gint nntp_url_equal (gconstpointer a, gconstpointer b);
+
+static CamelProviderConfEntry nntp_conf_entries[] = {
+ { CAMEL_PROVIDER_CONF_SECTION_START, "general", NULL, N_("Options") },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "filter-all", NULL,
+ N_("Apply _filters to new messages in all folders"), "0" },
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+ { CAMEL_PROVIDER_CONF_SECTION_START, "folders", NULL,
+ N_("Folders") },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "short-folder-names", NULL,
+ N_("_Show folders in short notation (e.g. c.o.linux rather "
+ "than comp.os.linux)"), "1" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "folder-hierarchy-relative", NULL,
+ N_("In the subscription _dialog, show relative folder names"), "1" },
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+ { CAMEL_PROVIDER_CONF_END }
+};
+
+CamelProviderPortEntry nntp_port_entries[] = {
+ { 119, N_("Default NNTP port"), FALSE },
+ { 563, N_("NNTP over TLS"), TRUE },
+ { 0, NULL, 0 }
+};
+
+static CamelProvider news_provider = {
+ "nntp",
+ N_("USENET news"),
+
+ N_("This is a provider for reading from and posting to "
+ "USENET newsgroups."),
+
+ "news",
+
+ CAMEL_PROVIDER_IS_REMOTE | CAMEL_PROVIDER_IS_SOURCE |
+ CAMEL_PROVIDER_IS_STORAGE | CAMEL_PROVIDER_SUPPORTS_SSL,
+
+ CAMEL_URL_NEED_HOST | CAMEL_URL_ALLOW_USER |
+ CAMEL_URL_ALLOW_PASSWORD | CAMEL_URL_ALLOW_AUTH,
+
+ nntp_conf_entries,
+
+ nntp_port_entries,
+
+ /* ... */
+};
+
+CamelServiceAuthType camel_nntp_anonymous_authtype = {
+ N_("Anonymous"),
+
+ N_("This option will connect to the NNTP server anonymously, without "
+ "authentication."),
+
+ "ANONYMOUS",
+ FALSE
+};
+
+CamelServiceAuthType camel_nntp_password_authtype = {
+ N_("Password"),
+
+ N_("This option will authenticate with the NNTP server using a "
+ "plaintext password."),
+
+ "PLAIN",
+ TRUE
+};
+
+void
+camel_provider_module_init (void)
+{
+ GList *auth_types;
+
+ auth_types = g_list_append (NULL, &camel_nntp_anonymous_authtype);
+ auth_types = g_list_append (auth_types, &camel_nntp_password_authtype);
+
+ news_provider.object_types[CAMEL_PROVIDER_STORE] = camel_nntp_store_get_type ();
+
+ news_provider.url_hash = nntp_url_hash;
+ news_provider.url_equal = nntp_url_equal;
+ news_provider.authtypes = auth_types;
+ news_provider.translation_domain = GETTEXT_PACKAGE;
+
+ camel_provider_register (&news_provider);
+}
+
+static void
+add_hash (guint *hash,
+ gchar *s)
+{
+ if (s)
+ *hash ^= g_str_hash(s);
+}
+
+static guint
+nntp_url_hash (gconstpointer key)
+{
+ const CamelURL *u = (CamelURL *) key;
+ guint hash = 0;
+
+ add_hash (&hash, u->user);
+ add_hash (&hash, u->host);
+ hash ^= u->port;
+
+ return hash;
+}
+
+static gint
+check_equal (gchar *s1,
+ gchar *s2)
+{
+ if (s1 == NULL) {
+ if (s2 == NULL)
+ return TRUE;
+ else
+ return FALSE;
+ }
+
+ if (s2 == NULL)
+ return FALSE;
+
+ return strcmp (s1, s2) == 0;
+}
+
+static gint
+nntp_url_equal (gconstpointer a,
+ gconstpointer b)
+{
+ const CamelURL *u1 = a, *u2 = b;
+
+ return check_equal (u1->protocol, u2->protocol)
+ && check_equal (u1->user, u2->user)
+ && check_equal (u1->host, u2->host)
+ && u1->port == u2->port;
+}
diff --git a/src/camel/providers/nntp/camel-nntp-resp-codes.h b/src/camel/providers/nntp/camel-nntp-resp-codes.h
new file mode 100644
index 000000000..d57ae6dc0
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-resp-codes.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-nntp-resp-codes.h : #defines for all the response codes we care about
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CAMEL_NNTP_RESP_CODES_H
+#define CAMEL_NNTP_RESP_CODES_H
+
+#define CAMEL_NNTP_OK(x) ((x) < 400)
+#define CAMEL_NNTP_ERR(x) (!CAMEL_NNTP_OK(x) && (x) < 500)
+#define CAMEL_NNTP_FAIL(x) (!CAMEL_NNTP_OK(x) && !CAMEL_NNTP_ERR(x))
+
+#define NNTP_GREETING_POSTING_OK 200
+#define NNTP_GREETING_NO_POSTING 201
+
+#define NNTP_EXTENSIONS_SUPPORTED 202
+#define NNTP_GROUP_SELECTED 211
+#define NNTP_LIST_FOLLOWS 215
+#define NNTP_ARTICLE_FOLLOWS 220
+#define NNTP_HEAD_FOLLOWS 221
+#define NNTP_DATA_FOLLOWS 224
+#define NNTP_NEW_ARTICLE_LIST_FOLLOWS 230
+#define NNTP_NEW_GROUP_LIST_FOLLOWS 231
+
+#define NNTP_NO_SUCH_GROUP 411
+#define NNTP_NO_SUCH_ARTICLE 430
+
+#define NNTP_NO_PERMISSION 502
+
+/* authentication */
+#define NNTP_AUTH_ACCEPTED 281
+#define NNTP_AUTH_CONTINUE 381
+#define NNTP_AUTH_REQUIRED 480
+#define NNTP_AUTH_REJECTED 482
+
+#define NNTP_PROTOCOL_ERROR 666
+
+#endif /* CAMEL_NNTP_RESP_CODES_H */
diff --git a/src/camel/providers/nntp/camel-nntp-settings.c b/src/camel/providers/nntp/camel-nntp-settings.c
new file mode 100644
index 000000000..94feefb13
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-settings.c
@@ -0,0 +1,391 @@
+/*
+ * camel-nntp-settings.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-nntp-settings.h"
+
+#define CAMEL_NNTP_SETTINGS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_NNTP_SETTINGS, CamelNNTPSettingsPrivate))
+
+struct _CamelNNTPSettingsPrivate {
+ gboolean filter_all;
+ gboolean folder_hierarchy_relative;
+ gboolean short_folder_names;
+};
+
+enum {
+ PROP_0,
+ PROP_AUTH_MECHANISM,
+ PROP_FILTER_ALL,
+ PROP_FOLDER_HIERARCHY_RELATIVE,
+ PROP_HOST,
+ PROP_PORT,
+ PROP_SECURITY_METHOD,
+ PROP_SHORT_FOLDER_NAMES,
+ PROP_USER
+};
+
+G_DEFINE_TYPE_WITH_CODE (
+ CamelNNTPSettings,
+ camel_nntp_settings,
+ CAMEL_TYPE_OFFLINE_SETTINGS,
+ G_IMPLEMENT_INTERFACE (
+ CAMEL_TYPE_NETWORK_SETTINGS, NULL))
+
+static void
+nntp_settings_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTH_MECHANISM:
+ camel_network_settings_set_auth_mechanism (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_FILTER_ALL:
+ camel_nntp_settings_set_filter_all (
+ CAMEL_NNTP_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_FOLDER_HIERARCHY_RELATIVE:
+ camel_nntp_settings_set_folder_hierarchy_relative (
+ CAMEL_NNTP_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_HOST:
+ camel_network_settings_set_host (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_PORT:
+ camel_network_settings_set_port (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_uint (value));
+ return;
+
+ case PROP_SECURITY_METHOD:
+ camel_network_settings_set_security_method (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_SHORT_FOLDER_NAMES:
+ camel_nntp_settings_set_short_folder_names (
+ CAMEL_NNTP_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_USER:
+ camel_network_settings_set_user (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+nntp_settings_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTH_MECHANISM:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_auth_mechanism (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_FILTER_ALL:
+ g_value_set_boolean (
+ value,
+ camel_nntp_settings_get_filter_all (
+ CAMEL_NNTP_SETTINGS (object)));
+ return;
+
+ case PROP_FOLDER_HIERARCHY_RELATIVE:
+ g_value_set_boolean (
+ value,
+ camel_nntp_settings_get_folder_hierarchy_relative (
+ CAMEL_NNTP_SETTINGS (object)));
+ return;
+
+ case PROP_HOST:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_host (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_PORT:
+ g_value_set_uint (
+ value,
+ camel_network_settings_get_port (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_SECURITY_METHOD:
+ g_value_set_enum (
+ value,
+ camel_network_settings_get_security_method (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_SHORT_FOLDER_NAMES:
+ g_value_set_boolean (
+ value,
+ camel_nntp_settings_get_short_folder_names (
+ CAMEL_NNTP_SETTINGS (object)));
+ return;
+
+ case PROP_USER:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_user (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+camel_nntp_settings_class_init (CamelNNTPSettingsClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelNNTPSettingsPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = nntp_settings_set_property;
+ object_class->get_property = nntp_settings_get_property;
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_AUTH_MECHANISM,
+ "auth-mechanism");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FOLDER_HIERARCHY_RELATIVE,
+ g_param_spec_boolean (
+ "folder-hierarchy-relative",
+ "Folder Hierarchy Relative",
+ "Show relative folder names when subscribing",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_HOST,
+ "host");
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_PORT,
+ "port");
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_SECURITY_METHOD,
+ "security-method");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SHORT_FOLDER_NAMES,
+ g_param_spec_boolean (
+ "short-folder-names",
+ "Short Folder Names",
+ "Use shortened folder names",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_USER,
+ "user");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FILTER_ALL,
+ g_param_spec_boolean (
+ "filter-all",
+ "Filter All",
+ "Whether to apply filters in all folders",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_nntp_settings_init (CamelNNTPSettings *settings)
+{
+ settings->priv = CAMEL_NNTP_SETTINGS_GET_PRIVATE (settings);
+}
+
+/**
+ * camel_nntp_settings_get_filter_all:
+ * @settings: a #CamelNNTPSettings
+ *
+ * Returns whether apply filters in all folders.
+ *
+ * Returns: whether to apply filters in all folders
+ *
+ * Since: 3.4
+ **/
+gboolean
+camel_nntp_settings_get_filter_all (CamelNNTPSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_NNTP_SETTINGS (settings), FALSE);
+
+ return settings->priv->filter_all;
+}
+
+/**
+ * camel_nntp_settings_set_filter_all:
+ * @settings: a #CamelNNTPSettings
+ * @filter_all: whether to apply filters in all folders
+ *
+ * Sets whether to apply filters in all folders.
+ *
+ * Since: 3.4
+ **/
+void
+camel_nntp_settings_set_filter_all (CamelNNTPSettings *settings,
+ gboolean filter_all)
+{
+ g_return_if_fail (CAMEL_IS_NNTP_SETTINGS (settings));
+
+ if (settings->priv->filter_all == filter_all)
+ return;
+
+ settings->priv->filter_all = filter_all;
+
+ g_object_notify (G_OBJECT (settings), "filter-all");
+}
+
+/**
+ * camel_nntp_settings_get_folder_hierarchy_relative:
+ * @settings: a #CamelNNTPSettings
+ *
+ * Returns whether to show relative folder names when allowing users to
+ * subscribe to folders. Since newsgroup folder names reveal the absolute
+ * path to the folder (e.g. comp.os.linux), displaying the full folder name
+ * in a complete hierarchical listing of the news server is redundant, but
+ * possibly harder to read.
+ *
+ * Returns: whether to show relative folder names
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_nntp_settings_get_folder_hierarchy_relative (CamelNNTPSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_NNTP_SETTINGS (settings), FALSE);
+
+ return settings->priv->folder_hierarchy_relative;
+}
+
+/**
+ * camel_nntp_settings_set_folder_hierarchy_relative:
+ * @settings: a #CamelNNTPSettings
+ * @folder_hierarchy_relative: whether to show relative folder names
+ *
+ * Sets whether to show relative folder names when allowing users to
+ * subscribe to folders. Since newsgroup folder names reveal the absolute
+ * path to the folder (e.g. comp.os.linux), displaying the full folder name
+ * in a complete hierarchical listing of the news server is redundant, but
+ * possibly harder to read.
+ *
+ * Since: 3.2
+ **/
+void
+camel_nntp_settings_set_folder_hierarchy_relative (CamelNNTPSettings *settings,
+ gboolean folder_hierarchy_relative)
+{
+ g_return_if_fail (CAMEL_IS_NNTP_SETTINGS (settings));
+
+ if (settings->priv->folder_hierarchy_relative == folder_hierarchy_relative)
+ return;
+
+ settings->priv->folder_hierarchy_relative = folder_hierarchy_relative;
+
+ g_object_notify (G_OBJECT (settings), "folder-hierarchy-relative");
+}
+
+/**
+ * camel_nntp_settings_get_short_folder_names:
+ * @settings: a #CamelNNTPSettings
+ *
+ * Returns whether to use shortened folder names (e.g. c.o.linux rather
+ * than comp.os.linux).
+ *
+ * Returns: whether to show shortened folder names
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_nntp_settings_get_short_folder_names (CamelNNTPSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_NNTP_SETTINGS (settings), FALSE);
+
+ return settings->priv->short_folder_names;
+}
+
+/**
+ * camel_nntp_settings_set_short_folder_names:
+ * @settings: a #CamelNNTPSettings
+ * @short_folder_names: whether to show shortened folder names
+ *
+ * Sets whether to show shortened folder names (e.g. c.o.linux rather than
+ * comp.os.linux).
+ *
+ * Since: 3.2
+ **/
+void
+camel_nntp_settings_set_short_folder_names (CamelNNTPSettings *settings,
+ gboolean short_folder_names)
+{
+ g_return_if_fail (CAMEL_IS_NNTP_SETTINGS (settings));
+
+ if (settings->priv->short_folder_names == short_folder_names)
+ return;
+
+ settings->priv->short_folder_names = short_folder_names;
+
+ g_object_notify (G_OBJECT (settings), "short-folder-names");
+}
+
diff --git a/src/camel/providers/nntp/camel-nntp-settings.h b/src/camel/providers/nntp/camel-nntp-settings.h
new file mode 100644
index 000000000..c934456a1
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-settings.h
@@ -0,0 +1,77 @@
+/*
+ * camel-nntp-settings.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_NNTP_SETTINGS_H
+#define CAMEL_NNTP_SETTINGS_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_NNTP_SETTINGS \
+ (camel_nntp_settings_get_type ())
+#define CAMEL_NNTP_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_NNTP_SETTINGS, CamelNNTPSettings))
+#define CAMEL_NNTP_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_NNTP_SETTINGS, CamelNNTPSettingsClass))
+#define CAMEL_IS_NNTP_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_NNTP_SETTINGS))
+#define CAMEL_IS_NNTP_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_NNTP_SETTINGS))
+#define CAMEL_NNTP_SETTINGS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_NNTP_SETTINGS, CamelNNTPSettingsClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelNNTPSettings CamelNNTPSettings;
+typedef struct _CamelNNTPSettingsClass CamelNNTPSettingsClass;
+typedef struct _CamelNNTPSettingsPrivate CamelNNTPSettingsPrivate;
+
+struct _CamelNNTPSettings {
+ CamelOfflineSettings parent;
+ CamelNNTPSettingsPrivate *priv;
+};
+
+struct _CamelNNTPSettingsClass {
+ CamelOfflineSettingsClass parent_class;
+};
+
+GType camel_nntp_settings_get_type
+ (void) G_GNUC_CONST;
+gboolean camel_nntp_settings_get_filter_all
+ (CamelNNTPSettings *settings);
+void camel_nntp_settings_set_filter_all
+ (CamelNNTPSettings *settings,
+ gboolean filter_all);
+gboolean camel_nntp_settings_get_folder_hierarchy_relative
+ (CamelNNTPSettings *settings);
+void camel_nntp_settings_set_folder_hierarchy_relative
+ (CamelNNTPSettings *settings,
+ gboolean folder_hierarchy_relative);
+gboolean camel_nntp_settings_get_short_folder_names
+ (CamelNNTPSettings *settings);
+void camel_nntp_settings_set_short_folder_names
+ (CamelNNTPSettings *settings,
+ gboolean short_folder_names);
+
+G_END_DECLS
+
+#endif /* CAMEL_NNTP_SETTINGS_H */
diff --git a/src/camel/providers/nntp/camel-nntp-store-summary.c b/src/camel/providers/nntp/camel-nntp-store-summary.c
new file mode 100644
index 000000000..cacccce67
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-store-summary.c
@@ -0,0 +1,389 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "camel-nntp-store-summary.h"
+
+#define d(x)
+#define io(x) /* io debug */
+
+#define CAMEL_NNTP_STORE_SUMMARY_VERSION_0 (0)
+#define CAMEL_NNTP_STORE_SUMMARY_VERSION_1 (1)
+
+#define CAMEL_NNTP_STORE_SUMMARY_VERSION (1)
+
+static gint summary_header_load (CamelStoreSummary *, FILE *);
+static gint summary_header_save (CamelStoreSummary *, FILE *);
+
+static CamelStoreInfo * store_info_load (CamelStoreSummary *, FILE *);
+static gint store_info_save (CamelStoreSummary *, FILE *, CamelStoreInfo *);
+static void store_info_free (CamelStoreSummary *, CamelStoreInfo *);
+
+static void store_info_set_string (CamelStoreSummary *, CamelStoreInfo *, int, const gchar *);
+
+G_DEFINE_TYPE (CamelNNTPStoreSummary, camel_nntp_store_summary, CAMEL_TYPE_STORE_SUMMARY)
+
+static void
+camel_nntp_store_summary_class_init (CamelNNTPStoreSummaryClass *class)
+{
+ CamelStoreSummaryClass *store_summary_class;
+
+ store_summary_class = CAMEL_STORE_SUMMARY_CLASS (class);
+ store_summary_class->store_info_size = sizeof (CamelNNTPStoreInfo);
+ store_summary_class->summary_header_load = summary_header_load;
+ store_summary_class->summary_header_save = summary_header_save;
+ store_summary_class->store_info_load = store_info_load;
+ store_summary_class->store_info_save = store_info_save;
+ store_summary_class->store_info_free = store_info_free;
+ store_summary_class->store_info_set_string = store_info_set_string;
+}
+
+static void
+camel_nntp_store_summary_init (CamelNNTPStoreSummary *nntp_store_summary)
+{
+ nntp_store_summary->version = CAMEL_NNTP_STORE_SUMMARY_VERSION;
+
+ memset (
+ &nntp_store_summary->last_newslist, 0,
+ sizeof (nntp_store_summary->last_newslist));
+}
+
+/**
+ * camel_nntp_store_summary_new:
+ *
+ * Create a new CamelNNTPStoreSummary object.
+ *
+ * Returns: A new CamelNNTPStoreSummary widget.
+ **/
+CamelNNTPStoreSummary *
+camel_nntp_store_summary_new (void)
+{
+ return g_object_new (CAMEL_TYPE_NNTP_STORE_SUMMARY, NULL);
+}
+
+/**
+ * camel_nntp_store_summary_full_name:
+ * @s:
+ * @full_name:
+ *
+ * Retrieve a summary item by full name.
+ *
+ * The returned #CamelNNTPStoreInfo is referenced for thread-safety and should
+ * be unreferenced with camel_store_summary_info_unref() when finished with it.
+ *
+ * Returns: The summary item, or NULL if the @full_name name
+ * is not available.
+ **/
+CamelNNTPStoreInfo *
+camel_nntp_store_summary_full_name (CamelNNTPStoreSummary *s,
+ const gchar *full_name)
+{
+ CamelStoreInfo *match = NULL;
+ GPtrArray *array;
+ guint ii;
+
+ array = camel_store_summary_array (CAMEL_STORE_SUMMARY (s));
+
+ for (ii = 0; ii < array->len; ii++) {
+ CamelNNTPStoreInfo *info;
+
+ info = g_ptr_array_index (array, ii);
+
+ if (g_str_equal (info->full_name, full_name)) {
+ match = camel_store_summary_info_ref (
+ CAMEL_STORE_SUMMARY (s),
+ (CamelStoreInfo *) info);
+ break;
+ }
+ }
+
+ camel_store_summary_array_free (CAMEL_STORE_SUMMARY (s), array);
+
+ return (CamelNNTPStoreInfo *) match;
+}
+
+gchar *
+camel_nntp_store_summary_full_to_path (CamelNNTPStoreSummary *s,
+ const gchar *full_name,
+ gchar dir_sep)
+{
+ gchar *path, *p;
+ gint c;
+ const gchar *f;
+
+ if (dir_sep != '/') {
+ p = path = g_alloca (strlen (full_name) * 3 + 1);
+ f = full_name;
+ while ((c = *f++ & 0xff)) {
+ if (c == dir_sep)
+ *p++ = '/';
+ else if (c == '/' || c == '%')
+ p += sprintf (p, "%%%02X", c);
+ else
+ *p++ = c;
+ }
+ *p = 0;
+ } else
+ path = (gchar *) full_name;
+
+ return camel_utf7_utf8 (path);
+}
+
+static guint32
+hexnib (guint32 c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ else if (c >= 'A' && c <= 'Z')
+ return c - 'A' + 10;
+ else
+ return 0;
+}
+
+gchar *
+camel_nntp_store_summary_path_to_full (CamelNNTPStoreSummary *s,
+ const gchar *path,
+ gchar dir_sep)
+{
+ gchar *full, *f;
+ guint32 c, v = 0;
+ const gchar *p;
+ gint state = 0;
+ gchar *subpath, *last = NULL;
+ gsize subpath_len = 0;
+ CamelStoreInfo *si;
+
+ /* check to see if we have a subpath of path already defined */
+ subpath_len = strlen (path) + 1;
+ subpath = g_alloca (subpath_len);
+ g_strlcpy (subpath, path, subpath_len);
+ do {
+ si = camel_store_summary_path ((CamelStoreSummary *) s, subpath);
+ if (si == NULL) {
+ last = strrchr (subpath, '/');
+ if (last)
+ *last = 0;
+ }
+ } while (si == NULL && last);
+
+ /* path is already present, use the raw version we have */
+ if (si && strlen (subpath) == strlen (path)) {
+ f = g_strdup (((CamelNNTPStoreInfo *) si)->full_name);
+ camel_store_summary_info_unref ((CamelStoreSummary *) s, si);
+ return f;
+ }
+
+ f = full = g_alloca (strlen (path) * 2 + 1);
+ if (si)
+ p = path + strlen (subpath);
+ else
+ p = path;
+
+ while ((c = camel_utf8_getc ((const guchar **) &p))) {
+ switch (state) {
+ case 0:
+ if (c == '%') {
+ state = 1;
+ } else {
+ if (c == '/')
+ c = dir_sep;
+ camel_utf8_putc ((guchar **) &f, c);
+ }
+ break;
+ case 1:
+ state = 2;
+ v = hexnib (c) << 4;
+ break;
+ case 2:
+ state = 0;
+ v |= hexnib (c);
+ camel_utf8_putc ((guchar **) &f, v);
+ break;
+ }
+ }
+ camel_utf8_putc ((guchar **) &f, c);
+
+ /* merge old path part if required */
+ f = camel_utf8_utf7 (full);
+ if (si) {
+ full = g_strdup_printf ("%s%s", ((CamelNNTPStoreInfo *) si)->full_name, f);
+ g_free (f);
+ camel_store_summary_info_unref ((CamelStoreSummary *) s, si);
+ f = full;
+ }
+
+ return f;
+}
+
+CamelNNTPStoreInfo *
+camel_nntp_store_summary_add_from_full (CamelNNTPStoreSummary *s,
+ const gchar *full,
+ gchar dir_sep)
+{
+ CamelNNTPStoreInfo *info;
+ gchar *pathu8;
+ gint len;
+ gchar *full_name;
+
+ d (printf ("adding full name '%s' '%c'\n", full, dir_sep));
+
+ len = strlen (full);
+ full_name = g_alloca (len + 1);
+ g_strlcpy (full_name, full, len + 1);
+ if (full_name[len - 1] == dir_sep)
+ full_name[len - 1] = 0;
+
+ info = camel_nntp_store_summary_full_name (s, full_name);
+ if (info) {
+ camel_store_summary_info_unref ((CamelStoreSummary *) s, (CamelStoreInfo *) info);
+ d (printf (" already there\n"));
+ return info;
+ }
+
+ pathu8 = camel_nntp_store_summary_full_to_path (s, full_name, dir_sep);
+
+ info = (CamelNNTPStoreInfo *) camel_store_summary_add_from_path ((CamelStoreSummary *) s, pathu8);
+ if (info) {
+ d (printf (" '%s' -> '%s'\n", pathu8, full_name));
+ camel_store_info_set_string ((CamelStoreSummary *) s, (CamelStoreInfo *) info, CAMEL_NNTP_STORE_INFO_FULL_NAME, full_name);
+ } else {
+ d (printf (" failed\n"));
+ }
+
+ return info;
+}
+
+static gint
+summary_header_load (CamelStoreSummary *s,
+ FILE *in)
+{
+ CamelNNTPStoreSummary *is = (CamelNNTPStoreSummary *) s;
+ gint32 version, nil;
+
+ if (CAMEL_STORE_SUMMARY_CLASS (camel_nntp_store_summary_parent_class)->summary_header_load ((CamelStoreSummary *) s, in) == -1
+ || camel_file_util_decode_fixed_int32 (in, &version) == -1)
+ return -1;
+
+ is->version = version;
+
+ if (version < CAMEL_NNTP_STORE_SUMMARY_VERSION_0) {
+ g_warning ("Store summary header version too low");
+ return -1;
+ }
+
+ if (fread (is->last_newslist, 1, NNTP_DATE_SIZE, in) < NNTP_DATE_SIZE)
+ return -1;
+
+ return camel_file_util_decode_fixed_int32 (in, &nil);
+}
+
+static gint
+summary_header_save (CamelStoreSummary *s,
+ FILE *out)
+{
+ CamelNNTPStoreSummary *is = (CamelNNTPStoreSummary *) s;
+
+ /* always write as latest version */
+ if (CAMEL_STORE_SUMMARY_CLASS (camel_nntp_store_summary_parent_class)->summary_header_save ((CamelStoreSummary *) s, out) == -1
+ || camel_file_util_encode_fixed_int32 (out, CAMEL_NNTP_STORE_SUMMARY_VERSION) == -1
+ || fwrite (is->last_newslist, 1, NNTP_DATE_SIZE, out) < NNTP_DATE_SIZE
+ || camel_file_util_encode_fixed_int32 (out, 0) == -1)
+ return -1;
+
+ return 0;
+}
+
+static CamelStoreInfo *
+store_info_load (CamelStoreSummary *s,
+ FILE *in)
+{
+ CamelNNTPStoreInfo *ni;
+
+ ni = (CamelNNTPStoreInfo *) CAMEL_STORE_SUMMARY_CLASS (camel_nntp_store_summary_parent_class)->store_info_load (s, in);
+ if (ni) {
+ if (camel_file_util_decode_string (in, &ni->full_name) == -1) {
+ camel_store_summary_info_unref (s, (CamelStoreInfo *) ni);
+ return NULL;
+ }
+ if (((CamelNNTPStoreSummary *) s)->version >= CAMEL_NNTP_STORE_SUMMARY_VERSION_1) {
+ if (camel_file_util_decode_uint32 (in, &ni->first) == -1
+ || camel_file_util_decode_uint32 (in, &ni->last) == -1) {
+ camel_store_summary_info_unref (s, (CamelStoreInfo *) ni);
+ return NULL;
+ }
+ }
+ /* set the URL */
+ }
+
+ return (CamelStoreInfo *) ni;
+}
+
+static gint
+store_info_save (CamelStoreSummary *s,
+ FILE *out,
+ CamelStoreInfo *mi)
+{
+ CamelNNTPStoreInfo *isi = (CamelNNTPStoreInfo *) mi;
+
+ if (CAMEL_STORE_SUMMARY_CLASS (camel_nntp_store_summary_parent_class)->store_info_save (s, out, mi) == -1
+ || camel_file_util_encode_string (out, isi->full_name) == -1
+ || camel_file_util_encode_uint32 (out, isi->first) == -1
+ || camel_file_util_encode_uint32 (out, isi->last) == -1)
+ return -1;
+
+ return 0;
+}
+
+static void
+store_info_free (CamelStoreSummary *s,
+ CamelStoreInfo *mi)
+{
+ CamelNNTPStoreInfo *nsi = (CamelNNTPStoreInfo *) mi;
+
+ g_free (nsi->full_name);
+ CAMEL_STORE_SUMMARY_CLASS (camel_nntp_store_summary_parent_class)->store_info_free (s, mi);
+}
+
+static void
+store_info_set_string (CamelStoreSummary *s,
+ CamelStoreInfo *mi,
+ gint type,
+ const gchar *str)
+{
+ CamelNNTPStoreInfo *nsi = (CamelNNTPStoreInfo *) mi;
+
+ g_return_if_fail (mi != NULL);
+
+ switch (type) {
+ case CAMEL_NNTP_STORE_INFO_FULL_NAME:
+ d (printf ("Set full name %s -> %s\n", nsi->full_name, str));
+ g_free (nsi->full_name);
+ nsi->full_name = g_strdup (str);
+ break;
+ default:
+ CAMEL_STORE_SUMMARY_CLASS (camel_nntp_store_summary_parent_class)->store_info_set_string (s, mi, type, str);
+ break;
+ }
+}
diff --git a/src/camel/providers/nntp/camel-nntp-store-summary.h b/src/camel/providers/nntp/camel-nntp-store-summary.h
new file mode 100644
index 000000000..c7cd5a518
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-store-summary.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/* currently, this is just a straigt s/imap/nntp from the IMAP file*/
+
+#ifndef CAMEL_NNTP_STORE_SUMMARY_H
+#define CAMEL_NNTP_STORE_SUMMARY_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_NNTP_STORE_SUMMARY \
+ (camel_nntp_store_summary_get_type ())
+#define CAMEL_NNTP_STORE_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_NNTP_STORE_SUMMARY, CamelNNTPStoreSummary))
+#define CAMEL_NNTP_STORE_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_NNTP_STORE_SUMMARY, CamelNNTPStoreSummaryClass))
+#define CAMEL_IS_NNTP_STORE_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_NNTP_STORE_SUMMARY))
+#define CAMEL_IS_NNTP_STORE_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_NNTP_STORE_SUMMARY))
+#define CAMEL_NNTP_STORE_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_NNTP_STORE_SUMMARY, CamelNNTPStoreSummaryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelNNTPStoreSummary CamelNNTPStoreSummary;
+typedef struct _CamelNNTPStoreSummaryClass CamelNNTPStoreSummaryClass;
+typedef struct _CamelNNTPStoreSummaryPrivate CamelNNTPStoreSummaryPrivate;
+
+typedef struct _CamelNNTPStoreInfo CamelNNTPStoreInfo;
+
+enum {
+ CAMEL_NNTP_STORE_INFO_FULL_NAME = CAMEL_STORE_INFO_LAST,
+ CAMEL_NNTP_STORE_INFO_LAST
+};
+
+struct _CamelNNTPStoreInfo {
+ CamelStoreInfo info;
+ gchar *full_name;
+ guint32 first; /* from LIST or NEWGROUPS return */
+ guint32 last;
+};
+
+#define NNTP_DATE_SIZE 14
+
+struct _CamelNNTPStoreSummary {
+ CamelStoreSummary summary;
+ CamelNNTPStoreSummaryPrivate *priv;
+
+ /* header info */
+ guint32 version; /* version of base part of file */
+ gchar last_newslist[NNTP_DATE_SIZE];
+};
+
+struct _CamelNNTPStoreSummaryClass {
+ CamelStoreSummaryClass summary_class;
+};
+
+GType camel_nntp_store_summary_get_type
+ (void);
+CamelNNTPStoreSummary *
+ camel_nntp_store_summary_new (void);
+
+/* converts to/from utf8 canonical nasmes */
+gchar * camel_nntp_store_summary_full_to_path
+ (CamelNNTPStoreSummary *s,
+ const gchar *full_name,
+ gchar dir_sep);
+
+gchar * camel_nntp_store_summary_path_to_full
+ (CamelNNTPStoreSummary *s,
+ const gchar *path,
+ gchar dir_sep);
+gchar * camel_nntp_store_summary_dotted_to_full
+ (CamelNNTPStoreSummary *s,
+ const gchar *dotted,
+ gchar dir_sep);
+
+CamelNNTPStoreInfo *
+ camel_nntp_store_summary_full_name
+ (CamelNNTPStoreSummary *s,
+ const gchar *full_name);
+CamelNNTPStoreInfo *
+ camel_nntp_store_summary_add_from_full
+ (CamelNNTPStoreSummary *s,
+ const gchar *full_name,
+ gchar dir_sep);
+
+/* a convenience lookup function. always use this if path known */
+gchar * camel_nntp_store_summary_full_from_path
+ (CamelNNTPStoreSummary *s,
+ const gchar *path);
+
+G_END_DECLS
+
+#endif /* CAMEL_NNTP_STORE_SUMMARY_H */
diff --git a/src/camel/providers/nntp/camel-nntp-store.c b/src/camel/providers/nntp/camel-nntp-store.c
new file mode 100644
index 000000000..100dd0998
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-store.c
@@ -0,0 +1,2356 @@
+/*ed.txtcamel-unused.txt-*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Christopher Toshok <toshok@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-nntp-folder.h"
+#include "camel-nntp-private.h"
+#include "camel-nntp-resp-codes.h"
+#include "camel-nntp-settings.h"
+#include "camel-nntp-store-summary.h"
+#include "camel-nntp-store.h"
+#include "camel-nntp-summary.h"
+
+#ifdef G_OS_WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+
+#define CAMEL_NNTP_STORE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_NNTP_STORE, CamelNNTPStorePrivate))
+
+#define w(x)
+#define dd(x) (camel_debug("nntp")?(x):0)
+
+#define NNTP_PORT 119
+#define NNTPS_PORT 563
+
+#define DUMP_EXTENSIONS
+
+struct _CamelNNTPStorePrivate {
+ GMutex property_lock;
+ CamelDataCache *cache;
+ CamelNNTPStream *stream;
+ CamelNNTPStoreSummary *summary;
+ CamelNNTPCapabilities capabilities;
+ gchar *current_group;
+};
+
+enum {
+ PROP_0,
+ PROP_CONNECTABLE,
+ PROP_HOST_REACHABLE
+};
+
+static GInitableIface *parent_initable_interface;
+
+/* Forward Declarations */
+static void camel_nntp_store_initable_init (GInitableIface *iface);
+static void camel_network_service_init (CamelNetworkServiceInterface *iface);
+static void camel_subscribable_init (CamelSubscribableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ CamelNNTPStore,
+ camel_nntp_store,
+ CAMEL_TYPE_OFFLINE_STORE,
+ G_IMPLEMENT_INTERFACE (
+ G_TYPE_INITABLE,
+ camel_nntp_store_initable_init)
+ G_IMPLEMENT_INTERFACE (
+ CAMEL_TYPE_NETWORK_SERVICE,
+ camel_network_service_init)
+ G_IMPLEMENT_INTERFACE (
+ CAMEL_TYPE_SUBSCRIBABLE,
+ camel_subscribable_init))
+
+static void
+nntp_store_reset_state (CamelNNTPStore *nntp_store,
+ CamelNNTPStream *nntp_stream)
+{
+ if (nntp_stream != NULL)
+ g_object_ref (nntp_stream);
+
+ g_mutex_lock (&nntp_store->priv->property_lock);
+
+ g_clear_object (&nntp_store->priv->stream);
+ nntp_store->priv->stream = nntp_stream;
+
+ g_free (nntp_store->priv->current_group);
+ nntp_store->priv->current_group = NULL;
+
+ nntp_store->priv->capabilities = 0;
+
+ g_mutex_unlock (&nntp_store->priv->property_lock);
+}
+
+static void
+nntp_store_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONNECTABLE:
+ camel_network_service_set_connectable (
+ CAMEL_NETWORK_SERVICE (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+nntp_store_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONNECTABLE:
+ g_value_take_object (
+ value,
+ camel_network_service_ref_connectable (
+ CAMEL_NETWORK_SERVICE (object)));
+ return;
+
+ case PROP_HOST_REACHABLE:
+ g_value_set_boolean (
+ value,
+ camel_network_service_get_host_reachable (
+ CAMEL_NETWORK_SERVICE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+nntp_store_dispose (GObject *object)
+{
+ CamelNNTPStorePrivate *priv;
+
+ priv = CAMEL_NNTP_STORE_GET_PRIVATE (object);
+
+ /* Only run this the first time. */
+ if (priv->summary != NULL) {
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (object), TRUE, NULL, NULL);
+ camel_store_summary_save (
+ CAMEL_STORE_SUMMARY (priv->summary));
+ }
+
+ g_clear_object (&priv->cache);
+ g_clear_object (&priv->stream);
+ g_clear_object (&priv->summary);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_nntp_store_parent_class)->dispose (object);
+}
+
+static void
+nntp_store_finalize (GObject *object)
+{
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (object);
+ struct _xover_header *xover, *xn;
+
+ xover = nntp_store->xover;
+ while (xover) {
+ xn = xover->next;
+ g_free (xover);
+ xover = xn;
+ }
+
+ g_mutex_clear (&nntp_store->priv->property_lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_nntp_store_parent_class)->finalize (object);
+}
+
+static gint
+check_capabilities (CamelNNTPStore *nntp_store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStream *nntp_stream;
+ gint ret;
+ gchar *line;
+ guint len;
+
+ ret = camel_nntp_raw_command_auth (
+ nntp_store, cancellable, error, &line, "CAPABILITIES");
+ if (ret != 101)
+ return -1;
+
+ nntp_stream = camel_nntp_store_ref_stream (nntp_store);
+
+ ret = camel_nntp_stream_line (
+ nntp_stream, (guchar **) &line, &len,
+ cancellable, error);
+ while (ret > 0) {
+ while (len > 0 && g_ascii_isspace (*line)) {
+ line++;
+ len--;
+ }
+
+ if (len == 4 && g_ascii_strncasecmp (line, "OVER", len) == 0)
+ camel_nntp_store_add_capabilities (
+ nntp_store, CAMEL_NNTP_CAPABILITY_OVER);
+ if (len == 8 && g_ascii_strncasecmp (line, "STARTTLS", len) == 0)
+ camel_nntp_store_add_capabilities (
+ nntp_store, CAMEL_NNTP_CAPABILITY_STARTTLS);
+
+ if (len == 1 && g_ascii_strncasecmp (line, ".", len) == 0) {
+ ret = 0;
+ break;
+ }
+
+ ret = camel_nntp_stream_line (
+ nntp_stream, (guchar **) &line, &len,
+ cancellable, error);
+ }
+
+ g_clear_object (&nntp_stream);
+
+ return ret;
+}
+
+static struct {
+ const gchar *name;
+ gint type;
+} headers[] = {
+ { "subject", 0 },
+ { "from", 0 },
+ { "date", 0 },
+ { "message-id", 1 },
+ { "references", 0 },
+ { "bytes", 2 },
+};
+
+static gint
+xover_setup (CamelNNTPStore *nntp_store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStream *nntp_stream;
+ gint ret, i;
+ gchar *line;
+ guint len;
+ guchar c, *p;
+ struct _xover_header *xover, *last;
+
+ /* manual override */
+ if (nntp_store->xover || getenv ("CAMEL_NNTP_DISABLE_XOVER") != NULL)
+ return 0;
+
+ ret = camel_nntp_raw_command_auth (
+ nntp_store, cancellable, error, &line, "list overview.fmt");
+ if (ret == -1) {
+ return -1;
+ } else if (ret != 215) {
+ /* unsupported command? ignore */
+ return 0;
+ }
+
+ last = (struct _xover_header *) &nntp_store->xover;
+
+ nntp_stream = camel_nntp_store_ref_stream (nntp_store);
+
+ /* supported command */
+ ret = camel_nntp_stream_line (
+ nntp_stream, (guchar **) &line, &len,
+ cancellable, error);
+ while (ret > 0) {
+ p = (guchar *) line;
+ xover = g_malloc0 (sizeof (*xover));
+ last->next = xover;
+ last = xover;
+ while ((c = *p++)) {
+ if (c == ':') {
+ p[-1] = 0;
+ for (i = 0; i < G_N_ELEMENTS (headers); i++) {
+ if (strcmp (line, headers[i].name) == 0) {
+ xover->name = headers[i].name;
+ if (strncmp ((gchar *) p, "full", 4) == 0)
+ xover->skip = strlen (xover->name) + 1;
+ else
+ xover->skip = 0;
+ xover->type = headers[i].type;
+ break;
+ }
+ }
+ break;
+ } else {
+ p[-1] = g_ascii_tolower (c);
+ }
+ }
+
+ ret = camel_nntp_stream_line (
+ nntp_stream, (guchar **) &line, &len,
+ cancellable, error);
+ }
+
+ g_clear_object (&nntp_stream);
+
+ return ret;
+}
+
+static gboolean
+connect_to_server (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStore *nntp_store;
+ CamelNNTPStream *nntp_stream = NULL;
+ CamelNetworkSettings *network_settings;
+ CamelNetworkSecurityMethod method;
+ CamelSettings *settings;
+ CamelSession *session;
+ CamelStream *stream;
+ GIOStream *base_stream;
+ guchar *buf;
+ guint len;
+ gchar *host, *user, *mechanism;
+ gboolean success = FALSE;
+
+ nntp_store = CAMEL_NNTP_STORE (service);
+
+ session = camel_service_ref_session (service);
+ if (!session) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ return FALSE;
+ }
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ user = camel_network_settings_dup_user (network_settings);
+ method = camel_network_settings_get_security_method (network_settings);
+ mechanism = camel_network_settings_dup_auth_mechanism (network_settings);
+
+ g_object_unref (settings);
+
+ base_stream = camel_network_service_connect_sync (
+ CAMEL_NETWORK_SERVICE (service), cancellable, error);
+
+ if (base_stream == NULL)
+ goto fail;
+
+ stream = camel_stream_new (base_stream);
+ nntp_stream = camel_nntp_stream_new (stream);
+ g_object_unref (stream);
+
+ /* Read the greeting, if any. */
+ if (camel_nntp_stream_line (nntp_stream, &buf, &len, cancellable, error) == -1) {
+ g_object_unref (base_stream);
+ g_prefix_error (
+ error, _("Could not read greeting from %s: "), host);
+ goto fail;
+ }
+
+ len = strtoul ((gchar *) buf, (gchar **) &buf, 10);
+ if (len != 200 && len != 201) {
+ while (buf && g_ascii_isspace (*buf))
+ buf++;
+
+ g_object_unref (base_stream);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("NNTP server %s returned error code %d: %s"),
+ host, len, buf);
+ goto fail;
+ }
+
+ nntp_store_reset_state (nntp_store, nntp_stream);
+
+ if (method == CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT) {
+ GIOStream *tls_stream;
+
+ /* May check capabilities, but they are not set yet; as the capability command can fail,
+ try STARTTLS blindly and fail in case it'll fail too. */
+
+ buf = NULL;
+
+ if (camel_nntp_raw_command (nntp_store, cancellable, error, (gchar **) &buf, "STARTTLS") == -1) {
+ g_object_unref (base_stream);
+ g_prefix_error (
+ error,
+ _("Failed to issue STARTTLS for NNTP server %s: "),
+ host);
+ goto fail;
+ }
+
+ if (!buf || !*buf || strtoul ((gchar *) buf, (gchar **) &buf, 10) != 382) {
+ while (buf && g_ascii_isspace (*buf))
+ buf++;
+
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("NNTP server %s doesn't support STARTTLS: %s"),
+ host, (buf && *buf) ? (const gchar *) buf : _("Unknown error"));
+ goto exit;
+ }
+
+ tls_stream = camel_network_service_starttls (CAMEL_NETWORK_SERVICE (nntp_store), base_stream, error);
+
+ g_clear_object (&base_stream);
+ g_clear_object (&nntp_stream);
+
+ if (tls_stream != NULL) {
+ stream = camel_stream_new (tls_stream);
+ nntp_stream = camel_nntp_stream_new (stream);
+ g_object_unref (stream);
+ g_object_unref (tls_stream);
+
+ nntp_store_reset_state (nntp_store, nntp_stream);
+ } else {
+ g_prefix_error (
+ error,
+ _("Failed to connect to NNTP server %s in secure mode: "),
+ host);
+ goto exit;
+ }
+ }
+
+ g_clear_object (&base_stream);
+
+ /* backward compatibility, empty 'mechanism' is a non-migrated account */
+ if ((user != NULL && *user != '\0' && (!mechanism || !*mechanism)) ||
+ (mechanism && *mechanism && g_strcmp0 (mechanism, "ANONYMOUS") != 0)) {
+
+ if (!user || !*user) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Cannot authenticate without a username"));
+ goto fail;
+ }
+
+ /* XXX No SASL support. */
+ if (!camel_session_authenticate_sync (
+ session, service, NULL, cancellable, error))
+ goto fail;
+ }
+
+ /* set 'reader' mode & ignore return code, also ping the server, inn goes offline very quickly otherwise */
+ if (camel_nntp_raw_command_auth (nntp_store, cancellable, error, (gchar **) &buf, "mode reader") == -1
+ || camel_nntp_raw_command_auth (nntp_store, cancellable, error, (gchar **) &buf, "date") == -1)
+ goto fail;
+
+ if (xover_setup (nntp_store, cancellable, error) == -1)
+ goto fail;
+
+ success = TRUE;
+
+ goto exit;
+
+fail:
+ nntp_store_reset_state (nntp_store, NULL);
+
+exit:
+ g_free (host);
+ g_free (user);
+ g_free (mechanism);
+
+ g_clear_object (&session);
+ g_clear_object (&nntp_stream);
+
+ return success;
+}
+
+static gchar *
+nntp_store_get_name (CamelService *service,
+ gboolean brief)
+{
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ gchar *host;
+ gchar *name;
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+
+ g_object_unref (settings);
+
+ if (brief)
+ name = g_strdup_printf ("%s", host);
+ else
+ name = g_strdup_printf (_("USENET News via %s"), host);
+
+ g_free (host);
+
+ return name;
+}
+
+static gboolean
+nntp_store_connect_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStore *nntp_store;
+
+ /* Chain up to parent's method. */
+ if (!CAMEL_SERVICE_CLASS (camel_nntp_store_parent_class)->connect_sync (service, cancellable, error))
+ return FALSE;
+
+ nntp_store = CAMEL_NNTP_STORE (service);
+
+ if (!connect_to_server (service, cancellable, error))
+ return FALSE;
+
+ if (check_capabilities (nntp_store, cancellable, NULL) != -1)
+ return TRUE;
+
+ /* disconnect and reconnect without capability check */
+
+ nntp_store_reset_state (nntp_store, NULL);
+
+ return connect_to_server (service, cancellable, error);
+}
+
+static gboolean
+nntp_store_disconnect_sync (CamelService *service,
+ gboolean clean,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStore *nntp_store;
+ gchar *line;
+
+ nntp_store = CAMEL_NNTP_STORE (service);
+
+ if (clean)
+ camel_nntp_raw_command (
+ nntp_store, cancellable, NULL, &line, "quit");
+
+ nntp_store_reset_state (nntp_store, NULL);
+
+ /* Chain up to parent's method. */
+ return CAMEL_SERVICE_CLASS (camel_nntp_store_parent_class)->disconnect_sync (service, clean, cancellable, error);
+}
+
+extern CamelServiceAuthType camel_nntp_anonymous_authtype;
+extern CamelServiceAuthType camel_nntp_password_authtype;
+
+static CamelAuthenticationResult
+nntp_store_authenticate_sync (CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ CamelNNTPStore *store;
+ CamelAuthenticationResult result;
+ const gchar *password;
+ gchar *line = NULL;
+ gchar *user;
+ gint status;
+
+ store = CAMEL_NNTP_STORE (service);
+
+ password = camel_service_get_password (service);
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ user = camel_network_settings_dup_user (network_settings);
+
+ g_object_unref (settings);
+
+ if (!user || !*user) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Cannot authenticate without a username"));
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+
+ if (password == NULL) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Authentication password not available"));
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+
+ /* XXX Currently only authinfo user/pass is supported. */
+ status = camel_nntp_raw_command (
+ store, cancellable, error, &line,
+ "authinfo user %s", user);
+ if (status == NNTP_AUTH_CONTINUE)
+ status = camel_nntp_raw_command (
+ store, cancellable, error, &line,
+ "authinfo pass %s", password);
+
+ switch (status) {
+ case NNTP_AUTH_ACCEPTED:
+ result = CAMEL_AUTHENTICATION_ACCEPTED;
+ break;
+
+ case NNTP_AUTH_REJECTED:
+ result = CAMEL_AUTHENTICATION_REJECTED;
+ break;
+
+ default:
+ result = CAMEL_AUTHENTICATION_ERROR;
+ break;
+ }
+
+exit:
+ g_free (user);
+
+ return result;
+}
+
+static GList *
+nntp_store_query_auth_types_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GList *auth_types;
+
+ auth_types = g_list_append (NULL, &camel_nntp_anonymous_authtype);
+ auth_types = g_list_append (auth_types, &camel_nntp_password_authtype);
+
+ return auth_types;
+}
+
+/*
+ * 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 gchar *
+nntp_newsgroup_name_short (const gchar *name)
+{
+ gchar *resptr, *tmp;
+ const gchar *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)
+{
+ CamelFolderInfo *fi;
+
+ fi = camel_folder_info_new ();
+ fi->full_name = g_strdup (si->path);
+
+ if (short_notation)
+ fi->display_name = nntp_newsgroup_name_short (si->path);
+ else
+ fi->display_name = g_strdup (si->path);
+
+ fi->unread = si->unread;
+ fi->total = si->total;
+ fi->flags = si->flags;
+
+ return fi;
+}
+
+static CamelFolderInfo *
+nntp_folder_info_from_name (CamelNNTPStore *store,
+ gboolean short_notation,
+ const gchar *name)
+{
+ CamelFolderInfo *fi;
+
+ fi = camel_folder_info_new ();
+ fi->full_name = g_strdup (name);
+
+ if (short_notation)
+ fi->display_name = nntp_newsgroup_name_short (name);
+ else
+ fi->display_name = g_strdup (name);
+
+ fi->unread = -1;
+
+ return fi;
+}
+
+/* handle list/newgroups response */
+static CamelStoreInfo *
+nntp_store_info_update (CamelNNTPStore *nntp_store,
+ gchar *line,
+ gboolean is_folder_list)
+{
+ CamelNNTPStoreSummary *nntp_store_summary;
+ CamelStoreSummary *store_summary;
+ CamelNNTPStoreInfo *si, *fsi;
+ gchar *relpath, *tmp;
+ gsize relpath_len = 0;
+ guint32 last = 0, first = 0, new = 0;
+
+ tmp = strchr (line, ' ');
+ if (tmp)
+ *tmp++ = 0;
+
+ nntp_store_summary = camel_nntp_store_ref_summary (nntp_store);
+
+ store_summary = CAMEL_STORE_SUMMARY (nntp_store_summary);
+ fsi = si = (CamelNNTPStoreInfo *)
+ camel_store_summary_path (store_summary, line);
+ if (si == NULL) {
+ si = (CamelNNTPStoreInfo *)
+ camel_store_summary_info_new (store_summary);
+
+ relpath_len = strlen (line) + 2;
+ relpath = g_alloca (relpath_len);
+ g_snprintf (relpath, relpath_len, "/%s", line);
+
+ si->info.path = g_strdup (line);
+ si->full_name = g_strdup (line); /* why do we keep this? */
+
+ camel_store_summary_add (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;
+ }
+ }
+
+ dd (printf ("store info update '%s' first '%u' last '%u'\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 : (is_folder_list ? -1 : 0);
+ si->info.unread += new; /* this is a _guess_ */
+ si->last = last;
+ si->first = first;
+
+ if (fsi != NULL)
+ camel_store_summary_info_unref (store_summary, &fsi->info);
+ else /* TODO see if we really did touch it */
+ camel_store_summary_touch (store_summary);
+
+ g_clear_object (&nntp_store_summary);
+
+ return (CamelStoreInfo *) si;
+}
+
+static CamelFolderInfo *
+nntp_store_get_subscribed_folder_info (CamelNNTPStore *nntp_store,
+ const gchar *top,
+ guint flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStoreSummary *nntp_store_summary;
+ CamelStoreSummary *store_summary;
+ CamelService *service;
+ CamelSettings *settings;
+ CamelFolderInfo *first = NULL, *last = NULL, *fi = NULL;
+ GPtrArray *array;
+ gboolean short_folder_names;
+ guint ii;
+
+ /* 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;
+
+ service = CAMEL_SERVICE (nntp_store);
+
+ settings = camel_service_ref_settings (service);
+
+ short_folder_names = camel_nntp_settings_get_short_folder_names (
+ CAMEL_NNTP_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ nntp_store_summary = camel_nntp_store_ref_summary (nntp_store);
+
+ store_summary = CAMEL_STORE_SUMMARY (nntp_store_summary);
+
+ array = camel_store_summary_array (store_summary);
+
+ for (ii = 0; ii < array->len; ii++) {
+ CamelStoreInfo *si;
+
+ si = g_ptr_array_index (array, ii);
+
+ if ((si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0)
+ continue;
+
+ /* 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;
+ gchar *line;
+
+ folder = (CamelNNTPFolder *)
+ camel_store_get_folder_sync (
+ (CamelStore *) nntp_store, si->path,
+ 0, cancellable, NULL);
+ if (folder) {
+ CamelFolderChangeInfo *changes = NULL;
+
+ if (camel_nntp_command (nntp_store, cancellable, NULL, folder, &line, NULL) != -1) {
+ if (camel_folder_change_info_changed (folder->changes)) {
+ changes = folder->changes;
+ folder->changes = camel_folder_change_info_new ();
+ }
+ }
+ if (changes) {
+ camel_folder_changed (CAMEL_FOLDER (folder), changes);
+ camel_folder_change_info_free (changes);
+ }
+ g_object_unref (folder);
+ }
+ }
+
+ fi = nntp_folder_info_from_store_info (
+ nntp_store, short_folder_names, 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_array_free (store_summary, array);
+
+ g_clear_object (&nntp_store_summary);
+
+ return first;
+}
+
+static CamelFolderInfo *
+tree_insert (CamelFolderInfo *root,
+ CamelFolderInfo *last,
+ CamelFolderInfo *fi)
+{
+ CamelFolderInfo *kfi;
+
+ if (!root)
+ root = fi;
+ else if (!last) {
+ kfi = root;
+ while (kfi->next)
+ kfi = kfi->next;
+ kfi->next = fi;
+ fi->parent = kfi->parent;
+ } else {
+ if (!last->child) {
+ last->child = fi;
+ fi->parent = last;
+ } else {
+ kfi = last->child;
+ while (kfi->next)
+ kfi = kfi->next;
+ kfi->next = fi;
+ fi->parent = last;
+ }
+ }
+ return root;
+}
+/* returns new root */
+static CamelFolderInfo *
+nntp_push_to_hierarchy (CamelNNTPStore *store,
+ CamelFolderInfo *root,
+ CamelFolderInfo *pfi,
+ GHashTable *known)
+{
+ CamelFolderInfo *fi, *last = NULL, *kfi;
+ gchar *name, *dot;
+
+ g_return_val_if_fail (pfi != NULL, root);
+ g_return_val_if_fail (known != NULL, root);
+
+ name = pfi->full_name;
+ g_return_val_if_fail (name != NULL, root);
+
+ while (dot = strchr (name, '.'), dot) {
+ *dot = '\0';
+
+ kfi = g_hash_table_lookup (known, pfi->full_name);
+ if (!kfi) {
+ fi = camel_folder_info_new ();
+ fi->full_name = g_strdup (pfi->full_name);
+ fi->display_name = g_strdup (name);
+
+ fi->unread = -1;
+ fi->total = -1;
+ fi->flags =
+ CAMEL_FOLDER_NOSELECT |
+ CAMEL_FOLDER_CHILDREN;
+
+ g_hash_table_insert (known, fi->full_name, fi);
+ root = tree_insert (root, last, fi);
+ last = fi;
+ } else {
+ last = kfi;
+ }
+
+ *dot = '.';
+ name = dot + 1;
+ }
+
+ g_free (pfi->display_name);
+ pfi->display_name = g_strdup (name);
+
+ return tree_insert (root, last, pfi);
+}
+
+static gboolean
+nntp_store_path_matches_top (const gchar *path,
+ const gchar *top,
+ gint toplen)
+{
+ g_return_val_if_fail (path != NULL, FALSE);
+
+ if (toplen <= 0 || !top)
+ return TRUE;
+
+ if (strncmp (path, top, toplen) != 0) {
+ gchar *short_path;
+ gboolean matches = FALSE;
+
+ short_path = nntp_newsgroup_name_short (path);
+ if (!short_path)
+ return FALSE;
+
+ if (strncmp (short_path, top, toplen) == 0) {
+ matches = path[toplen] == 0 || path[toplen] == '.';
+ }
+
+ g_free (short_path);
+
+ return matches;
+ }
+
+ return path[toplen] == 0 || path[toplen] == '.';
+}
+
+/*
+ * get folder info, using the information in our StoreSummary
+ */
+static CamelFolderInfo *
+nntp_store_get_cached_folder_info (CamelNNTPStore *nntp_store,
+ const gchar *top,
+ guint flags,
+ GError **error)
+{
+ CamelNNTPStoreSummary *nntp_store_summary;
+ CamelStoreSummary *store_summary;
+ CamelService *service;
+ CamelSettings *settings;
+ CamelFolderInfo *first = NULL, *last = NULL, *fi = NULL;
+ GHashTable *known; /* folder name to folder info */
+ GPtrArray *array;
+ gboolean folder_hierarchy_relative;
+ gchar *tmpname;
+ gint toplen = top ? strlen (top) : 0;
+ gint subscribed_or_flag;
+ gint root_or_flag;
+ gint recursive_flag;
+ gint is_folder_list;
+ guint ii;
+
+ subscribed_or_flag =
+ (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) ? 0 : 1;
+ root_or_flag =
+ (top == NULL || top[0] == '\0') ? 1 : 0;
+ recursive_flag =
+ (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE);
+ is_folder_list =
+ (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST);
+
+ service = CAMEL_SERVICE (nntp_store);
+
+ settings = camel_service_ref_settings (service);
+
+ folder_hierarchy_relative =
+ camel_nntp_settings_get_folder_hierarchy_relative (
+ CAMEL_NNTP_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ nntp_store_summary = camel_nntp_store_ref_summary (nntp_store);
+
+ known = g_hash_table_new (g_str_hash, g_str_equal);
+
+ store_summary = CAMEL_STORE_SUMMARY (nntp_store_summary);
+
+ array = camel_store_summary_array (store_summary);
+
+ for (ii = 0; ii < array->len; ii++) {
+ CamelStoreInfo *si;
+
+ si = g_ptr_array_index (array, ii);
+
+ if ((subscribed_or_flag || (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED))
+ && (root_or_flag || nntp_store_path_matches_top (si->path, top, toplen))) {
+ if (recursive_flag || is_folder_list || strchr (si->path + toplen + 1, '.') == NULL) {
+ /* add the item */
+ fi = nntp_folder_info_from_store_info (nntp_store, FALSE, si);
+ if (!fi)
+ continue;
+ if (folder_hierarchy_relative) {
+ g_free (fi->display_name);
+ fi->display_name = g_strdup (si->path + ((toplen <= 1) ? 0 : (toplen + 1)));
+ }
+ } 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)] != '.') {
+ gchar *dot;
+ tmpname = g_strdup (si->path);
+ dot = strchr (tmpname + toplen + 1, '.');
+ if (dot)
+ *dot = '\0';
+ fi = nntp_folder_info_from_name (nntp_store, FALSE, tmpname);
+ if (!fi)
+ continue;
+
+ fi->flags |= CAMEL_FOLDER_NOSELECT;
+ if (folder_hierarchy_relative) {
+ g_free (fi->display_name);
+ fi->display_name = g_strdup (tmpname + ((toplen <= 1) ? 0 : (toplen + 1)));
+ }
+ g_free (tmpname);
+ } else {
+ continue;
+ }
+ }
+
+ if (fi->full_name && g_hash_table_lookup (known, fi->full_name)) {
+ /* a duplicate has been found above */
+ camel_folder_info_free (fi);
+ continue;
+ }
+
+ g_hash_table_insert (known, fi->full_name, fi);
+
+ if (is_folder_list) {
+ /* create a folder hierarchy rather than a flat list */
+ first = nntp_push_to_hierarchy (nntp_store, first, fi, known);
+ } else {
+ 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 */
+ break;
+ }
+ }
+
+ camel_store_summary_array_free (store_summary, array);
+
+ g_hash_table_destroy (known);
+
+ g_clear_object (&nntp_store_summary);
+
+ return first;
+}
+
+static void
+store_info_remove (gpointer key,
+ gpointer value,
+ gpointer 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);
+}
+
+/* retrieves the date from the NNTP server */
+static gboolean
+nntp_get_date (CamelNNTPStore *nntp_store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStoreSummary *nntp_store_summary;
+ guchar *line;
+ gint ret;
+ gboolean success = FALSE;
+
+ ret = camel_nntp_command (
+ nntp_store, cancellable, error, NULL,
+ (gchar **) &line, "date");
+
+ nntp_store_summary = camel_nntp_store_ref_summary (nntp_store);
+ nntp_store_summary->last_newslist[0] = 0;
+
+ if (ret == 111) {
+ const gchar *ptr;
+
+ ptr = (gchar *) line + 3;
+ while (*ptr == ' ' || *ptr == '\t')
+ ptr++;
+
+ if (strlen (ptr) == NNTP_DATE_SIZE) {
+ memcpy (nntp_store_summary->last_newslist, ptr, NNTP_DATE_SIZE);
+ success = TRUE;
+ }
+ }
+
+ g_clear_object (&nntp_store_summary);
+
+ return success;
+}
+
+static CamelFolderInfo *
+nntp_store_get_folder_info_all (CamelNNTPStore *nntp_store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStream *nntp_stream = NULL;
+ CamelNNTPStoreSummary *nntp_store_summary;
+ guint len;
+ guchar *line;
+ gint ret = -1;
+ CamelFolderInfo *fi = NULL;
+ gboolean is_folder_list = (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST) != 0;
+
+ nntp_store_summary = camel_nntp_store_ref_summary (nntp_store);
+
+ if (top == NULL)
+ top = "";
+
+ if (top == NULL || top[0] == 0) {
+ /* we may need to update */
+ if (nntp_store_summary->last_newslist[0] != 0) {
+ gchar date[14];
+ memcpy (date, nntp_store_summary->last_newslist + 2, 6); /* YYMMDDD */
+ date[6] = ' ';
+ memcpy (date + 7, nntp_store_summary->last_newslist + 8, 6); /* HHMMSS */
+ date[13] = '\0';
+
+ /* Some servers don't support date (!), so fallback if they dont */
+ if (!nntp_get_date (nntp_store, cancellable, NULL))
+ goto do_complete_list_nodate;
+
+ ret = camel_nntp_command (nntp_store, cancellable, error, NULL, (gchar **) &line, "newgroups %s", date);
+ if (ret == -1)
+ goto error;
+ else if (ret != 231) {
+ /* newgroups not supported :S so reload the complete list */
+ nntp_store_summary->last_newslist[0] = 0;
+ goto do_complete_list;
+ }
+
+ nntp_stream = camel_nntp_store_ref_stream (nntp_store);
+
+ while ((ret = camel_nntp_stream_line (nntp_stream, &line, &len, cancellable, error)) > 0)
+ nntp_store_info_update (nntp_store, (gchar *) line, is_folder_list);
+ } else {
+ CamelStoreSummary *store_summary;
+ CamelStoreInfo *si;
+ GPtrArray *array;
+ GHashTable *all;
+ guint ii;
+
+ do_complete_list:
+ /* seems we do need a complete list */
+ /* at first, we do a DATE to find out the last load occasion */
+ nntp_get_date (nntp_store, cancellable, NULL);
+ do_complete_list_nodate:
+ ret = camel_nntp_command (nntp_store, cancellable, error, NULL, (gchar **) &line, "list");
+ if (ret == -1)
+ goto error;
+ else if (ret != 215) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_INVALID,
+ _("Error retrieving newsgroups:\n\n%s"), line);
+ goto error;
+ }
+
+ all = g_hash_table_new (g_str_hash, g_str_equal);
+
+ store_summary = CAMEL_STORE_SUMMARY (nntp_store_summary);
+ array = camel_store_summary_array (store_summary);
+
+ for (ii = 0; ii < array->len; ii++) {
+ si = g_ptr_array_index (array, ii);
+ camel_store_summary_info_ref (store_summary, si);
+ g_hash_table_insert (all, si->path, si);
+ }
+
+ camel_store_summary_array_free (store_summary, array);
+
+ nntp_stream = camel_nntp_store_ref_stream (nntp_store);
+
+ while ((ret = camel_nntp_stream_line (nntp_stream, &line, &len, cancellable, error)) > 0) {
+ si = nntp_store_info_update (nntp_store, (gchar *) line, is_folder_list);
+ g_hash_table_remove (all, si->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 (
+ CAMEL_STORE_SUMMARY (nntp_store_summary));
+ }
+
+ fi = nntp_store_get_cached_folder_info (nntp_store, top, flags, error);
+
+error:
+ g_clear_object (&nntp_stream);
+ g_clear_object (&nntp_store_summary);
+
+ return fi;
+}
+
+static gboolean
+nntp_store_can_refresh_folder (CamelStore *store,
+ CamelFolderInfo *info,
+ GError **error)
+{
+ /* any nntp folder can be refreshed */
+ return TRUE;
+}
+
+static CamelFolder *
+nntp_store_get_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return camel_nntp_folder_new (
+ store, folder_name, cancellable, error);
+}
+
+static CamelFolderInfo *
+nntp_store_get_folder_info_sync (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (store);
+ CamelServiceConnectionStatus status;
+ CamelFolderInfo *first = NULL;
+
+ status = camel_service_get_connection_status (CAMEL_SERVICE (store));
+
+ dd (printf (
+ "g_f_i: fast %d subscr %d recursive %d top \"%s\"\n",
+ flags & CAMEL_STORE_FOLDER_INFO_FAST,
+ flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
+ flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE,
+ top ? top:""));
+
+ if (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) {
+ first = nntp_store_get_subscribed_folder_info (
+ nntp_store, top, flags, cancellable, error);
+ } else if (status == CAMEL_SERVICE_CONNECTED) {
+ first = nntp_store_get_folder_info_all (
+ nntp_store, top, flags, cancellable, error);
+ } else {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete "
+ "this operation"));
+ }
+
+ return first;
+}
+
+static CamelFolderInfo *
+nntp_store_create_folder_sync (CamelStore *store,
+ const gchar *parent_name,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID,
+ _("You cannot create a folder in a News store: "
+ "subscribe instead."));
+
+ return NULL;
+}
+
+static gboolean
+nntp_store_rename_folder_sync (CamelStore *store,
+ const gchar *old_name,
+ const gchar *new_name_in,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID,
+ _("You cannot rename a folder in a News store."));
+
+ return FALSE;
+}
+
+static gboolean
+nntp_store_delete_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSubscribable *subscribable;
+ CamelSubscribableInterface *iface;
+
+ subscribable = CAMEL_SUBSCRIBABLE (store);
+ iface = CAMEL_SUBSCRIBABLE_GET_INTERFACE (subscribable);
+
+ iface->unsubscribe_folder_sync (
+ subscribable, folder_name, cancellable, NULL);
+
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID,
+ _("You cannot remove a folder in a News store: "
+ "unsubscribe instead."));
+
+ return FALSE;
+}
+
+/* nntp stores part of its data in user_data_dir and part in user_cache_dir,
+ * thus check whether to migrate based on folders.db file */
+static void
+nntp_migrate_to_user_cache_dir (CamelService *service)
+{
+ const gchar *user_data_dir, *user_cache_dir;
+ gchar *udd_folders_db, *ucd_folders_db;
+
+ g_return_if_fail (service != NULL);
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+
+ user_data_dir = camel_service_get_user_data_dir (service);
+ user_cache_dir = camel_service_get_user_cache_dir (service);
+
+ g_return_if_fail (user_data_dir != NULL);
+ g_return_if_fail (user_cache_dir != NULL);
+
+ udd_folders_db = g_build_filename (user_data_dir, "folders.db", NULL);
+ ucd_folders_db = g_build_filename (user_cache_dir, "folders.db", NULL);
+
+ /* migrate only if the source directory exists and the destination doesn't */
+ if (g_file_test (udd_folders_db, G_FILE_TEST_EXISTS) &&
+ !g_file_test (ucd_folders_db, G_FILE_TEST_EXISTS)) {
+ gchar *parent_dir;
+
+ parent_dir = g_path_get_dirname (user_cache_dir);
+ g_mkdir_with_parents (parent_dir, S_IRWXU);
+ g_free (parent_dir);
+
+ if (g_rename (user_data_dir, user_cache_dir) == -1) {
+ g_debug ("%s: Failed to migrate '%s' to '%s': %s", G_STRFUNC, user_data_dir, user_cache_dir, g_strerror (errno));
+ } else if (g_mkdir_with_parents (user_data_dir, S_IRWXU) != -1) {
+ gchar *udd_ev_store_summary, *ucd_ev_store_summary;
+
+ udd_ev_store_summary = g_build_filename (user_data_dir, ".ev-store-summary", NULL);
+ ucd_ev_store_summary = g_build_filename (user_cache_dir, ".ev-store-summary", NULL);
+
+ /* return back the .ev-store-summary file, it's saved in user_data_dir */
+ if (g_rename (ucd_ev_store_summary, udd_ev_store_summary) == -1)
+ g_debug ("%s: Failed to return back '%s' to '%s': %s", G_STRFUNC, ucd_ev_store_summary, udd_ev_store_summary, g_strerror (errno));
+ }
+ }
+
+ g_free (udd_folders_db);
+ g_free (ucd_folders_db);
+}
+
+static gboolean
+nntp_store_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelDataCache *nntp_cache;
+ CamelNNTPStore *nntp_store;
+ CamelStore *store;
+ CamelService *service;
+ const gchar *user_data_dir;
+ const gchar *user_cache_dir;
+ gchar *filename;
+
+ nntp_store = CAMEL_NNTP_STORE (initable);
+ store = CAMEL_STORE (initable);
+ service = CAMEL_SERVICE (initable);
+
+ store->flags |= CAMEL_STORE_USE_CACHE_DIR;
+ nntp_migrate_to_user_cache_dir (service);
+
+ /* Chain up to parent interface's init() method. */
+ if (!parent_initable_interface->init (initable, cancellable, error))
+ return FALSE;
+
+ service = CAMEL_SERVICE (initable);
+ user_data_dir = camel_service_get_user_data_dir (service);
+ user_cache_dir = camel_service_get_user_cache_dir (service);
+
+ if (g_mkdir_with_parents (user_data_dir, S_IRWXU) == -1) {
+ g_set_error_literal (
+ error, G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ g_strerror (errno));
+ return FALSE;
+ }
+
+ filename = g_build_filename (
+ user_data_dir, ".ev-store-summary", NULL);
+ nntp_store->priv->summary = camel_nntp_store_summary_new ();
+ camel_store_summary_set_filename (
+ CAMEL_STORE_SUMMARY (nntp_store->priv->summary), filename);
+ camel_store_summary_load (
+ CAMEL_STORE_SUMMARY (nntp_store->priv->summary));
+ g_free (filename);
+
+ /* setup store-wide cache */
+ nntp_cache = camel_data_cache_new (user_cache_dir, error);
+ if (nntp_cache == NULL)
+ return FALSE;
+
+ /* Default cache expiry - 2 weeks old, or not visited in 5 days */
+ camel_data_cache_set_expire_age (nntp_cache, 60 * 60 * 24 * 14);
+ camel_data_cache_set_expire_access (nntp_cache, 60 * 60 * 24 * 5);
+
+ nntp_store->priv->cache = nntp_cache; /* takes ownership */
+
+ return TRUE;
+}
+
+static const gchar *
+nntp_store_get_service_name (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method)
+{
+ const gchar *service_name;
+
+ switch (method) {
+ case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
+ service_name = "nntps";
+ break;
+
+ default:
+ service_name = "nntp";
+ break;
+ }
+
+ return service_name;
+}
+
+static guint16
+nntp_store_get_default_port (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method)
+{
+ guint16 default_port;
+
+ switch (method) {
+ case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
+ default_port = NNTPS_PORT;
+ break;
+
+ default:
+ default_port = NNTP_PORT;
+ break;
+ }
+
+ return default_port;
+}
+
+static gboolean
+nntp_store_folder_is_subscribed (CamelSubscribable *subscribable,
+ const gchar *folder_name)
+{
+ CamelNNTPStore *nntp_store;
+ CamelNNTPStoreSummary *nntp_store_summary;
+ CamelStoreSummary *store_summary;
+ CamelStoreInfo *si;
+ gint truth = FALSE;
+
+ nntp_store = CAMEL_NNTP_STORE (subscribable);
+ nntp_store_summary = camel_nntp_store_ref_summary (nntp_store);
+
+ store_summary = CAMEL_STORE_SUMMARY (nntp_store_summary);
+ si = camel_store_summary_path (store_summary, folder_name);
+
+ if (si != NULL) {
+ truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
+ camel_store_summary_info_unref (store_summary, si);
+ }
+
+ g_clear_object (&nntp_store_summary);
+
+ return truth;
+}
+
+static gboolean
+nntp_store_subscribe_folder_sync (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStore *nntp_store;
+ CamelNNTPStoreSummary *nntp_store_summary;
+ CamelStoreSummary *store_summary;
+ CamelService *service;
+ CamelSettings *settings;
+ CamelStoreInfo *si;
+ gboolean short_folder_names;
+ gboolean success = TRUE;
+
+ service = CAMEL_SERVICE (subscribable);
+
+ settings = camel_service_ref_settings (service);
+
+ short_folder_names = camel_nntp_settings_get_short_folder_names (
+ CAMEL_NNTP_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ nntp_store = CAMEL_NNTP_STORE (subscribable);
+ nntp_store_summary = camel_nntp_store_ref_summary (nntp_store);
+
+ store_summary = CAMEL_STORE_SUMMARY (nntp_store_summary);
+ si = camel_store_summary_path (store_summary, folder_name);
+
+ if (si == NULL) {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID,
+ _("You cannot subscribe to this newsgroup:\n\n"
+ "No such newsgroup. The selected item is a "
+ "probably a parent folder."));
+ success = FALSE;
+ } else {
+ if (!(si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) {
+ CamelFolderInfo *fi;
+
+ si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+
+ fi = nntp_folder_info_from_store_info (
+ nntp_store, short_folder_names, si);
+ fi->flags |=
+ CAMEL_FOLDER_NOINFERIORS |
+ CAMEL_FOLDER_NOCHILDREN;
+
+ camel_store_summary_touch (store_summary);
+ camel_store_summary_save (store_summary);
+
+ camel_subscribable_folder_subscribed (
+ subscribable, fi);
+
+ camel_folder_info_free (fi);
+ }
+
+ camel_store_summary_info_unref (store_summary, si);
+ }
+
+ g_clear_object (&nntp_store_summary);
+
+ return success;
+}
+
+static gboolean
+nntp_store_unsubscribe_folder_sync (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStore *nntp_store;
+ CamelNNTPStoreSummary *nntp_store_summary;
+ CamelStoreSummary *store_summary;
+ CamelService *service;
+ CamelSettings *settings;
+ CamelStoreInfo *si;
+ gboolean short_folder_names;
+ gboolean success = TRUE;
+
+ service = CAMEL_SERVICE (subscribable);
+
+ settings = camel_service_ref_settings (service);
+
+ short_folder_names = camel_nntp_settings_get_short_folder_names (
+ CAMEL_NNTP_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ nntp_store = CAMEL_NNTP_STORE (subscribable);
+ nntp_store_summary = camel_nntp_store_ref_summary (nntp_store);
+
+ store_summary = CAMEL_STORE_SUMMARY (nntp_store_summary);
+ si = camel_store_summary_path (store_summary, folder_name);
+
+ if (si == NULL) {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID,
+ _("You cannot unsubscribe to this newsgroup:\n\n"
+ "newsgroup does not exist!"));
+ success = FALSE;
+ } else {
+ if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
+ CamelFolderInfo *fi;
+
+ si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+
+ fi = nntp_folder_info_from_store_info (
+ nntp_store, short_folder_names, si);
+
+ camel_store_summary_touch (store_summary);
+ camel_store_summary_save (store_summary);
+
+ camel_subscribable_folder_unsubscribed (
+ subscribable, fi);
+
+ camel_folder_info_free (fi);
+ }
+
+ camel_store_summary_info_unref (store_summary, si);
+ }
+
+ g_clear_object (&nntp_store_summary);
+
+ return success;
+}
+
+static void
+camel_nntp_store_class_init (CamelNNTPStoreClass *class)
+{
+ GObjectClass *object_class;
+ CamelServiceClass *service_class;
+ CamelStoreClass *store_class;
+
+ g_type_class_add_private (class, sizeof (CamelNNTPStorePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = nntp_store_set_property;
+ object_class->get_property = nntp_store_get_property;
+ object_class->dispose = nntp_store_dispose;
+ object_class->finalize = nntp_store_finalize;
+
+ service_class = CAMEL_SERVICE_CLASS (class);
+ service_class->settings_type = CAMEL_TYPE_NNTP_SETTINGS;
+ service_class->get_name = nntp_store_get_name;
+ service_class->connect_sync = nntp_store_connect_sync;
+ service_class->disconnect_sync = nntp_store_disconnect_sync;
+ service_class->authenticate_sync = nntp_store_authenticate_sync;
+ service_class->query_auth_types_sync = nntp_store_query_auth_types_sync;
+
+ store_class = CAMEL_STORE_CLASS (class);
+ store_class->can_refresh_folder = nntp_store_can_refresh_folder;
+ store_class->get_folder_sync = nntp_store_get_folder_sync;
+ store_class->get_folder_info_sync = nntp_store_get_folder_info_sync;
+ store_class->create_folder_sync = nntp_store_create_folder_sync;
+ store_class->delete_folder_sync = nntp_store_delete_folder_sync;
+ store_class->rename_folder_sync = nntp_store_rename_folder_sync;
+
+ /* Inherited from CamelNetworkService. */
+ g_object_class_override_property (
+ object_class,
+ PROP_CONNECTABLE,
+ "connectable");
+
+ /* Inherited from CamelNetworkService. */
+ g_object_class_override_property (
+ object_class,
+ PROP_HOST_REACHABLE,
+ "host-reachable");
+}
+
+static void
+camel_nntp_store_initable_init (GInitableIface *iface)
+{
+ parent_initable_interface = g_type_interface_peek_parent (iface);
+
+ iface->init = nntp_store_initable_init;
+}
+
+static void
+camel_network_service_init (CamelNetworkServiceInterface *iface)
+{
+ iface->get_service_name = nntp_store_get_service_name;
+ iface->get_default_port = nntp_store_get_default_port;
+}
+
+static void
+camel_subscribable_init (CamelSubscribableInterface *iface)
+{
+ iface->folder_is_subscribed = nntp_store_folder_is_subscribed;
+ iface->subscribe_folder_sync = nntp_store_subscribe_folder_sync;
+ iface->unsubscribe_folder_sync = nntp_store_unsubscribe_folder_sync;
+}
+
+static void
+camel_nntp_store_init (CamelNNTPStore *nntp_store)
+{
+ nntp_store->priv = CAMEL_NNTP_STORE_GET_PRIVATE (nntp_store);
+
+ g_mutex_init (&nntp_store->priv->property_lock);
+
+ /* Clear the default flags. We don't want a virtual Junk or Trash
+ * folder and the user can't create/delete/rename newsgroup folders. */
+ CAMEL_STORE (nntp_store)->flags = 0;
+}
+
+/**
+ * camel_nntp_store_ref_cache:
+ * @nntp_store: a #CamelNNTPStore
+ *
+ * Returns the #CamelDataCache for @nntp_store.
+ *
+ * The returned #CamelDataCache is referenced for thread-safety and must
+ * be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelDataCache
+ **/
+CamelDataCache *
+camel_nntp_store_ref_cache (CamelNNTPStore *nntp_store)
+{
+ CamelDataCache *cache = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_NNTP_STORE (nntp_store), NULL);
+
+ g_mutex_lock (&nntp_store->priv->property_lock);
+
+ if (nntp_store->priv->cache != NULL)
+ cache = g_object_ref (nntp_store->priv->cache);
+
+ g_mutex_unlock (&nntp_store->priv->property_lock);
+
+ return cache;
+}
+
+/**
+ * camel_nntp_store_ref_stream:
+ * @nntp_store: a #CamelNNTPStore
+ *
+ * Returns the #CamelNNTPStream for @nntp_store.
+ *
+ * The returned #CamelNNTPStream is referenced for thread-safety and must
+ * be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelNNTPStream
+ **/
+CamelNNTPStream *
+camel_nntp_store_ref_stream (CamelNNTPStore *nntp_store)
+{
+ CamelNNTPStream *stream = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_NNTP_STORE (nntp_store), NULL);
+
+ g_mutex_lock (&nntp_store->priv->property_lock);
+
+ if (nntp_store->priv->stream != NULL)
+ stream = g_object_ref (nntp_store->priv->stream);
+
+ g_mutex_unlock (&nntp_store->priv->property_lock);
+
+ return stream;
+}
+
+/**
+ * camel_nntp_store_ref_summary:
+ * @nntp_store: a #CamelNNTPStore
+ *
+ * Returns the #CamelNNTPStoreSummary for @nntp_store.
+ *
+ * The returned #CamelNNTPStoreSummary is referenced for thread-safety and
+ * must be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelNNTPStoreSummary
+ **/
+CamelNNTPStoreSummary *
+camel_nntp_store_ref_summary (CamelNNTPStore *nntp_store)
+{
+ CamelNNTPStoreSummary *summary = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_NNTP_STORE (nntp_store), NULL);
+
+ g_mutex_lock (&nntp_store->priv->property_lock);
+
+ if (nntp_store->priv->summary != NULL)
+ summary = g_object_ref (nntp_store->priv->summary);
+
+ g_mutex_unlock (&nntp_store->priv->property_lock);
+
+ return summary;
+}
+
+/**
+ * camel_nntp_store_get_current_group:
+ * @nntp_store: a #CamelNNTPStore
+ *
+ * Returns the currently selected newsgroup name, or %NULL if no newsgroup
+ * is selected.
+ *
+ * Returns: the currently selected newsgroup name, or %NULL
+ **/
+const gchar *
+camel_nntp_store_get_current_group (CamelNNTPStore *nntp_store)
+{
+ g_return_val_if_fail (CAMEL_IS_NNTP_STORE (nntp_store), NULL);
+
+ return nntp_store->priv->current_group;
+}
+
+/**
+ * camel_nntp_store_dup_current_group:
+ * @nntp_store: a #CamelNNTPStore
+ *
+ * Thread-safe variation of camel_nntp_store_get_current_group().
+ * Use this function when accessing @nntp_store from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated string, or %NULL
+ **/
+gchar *
+camel_nntp_store_dup_current_group (CamelNNTPStore *nntp_store)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_NNTP_STORE (nntp_store), NULL);
+
+ g_mutex_lock (&nntp_store->priv->property_lock);
+
+ protected = camel_nntp_store_get_current_group (nntp_store);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&nntp_store->priv->property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_nntp_store_set_current_group:
+ * @nntp_store: a #CamelNNTPStore
+ * @current_group: a newsgroup name
+ *
+ * Sets the name of the currently selected newsgroup.
+ **/
+void
+camel_nntp_store_set_current_group (CamelNNTPStore *nntp_store,
+ const gchar *current_group)
+{
+ g_return_if_fail (CAMEL_IS_NNTP_STORE (nntp_store));
+
+ g_mutex_lock (&nntp_store->priv->property_lock);
+
+ if (g_strcmp0 (current_group, nntp_store->priv->current_group) == 0) {
+ g_mutex_unlock (&nntp_store->priv->property_lock);
+ return;
+ }
+
+ g_free (nntp_store->priv->current_group);
+ nntp_store->priv->current_group = g_strdup (current_group);
+
+ g_mutex_unlock (&nntp_store->priv->property_lock);
+}
+
+/**
+ * camel_nntp_store_add_capabilities:
+ * @nntp_store: a #CamelNNTPStore
+ * @caps: #CamelNNTPCapabilities to add
+ *
+ * Adds @caps to the set of known capabilities for @nntp_store.
+ **/
+void
+camel_nntp_store_add_capabilities (CamelNNTPStore *nntp_store,
+ CamelNNTPCapabilities caps)
+{
+ g_return_if_fail (CAMEL_IS_NNTP_STORE (nntp_store));
+
+ g_mutex_lock (&nntp_store->priv->property_lock);
+
+ nntp_store->priv->capabilities |= caps;
+
+ g_mutex_unlock (&nntp_store->priv->property_lock);
+}
+
+/**
+ * camel_nntp_store_has_capabilities:
+ * @nntp_store: a #CamelNNTPStore
+ * @caps: #CamelNNTPCapabilities to check
+ *
+ * Returns whether the set of known capabilities for @nntp_store includes
+ * ALL the capabilities specified by @caps.
+ *
+ * Returns: %TRUE if @nntp_store includes ALL capabilities in @caps
+ **/
+gboolean
+camel_nntp_store_has_capabilities (CamelNNTPStore *nntp_store,
+ CamelNNTPCapabilities caps)
+{
+ gboolean result;
+
+ g_return_val_if_fail (CAMEL_IS_NNTP_STORE (nntp_store), FALSE);
+
+ g_mutex_lock (&nntp_store->priv->property_lock);
+
+ result = ((nntp_store->priv->capabilities & caps) == caps);
+
+ g_mutex_unlock (&nntp_store->priv->property_lock);
+
+ return result;
+}
+
+/**
+ * camel_nntp_store_remove_capabilities:
+ * @nntp_store: a #CamelNNTPStore
+ * @caps: #CamelNNTPCapabilities to remove
+ *
+ * Removes @caps from the set of known capablities for @nntp_store.
+ **/
+void
+camel_nntp_store_remove_capabilities (CamelNNTPStore *nntp_store,
+ CamelNNTPCapabilities caps)
+{
+ g_return_if_fail (CAMEL_IS_NNTP_STORE (nntp_store));
+
+ g_mutex_lock (&nntp_store->priv->property_lock);
+
+ nntp_store->priv->capabilities &= ~caps;
+
+ g_mutex_unlock (&nntp_store->priv->property_lock);
+}
+
+/* Enter owning lock */
+gint
+camel_nntp_raw_commandv (CamelNNTPStore *nntp_store,
+ GCancellable *cancellable,
+ GError **error,
+ gchar **line,
+ const gchar *fmt,
+ va_list ap)
+{
+ CamelNNTPStream *nntp_stream;
+ GString *buffer;
+ const guchar *p, *ps;
+ guchar c;
+ gchar *s;
+ gint d;
+ guint u, u2;
+
+ nntp_stream = camel_nntp_store_ref_stream (nntp_store);
+ g_return_val_if_fail (nntp_stream != NULL, -1);
+ g_return_val_if_fail (nntp_stream->mode != CAMEL_NNTP_STREAM_DATA, -1);
+
+ camel_nntp_stream_set_mode (nntp_stream, CAMEL_NNTP_STREAM_LINE);
+
+ p = (const guchar *) fmt;
+ ps = (const guchar *) p;
+
+ buffer = g_string_sized_new (256);
+
+ while ((c = *p++)) {
+ gchar *strval = NULL;
+
+ switch (c) {
+ case '%':
+ c = *p++;
+ g_string_append_len (
+ buffer, (const gchar *) ps,
+ p - ps - (c == '%' ? 1 : 2));
+ ps = p;
+ switch (c) {
+ case 's':
+ s = va_arg (ap, gchar *);
+ g_string_append (buffer, s);
+ break;
+ case 'd':
+ d = va_arg (ap, gint);
+ g_string_append_printf (buffer, "%d", d);
+ break;
+ case 'u':
+ u = va_arg (ap, guint);
+ g_string_append_printf (buffer, "%u", u);
+ break;
+ case 'm':
+ s = va_arg (ap, gchar *);
+ g_string_append_printf (buffer, "<%s>", s);
+ break;
+ case 'r':
+ u = va_arg (ap, guint);
+ u2 = va_arg (ap, guint);
+ if (u == u2)
+ g_string_append_printf (
+ buffer, "%u", u);
+ else
+ g_string_append_printf (
+ buffer, "%u-%u", u, u2);
+ break;
+ default:
+ g_warning ("Passing unknown format to nntp_command: %c\n", c);
+ }
+
+ g_free (strval);
+ strval = NULL;
+ }
+ }
+
+ g_string_append_len (buffer, (const gchar *) ps, p - ps - 1);
+ g_string_append_len (buffer, "\r\n", 2);
+
+ if (camel_stream_write (
+ CAMEL_STREAM (nntp_stream),
+ buffer->str, buffer->len,
+ cancellable, error) == -1)
+ goto ioerror;
+
+ if (camel_nntp_stream_line (nntp_stream, (guchar **) line, &u, cancellable, error) == -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 (nntp_stream, CAMEL_NNTP_STREAM_DATA);
+
+ goto exit;
+
+ioerror:
+ g_prefix_error (error, _("NNTP Command failed: "));
+ u = -1;
+
+exit:
+ g_clear_object (&nntp_stream);
+ g_string_free (buffer, TRUE);
+
+ return u;
+}
+
+gint
+camel_nntp_raw_command (CamelNNTPStore *nntp_store,
+ GCancellable *cancellable,
+ GError **error,
+ gchar **line,
+ const gchar *fmt,
+ ...)
+{
+ gint ret;
+ va_list ap;
+
+ va_start (ap, fmt);
+ ret = camel_nntp_raw_commandv (
+ nntp_store, cancellable, error, 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 */
+gint
+camel_nntp_raw_command_auth (CamelNNTPStore *nntp_store,
+ GCancellable *cancellable,
+ GError **error,
+ gchar **line,
+ const gchar *fmt,
+ ...)
+{
+ CamelService *service;
+ CamelSession *session;
+ gint ret, retry, go;
+ va_list ap;
+
+ service = CAMEL_SERVICE (nntp_store);
+ session = camel_service_ref_session (service);
+ if (!session) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ return -1;
+ }
+
+ retry = 0;
+
+ do {
+ go = FALSE;
+ retry++;
+
+ va_start (ap, fmt);
+ ret = camel_nntp_raw_commandv (
+ nntp_store, cancellable, error, line, fmt, ap);
+ va_end (ap);
+
+ if (ret == NNTP_AUTH_REQUIRED) {
+ go = camel_session_authenticate_sync (
+ session, service, NULL, cancellable, error);
+ if (!go)
+ ret = -1;
+ }
+ } while (retry < 3 && go);
+
+ g_object_unref (session);
+
+ return ret;
+}
+
+gint
+camel_nntp_command (CamelNNTPStore *nntp_store,
+ GCancellable *cancellable,
+ GError **error,
+ CamelNNTPFolder *folder,
+ gchar **line,
+ const gchar *fmt,
+ ...)
+{
+ CamelNNTPStream *nntp_stream = NULL;
+ CamelServiceConnectionStatus status;
+ CamelService *service;
+ CamelSession *session;
+ gboolean success;
+ const gchar *full_name = NULL;
+ const guchar *p;
+ va_list ap;
+ gint ret, retry;
+ guint u;
+ GError *local_error = NULL;
+
+ service = CAMEL_SERVICE (nntp_store);
+ status = camel_service_get_connection_status (service);
+
+ if (status != CAMEL_SERVICE_CONNECTED) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_NOT_CONNECTED,
+ _("Not connected."));
+ ret = -1;
+ goto exit;
+ }
+
+ if (folder != NULL)
+ full_name = camel_folder_get_full_name (CAMEL_FOLDER (folder));
+
+ retry = 0;
+ do {
+ gboolean need_group_command;
+ gchar *current_group;
+
+ retry++;
+
+ nntp_stream = camel_nntp_store_ref_stream (nntp_store);
+
+ if (nntp_stream == NULL) {
+ gboolean success;
+
+ success = camel_service_connect_sync (
+ service, cancellable, error);
+
+ if (!success) {
+ ret = -1;
+ goto exit;
+ }
+
+ /* If we successfully connected then
+ * we should obtain a CamelNNTPStream. */
+ nntp_stream = camel_nntp_store_ref_stream (nntp_store);
+ g_return_val_if_fail (nntp_stream != NULL, -1);
+ }
+
+ /* Check for unprocessed data, !*/
+ if (nntp_stream->mode == CAMEL_NNTP_STREAM_DATA) {
+ g_warning ("Unprocessed data left in stream, flushing");
+ while (camel_nntp_stream_getd (nntp_stream, (guchar **) &p, &u, cancellable, error) > 0)
+ ;
+ }
+ camel_nntp_stream_set_mode (nntp_stream, CAMEL_NNTP_STREAM_LINE);
+
+ current_group =
+ camel_nntp_store_dup_current_group (nntp_store);
+ need_group_command =
+ (full_name != NULL) &&
+ (g_strcmp0 (current_group, full_name) != 0);
+ g_free (current_group);
+
+ if (need_group_command) {
+ ret = camel_nntp_raw_command_auth (
+ nntp_store, cancellable, &local_error,
+ line, "group %s", full_name);
+ if (ret == 211) {
+ camel_nntp_store_set_current_group (
+ nntp_store, full_name);
+ if (camel_nntp_folder_selected (folder, *line, cancellable, &local_error) < 0) {
+ ret = -1;
+ goto error;
+ }
+ } else {
+ goto error;
+ }
+ }
+
+ /* dummy fmt, we just wanted to select the folder */
+ if (fmt == NULL) {
+ ret = 0;
+ goto exit;
+ }
+
+ va_start (ap, fmt);
+ ret = camel_nntp_raw_commandv (
+ nntp_store, cancellable, &local_error, line, fmt, ap);
+ va_end (ap);
+ error:
+ switch (ret) {
+ case NNTP_AUTH_REQUIRED:
+ session = camel_service_ref_session (service);
+ if (session) {
+ success = camel_session_authenticate_sync (
+ session, service, NULL, cancellable, error);
+ g_object_unref (session);
+ } else {
+ success = FALSE;
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ }
+
+ if (!success) {
+ ret = -1;
+ goto exit;
+ }
+ retry--;
+ ret = -1;
+ continue;
+ case 411: /* no such group */
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID,
+ _("No such folder: %s"), *line);
+ ret = -1;
+ goto exit;
+ 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 (?) */
+ if (camel_service_get_connection_status (service) != CAMEL_SERVICE_CONNECTING) {
+ /* Reset the cancellable, thus the disconnect attempt can succeed. */
+ if (g_cancellable_is_cancelled (cancellable))
+ g_cancellable_reset (cancellable);
+
+ camel_service_disconnect_sync (
+ service, FALSE, cancellable, NULL);
+ }
+ ret = -1;
+ continue;
+ case -1: /* i/o error */
+ if (camel_service_get_connection_status (service) != CAMEL_SERVICE_CONNECTING) {
+ /* Reset the cancellable, thus the disconnect attempt can succeed. */
+ if (g_cancellable_is_cancelled (cancellable))
+ g_cancellable_reset (cancellable);
+
+ camel_service_disconnect_sync (
+ service, FALSE, cancellable, NULL);
+ }
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED) || retry >= 3) {
+ g_propagate_error (error, local_error);
+ ret = -1;
+ goto exit;
+ }
+ g_clear_error (&local_error);
+ break;
+ }
+
+ g_clear_object (&nntp_stream);
+
+ } while (ret == -1 && retry < 3);
+
+exit:
+ g_clear_object (&nntp_stream);
+
+ return ret;
+}
diff --git a/src/camel/providers/nntp/camel-nntp-store.h b/src/camel/providers/nntp/camel-nntp-store.h
new file mode 100644
index 000000000..8050b9612
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-store.h
@@ -0,0 +1,138 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-nntp-store.h : class for an nntp store
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_NNTP_STORE_H
+#define CAMEL_NNTP_STORE_H
+
+#include <camel/camel.h>
+
+#include "camel-nntp-stream.h"
+#include "camel-nntp-store-summary.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_NNTP_STORE \
+ (camel_nntp_store_get_type ())
+#define CAMEL_NNTP_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_NNTP_STORE, CamelNNTPStore))
+#define CAMEL_NNTP_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_NNTP_STORE, CamelNNTPStoreClass))
+#define CAMEL_IS_NNTP_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_NNTP_STORE))
+#define CAMEL_IS_NNTP_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_NNTP_STORE))
+#define CAMEL_NNTP_STORE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_NNTP_STORE, CamelNNTPStoreClass))
+
+G_BEGIN_DECLS
+
+struct _CamelNNTPFolder;
+
+typedef struct _CamelNNTPStore CamelNNTPStore;
+typedef struct _CamelNNTPStoreClass CamelNNTPStoreClass;
+typedef struct _CamelNNTPStorePrivate CamelNNTPStorePrivate;
+
+typedef enum _xover_t {
+ XOVER_STRING = 0,
+ XOVER_MSGID,
+ XOVER_SIZE
+} xover_t;
+
+struct _xover_header {
+ struct _xover_header *next;
+
+ const gchar *name;
+ guint skip : 8;
+ xover_t type : 8;
+};
+
+/* names of supported capabilities on the server */
+typedef enum {
+ CAMEL_NNTP_CAPABILITY_OVER = 1 << 0, /* supports OVER command */
+ CAMEL_NNTP_CAPABILITY_STARTTLS = 1 << 1 /* supports STARTTLS */
+} CamelNNTPCapabilities;
+
+struct _CamelNNTPStore {
+ CamelOfflineStore parent;
+ CamelNNTPStorePrivate *priv;
+
+ struct _xover_header *xover;
+};
+
+struct _CamelNNTPStoreClass {
+ CamelOfflineStoreClass parent_class;
+};
+
+GType camel_nntp_store_get_type (void);
+CamelDataCache *
+ camel_nntp_store_ref_cache (CamelNNTPStore *nntp_store);
+CamelNNTPStream *
+ camel_nntp_store_ref_stream (CamelNNTPStore *nntp_store);
+CamelNNTPStoreSummary *
+ camel_nntp_store_ref_summary (CamelNNTPStore *nntp_store);
+const gchar * camel_nntp_store_get_current_group
+ (CamelNNTPStore *nntp_store);
+gchar * camel_nntp_store_dup_current_group
+ (CamelNNTPStore *nntp_store);
+void camel_nntp_store_set_current_group
+ (CamelNNTPStore *nntp_store,
+ const gchar *current_group);
+void camel_nntp_store_add_capabilities
+ (CamelNNTPStore *nntp_store,
+ CamelNNTPCapabilities caps);
+gboolean camel_nntp_store_has_capabilities
+ (CamelNNTPStore *nntp_store,
+ CamelNNTPCapabilities caps);
+void camel_nntp_store_remove_capabilities
+ (CamelNNTPStore *nntp_store,
+ CamelNNTPCapabilities caps);
+gint camel_nntp_raw_commandv (CamelNNTPStore *nntp_store,
+ GCancellable *cancellable,
+ GError **error,
+ gchar **line,
+ const gchar *fmt,
+ va_list ap);
+gint camel_nntp_raw_command (CamelNNTPStore *nntp_store,
+ GCancellable *cancellable,
+ GError **error,
+ gchar **line,
+ const gchar *fmt,
+ ...);
+gint camel_nntp_raw_command_auth (CamelNNTPStore *nntp_store,
+ GCancellable *cancellable,
+ GError **error,
+ gchar **line,
+ const gchar *fmt,
+ ...);
+gint camel_nntp_command (CamelNNTPStore *nntp_store,
+ GCancellable *cancellable,
+ GError **error,
+ struct _CamelNNTPFolder *folder,
+ gchar **line,
+ const gchar *fmt,
+ ...);
+
+G_END_DECLS
+
+#endif /* CAMEL_NNTP_STORE_H */
+
diff --git a/src/camel/providers/nntp/camel-nntp-stream.c b/src/camel/providers/nntp/camel-nntp-stream.c
new file mode 100644
index 000000000..ad5d7da45
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-stream.c
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-nntp-stream.h"
+
+#define dd(x) (camel_debug ("nntp:stream")?(x):0)
+
+#ifndef ECONNRESET
+#define ECONNRESET EIO
+#endif
+
+#define CAMEL_NNTP_STREAM_SIZE (4096)
+#define CAMEL_NNTP_STREAM_LINE_SIZE (1024) /* maximum line size */
+
+G_DEFINE_TYPE (CamelNNTPStream, camel_nntp_stream, CAMEL_TYPE_STREAM)
+
+static void
+nntp_stream_dispose (GObject *object)
+{
+ CamelNNTPStream *stream = CAMEL_NNTP_STREAM (object);
+
+ if (stream->source != NULL) {
+ g_object_unref (stream->source);
+ stream->source = NULL;
+ }
+
+ /* Chain up to parent's dispose () method. */
+ G_OBJECT_CLASS (camel_nntp_stream_parent_class)->dispose (object);
+}
+
+static void
+nntp_stream_finalize (GObject *object)
+{
+ CamelNNTPStream *stream = CAMEL_NNTP_STREAM (object);
+
+ g_free (stream->buf);
+ g_free (stream->linebuf);
+
+ /* Chain up to parent's finalize () method. */
+ G_OBJECT_CLASS (camel_nntp_stream_parent_class)->finalize (object);
+}
+
+static gint
+nntp_stream_fill (CamelNNTPStream *is,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint 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, (gchar *) is->end,
+ CAMEL_NNTP_STREAM_SIZE - (is->end - is->buf),
+ cancellable, error);
+ if (left > 0) {
+ is->end += left;
+ is->end[0] = '\n';
+ return is->end - is->ptr;
+ } else {
+ if (left == 0) {
+ errno = ECONNRESET;
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ }
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static gssize
+nntp_stream_read (CamelStream *stream,
+ gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStream *is = (CamelNNTPStream *) stream;
+ gchar *o, *oe;
+ guchar *p, *e, c;
+ gint 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 (nntp_stream_fill (is, cancellable, error) == -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;
+ 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 (nntp_stream_fill (is, cancellable, error) == -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;
+
+ return o - buffer;
+}
+
+static gssize
+nntp_stream_write (CamelStream *stream,
+ const gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStream *is = (CamelNNTPStream *) stream;
+
+ return camel_stream_write (is->source, buffer, n, cancellable, error);
+}
+
+static gint
+nntp_stream_close (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* nop? */
+ return 0;
+}
+
+static gint
+nntp_stream_flush (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* nop? */
+ return 0;
+}
+
+static gboolean
+nntp_stream_eos (CamelStream *stream)
+{
+ CamelNNTPStream *is = (CamelNNTPStream *) stream;
+
+ return is->mode != CAMEL_NNTP_STREAM_DATA;
+}
+
+static void
+camel_nntp_stream_class_init (CamelNNTPStreamClass *class)
+{
+ GObjectClass *object_class;
+ CamelStreamClass *stream_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = nntp_stream_dispose;
+ object_class->finalize = nntp_stream_finalize;
+
+ stream_class = CAMEL_STREAM_CLASS (class);
+ stream_class->read = nntp_stream_read;
+ stream_class->write = nntp_stream_write;
+ stream_class->close = nntp_stream_close;
+ stream_class->flush = nntp_stream_flush;
+ stream_class->eos = nntp_stream_eos;
+}
+
+static void
+camel_nntp_stream_init (CamelNNTPStream *is)
+{
+ /* +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;
+}
+
+CamelNNTPStream *
+camel_nntp_stream_new (CamelStream *source)
+{
+ CamelNNTPStream *nntp_stream;
+
+ nntp_stream = g_object_new (CAMEL_TYPE_NNTP_STREAM, NULL);
+ nntp_stream->source = g_object_ref (source);
+
+ return nntp_stream;
+}
+
+/* Get one line from the nntp stream */
+gint
+camel_nntp_stream_line (CamelNNTPStream *is,
+ guchar **data,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error)
+{
+ register guchar c, *p, *o, *oe;
+ gint newlen, oldlen;
+ guchar *e;
+
+ g_return_val_if_fail (is != NULL, -1);
+ g_return_val_if_fail (data != NULL, -1);
+ g_return_val_if_fail (len != NULL, -1);
+
+ 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 (nntp_stream_fill (is, cancellable, error) == -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 (nntp_stream_fill (is, cancellable, error) == -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;
+ }
+}
+
+/* returns -1 on error, 0 if last lot of data, >0 if more remaining */
+gint
+camel_nntp_stream_gets (CamelNNTPStream *is,
+ guchar **start,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint max;
+ guchar *end;
+
+ g_return_val_if_fail (is != NULL, -1);
+ g_return_val_if_fail (start != NULL, -1);
+ g_return_val_if_fail (len != NULL, -1);
+
+ *len = 0;
+
+ max = is->end - is->ptr;
+ if (max == 0) {
+ max = nntp_stream_fill (is, cancellable, error);
+ 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;
+
+ return end == NULL ? 1 : 0;
+}
+
+void
+camel_nntp_stream_set_mode (CamelNNTPStream *is,
+ camel_nntp_stream_mode_t mode)
+{
+ g_return_if_fail (is != NULL);
+
+ is->mode = mode;
+}
+
+/* returns -1 on erorr, 0 if last data, >0 if more data left */
+gint
+camel_nntp_stream_getd (CamelNNTPStream *is,
+ guchar **start,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guchar *p, *e, *s;
+ gint state;
+
+ g_return_val_if_fail (is != NULL, -1);
+ g_return_val_if_fail (start != NULL, -1);
+ g_return_val_if_fail (len != NULL, -1);
+
+ *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 (nntp_stream_fill (is, cancellable, error) == -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;
+
+ 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;
+
+ return 1;
+ }
+ }
+ state = 1;
+ break;
+ 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;
+
+ return 1;
+}
+
diff --git a/src/camel/providers/nntp/camel-nntp-stream.h b/src/camel/providers/nntp/camel-nntp-stream.h
new file mode 100644
index 000000000..a54ca16ff
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-stream.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_NNTP_STREAM_H
+#define CAMEL_NNTP_STREAM_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_NNTP_STREAM \
+ (camel_nntp_stream_get_type ())
+#define CAMEL_NNTP_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_NNTP_STREAM, CamelNNTPStream))
+#define CAMEL_NNTP_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_NNTP_STREAM, CamelNNTPStreamClass))
+#define CAMEL_IS_NNTP_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_NNTP_STREAM))
+#define CAMEL_IS_NNTP_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_NNTP_STREAM))
+#define CAMEL_NNTP_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_NNTP_STREAM, CamelNNTPStreamClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelNNTPStream CamelNNTPStream;
+typedef struct _CamelNNTPStreamClass CamelNNTPStreamClass;
+
+typedef enum {
+ CAMEL_NNTP_STREAM_LINE,
+ CAMEL_NNTP_STREAM_DATA,
+ CAMEL_NNTP_STREAM_EOD /* end of data, acts as if end of stream */
+} camel_nntp_stream_mode_t;
+
+struct _CamelNNTPStream {
+ CamelStream parent;
+
+ CamelStream *source;
+
+ camel_nntp_stream_mode_t mode;
+ gint state;
+
+ guchar *buf, *ptr, *end;
+ guchar *linebuf, *lineptr, *lineend;
+};
+
+struct _CamelNNTPStreamClass {
+ CamelStreamClass parent_class;
+};
+
+GType camel_nntp_stream_get_type (void);
+CamelNNTPStream *
+ camel_nntp_stream_new (CamelStream *source);
+void camel_nntp_stream_set_mode (CamelNNTPStream *is,
+ camel_nntp_stream_mode_t mode);
+gint camel_nntp_stream_line (CamelNNTPStream *is,
+ guchar **data,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error);
+gint camel_nntp_stream_gets (CamelNNTPStream *is,
+ guchar **start,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error);
+gint camel_nntp_stream_getd (CamelNNTPStream *is,
+ guchar **start,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_NNTP_STREAM_H */
diff --git a/src/camel/providers/nntp/camel-nntp-summary.c b/src/camel/providers/nntp/camel-nntp-summary.c
new file mode 100644
index 000000000..d4c47aa11
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-summary.c
@@ -0,0 +1,584 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-nntp-folder.h"
+#include "camel-nntp-store.h"
+#include "camel-nntp-stream.h"
+#include "camel-nntp-summary.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)
+
+#define CAMEL_NNTP_SUMMARY_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_NNTP_SUMMARY, CamelNNTPSummaryPrivate))
+
+struct _CamelNNTPSummaryPrivate {
+ gchar *uid;
+
+ struct _xover_header *xover; /* xoverview format */
+ gint xover_setup;
+};
+
+static CamelMessageInfo * message_info_new_from_header (CamelFolderSummary *, struct _camel_header_raw *);
+static gboolean summary_header_from_db (CamelFolderSummary *s, CamelFIRecord *mir);
+static CamelFIRecord * summary_header_to_db (CamelFolderSummary *s, GError **error);
+
+G_DEFINE_TYPE (CamelNNTPSummary, camel_nntp_summary, CAMEL_TYPE_FOLDER_SUMMARY)
+
+static void
+camel_nntp_summary_class_init (CamelNNTPSummaryClass *class)
+{
+ CamelFolderSummaryClass *folder_summary_class;
+
+ g_type_class_add_private (class, sizeof (CamelNNTPSummaryPrivate));
+
+ folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (class);
+ folder_summary_class->message_info_size = sizeof (CamelMessageInfoBase);
+ folder_summary_class->content_info_size = sizeof (CamelMessageContentInfo);
+ folder_summary_class->message_info_new_from_header = message_info_new_from_header;
+ folder_summary_class->summary_header_from_db = summary_header_from_db;
+ folder_summary_class->summary_header_to_db = summary_header_to_db;
+}
+
+static void
+camel_nntp_summary_init (CamelNNTPSummary *nntp_summary)
+{
+ CamelFolderSummary *summary = CAMEL_FOLDER_SUMMARY (nntp_summary);
+
+ nntp_summary->priv = CAMEL_NNTP_SUMMARY_GET_PRIVATE (nntp_summary);
+
+ /* and a unique file version */
+ summary->version += CAMEL_NNTP_SUMMARY_VERSION;
+}
+
+CamelNNTPSummary *
+camel_nntp_summary_new (CamelFolder *folder)
+{
+ CamelNNTPSummary *cns;
+
+ cns = g_object_new (CAMEL_TYPE_NNTP_SUMMARY, "folder", folder, NULL);
+
+ camel_folder_summary_set_build_content ((CamelFolderSummary *) cns, FALSE);
+
+ return cns;
+}
+
+static CamelMessageInfo *
+message_info_new_from_header (CamelFolderSummary *s,
+ struct _camel_header_raw *h)
+{
+ CamelMessageInfoBase *mi;
+ CamelNNTPSummary *cns = (CamelNNTPSummary *) s;
+
+ /* error to call without this setup */
+ if (cns->priv->uid == NULL)
+ return NULL;
+
+ mi = (CamelMessageInfoBase *) CAMEL_FOLDER_SUMMARY_CLASS (camel_nntp_summary_parent_class)->message_info_new_from_header (s, h);
+ if (mi) {
+ camel_pstring_free (mi->uid);
+ mi->uid = camel_pstring_strdup (cns->priv->uid);
+ g_free (cns->priv->uid);
+ cns->priv->uid = NULL;
+ }
+
+ return (CamelMessageInfo *) mi;
+}
+
+static gboolean
+summary_header_from_db (CamelFolderSummary *s,
+ CamelFIRecord *mir)
+{
+ CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY (s);
+ gchar *part;
+
+ if (!CAMEL_FOLDER_SUMMARY_CLASS (camel_nntp_summary_parent_class)->summary_header_from_db (s, mir))
+ return FALSE;
+
+ part = mir->bdata;
+
+ cns->version = bdata_extract_digit (&part);
+ cns->high = bdata_extract_digit (&part);
+ cns->low = bdata_extract_digit (&part);
+
+ return TRUE;
+}
+
+static CamelFIRecord *
+summary_header_to_db (CamelFolderSummary *s,
+ GError **error)
+{
+ CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY (s);
+ struct _CamelFIRecord *fir;
+
+ fir = CAMEL_FOLDER_SUMMARY_CLASS (camel_nntp_summary_parent_class)->summary_header_to_db (s, error);
+ if (!fir)
+ return NULL;
+ fir->bdata = g_strdup_printf ("%d %d %d", CAMEL_NNTP_SUMMARY_VERSION, cns->high, cns->low);
+
+ return fir;
+}
+
+/* ********************************************************************** */
+
+/* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
+static gint
+add_range_xover (CamelNNTPSummary *cns,
+ CamelNNTPStore *nntp_store,
+ guint high,
+ guint low,
+ CamelFolderChangeInfo *changes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPCapabilities capability = CAMEL_NNTP_CAPABILITY_OVER;
+ CamelNNTPStream *nntp_stream;
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelFolderSummary *s;
+ struct _camel_header_raw *headers = NULL;
+ gchar *line, *tab;
+ gchar *host;
+ guint len;
+ gint ret;
+ guint n, count, total, size;
+ gboolean folder_filter_recent;
+ struct _xover_header *xover;
+
+ s = (CamelFolderSummary *) cns;
+ folder_filter_recent = camel_folder_summary_get_folder (s) &&
+ (camel_folder_summary_get_folder (s)->folder_flags & CAMEL_FOLDER_FILTER_RECENT) != 0;
+
+ service = CAMEL_SERVICE (nntp_store);
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+
+ g_object_unref (settings);
+
+ camel_operation_push_message (
+ cancellable, _("%s: Scanning new messages"), host);
+
+ g_free (host);
+
+ if (camel_nntp_store_has_capabilities (nntp_store, capability))
+ ret = camel_nntp_raw_command_auth (
+ nntp_store, cancellable, error,
+ &line, "over %r", low, high);
+ else
+ ret = -1;
+ if (ret != 224) {
+ camel_nntp_store_remove_capabilities (nntp_store, capability);
+ ret = camel_nntp_raw_command_auth (
+ nntp_store, cancellable, error,
+ &line, "xover %r", low, high);
+ }
+
+ if (ret != 224) {
+ camel_operation_pop_message (cancellable);
+ if (ret != -1)
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Unexpected server response from xover: %s"), line);
+ return -1;
+ }
+
+ nntp_stream = camel_nntp_store_ref_stream (nntp_store);
+
+ count = 0;
+ total = high - low + 1;
+ while ((ret = camel_nntp_stream_line (nntp_stream, (guchar **) &line, &len, cancellable, error)) > 0) {
+ camel_operation_progress (cancellable, (count * 100) / total);
+ count++;
+ n = strtoul (line, &tab, 10);
+ if (*tab != '\t')
+ continue;
+ tab++;
+ xover = nntp_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) {
+ if (!camel_folder_summary_check_uid (s, cns->priv->uid)) {
+ CamelMessageInfo *mi;
+
+ mi = camel_folder_summary_info_new_from_header (s, headers);
+ ((CamelMessageInfoBase *) mi)->size = size;
+ camel_folder_summary_add (s, mi);
+
+ cns->high = n;
+ camel_folder_change_info_add_uid (changes, camel_message_info_get_uid (mi));
+ if (folder_filter_recent)
+ camel_folder_change_info_recent_uid (changes, camel_message_info_get_uid (mi));
+ }
+ }
+
+ if (cns->priv->uid) {
+ g_free (cns->priv->uid);
+ cns->priv->uid = NULL;
+ }
+
+ camel_header_raw_clear (&headers);
+ }
+
+ g_clear_object (&nntp_stream);
+
+ camel_operation_pop_message (cancellable);
+
+ return ret;
+}
+
+/* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
+static gint
+add_range_head (CamelNNTPSummary *cns,
+ CamelNNTPStore *nntp_store,
+ guint high,
+ guint low,
+ CamelFolderChangeInfo *changes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStream *nntp_stream;
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ CamelService *service;
+ CamelFolderSummary *s;
+ gint ret = -1;
+ gchar *line, *msgid;
+ guint i, n, count, total;
+ CamelMessageInfo *mi;
+ CamelMimeParser *mp;
+ gchar *host;
+ gboolean folder_filter_recent;
+
+ s = (CamelFolderSummary *) cns;
+ folder_filter_recent = camel_folder_summary_get_folder (s) &&
+ (camel_folder_summary_get_folder (s)->folder_flags & CAMEL_FOLDER_FILTER_RECENT) != 0;
+
+ mp = camel_mime_parser_new ();
+
+ service = CAMEL_SERVICE (nntp_store);
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+
+ g_object_unref (settings);
+
+ camel_operation_push_message (
+ cancellable, _("%s: Scanning new messages"), host);
+
+ g_free (host);
+
+ nntp_stream = camel_nntp_store_ref_stream (nntp_store);
+
+ count = 0;
+ total = high - low + 1;
+ for (i = low; i < high + 1; i++) {
+ camel_operation_progress (cancellable, (count * 100) / total);
+ count++;
+ ret = camel_nntp_raw_command_auth (
+ nntp_store, cancellable, error, &line, "head %u", i);
+ /* unknown article, ignore */
+ if (ret == 423)
+ continue;
+ else if (ret == -1)
+ goto error;
+ else if (ret != 221) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Unexpected server response from head: %s"),
+ line);
+ goto ioerror;
+ }
+ line += 3;
+ n = strtoul (line, &line, 10);
+ if (n != i)
+ g_warning ("retrieved message '%u' when i expected '%u'?\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);
+ if (!camel_folder_summary_check_uid (s, cns->priv->uid)) {
+ if (camel_mime_parser_init_with_stream (mp, CAMEL_STREAM (nntp_stream), error) == -1)
+ goto error;
+ mi = camel_folder_summary_info_new_from_parser (s, mp);
+ camel_folder_summary_add (s, mi);
+ 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_get_uid (mi));
+ if (folder_filter_recent)
+ camel_folder_change_info_recent_uid (changes, camel_message_info_get_uid (mi));
+ }
+ if (cns->priv->uid) {
+ g_free (cns->priv->uid);
+ cns->priv->uid = NULL;
+ }
+ }
+ }
+
+ ret = 0;
+
+error:
+ if (ret == -1) {
+ if (errno == EINTR)
+ g_set_error (
+ error, G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Cancelled"));
+ else
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Operation failed: %s"),
+ g_strerror (errno));
+ }
+
+ioerror:
+ if (cns->priv->uid) {
+ g_free (cns->priv->uid);
+ cns->priv->uid = NULL;
+ }
+ g_object_unref (mp);
+
+ g_clear_object (&nntp_stream);
+
+ camel_operation_pop_message (cancellable);
+
+ return ret;
+}
+
+/* Assumes we have the stream */
+/* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
+gint
+camel_nntp_summary_check (CamelNNTPSummary *cns,
+ CamelNNTPStore *store,
+ gchar *line,
+ CamelFolderChangeInfo *changes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelNNTPStoreSummary *nntp_store_summary;
+ CamelStoreSummary *store_summary;
+ CamelFolderSummary *s;
+ gint ret = 0, i;
+ guint n, f, l;
+ gint count;
+ gchar *folder = NULL;
+ CamelNNTPStoreInfo *si = NULL;
+ CamelStore *parent_store;
+ GList *del = NULL;
+ const gchar *full_name;
+
+ s = (CamelFolderSummary *) cns;
+
+ full_name = camel_folder_get_full_name (camel_folder_summary_get_folder (s));
+ parent_store = camel_folder_get_parent_store (camel_folder_summary_get_folder (s));
+
+ line +=3;
+ n = strtoul (line, &line, 10);
+ f = strtoul (line, &line, 10);
+ l = strtoul (line, &line, 10);
+ if (line[0] == ' ') {
+ gchar *tmp;
+ gsize tmp_len;
+
+ folder = line + 1;
+ tmp = strchr (folder, ' ');
+ if (tmp)
+ *tmp = 0;
+
+ tmp_len = strlen (folder) + 1;
+ tmp = g_alloca (tmp_len);
+ g_strlcpy (tmp, folder, tmp_len);
+ 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) {
+ CamelDataCache *nntp_cache;
+ GPtrArray *known_uids;
+
+ nntp_cache = camel_nntp_store_ref_cache (store);
+
+ known_uids = camel_folder_summary_get_array (s);
+ if (known_uids) {
+ for (i = 0; i < known_uids->len; i++) {
+ const gchar *uid;
+ const gchar *msgid;
+
+ uid = g_ptr_array_index (known_uids, i);
+ n = strtoul (uid, NULL, 10);
+
+ if (n < f || n > l) {
+ CamelMessageInfo *mi;
+
+ 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 (nntp_cache, "cache", msgid + 1, NULL);
+ camel_folder_change_info_remove_uid (changes, uid);
+ del = g_list_prepend (del, (gpointer) camel_pstring_strdup (uid));
+
+ mi = camel_folder_summary_peek_loaded (s, uid);
+ if (mi) {
+ camel_folder_summary_remove (s, mi);
+ camel_message_info_unref (mi);
+ } else {
+ camel_folder_summary_remove_uid (s, uid);
+ }
+ }
+ }
+ camel_folder_summary_free_array (known_uids);
+ }
+ cns->low = f;
+
+ g_clear_object (&nntp_cache);
+ }
+
+ camel_db_delete_uids (parent_store->cdb_w, full_name, del, NULL);
+ g_list_foreach (del, (GFunc) camel_pstring_free, NULL);
+ g_list_free (del);
+
+ 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, cancellable, error);
+ else
+ ret = add_range_head (
+ cns, store, l, cns->high + 1,
+ changes, cancellable, error);
+ }
+
+ /* TODO: not from here */
+ camel_folder_summary_touch (s);
+ camel_folder_summary_save_to_db (s, NULL);
+
+update:
+ /* update store summary if we have it */
+
+ nntp_store_summary = camel_nntp_store_ref_summary (store);
+
+ store_summary = CAMEL_STORE_SUMMARY (nntp_store_summary);
+
+ if (folder != NULL)
+ si = (CamelNNTPStoreInfo *)
+ camel_store_summary_path (store_summary, folder);
+
+ if (si != NULL) {
+ guint32 unread = 0;
+
+ count = camel_folder_summary_count (s);
+ camel_db_count_unread_message_info (
+ parent_store->cdb_r, full_name, &unread, NULL);
+
+ 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 (store_summary);
+ camel_store_summary_save (store_summary);
+ }
+ camel_store_summary_info_unref (
+ store_summary, (CamelStoreInfo *) si);
+
+ } else if (folder != NULL) {
+ g_warning ("Group '%s' not present in summary", folder);
+
+ } else {
+ g_warning ("Missing group from group response");
+ }
+
+ g_clear_object (&nntp_store_summary);
+
+ return ret;
+}
diff --git a/src/camel/providers/nntp/camel-nntp-summary.h b/src/camel/providers/nntp/camel-nntp-summary.h
new file mode 100644
index 000000000..941721afd
--- /dev/null
+++ b/src/camel/providers/nntp/camel-nntp-summary.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_NNTP_SUMMARY_H
+#define CAMEL_NNTP_SUMMARY_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_NNTP_SUMMARY \
+ (camel_nntp_summary_get_type ())
+#define CAMEL_NNTP_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_NNTP_SUMMARY, CamelNNTPSummary))
+#define CAMEL_NNTP_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_NNTP_SUMMARY, CamelNNTPSummaryClass))
+#define CAMEL_IS_NNTP_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_NNTP_SUMMARY))
+#define CAMEL_IS_NNTP_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_NNTP_SUMMARY))
+#define CAMEL_NNTP_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_NNTP_SUMMARY, CamelNNTPSummaryClass))
+
+G_BEGIN_DECLS
+
+struct _CamelNNTPStore;
+struct _CamelFolderChangeInfo;
+
+typedef struct _CamelNNTPSummary CamelNNTPSummary;
+typedef struct _CamelNNTPSummaryClass CamelNNTPSummaryClass;
+typedef struct _CamelNNTPSummaryPrivate CamelNNTPSummaryPrivate;
+
+struct _CamelNNTPSummary {
+ CamelFolderSummary parent;
+ CamelNNTPSummaryPrivate *priv;
+
+ guint32 version;
+ guint32 high, low;
+};
+
+struct _CamelNNTPSummaryClass {
+ CamelFolderSummaryClass parent_class;
+};
+
+GType camel_nntp_summary_get_type (void);
+CamelNNTPSummary *
+ camel_nntp_summary_new (CamelFolder *folder);
+gint camel_nntp_summary_check (CamelNNTPSummary *cns,
+ struct _CamelNNTPStore *store,
+ gchar *line,
+ CamelFolderChangeInfo *changes,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_NNTP_SUMMARY_H */
+
diff --git a/src/camel/providers/nntp/libcamelnntp.urls b/src/camel/providers/nntp/libcamelnntp.urls
new file mode 100644
index 000000000..dee2e70f1
--- /dev/null
+++ b/src/camel/providers/nntp/libcamelnntp.urls
@@ -0,0 +1,2 @@
+news
+nntp
diff --git a/src/camel/providers/pop3/CMakeLists.txt b/src/camel/providers/pop3/CMakeLists.txt
new file mode 100644
index 000000000..1adf8bd47
--- /dev/null
+++ b/src/camel/providers/pop3/CMakeLists.txt
@@ -0,0 +1,53 @@
+set(SOURCES
+ camel-pop3-engine.c
+ camel-pop3-engine.h
+ camel-pop3-folder.c
+ camel-pop3-folder.h
+ camel-pop3-provider.c
+ camel-pop3-settings.c
+ camel-pop3-settings.h
+ camel-pop3-stream.c
+ camel-pop3-stream.h
+ camel-pop3-store.c
+ camel-pop3-store.h
+)
+
+set(DEPENDENCIES
+ camel
+)
+
+add_library(camelpop3 MODULE ${SOURCES})
+
+add_dependencies(camelpop3
+ ${DEPENDENCIES}
+)
+
+target_compile_definitions(camelpop3 PRIVATE
+ -DG_LOG_DOMAIN=\"camel-pop3-provider\"
+)
+
+target_compile_options(camelpop3 PUBLIC
+ ${CAMEL_CFLAGS}
+)
+
+target_include_directories(camelpop3 PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/camel
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_SOURCE_DIR}/src/camel
+ ${CAMEL_INCLUDE_DIRS}
+)
+
+target_link_libraries(camelpop3
+ ${DEPENDENCIES}
+ ${CAMEL_LDFLAGS}
+)
+
+install(TARGETS camelpop3
+ DESTINATION ${camel_providerdir}
+)
+
+install(FILES libcamelpop3.urls
+ DESTINATION ${camel_providerdir}
+)
diff --git a/src/camel/providers/pop3/camel-pop3-engine.c b/src/camel/providers/pop3/camel-pop3-engine.c
new file mode 100644
index 000000000..7ac7b84d3
--- /dev/null
+++ b/src/camel/providers/pop3/camel-pop3-engine.c
@@ -0,0 +1,556 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-pop3-engine.h"
+#include "camel-pop3-stream.h"
+
+/* max 'outstanding' bytes in output stream, so we can't deadlock waiting
+ * for the server to accept our data when pipelining */
+#define CAMEL_POP3_SEND_LIMIT (1024)
+
+extern CamelServiceAuthType camel_pop3_password_authtype;
+extern CamelServiceAuthType camel_pop3_apop_authtype;
+
+#define dd(x) (camel_debug ("pop3")?(x):0)
+
+static gboolean get_capabilities (CamelPOP3Engine *pe, GCancellable *cancellable, GError **error);
+
+G_DEFINE_TYPE (CamelPOP3Engine, camel_pop3_engine, G_TYPE_OBJECT)
+
+static void
+pop3_engine_dispose (GObject *object)
+{
+ CamelPOP3Engine *engine = CAMEL_POP3_ENGINE (object);
+
+ if (engine->stream != NULL) {
+ g_object_unref (engine->stream);
+ engine->stream = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_pop3_engine_parent_class)->dispose (object);
+}
+
+static void
+pop3_engine_finalize (GObject *object)
+{
+ CamelPOP3Engine *engine = CAMEL_POP3_ENGINE (object);
+
+ /* FIXME: Also flush/free any outstanding requests, etc */
+
+ g_list_free (engine->auth);
+ g_free (engine->apop);
+
+ g_mutex_clear (&engine->busy_lock);
+ g_cond_clear (&engine->busy_cond);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_pop3_engine_parent_class)->finalize (object);
+}
+
+static void
+camel_pop3_engine_class_init (CamelPOP3EngineClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = pop3_engine_dispose;
+ object_class->finalize = pop3_engine_finalize;
+}
+
+static void
+camel_pop3_engine_init (CamelPOP3Engine *engine)
+{
+ g_queue_init (&engine->active);
+ g_queue_init (&engine->queue);
+ g_queue_init (&engine->done);
+ g_mutex_init (&engine->busy_lock);
+ g_cond_init (&engine->busy_cond);
+ engine->is_busy = FALSE;
+ engine->state = CAMEL_POP3_ENGINE_DISCONNECT;
+}
+
+static gint
+read_greeting (CamelPOP3Engine *pe,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guchar *line, *apop, *apopend;
+ guint len;
+
+ g_return_val_if_fail (pe != NULL, -1);
+
+ /* first, read the greeting */
+ if (camel_pop3_stream_line (pe->stream, &line, &len, cancellable, error) == -1
+ || strncmp ((gchar *) line, "+OK", 3) != 0)
+ return -1;
+
+ if ((apop = (guchar *) strchr ((gchar *) line + 3, '<'))
+ && (apopend = (guchar *) strchr ((gchar *) apop, '>'))) {
+ apopend[1] = 0;
+ pe->apop = g_strdup ((gchar *) apop);
+ pe->capa = CAMEL_POP3_CAP_APOP;
+ pe->auth = g_list_append (pe->auth, &camel_pop3_apop_authtype);
+ }
+
+ pe->auth = g_list_prepend (pe->auth, &camel_pop3_password_authtype);
+
+ return 0;
+}
+
+/**
+ * camel_pop3_engine_new:
+ * @source: source stream
+ * @flags: engine flags
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: optional #GError, or %NULL
+ *
+ * Returns a NULL stream. A null stream is always at eof, and
+ * always returns success for all reads and writes.
+ *
+ * Returns: the stream
+ **/
+CamelPOP3Engine *
+camel_pop3_engine_new (CamelStream *source,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelPOP3Engine *pe;
+
+ pe = g_object_new (CAMEL_TYPE_POP3_ENGINE, NULL);
+
+ pe->stream = (CamelPOP3Stream *) camel_pop3_stream_new (source);
+ pe->state = CAMEL_POP3_ENGINE_AUTH;
+ pe->flags = flags;
+
+ if (read_greeting (pe, cancellable, error) == -1 ||
+ !get_capabilities (pe, cancellable, error)) {
+ g_object_unref (pe);
+ return NULL;
+ }
+
+ return pe;
+}
+
+/**
+ * camel_pop3_engine_reget_capabilities:
+ * @engine: pop3 engine
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: optional #GError, or %NULL
+ *
+ * Regets server capabilities (needed after a STLS command is issued for example).
+ **/
+gboolean
+camel_pop3_engine_reget_capabilities (CamelPOP3Engine *engine,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_POP3_ENGINE (engine), FALSE);
+
+ return get_capabilities (engine, cancellable, error);
+}
+
+/* TODO: read implementation too?
+ * etc? */
+static struct {
+ const gchar *cap;
+ guint32 flag;
+} capa[] = {
+ { "APOP" , CAMEL_POP3_CAP_APOP },
+ { "TOP" , CAMEL_POP3_CAP_TOP },
+ { "UIDL", CAMEL_POP3_CAP_UIDL },
+ { "PIPELINING", CAMEL_POP3_CAP_PIPE },
+ { "STLS", CAMEL_POP3_CAP_STLS }, /* STARTTLS */
+};
+
+static void
+cmd_capa (CamelPOP3Engine *pe,
+ CamelPOP3Stream *stream,
+ GCancellable *cancellable,
+ GError **error,
+ gpointer data)
+{
+ guchar *line, *tok, *next;
+ guint len;
+ gint ret;
+ gint i;
+ CamelServiceAuthType *auth;
+
+ dd (printf ("cmd_capa\n"));
+
+ g_return_if_fail (pe != NULL);
+
+ do {
+ ret = camel_pop3_stream_line (stream, &line, &len, cancellable, error);
+ if (ret >= 0) {
+ if (strncmp ((gchar *) line, "SASL ", 5) == 0) {
+ tok = line + 5;
+ dd (printf ("scanning tokens '%s'\n", tok));
+ while (tok) {
+ next = (guchar *) strchr ((gchar *) tok, ' ');
+ if (next)
+ *next++ = 0;
+ auth = camel_sasl_authtype ((const gchar *) tok);
+ if (auth) {
+ dd (printf ("got auth type '%s'\n", tok));
+ pe->auth = g_list_prepend (pe->auth, auth);
+ } else {
+ dd (printf ("unsupported auth type '%s'\n", tok));
+ }
+ tok = next;
+ }
+ } else {
+ for (i = 0; i < G_N_ELEMENTS (capa); i++) {
+ if (strcmp ((gchar *) capa[i].cap, (gchar *) line) == 0)
+ pe->capa |= capa[i].flag;
+ }
+ }
+ }
+ } while (ret > 0);
+}
+
+static gboolean
+get_capabilities (CamelPOP3Engine *pe,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelPOP3Command *pc;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (pe != NULL, FALSE);
+
+ if (!(pe->flags & CAMEL_POP3_ENGINE_DISABLE_EXTENSIONS)) {
+ if (!camel_pop3_engine_busy_lock (pe, cancellable, error))
+ return FALSE;
+
+ pc = camel_pop3_engine_command_new (pe, CAMEL_POP3_COMMAND_MULTI, cmd_capa, NULL, cancellable, &local_error, "CAPA\r\n");
+ while (camel_pop3_engine_iterate (pe, pc, cancellable, &local_error) > 0)
+ ;
+ camel_pop3_engine_command_free (pe, pc);
+
+ if (!local_error && pe->state == CAMEL_POP3_ENGINE_TRANSACTION && !(pe->capa & CAMEL_POP3_CAP_UIDL)) {
+ /* check for UIDL support manually */
+ pc = camel_pop3_engine_command_new (pe, CAMEL_POP3_COMMAND_SIMPLE, NULL, NULL, cancellable, &local_error, "UIDL 1\r\n");
+ while (camel_pop3_engine_iterate (pe, pc, cancellable, &local_error) > 0)
+ ;
+
+ if (pc->state == CAMEL_POP3_COMMAND_OK)
+ pe->capa |= CAMEL_POP3_CAP_UIDL;
+
+ camel_pop3_engine_command_free (pe, pc);
+ }
+
+ camel_pop3_engine_busy_unlock (pe);
+ }
+
+ if (local_error) {
+ g_propagate_error (error, local_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* returns true if the command was sent, false if it was just queued */
+static gboolean
+engine_command_queue (CamelPOP3Engine *pe,
+ CamelPOP3Command *pc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (pe != NULL, FALSE);
+ g_return_val_if_fail (pc != NULL, FALSE);
+
+ if (((pe->capa & CAMEL_POP3_CAP_PIPE) == 0 || (pe->sentlen + strlen (pc->data)) > CAMEL_POP3_SEND_LIMIT)
+ && pe->current != NULL) {
+ g_queue_push_tail (&pe->queue, pc);
+ return FALSE;
+ }
+
+ /* ??? */
+ if (camel_stream_write ((CamelStream *) pe->stream, pc->data, strlen (pc->data), cancellable, error) == -1) {
+ g_queue_push_tail (&pe->queue, pc);
+ return FALSE;
+ }
+
+ pe->sentlen += strlen (pc->data);
+
+ pc->state = CAMEL_POP3_COMMAND_DISPATCHED;
+
+ if (pe->current == NULL)
+ pe->current = pc;
+ else
+ g_queue_push_tail (&pe->active, pc);
+
+ return TRUE;
+}
+
+/* returns -1 on error, 0 when no work to do, or >0 if work remaining */
+gint
+camel_pop3_engine_iterate (CamelPOP3Engine *pe,
+ CamelPOP3Command *pcwait,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guchar *p;
+ guint len;
+ CamelPOP3Command *pc;
+ GList *link;
+
+ g_return_val_if_fail (pe != NULL, -1);
+
+ if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
+ return 0;
+
+ pc = pe->current;
+ if (pc == NULL)
+ return 0;
+
+ /* LOCK */
+
+ if (camel_pop3_stream_line (pe->stream, &pe->line, &pe->linelen, cancellable, error) == -1)
+ goto ioerror;
+
+ p = pe->line;
+ switch (p[0]) {
+ case '+':
+ dd (printf ("Got + response\n"));
+ if (pc->flags & CAMEL_POP3_COMMAND_MULTI) {
+ gint fret;
+
+ pc->state = CAMEL_POP3_COMMAND_DATA;
+ camel_pop3_stream_set_mode (pe->stream, CAMEL_POP3_STREAM_DATA);
+
+ if (pc->func) {
+ GError *local_error = NULL;
+
+ pc->func (pe, pe->stream, cancellable, &local_error, pc->func_data);
+ if (local_error) {
+ pc->state = CAMEL_POP3_COMMAND_ERR;
+ pc->error_str = g_strdup (local_error->message);
+ g_propagate_error (error, local_error);
+ goto ioerror;
+ }
+ }
+
+ /* Make sure we get all data before going back to command mode */
+ while (fret = camel_pop3_stream_getd (pe->stream, &p, &len, cancellable, error), fret > 0)
+ ;
+ camel_pop3_stream_set_mode (pe->stream, CAMEL_POP3_STREAM_LINE);
+
+ if (fret < 0)
+ goto ioerror;
+ } else {
+ pc->state = CAMEL_POP3_COMMAND_OK;
+ }
+ break;
+ case '-': {
+ const gchar *text = (const gchar *) p;
+
+ pc->state = CAMEL_POP3_COMMAND_ERR;
+ pc->error_str = g_strdup (g_ascii_strncasecmp (text, "-ERR ", 5) == 0 ? text + 5 : text + 1);
+ }
+ break;
+ default:
+ /* what do we do now? f'knows! */
+ g_warning ("Bad server response: %s\n", p);
+ pc->state = CAMEL_POP3_COMMAND_ERR;
+ pc->error_str = g_strdup ((const gchar *) p + 1);
+ break;
+ }
+
+ g_queue_push_tail (&pe->done, pc);
+ pe->sentlen -= pc->data ? strlen (pc->data) : 0;
+
+ /* Set next command */
+ pe->current = g_queue_pop_head (&pe->active);
+
+ /* Check the queue for any commands we can now send also. */
+ link = g_queue_peek_head_link (&pe->queue);
+
+ while (link != NULL) {
+ pc = (CamelPOP3Command *) link->data;
+
+ if (((pe->capa & CAMEL_POP3_CAP_PIPE) == 0 || (pe->sentlen + (pc->data ? strlen (pc->data) : 0)) > CAMEL_POP3_SEND_LIMIT)
+ && pe->current != NULL)
+ break;
+
+ if (camel_stream_write ((CamelStream *) pe->stream, pc->data, pc->data ? strlen (pc->data) : 0, cancellable, error) == -1)
+ goto ioerror;
+
+ pe->sentlen += (pc->data ? strlen (pc->data) : 0);
+ pc->state = CAMEL_POP3_COMMAND_DISPATCHED;
+
+ if (pe->current == NULL)
+ pe->current = pc;
+ else
+ g_queue_push_tail (&pe->active, pc);
+
+ g_queue_delete_link (&pe->queue, link);
+ link = g_queue_peek_head_link (&pe->queue);
+ }
+
+ /* UNLOCK */
+
+ if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
+ return 0;
+
+ return pe->current == NULL ? 0 : 1;
+
+ioerror:
+ /* We assume all outstanding commands will fail. */
+
+ while ((pc = g_queue_pop_head (&pe->active)) != NULL) {
+ pc->state = CAMEL_POP3_COMMAND_ERR;
+ g_queue_push_tail (&pe->done, pc);
+ }
+
+ while ((pc = g_queue_pop_head (&pe->queue)) != NULL) {
+ pc->state = CAMEL_POP3_COMMAND_ERR;
+ g_queue_push_tail (&pe->done, pc);
+ }
+
+ if (pe->current != NULL) {
+ pe->current->state = CAMEL_POP3_COMMAND_ERR;
+ g_queue_push_tail (&pe->done, pe->current);
+ pe->current = NULL;
+ }
+
+ return -1;
+}
+
+static void
+camel_pop3_engine_wait_cancelled_cb (GCancellable *cancellable,
+ gpointer user_data)
+{
+ CamelPOP3Engine *pe = user_data;
+
+ g_return_if_fail (CAMEL_IS_POP3_ENGINE (pe));
+
+ g_mutex_lock (&pe->busy_lock);
+ g_cond_broadcast (&pe->busy_cond);
+ g_mutex_unlock (&pe->busy_lock);
+}
+
+/* Returns whether received the busy lock; if TRUE, then release it
+ with camel_pop3_engine_busy_unlock() when done with it. */
+gboolean
+camel_pop3_engine_busy_lock (CamelPOP3Engine *pe,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gulong handler_id = 0;
+ gboolean got_lock = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_POP3_ENGINE (pe), FALSE);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ if (cancellable)
+ handler_id = g_cancellable_connect (cancellable, G_CALLBACK (camel_pop3_engine_wait_cancelled_cb), pe, NULL);
+
+ g_mutex_lock (&pe->busy_lock);
+ while (pe->is_busy) {
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ break;
+
+ g_cond_wait (&pe->busy_cond, &pe->busy_lock);
+ }
+
+ if (!pe->is_busy && !g_cancellable_is_cancelled (cancellable)) {
+ pe->is_busy = TRUE;
+ got_lock = TRUE;
+ }
+
+ g_mutex_unlock (&pe->busy_lock);
+
+ if (handler_id)
+ g_cancellable_disconnect (cancellable, handler_id);
+
+ return got_lock;
+}
+
+void
+camel_pop3_engine_busy_unlock (CamelPOP3Engine *pe)
+{
+ g_return_if_fail (CAMEL_IS_POP3_ENGINE (pe));
+
+ g_mutex_lock (&pe->busy_lock);
+
+ g_warn_if_fail (pe->is_busy);
+ pe->is_busy = FALSE;
+
+ g_cond_broadcast (&pe->busy_cond);
+
+ g_mutex_unlock (&pe->busy_lock);
+}
+
+CamelPOP3Command *
+camel_pop3_engine_command_new (CamelPOP3Engine *pe,
+ guint32 flags,
+ CamelPOP3CommandFunc func,
+ gpointer data,
+ GCancellable *cancellable,
+ GError **error,
+ const gchar *fmt,
+ ...)
+{
+ CamelPOP3Command *pc;
+ va_list ap;
+
+ g_return_val_if_fail (pe != NULL, NULL);
+
+ pc = g_malloc0 (sizeof (*pc));
+ pc->func = func;
+ pc->func_data = data;
+ pc->flags = flags;
+
+ va_start (ap, fmt);
+ pc->data = g_strdup_vprintf (fmt, ap);
+ va_end (ap);
+ pc->state = CAMEL_POP3_COMMAND_IDLE;
+ pc->error_str = NULL;
+
+ /* TODO: what about write errors? */
+ engine_command_queue (pe, pc, cancellable, error);
+
+ return pc;
+}
+
+void
+camel_pop3_engine_command_free (CamelPOP3Engine *pe,
+ CamelPOP3Command *pc)
+{
+ g_return_if_fail (pc != NULL);
+
+ if (pe && pe->current != pc)
+ g_queue_remove (&pe->done, pc);
+ g_free (pc->error_str);
+ g_free (pc->data);
+ g_free (pc);
+}
diff --git a/src/camel/providers/pop3/camel-pop3-engine.h b/src/camel/providers/pop3/camel-pop3-engine.h
new file mode 100644
index 000000000..c71a464da
--- /dev/null
+++ b/src/camel/providers/pop3/camel-pop3-engine.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_POP3_ENGINE_H
+#define CAMEL_POP3_ENGINE_H
+
+#include <camel/camel.h>
+
+#include "camel-pop3-stream.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_POP3_ENGINE \
+ (camel_pop3_engine_get_type ())
+#define CAMEL_POP3_ENGINE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_POP3_ENGINE, CamelPOP3Engine))
+#define CAMEL_POP3_ENGINE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_POP3_ENGINE, CamelPOP3EngineClass))
+#define CAMEL_IS_POP3_ENGINE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_POP3_ENGINE))
+#define CAMEL_IS_POP3_ENGINE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_POP3_ENGINE))
+#define CAMEL_POP3_ENGINE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_POP3_ENGINE, CamelPOP3EngineClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelPOP3Engine CamelPOP3Engine;
+typedef struct _CamelPOP3EngineClass CamelPOP3EngineClass;
+typedef struct _CamelPOP3Command CamelPOP3Command;
+
+/* POP3 connection states, actually since we're given
+ * a connected socket, we always start in auth state. */
+typedef enum {
+ CAMEL_POP3_ENGINE_DISCONNECT = 0,
+ CAMEL_POP3_ENGINE_AUTH,
+ CAMEL_POP3_ENGINE_TRANSACTION,
+ CAMEL_POP3_ENGINE_UPDATE
+} camel_pop3_engine_t;
+
+/* state of a command */
+typedef enum {
+ CAMEL_POP3_COMMAND_IDLE = 0, /* command created or queued, not yet sent (e.g. non pipelined server) */
+ CAMEL_POP3_COMMAND_DISPATCHED, /* command sent to server */
+
+ /* completion codes */
+ CAMEL_POP3_COMMAND_OK, /* plain ok response */
+ CAMEL_POP3_COMMAND_DATA, /* processing command response */
+ CAMEL_POP3_COMMAND_ERR /* error response */
+} camel_pop3_command_t;
+
+/* flags for command types */
+enum {
+ CAMEL_POP3_COMMAND_SIMPLE = 0, /* dont expect multiline response */
+ CAMEL_POP3_COMMAND_MULTI = 1 /* expect multiline response */
+};
+
+/* flags for server options */
+enum {
+ CAMEL_POP3_CAP_APOP = 1 << 0,
+ CAMEL_POP3_CAP_UIDL = 1 << 1,
+ CAMEL_POP3_CAP_SASL = 1 << 2,
+ CAMEL_POP3_CAP_TOP = 1 << 3,
+ CAMEL_POP3_CAP_PIPE = 1 << 4,
+ CAMEL_POP3_CAP_STLS = 1 << 5
+};
+
+/* enable/disable flags for the engine itself */
+enum {
+ CAMEL_POP3_ENGINE_DISABLE_EXTENSIONS = 1 << 0
+};
+
+typedef void (*CamelPOP3CommandFunc) (CamelPOP3Engine *pe,
+ CamelPOP3Stream *stream,
+ GCancellable *cancellable,
+ GError **error,
+ gpointer data);
+
+struct _CamelPOP3Command {
+ guint32 flags;
+ camel_pop3_command_t state;
+ gchar *error_str;
+
+ CamelPOP3CommandFunc func;
+ gpointer func_data;
+
+ gint data_size;
+ gchar *data;
+};
+
+struct _CamelPOP3Engine {
+ GObject parent;
+
+ guint32 flags;
+
+ camel_pop3_engine_t state;
+
+ GList *auth; /* authtypes supported */
+
+ guint32 capa; /* capabilities */
+ gchar *apop; /* apop time string */
+
+ guchar *line; /* current line buffer */
+ guint linelen;
+
+ CamelPOP3Stream *stream;
+
+ guint sentlen; /* data sent (so we dont overflow network buffer) */
+
+ GQueue active; /* active commands */
+ GQueue queue; /* queue of waiting commands */
+ GQueue done; /* list of done commands, awaiting free */
+
+ CamelPOP3Command *current; /* currently busy (downloading) response */
+
+ GMutex busy_lock;
+ GCond busy_cond;
+ gboolean is_busy;
+};
+
+struct _CamelPOP3EngineClass {
+ GObjectClass parent_class;
+};
+
+GType camel_pop3_engine_get_type (void);
+CamelPOP3Engine *
+ camel_pop3_engine_new (CamelStream *source,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_pop3_engine_reget_capabilities
+ (CamelPOP3Engine *engine,
+ GCancellable *cancellable,
+ GError **error);
+gint camel_pop3_engine_iterate (CamelPOP3Engine *pe,
+ CamelPOP3Command *pc,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_pop3_engine_busy_lock (CamelPOP3Engine *pe,
+ GCancellable *cancellable,
+ GError **error);
+void camel_pop3_engine_busy_unlock (CamelPOP3Engine *pe);
+CamelPOP3Command *
+ camel_pop3_engine_command_new (CamelPOP3Engine *pe,
+ guint32 flags,
+ CamelPOP3CommandFunc func,
+ gpointer data,
+ GCancellable *cancellable,
+ GError **error,
+ const gchar *fmt,
+ ...);
+void camel_pop3_engine_command_free (CamelPOP3Engine *pe,
+ CamelPOP3Command *pc);
+
+G_END_DECLS
+
+#endif /* CAMEL_POP3_ENGINE_H */
diff --git a/src/camel/providers/pop3/camel-pop3-folder.c b/src/camel/providers/pop3/camel-pop3-folder.c
new file mode 100644
index 000000000..bb7042e95
--- /dev/null
+++ b/src/camel/providers/pop3/camel-pop3-folder.c
@@ -0,0 +1,1170 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-pop3-folder.c : class for a pop3 folder
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-pop3-folder.h"
+#include "camel-pop3-store.h"
+#include "camel-pop3-settings.h"
+
+#define d(x) if (camel_debug("pop3")) x;
+
+typedef struct _CamelPOP3FolderInfo CamelPOP3FolderInfo;
+
+struct _CamelPOP3FolderInfo {
+ guint32 id;
+ guint32 size;
+ guint32 flags;
+ guint32 index; /* index of request */
+ gchar *uid;
+ struct _CamelPOP3Command *cmd;
+ struct _CamelStream *stream;
+};
+
+G_DEFINE_TYPE (CamelPOP3Folder, camel_pop3_folder, CAMEL_TYPE_FOLDER)
+
+static void
+cmd_uidl (CamelPOP3Engine *pe,
+ CamelPOP3Stream *stream,
+ GCancellable *cancellable,
+ GError **error,
+ gpointer data)
+{
+ gint ret;
+ guint len;
+ guchar *line;
+ gchar uid[1025];
+ guint id;
+ CamelPOP3FolderInfo *fi;
+ CamelPOP3Folder *folder = data;
+
+ do {
+ ret = camel_pop3_stream_line (stream, &line, &len, cancellable, error);
+ if (ret >= 0) {
+ if (strlen ((gchar *) line) > 1024)
+ line[1024] = 0;
+ if (sscanf ((gchar *) line, "%u %s", &id, uid) == 2) {
+ fi = g_hash_table_lookup (folder->uids_id, GINT_TO_POINTER (id));
+ if (fi) {
+ camel_operation_progress (cancellable, (fi->index + 1) * 100 / folder->uids->len);
+ fi->uid = g_strdup (uid);
+ g_hash_table_insert (folder->uids_fi, fi->uid, fi);
+ } else {
+ g_warning ("ID %u (uid: %s) not in previous LIST output", id, uid);
+ }
+ }
+ }
+ } while (ret > 0);
+}
+
+/* create a uid from md5 of 'top' output */
+static void
+cmd_builduid (CamelPOP3Engine *pe,
+ CamelPOP3Stream *stream,
+ GCancellable *cancellable,
+ GError **error,
+ gpointer data)
+{
+ GChecksum *checksum;
+ CamelPOP3FolderInfo *fi = data;
+ struct _camel_header_raw *h;
+ CamelMimeParser *mp;
+ guint8 *digest;
+ gsize length;
+
+ length = g_checksum_type_get_length (G_CHECKSUM_MD5);
+ digest = g_alloca (length);
+
+ /* TODO; somehow work out the limit and use that for proper progress reporting
+ * We need a pointer to the folder perhaps? */
+ /* camel_operation_progress (cancellable, fi->id); */
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ mp = camel_mime_parser_new ();
+ camel_mime_parser_init_with_stream (mp, (CamelStream *) stream, NULL);
+ switch (camel_mime_parser_step (mp, NULL, NULL)) {
+ case CAMEL_MIME_PARSER_STATE_HEADER:
+ case CAMEL_MIME_PARSER_STATE_MESSAGE:
+ case CAMEL_MIME_PARSER_STATE_MULTIPART:
+ h = camel_mime_parser_headers_raw (mp);
+ while (h) {
+ if (g_ascii_strcasecmp (h->name, "status") != 0
+ && g_ascii_strcasecmp (h->name, "x-status") != 0) {
+ g_checksum_update (checksum, (guchar *) h->name, -1);
+ g_checksum_update (checksum, (guchar *) h->value, -1);
+ }
+ h = h->next;
+ }
+ default:
+ break;
+ }
+ g_object_unref (mp);
+ g_checksum_get_digest (checksum, digest, &length);
+ g_checksum_free (checksum);
+
+ fi->uid = g_base64_encode ((guchar *) digest, length);
+
+ d (printf ("building uid for id '%d' = '%s'\n", fi->id, fi->uid));
+}
+
+static void
+cmd_list (CamelPOP3Engine *pe,
+ CamelPOP3Stream *stream,
+ GCancellable *cancellable,
+ GError **error,
+ gpointer data)
+{
+ gint ret;
+ guint len, id, size;
+ guchar *line;
+ CamelFolder *folder = data;
+ CamelPOP3FolderInfo *fi;
+ CamelPOP3Folder *pop3_folder;
+
+ g_return_if_fail (pe != NULL);
+
+ pop3_folder = (CamelPOP3Folder *) folder;
+
+ do {
+ ret = camel_pop3_stream_line (stream, &line, &len, cancellable, error);
+ if (ret >= 0) {
+ if (sscanf ((gchar *) line, "%u %u", &id, &size) == 2) {
+ fi = g_malloc0 (sizeof (*fi));
+ fi->size = size;
+ fi->id = id;
+ fi->index = ((CamelPOP3Folder *) folder)->uids->len;
+ if ((pe->capa & CAMEL_POP3_CAP_UIDL) == 0)
+ fi->cmd = camel_pop3_engine_command_new (
+ pe,
+ CAMEL_POP3_COMMAND_MULTI,
+ cmd_builduid, fi,
+ cancellable, error,
+ "TOP %u 0\r\n", id);
+ g_ptr_array_add (pop3_folder->uids, fi);
+ g_hash_table_insert (
+ pop3_folder->uids_id,
+ GINT_TO_POINTER (id), fi);
+ }
+ }
+ } while (ret > 0);
+}
+
+static void
+cmd_tocache (CamelPOP3Engine *pe,
+ CamelPOP3Stream *stream,
+ GCancellable *cancellable,
+ GError **error,
+ gpointer data)
+{
+ CamelPOP3FolderInfo *fi = data;
+ gchar buffer[2048];
+ gint w = 0, n;
+ GError *local_error = NULL;
+
+ /* What if it fails? */
+
+ /* We write an '*' to the start of the stream to say its not complete yet */
+ /* This should probably be part of the cache code */
+ if ((n = camel_stream_write (fi->stream, "*", 1, cancellable, &local_error)) == -1)
+ goto done;
+
+ while ((n = camel_stream_read ((CamelStream *) stream, buffer, sizeof (buffer), cancellable, &local_error)) > 0) {
+ n = camel_stream_write (fi->stream, buffer, n, cancellable, &local_error);
+ if (n == -1)
+ break;
+
+ w += n;
+ if (w > fi->size)
+ w = fi->size;
+ if (fi->size != 0)
+ camel_operation_progress (cancellable, (w * 100) / fi->size);
+ }
+
+ /* it all worked, output a '#' to say we're a-ok */
+ if (local_error == NULL) {
+ g_seekable_seek (
+ G_SEEKABLE (fi->stream),
+ 0, G_SEEK_SET, cancellable, NULL);
+ camel_stream_write (fi->stream, "#", 1, cancellable, &local_error);
+ }
+
+done:
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ }
+
+ g_object_unref (fi->stream);
+ fi->stream = NULL;
+}
+
+static void
+pop3_folder_dispose (GObject *object)
+{
+ CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (object);
+ CamelPOP3Store *pop3_store = NULL;
+ CamelStore *parent_store;
+
+ parent_store = camel_folder_get_parent_store (CAMEL_FOLDER (object));
+ if (parent_store)
+ pop3_store = CAMEL_POP3_STORE (parent_store);
+
+ if (pop3_folder->uids) {
+ gint i;
+ CamelPOP3FolderInfo **fi = (CamelPOP3FolderInfo **) pop3_folder->uids->pdata;
+ gboolean is_online = camel_service_get_connection_status (CAMEL_SERVICE (parent_store)) == CAMEL_SERVICE_CONNECTED;
+
+ for (i = 0; i < pop3_folder->uids->len; i++, fi++) {
+ if (fi[0]->cmd && pop3_store && is_online) {
+ CamelPOP3Engine *pop3_engine;
+
+ pop3_engine = camel_pop3_store_ref_engine (pop3_store);
+
+ while (camel_pop3_engine_iterate (pop3_engine, fi[0]->cmd, NULL, NULL) > 0)
+ ;
+ camel_pop3_engine_command_free (pop3_engine, fi[0]->cmd);
+
+ g_clear_object (&pop3_engine);
+ }
+
+ g_free (fi[0]->uid);
+ g_free (fi[0]);
+ }
+
+ g_ptr_array_free (pop3_folder->uids, TRUE);
+ pop3_folder->uids = NULL;
+ }
+
+ if (pop3_folder->uids_fi) {
+ g_hash_table_destroy (pop3_folder->uids_fi);
+ pop3_folder->uids_fi = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_pop3_folder_parent_class)->dispose (object);
+}
+
+static gint
+pop3_folder_get_message_count (CamelFolder *folder)
+{
+ CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
+
+ return pop3_folder->uids->len;
+}
+
+static GPtrArray *
+pop3_folder_get_uids (CamelFolder *folder)
+{
+ CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
+ GPtrArray *uids = g_ptr_array_new ();
+ CamelPOP3FolderInfo **fi = (CamelPOP3FolderInfo **) pop3_folder->uids->pdata;
+ gint i;
+
+ for (i = 0; i < pop3_folder->uids->len; i++,fi++) {
+ if (fi[0]->uid)
+ g_ptr_array_add (uids, fi[0]->uid);
+ }
+
+ return uids;
+}
+
+static GPtrArray *
+pop3_get_uncached_uids (CamelFolder *folder,
+ GPtrArray *uids,
+ GError **error)
+{
+ CamelPOP3Folder *pop3_folder;
+ CamelPOP3Store *pop3_store;
+ GPtrArray *uncached_uids;
+ gint ii;
+
+ g_return_val_if_fail (CAMEL_IS_POP3_FOLDER (folder), NULL);
+ g_return_val_if_fail (uids != NULL, NULL);
+
+ pop3_folder = CAMEL_POP3_FOLDER (folder);
+ pop3_store = CAMEL_POP3_STORE (camel_folder_get_parent_store (folder));
+
+ uncached_uids = g_ptr_array_new ();
+
+ for (ii = 0; ii < uids->len; ii++) {
+ const gchar *uid = uids->pdata[ii];
+ CamelPOP3FolderInfo *fi;
+ gboolean uid_is_cached = FALSE;
+
+ fi = g_hash_table_lookup (pop3_folder->uids_fi, uid);
+
+ if (fi != NULL) {
+ uid_is_cached = camel_pop3_store_cache_has (
+ pop3_store, fi->uid);
+ }
+
+ if (!uid_is_cached) {
+ g_ptr_array_add (
+ uncached_uids, (gpointer)
+ camel_pstring_strdup (uid));
+ }
+ }
+
+ return uncached_uids;
+}
+
+static gchar *
+pop3_folder_get_filename (CamelFolder *folder,
+ const gchar *uid,
+ GError **error)
+{
+ CamelStore *parent_store;
+ CamelPOP3Folder *pop3_folder;
+ CamelPOP3Store *pop3_store;
+ CamelDataCache *pop3_cache;
+ CamelPOP3FolderInfo *fi;
+ gchar *filename;
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ pop3_folder = CAMEL_POP3_FOLDER (folder);
+ pop3_store = CAMEL_POP3_STORE (parent_store);
+
+ fi = g_hash_table_lookup (pop3_folder->uids_fi, uid);
+ if (fi == NULL) {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID_UID,
+ _("No message with UID %s"), uid);
+ return NULL;
+ }
+
+ pop3_cache = camel_pop3_store_ref_cache (pop3_store);
+ if (!pop3_cache) {
+ g_warn_if_reached ();
+ return NULL;
+ }
+
+ filename = camel_data_cache_get_filename (
+ pop3_cache, "cache", fi->uid);
+ g_clear_object (&pop3_cache);
+
+ return filename;
+}
+
+static gboolean
+pop3_folder_set_message_flags (CamelFolder *folder,
+ const gchar *uid,
+ CamelMessageFlags flags,
+ CamelMessageFlags set)
+{
+ CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
+ CamelPOP3FolderInfo *fi;
+ gboolean res = FALSE;
+
+ fi = g_hash_table_lookup (pop3_folder->uids_fi, uid);
+ if (fi) {
+ guint32 new = (fi->flags & ~flags) | (set & flags);
+
+ if (fi->flags != new) {
+ fi->flags = new;
+ res = TRUE;
+ }
+ }
+
+ return res;
+}
+
+static CamelMimeMessage *
+pop3_folder_get_message_internal_sync (CamelFolder *folder,
+ const gchar *uid,
+ gboolean already_locked,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *parent_store;
+ CamelMimeMessage *message = NULL;
+ CamelPOP3Store *pop3_store;
+ CamelPOP3Folder *pop3_folder;
+ CamelPOP3Engine *pop3_engine;
+ CamelPOP3Command *pcr;
+ CamelPOP3FolderInfo *fi;
+ gchar buffer[1];
+ gint i, last;
+ CamelStream *stream = NULL;
+ CamelService *service;
+ CamelSettings *settings;
+ gboolean auto_fetch;
+
+ g_return_val_if_fail (uid != NULL, NULL);
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ pop3_folder = CAMEL_POP3_FOLDER (folder);
+ pop3_store = CAMEL_POP3_STORE (parent_store);
+
+ service = CAMEL_SERVICE (parent_store);
+
+ settings = camel_service_ref_settings (service);
+
+ g_object_get (
+ settings,
+ "auto-fetch", &auto_fetch,
+ NULL);
+
+ g_object_unref (settings);
+
+ fi = g_hash_table_lookup (pop3_folder->uids_fi, uid);
+ if (fi == NULL) {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID_UID,
+ _("No message with UID %s"), uid);
+ return NULL;
+ }
+
+ if (camel_service_get_connection_status (CAMEL_SERVICE (parent_store)) != CAMEL_SERVICE_CONNECTED) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ return NULL;
+ }
+
+ /* Sigh, most of the crap in this function is so that the cancel button
+ * returns the proper exception code. Sigh. */
+
+ camel_operation_push_message (
+ cancellable, _("Retrieving POP message %d"), fi->id);
+
+ pop3_engine = camel_pop3_store_ref_engine (pop3_store);
+
+ if (!already_locked && !camel_pop3_engine_busy_lock (pop3_engine, cancellable, error))
+ goto fail;
+
+ /* If we have an oustanding retrieve message running, wait for that to complete
+ * & then retrieve from cache, otherwise, start a new one, and similar */
+
+ if (fi->cmd != NULL) {
+ while ((i = camel_pop3_engine_iterate (pop3_engine, fi->cmd, cancellable, error)) > 0)
+ ;
+
+ /* getting error code? */
+ /*g_warn_if_fail (fi->cmd->state == CAMEL_POP3_COMMAND_DATA);*/
+ camel_pop3_engine_command_free (pop3_engine, fi->cmd);
+ fi->cmd = NULL;
+
+ if (i == -1) {
+ g_prefix_error (
+ error, _("Cannot get message %s: "), uid);
+ if (!already_locked)
+ camel_pop3_engine_busy_unlock (pop3_engine);
+ goto fail;
+ }
+ }
+
+ /* check to see if we have safely written flag set */
+ stream = camel_pop3_store_cache_get (pop3_store, fi->uid, NULL);
+ if (!stream) {
+ GError *local_error = NULL;
+
+ /* Initiate retrieval, if disk backing fails, use a memory backing */
+ stream = camel_pop3_store_cache_add (pop3_store, fi->uid, NULL);
+ if (stream == NULL)
+ stream = camel_stream_mem_new ();
+
+ /* ref it, the cache storage routine unref's when done */
+ fi->stream = g_object_ref (stream);
+ pcr = camel_pop3_engine_command_new (
+ pop3_engine,
+ CAMEL_POP3_COMMAND_MULTI,
+ cmd_tocache, fi,
+ cancellable, &local_error,
+ "RETR %u\r\n", fi->id);
+
+ if (local_error) {
+ if (pcr)
+ camel_pop3_engine_command_free (pop3_engine, pcr);
+
+ g_propagate_error (error, local_error);
+ g_prefix_error (
+ error, _("Cannot get message %s: "), uid);
+ goto done;
+ }
+
+ /* Also initiate retrieval of some of the following
+ * messages, assume we'll be receiving them. */
+ if (auto_fetch) {
+ /* This should keep track of the last one retrieved,
+ * also how many are still oustanding incase of random
+ * access on large folders. */
+ i = fi->index + 1;
+ last = MIN (i + 10, pop3_folder->uids->len);
+ for (; i < last; i++) {
+ CamelPOP3FolderInfo *pfi = pop3_folder->uids->pdata[i];
+
+ if (pfi->uid && pfi->cmd == NULL) {
+ pfi->stream = camel_pop3_store_cache_add (
+ pop3_store, pfi->uid, NULL);
+ if (pfi->stream != NULL) {
+ pfi->cmd = camel_pop3_engine_command_new (
+ pop3_engine,
+ CAMEL_POP3_COMMAND_MULTI,
+ cmd_tocache, pfi,
+ cancellable, &local_error,
+ "RETR %u\r\n", pfi->id);
+
+ if (local_error) {
+ if (pcr)
+ camel_pop3_engine_command_free (pop3_engine, pcr);
+
+ g_propagate_error (error, local_error);
+ g_prefix_error (
+ error, _("Cannot get message %s: "), uid);
+ goto done;
+ }
+ }
+ }
+ }
+ }
+
+ /* now wait for the first one to finish */
+ while (!local_error && (i = camel_pop3_engine_iterate (pop3_engine, pcr, cancellable, &local_error)) > 0)
+ ;
+
+ /* getting error code? */
+ /*g_warn_if_fail (pcr->state == CAMEL_POP3_COMMAND_DATA);*/
+ camel_pop3_engine_command_free (pop3_engine, pcr);
+ g_seekable_seek (
+ G_SEEKABLE (stream), 0, G_SEEK_SET, NULL, NULL);
+
+ /* Check to see we have safely written flag set */
+ if (i == -1 || local_error) {
+ g_propagate_error (error, local_error);
+ g_prefix_error (
+ error, _("Cannot get message %s: "), uid);
+ goto done;
+ }
+
+ if (camel_stream_read (stream, buffer, 1, cancellable, error) == -1)
+ goto done;
+
+ if (buffer[0] != '#') {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot get message %s: %s"), uid,
+ _("Unknown reason"));
+ goto done;
+ }
+ }
+
+ message = camel_mime_message_new ();
+ if (stream != NULL && !camel_data_wrapper_construct_from_stream_sync (
+ CAMEL_DATA_WRAPPER (message), stream, cancellable, error)) {
+ g_prefix_error (error, _("Cannot get message %s: "), uid);
+ g_object_unref (message);
+ message = NULL;
+ } else {
+ /* because the UID in the local store doesn't match with the UID in the pop3 store */
+ camel_medium_add_header (CAMEL_MEDIUM (message), "X-Evolution-POP3-UID", uid);
+ }
+done:
+ if (!already_locked)
+ camel_pop3_engine_busy_unlock (pop3_engine);
+ g_clear_object (&stream);
+fail:
+ g_clear_object (&pop3_engine);
+
+ camel_operation_pop_message (cancellable);
+
+ return message;
+}
+
+static CamelMimeMessage *
+pop3_folder_get_message_sync (CamelFolder *folder,
+ const gchar *uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return pop3_folder_get_message_internal_sync (folder, uid, FALSE, cancellable, error);
+}
+
+static gboolean
+pop3_folder_refresh_info_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *parent_store;
+ CamelPOP3Store *pop3_store;
+ CamelPOP3Folder *pop3_folder = (CamelPOP3Folder *) folder;
+ CamelPOP3Engine *pop3_engine;
+ CamelPOP3Command *pcl, *pcu = NULL;
+ gboolean success = TRUE;
+ GError *local_error = NULL;
+ gint i;
+
+ parent_store = camel_folder_get_parent_store (folder);
+ pop3_store = CAMEL_POP3_STORE (parent_store);
+
+ if (camel_service_get_connection_status (CAMEL_SERVICE (parent_store)) != CAMEL_SERVICE_CONNECTED) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ return FALSE;
+ }
+
+ pop3_engine = camel_pop3_store_ref_engine (pop3_store);
+
+ if (!camel_pop3_engine_busy_lock (pop3_engine, cancellable, error)) {
+ g_clear_object (&pop3_engine);
+ return FALSE;
+ }
+
+ camel_operation_push_message (
+ cancellable, _("Retrieving POP summary"));
+
+ /* Get rid of the old cache */
+ if (pop3_folder->uids) {
+ gint i;
+ CamelPOP3FolderInfo *last_fi;
+
+ if (pop3_folder->uids->len) {
+ last_fi = pop3_folder->uids->pdata[pop3_folder->uids->len - 1];
+ if (last_fi)
+ pop3_folder->latest_id = last_fi->id;
+ else
+ pop3_folder->latest_id = -1;
+ } else
+ pop3_folder->latest_id = -1;
+
+ for (i = 0; i < pop3_folder->uids->len; i++) {
+ CamelPOP3FolderInfo *fi = pop3_folder->uids->pdata[i];
+ if (fi->cmd) {
+ camel_pop3_engine_command_free (pop3_engine, fi->cmd);
+ fi->cmd = NULL;
+ }
+ g_free (fi->uid);
+ g_free (fi);
+ }
+
+ g_ptr_array_free (pop3_folder->uids, TRUE);
+ }
+
+ if (pop3_folder->uids_fi) {
+ g_hash_table_destroy (pop3_folder->uids_fi);
+ pop3_folder->uids_fi = NULL;
+ }
+
+ /* Get a new working set. */
+ pop3_folder->uids = g_ptr_array_new ();
+ pop3_folder->uids_fi = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* only used during setup */
+ pop3_folder->uids_id = g_hash_table_new (NULL, NULL);
+
+ pcl = camel_pop3_engine_command_new (
+ pop3_engine,
+ CAMEL_POP3_COMMAND_MULTI,
+ cmd_list, folder,
+ cancellable, &local_error,
+ "LIST\r\n");
+ if (!local_error && pop3_engine && (pop3_engine->capa & CAMEL_POP3_CAP_UIDL) != 0)
+ pcu = camel_pop3_engine_command_new (
+ pop3_engine,
+ CAMEL_POP3_COMMAND_MULTI,
+ cmd_uidl, folder,
+ cancellable, &local_error,
+ "UIDL\r\n");
+ while (!local_error && (i = camel_pop3_engine_iterate (pop3_engine, NULL, cancellable, &local_error)) > 0)
+ ;
+
+ if (local_error) {
+ g_propagate_error (error, local_error);
+ g_prefix_error (error, _("Cannot get POP summary: "));
+ success = FALSE;
+ } else if (i == -1) {
+ g_set_error_literal (error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Cannot get POP summary: "));
+ success = FALSE;
+ }
+
+ /* TODO: check every id has a uid & commands returned OK too? */
+
+ if (pcl) {
+ if (success && pcl->state == CAMEL_POP3_COMMAND_ERR) {
+ success = FALSE;
+
+ if (pcl->error_str)
+ g_set_error_literal (error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, pcl->error_str);
+ else
+ g_set_error_literal (error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Cannot get POP summary: "));
+ }
+
+ camel_pop3_engine_command_free (pop3_engine, pcl);
+ }
+
+ if (pcu) {
+ if (success && pcu->state == CAMEL_POP3_COMMAND_ERR) {
+ success = FALSE;
+
+ if (pcu->error_str)
+ g_set_error_literal (error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, pcu->error_str);
+ else
+ g_set_error_literal (error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Cannot get POP summary: "));
+ }
+
+ camel_pop3_engine_command_free (pop3_engine, pcu);
+ } else {
+ for (i = 0; i < pop3_folder->uids->len; i++) {
+ CamelPOP3FolderInfo *fi = pop3_folder->uids->pdata[i];
+ if (fi->cmd) {
+ if (success && fi->cmd->state == CAMEL_POP3_COMMAND_ERR) {
+ success = FALSE;
+
+ if (fi->cmd->error_str)
+ g_set_error_literal (error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, fi->cmd->error_str);
+ else
+ g_set_error_literal (error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Cannot get POP summary: "));
+ }
+
+ camel_pop3_engine_command_free (pop3_engine, fi->cmd);
+ fi->cmd = NULL;
+ }
+ if (fi->uid) {
+ g_hash_table_insert (pop3_folder->uids_fi, fi->uid, fi);
+ }
+ }
+ }
+
+ /* dont need this anymore */
+ g_hash_table_destroy (pop3_folder->uids_id);
+ pop3_folder->uids_id = NULL;
+
+ camel_pop3_engine_busy_unlock (pop3_engine);
+ g_clear_object (&pop3_engine);
+
+ camel_operation_pop_message (cancellable);
+
+ return success;
+}
+
+static gboolean
+pop3_folder_synchronize_sync (CamelFolder *folder,
+ gboolean expunge,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelService *service;
+ CamelSettings *settings;
+ CamelStore *parent_store;
+ CamelPOP3Folder *pop3_folder;
+ CamelPOP3Store *pop3_store;
+ CamelDataCache *pop3_cache;
+ CamelPOP3Engine *pop3_engine;
+ CamelPOP3FolderInfo *fi;
+ gint delete_after_days;
+ gboolean delete_expunged;
+ gboolean keep_on_server;
+ gboolean is_online;
+ gint i;
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ pop3_folder = CAMEL_POP3_FOLDER (folder);
+ pop3_store = CAMEL_POP3_STORE (parent_store);
+
+ service = CAMEL_SERVICE (parent_store);
+ is_online = camel_service_get_connection_status (service) == CAMEL_SERVICE_CONNECTED;
+
+ settings = camel_service_ref_settings (service);
+
+ g_object_get (
+ settings,
+ "delete-after-days", &delete_after_days,
+ "delete-expunged", &delete_expunged,
+ "keep-on-server", &keep_on_server,
+ NULL);
+
+ g_object_unref (settings);
+
+ if (is_online && delete_after_days > 0 && !expunge && !g_cancellable_is_cancelled (cancellable)) {
+ camel_operation_push_message (
+ cancellable, _("Expunging old messages"));
+
+ camel_pop3_folder_delete_old (
+ folder, delete_after_days, cancellable, error);
+
+ camel_operation_pop_message (cancellable);
+ }
+
+ if (g_cancellable_is_cancelled (cancellable)) {
+ if (error && !*error) {
+ /* coverity[unchecked_value] */
+ g_cancellable_set_error_if_cancelled (cancellable, error);
+ }
+ return FALSE;
+ }
+
+ if (!expunge || (keep_on_server && !delete_expunged))
+ return TRUE;
+
+ if (!is_online) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ return FALSE;
+ }
+
+ camel_operation_push_message (
+ cancellable, _("Expunging deleted messages"));
+
+ pop3_cache = camel_pop3_store_ref_cache (pop3_store);
+ pop3_engine = camel_pop3_store_ref_engine (pop3_store);
+
+ if (!camel_pop3_engine_busy_lock (pop3_engine, cancellable, error)) {
+ g_clear_object (&pop3_cache);
+ g_clear_object (&pop3_engine);
+
+ camel_operation_pop_message (cancellable);
+
+ return FALSE;
+ }
+
+ for (i = 0; i < pop3_folder->uids->len; i++) {
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ camel_pop3_engine_busy_unlock (pop3_engine);
+ g_clear_object (&pop3_cache);
+ g_clear_object (&pop3_engine);
+
+ camel_operation_pop_message (cancellable);
+
+ return FALSE;
+ }
+
+ fi = pop3_folder->uids->pdata[i];
+ /* busy already? wait for that to finish first */
+ if (fi->cmd) {
+ while (camel_pop3_engine_iterate (pop3_engine, fi->cmd, cancellable, NULL) > 0)
+ ;
+ camel_pop3_engine_command_free (pop3_engine, fi->cmd);
+ fi->cmd = NULL;
+ }
+
+ if (fi->flags & CAMEL_MESSAGE_DELETED) {
+ fi->cmd = camel_pop3_engine_command_new (
+ pop3_engine,
+ 0, NULL, NULL,
+ cancellable, NULL,
+ "DELE %u\r\n", fi->id);
+
+ /* also remove from cache */
+ if (pop3_cache != NULL && fi->uid)
+ camel_data_cache_remove (pop3_cache, "cache", fi->uid, NULL);
+ }
+ }
+
+ for (i = 0; i < pop3_folder->uids->len; i++) {
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ camel_pop3_engine_busy_unlock (pop3_engine);
+ g_clear_object (&pop3_cache);
+ g_clear_object (&pop3_engine);
+
+ camel_operation_pop_message (cancellable);
+
+ return FALSE;
+ }
+
+ fi = pop3_folder->uids->pdata[i];
+ /* wait for delete commands to finish */
+ if (fi->cmd) {
+ while (camel_pop3_engine_iterate (pop3_engine, fi->cmd, cancellable, NULL) > 0)
+ ;
+ camel_pop3_engine_command_free (pop3_engine, fi->cmd);
+ fi->cmd = NULL;
+ }
+ camel_operation_progress (
+ cancellable, (i + 1) * 100 / pop3_folder->uids->len);
+ }
+
+ camel_pop3_engine_busy_unlock (pop3_engine);
+ g_clear_object (&pop3_cache);
+ g_clear_object (&pop3_engine);
+
+ camel_operation_pop_message (cancellable);
+
+ return camel_pop3_store_expunge (pop3_store, cancellable, error);
+}
+
+static void
+camel_pop3_folder_class_init (CamelPOP3FolderClass *class)
+{
+ GObjectClass *object_class;
+ CamelFolderClass *folder_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = pop3_folder_dispose;
+
+ folder_class = CAMEL_FOLDER_CLASS (class);
+ folder_class->get_message_count = pop3_folder_get_message_count;
+ folder_class->get_uids = pop3_folder_get_uids;
+ folder_class->free_uids = camel_folder_free_shallow;
+ folder_class->get_uncached_uids = pop3_get_uncached_uids;
+ folder_class->get_filename = pop3_folder_get_filename;
+ folder_class->set_message_flags = pop3_folder_set_message_flags;
+ folder_class->get_message_sync = pop3_folder_get_message_sync;
+ folder_class->refresh_info_sync = pop3_folder_refresh_info_sync;
+ folder_class->synchronize_sync = pop3_folder_synchronize_sync;
+}
+
+static void
+camel_pop3_folder_init (CamelPOP3Folder *pop3_folder)
+{
+ pop3_folder->uids = g_ptr_array_new ();
+ pop3_folder->uids_fi = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+CamelFolder *
+camel_pop3_folder_new (CamelStore *parent,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelPOP3Folder *pop3_folder;
+
+ d (printf ("opening pop3 INBOX folder\n"));
+
+ folder = g_object_new (
+ CAMEL_TYPE_POP3_FOLDER,
+ "full-name", "inbox", "display-name", "inbox",
+ "parent-store", parent, NULL);
+
+ pop3_folder = (CamelPOP3Folder *) folder;
+
+ pop3_folder->fetch_more = 0;
+ if (camel_service_get_connection_status (CAMEL_SERVICE (parent)) != CAMEL_SERVICE_CONNECTED)
+ return folder;
+
+ /* mt-ok, since we dont have the folder-lock for new() */
+ if (!camel_folder_refresh_info_sync (folder, cancellable, error)) {
+ g_object_unref (folder);
+ folder = NULL;
+ }
+
+ return folder;
+}
+
+static gboolean
+pop3_get_message_time_from_cache (CamelFolder *folder,
+ const gchar *uid,
+ time_t *message_time)
+{
+ CamelStore *parent_store;
+ CamelPOP3Store *pop3_store;
+ CamelStream *stream = NULL;
+ gboolean res = FALSE;
+
+ g_return_val_if_fail (folder != NULL, FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+ g_return_val_if_fail (message_time != NULL, FALSE);
+
+ parent_store = camel_folder_get_parent_store (folder);
+ pop3_store = CAMEL_POP3_STORE (parent_store);
+
+ stream = camel_pop3_store_cache_get (pop3_store, uid, NULL);
+ if (stream != NULL) {
+ CamelMimeMessage *message;
+ GError *error = NULL;
+
+ message = camel_mime_message_new ();
+ camel_data_wrapper_construct_from_stream_sync (
+ (CamelDataWrapper *) message, stream, NULL, &error);
+ if (error != NULL) {
+ g_warning (_("Cannot get message %s: %s"), uid, error->message);
+ g_error_free (error);
+
+ g_object_unref (message);
+ message = NULL;
+ }
+
+ if (message) {
+ res = TRUE;
+ *message_time = message->date + message->date_offset;
+
+ g_object_unref (message);
+ }
+
+ g_object_unref (stream);
+ }
+
+ return res;
+}
+
+gboolean
+camel_pop3_folder_delete_old (CamelFolder *folder,
+ gint days_to_delete,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *parent_store;
+ CamelPOP3Folder *pop3_folder;
+ CamelPOP3FolderInfo *fi;
+ CamelPOP3Engine *pop3_engine;
+ CamelPOP3Store *pop3_store;
+ CamelDataCache *pop3_cache;
+ CamelMimeMessage *message;
+ time_t temp, message_time;
+ gint i;
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ if (camel_service_get_connection_status (CAMEL_SERVICE (parent_store)) != CAMEL_SERVICE_CONNECTED) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ return FALSE;
+ }
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ pop3_folder = CAMEL_POP3_FOLDER (folder);
+ pop3_store = CAMEL_POP3_STORE (parent_store);
+ pop3_cache = camel_pop3_store_ref_cache (pop3_store);
+ pop3_engine = camel_pop3_store_ref_engine (pop3_store);
+
+ if (!camel_pop3_engine_busy_lock (pop3_engine, cancellable, error)) {
+ g_clear_object (&pop3_cache);
+ g_clear_object (&pop3_engine);
+
+ return FALSE;
+ }
+
+ temp = time (&temp);
+
+ d (printf ("%s(%d): pop3_folder->uids->len=[%d]\n", __FILE__, __LINE__, pop3_folder->uids->len));
+ for (i = 0; i < pop3_folder->uids->len; i++) {
+ message_time = 0;
+ fi = pop3_folder->uids->pdata[i];
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ camel_pop3_engine_busy_unlock (pop3_engine);
+ g_clear_object (&pop3_cache);
+ g_clear_object (&pop3_engine);
+
+ return FALSE;
+ }
+
+ if (fi->cmd) {
+ while (camel_pop3_engine_iterate (pop3_engine, fi->cmd, cancellable, NULL) > 0) {
+ ; /* do nothing - iterating until end */
+ }
+
+ camel_pop3_engine_command_free (pop3_engine, fi->cmd);
+ fi->cmd = NULL;
+ }
+
+ /* continue, if message wasn't received yet */
+ if (!fi->uid)
+ continue;
+
+ d (printf ("%s(%d): fi->uid=[%s]\n", __FILE__, __LINE__, fi->uid));
+ if (!pop3_get_message_time_from_cache (folder, fi->uid, &message_time)) {
+ d (printf ("could not get message time from cache, trying from pop3\n"));
+ message = pop3_folder_get_message_internal_sync (
+ folder, fi->uid, TRUE, cancellable, error);
+ if (message) {
+ message_time = message->date + message->date_offset;
+ g_object_unref (message);
+ }
+ }
+
+ if (message_time) {
+ gdouble time_diff = difftime (temp, message_time);
+ gint day_lag = time_diff / (60 * 60 * 24);
+
+ d (printf (
+ "%s(%d): message_time= [%" G_GINT64_FORMAT "]\n",
+ __FILE__, __LINE__, (gint64) message_time));
+ d (printf (
+ "%s(%d): day_lag=[%d] \t days_to_delete=[%d]\n",
+ __FILE__, __LINE__, day_lag, days_to_delete));
+
+ if (day_lag >= days_to_delete) {
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ camel_pop3_engine_busy_unlock (pop3_engine);
+ g_clear_object (&pop3_cache);
+ g_clear_object (&pop3_engine);
+
+ return FALSE;
+ }
+
+ if (fi->cmd) {
+ while (camel_pop3_engine_iterate (pop3_engine, fi->cmd, cancellable, NULL) > 0) {
+ ; /* do nothing - iterating until end */
+ }
+
+ camel_pop3_engine_command_free (pop3_engine, fi->cmd);
+ fi->cmd = NULL;
+ }
+ d (printf (
+ "%s(%d): Deleting old messages\n",
+ __FILE__, __LINE__));
+ fi->cmd = camel_pop3_engine_command_new (
+ pop3_engine,
+ 0, NULL, NULL,
+ cancellable, NULL,
+ "DELE %u\r\n", fi->id);
+ /* also remove from cache */
+ if (pop3_cache != NULL && fi->uid) {
+ camel_data_cache_remove (pop3_cache, "cache", fi->uid, NULL);
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < pop3_folder->uids->len; i++) {
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ camel_pop3_engine_busy_unlock (pop3_engine);
+ g_clear_object (&pop3_cache);
+ g_clear_object (&pop3_engine);
+
+ return FALSE;
+ }
+
+ fi = pop3_folder->uids->pdata[i];
+ /* wait for delete commands to finish */
+ if (fi->cmd) {
+ while (camel_pop3_engine_iterate (pop3_engine, fi->cmd, cancellable, NULL) > 0)
+ ;
+ camel_pop3_engine_command_free (pop3_engine, fi->cmd);
+ fi->cmd = NULL;
+ }
+ camel_operation_progress (
+ cancellable, (i + 1) * 100 / pop3_folder->uids->len);
+ }
+
+ camel_pop3_engine_busy_unlock (pop3_engine);
+ g_clear_object (&pop3_cache);
+ g_clear_object (&pop3_engine);
+
+ return camel_pop3_store_expunge (pop3_store, cancellable, error);
+}
diff --git a/src/camel/providers/pop3/camel-pop3-folder.h b/src/camel/providers/pop3/camel-pop3-folder.h
new file mode 100644
index 000000000..2cd2ab14e
--- /dev/null
+++ b/src/camel/providers/pop3/camel-pop3-folder.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-pop3-folder.h : Class for a POP3 folder
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_POP3_FOLDER_H
+#define CAMEL_POP3_FOLDER_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_POP3_FOLDER \
+ (camel_pop3_folder_get_type ())
+#define CAMEL_POP3_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_POP3_FOLDER, CamelPOP3Folder))
+#define CAMEL_POP3_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_POP3_FOLDER, CamelPOP3FolderClass))
+#define CAMEL_IS_POP3_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_POP3_FOLDER))
+#define CAMEL_IS_POP3_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_POP3_FOLDER))
+#define CAMEL_POP3_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_POP3_FOLDER, CamelPOP3FolderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelPOP3Folder CamelPOP3Folder;
+typedef struct _CamelPOP3FolderClass CamelPOP3FolderClass;
+
+struct _CamelPOP3Folder {
+ CamelFolder parent;
+
+ GPtrArray *uids;
+
+ /* messageinfo uid to CamelPOP3FolderInfo *,
+ * which is stored in uids array */
+ GHashTable *uids_fi;
+
+ /* messageinfo by id */
+ GHashTable *uids_id;
+
+ GKeyFile *key_file;
+ gint fetch_more;
+ CamelFetchType fetch_type;
+ gint first_id;
+ gint latest_id;
+};
+
+struct _CamelPOP3FolderClass {
+ CamelFolderClass parent_class;
+};
+
+GType camel_pop3_folder_get_type (void);
+CamelFolder * camel_pop3_folder_new (CamelStore *parent,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_pop3_folder_delete_old (CamelFolder *folder,
+ gint days_to_delete,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_POP3_FOLDER_H */
diff --git a/src/camel/providers/pop3/camel-pop3-provider.c b/src/camel/providers/pop3/camel-pop3-provider.c
new file mode 100644
index 000000000..c3e7d1562
--- /dev/null
+++ b/src/camel/providers/pop3/camel-pop3-provider.c
@@ -0,0 +1,172 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-pop3-provider.c: pop3 provider registration code
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors :
+ * Dan Winship <danw@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-pop3-store.h"
+
+static guint pop3_url_hash (gconstpointer key);
+static gint pop3_url_equal (gconstpointer a, gconstpointer b);
+
+static CamelProviderConfEntry pop3_conf_entries[] = {
+ { CAMEL_PROVIDER_CONF_SECTION_START, "storage", NULL,
+ N_("Message Storage") },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "keep-on-server", NULL,
+ N_("_Leave messages on server"), "1" },
+ { CAMEL_PROVIDER_CONF_CHECKSPIN, "delete-after-days", "keep-on-server",
+ /* Translators: '%s' is replaced with a widget, where user can
+ * select how many days can be message left on the server. */
+ N_("_Delete after %s day(s)"), "0:1:7:365" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "delete-expunged", "keep-on-server",
+ N_("Delete _expunged from local Inbox"), "0" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "disable-extensions", NULL,
+ N_("Disable _support for all POP3 extensions"), "0" },
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+ { CAMEL_PROVIDER_CONF_END }
+};
+
+CamelProviderPortEntry pop3_port_entries[] = {
+ { 110, N_("Default POP3 port"), FALSE },
+ { 995, N_("POP3 over TLS"), TRUE },
+ { 0, NULL, 0 }
+};
+
+static CamelProvider pop3_provider = {
+ "pop",
+
+ N_("POP"),
+
+ N_("For connecting to and downloading mail from POP servers."),
+
+ "mail",
+
+ CAMEL_PROVIDER_IS_REMOTE | CAMEL_PROVIDER_IS_SOURCE |
+ CAMEL_PROVIDER_SUPPORTS_SSL |
+ CAMEL_PROVIDER_SUPPORTS_MOBILE_DEVICES,
+
+ CAMEL_URL_NEED_USER | CAMEL_URL_NEED_HOST | CAMEL_URL_ALLOW_AUTH,
+
+ pop3_conf_entries,
+
+ pop3_port_entries,
+
+ /* ... */
+};
+
+CamelServiceAuthType camel_pop3_password_authtype = {
+ N_("Password"),
+
+ N_("This option will connect to the POP server using a plaintext "
+ "password. This is the only option supported by many POP servers."),
+
+ "",
+ TRUE
+};
+
+CamelServiceAuthType camel_pop3_apop_authtype = {
+ "APOP",
+
+ N_("This option will connect to the POP server using an encrypted "
+ "password via the APOP protocol. This may not work for all users "
+ "even on servers that claim to support it."),
+
+ "+APOP",
+ TRUE
+};
+
+void
+camel_provider_module_init (void)
+{
+ CamelServiceAuthType *auth;
+
+ pop3_provider.object_types[CAMEL_PROVIDER_STORE] =
+ CAMEL_TYPE_POP3_STORE;
+ pop3_provider.url_hash = pop3_url_hash;
+ pop3_provider.url_equal = pop3_url_equal;
+
+ pop3_provider.authtypes = camel_sasl_authtype_list (FALSE);
+ auth = camel_sasl_authtype ("LOGIN");
+ if (auth)
+ pop3_provider.authtypes = g_list_prepend (
+ pop3_provider.authtypes, auth);
+ pop3_provider.authtypes = g_list_prepend (
+ pop3_provider.authtypes,
+ &camel_pop3_apop_authtype);
+ pop3_provider.authtypes = g_list_prepend (
+ pop3_provider.authtypes,
+ &camel_pop3_password_authtype);
+ pop3_provider.translation_domain = GETTEXT_PACKAGE;
+
+ camel_provider_register (&pop3_provider);
+}
+
+static void
+add_hash (guint *hash,
+ gchar *s)
+{
+ if (s)
+ *hash ^= g_str_hash(s);
+}
+
+static guint
+pop3_url_hash (gconstpointer key)
+{
+ const CamelURL *u = (CamelURL *) key;
+ guint hash = 0;
+
+ add_hash (&hash, u->user);
+ add_hash (&hash, u->host);
+ hash ^= u->port;
+
+ return hash;
+}
+
+static gint
+check_equal (gchar *s1,
+ gchar *s2)
+{
+ if (s1 == NULL) {
+ if (s2 == NULL)
+ return TRUE;
+ else
+ return FALSE;
+ }
+
+ if (s2 == NULL)
+ return FALSE;
+
+ return strcmp (s1, s2) == 0;
+}
+
+static gint
+pop3_url_equal (gconstpointer a,
+ gconstpointer b)
+{
+ const CamelURL *u1 = a, *u2 = b;
+
+ return check_equal (u1->protocol, u2->protocol)
+ && check_equal (u1->user, u2->user)
+ && check_equal (u1->host, u2->host)
+ && u1->port == u2->port;
+}
diff --git a/src/camel/providers/pop3/camel-pop3-settings.c b/src/camel/providers/pop3/camel-pop3-settings.c
new file mode 100644
index 000000000..e4e048fe3
--- /dev/null
+++ b/src/camel/providers/pop3/camel-pop3-settings.c
@@ -0,0 +1,543 @@
+/*
+ * camel-pop3-settings.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-pop3-settings.h"
+
+#define CAMEL_POP3_SETTINGS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_POP3_SETTINGS, CamelPOP3SettingsPrivate))
+
+struct _CamelPOP3SettingsPrivate {
+ gint delete_after_days;
+ gboolean delete_expunged;
+ gboolean disable_extensions;
+ gboolean keep_on_server;
+ gboolean auto_fetch;
+};
+
+enum {
+ PROP_0,
+ PROP_AUTH_MECHANISM,
+ PROP_DELETE_AFTER_DAYS,
+ PROP_DELETE_EXPUNGED,
+ PROP_DISABLE_EXTENSIONS,
+ PROP_HOST,
+ PROP_KEEP_ON_SERVER,
+ PROP_PORT,
+ PROP_SECURITY_METHOD,
+ PROP_USER,
+ PROP_AUTO_FETCH
+};
+
+G_DEFINE_TYPE_WITH_CODE (
+ CamelPOP3Settings,
+ camel_pop3_settings,
+ CAMEL_TYPE_STORE_SETTINGS,
+ G_IMPLEMENT_INTERFACE (
+ CAMEL_TYPE_NETWORK_SETTINGS, NULL))
+
+static void
+pop3_settings_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTH_MECHANISM:
+ camel_network_settings_set_auth_mechanism (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_DELETE_AFTER_DAYS:
+ camel_pop3_settings_set_delete_after_days (
+ CAMEL_POP3_SETTINGS (object),
+ g_value_get_int (value));
+ return;
+
+ case PROP_DELETE_EXPUNGED:
+ camel_pop3_settings_set_delete_expunged (
+ CAMEL_POP3_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_DISABLE_EXTENSIONS:
+ camel_pop3_settings_set_disable_extensions (
+ CAMEL_POP3_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_HOST:
+ camel_network_settings_set_host (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_KEEP_ON_SERVER:
+ camel_pop3_settings_set_keep_on_server (
+ CAMEL_POP3_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_PORT:
+ camel_network_settings_set_port (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_uint (value));
+ return;
+
+ case PROP_SECURITY_METHOD:
+ camel_network_settings_set_security_method (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_USER:
+ camel_network_settings_set_user (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_AUTO_FETCH:
+ camel_pop3_settings_set_auto_fetch (
+ CAMEL_POP3_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+pop3_settings_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTH_MECHANISM:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_auth_mechanism (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_DELETE_AFTER_DAYS:
+ g_value_set_int (
+ value,
+ camel_pop3_settings_get_delete_after_days (
+ CAMEL_POP3_SETTINGS (object)));
+ return;
+
+ case PROP_DELETE_EXPUNGED:
+ g_value_set_boolean (
+ value,
+ camel_pop3_settings_get_delete_expunged (
+ CAMEL_POP3_SETTINGS (object)));
+ return;
+
+ case PROP_DISABLE_EXTENSIONS:
+ g_value_set_boolean (
+ value,
+ camel_pop3_settings_get_disable_extensions (
+ CAMEL_POP3_SETTINGS (object)));
+ return;
+
+ case PROP_HOST:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_host (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_KEEP_ON_SERVER:
+ g_value_set_boolean (
+ value,
+ camel_pop3_settings_get_keep_on_server (
+ CAMEL_POP3_SETTINGS (object)));
+ return;
+
+ case PROP_PORT:
+ g_value_set_uint (
+ value,
+ camel_network_settings_get_port (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_SECURITY_METHOD:
+ g_value_set_enum (
+ value,
+ camel_network_settings_get_security_method (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_USER:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_user (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_AUTO_FETCH:
+ g_value_set_boolean (
+ value,
+ camel_pop3_settings_get_auto_fetch (
+ CAMEL_POP3_SETTINGS (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+camel_pop3_settings_class_init (CamelPOP3SettingsClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelPOP3SettingsPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = pop3_settings_set_property;
+ object_class->get_property = pop3_settings_get_property;
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_AUTH_MECHANISM,
+ "auth-mechanism");
+
+ /* Default to 0 days (i.e. do not delete) so we don't delete
+ * mail on the POP server when the user's not expecting it. */
+ g_object_class_install_property (
+ object_class,
+ PROP_DELETE_AFTER_DAYS,
+ g_param_spec_int (
+ "delete-after-days",
+ "Delete After Days",
+ "Delete messages left on server after N days",
+ 0,
+ 365,
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_DELETE_EXPUNGED,
+ g_param_spec_boolean (
+ "delete-expunged",
+ "Delete Expunged",
+ "Delete expunged from local Inbox",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_DISABLE_EXTENSIONS,
+ g_param_spec_boolean (
+ "disable-extensions",
+ "Disable Extensions",
+ "Disable support for all POP3 extensions",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_HOST,
+ "host");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_KEEP_ON_SERVER,
+ g_param_spec_boolean (
+ "keep-on-server",
+ "Keep On Server",
+ "Leave messages on POP3 server",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_AUTO_FETCH,
+ g_param_spec_boolean (
+ "auto-fetch",
+ "Auto Fetch mails",
+ "Automatically fetch additional mails that may be downloaded later.",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_PORT,
+ "port");
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_SECURITY_METHOD,
+ "security-method");
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_USER,
+ "user");
+}
+
+static void
+camel_pop3_settings_init (CamelPOP3Settings *settings)
+{
+ settings->priv = CAMEL_POP3_SETTINGS_GET_PRIVATE (settings);
+}
+
+/**
+ * camel_pop3_settings_get_delete_after_days:
+ * @settings: a #CamelPOP3Settings
+ *
+ * Returns the number of days to leave messages on the POP3 server before
+ * automatically deleting them. If the value is zero, messages will not
+ * be automatically deleted. The @settings's #CamelPOP3Settings:keep-on-server
+ * property must be %TRUE for this to have any effect.
+ *
+ * Returns: the number of days to leave messages on the server before
+ * automatically deleting them
+ *
+ * Since: 3.2
+ **/
+gint
+camel_pop3_settings_get_delete_after_days (CamelPOP3Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_POP3_SETTINGS (settings), 0);
+
+ return settings->priv->delete_after_days;
+}
+
+/**
+ * camel_pop3_settings_set_delete_after_days:
+ * @settings: a #CamelPOP3Settings
+ * @delete_after_days: the number of days to leave messages on the server
+ * before automatically deleting them
+ *
+ * Sets the number of days to leave messages on the POP3 server before
+ * automatically deleting them. If the value is zero, messages will not
+ * be automatically deleted. The @settings's #CamelPOP3Settings:keep-on-server
+ * property must be %TRUE for this to have any effect.
+ *
+ * Since: 3.2
+ **/
+void
+camel_pop3_settings_set_delete_after_days (CamelPOP3Settings *settings,
+ gint delete_after_days)
+{
+ g_return_if_fail (CAMEL_IS_POP3_SETTINGS (settings));
+
+ if (settings->priv->delete_after_days == delete_after_days)
+ return;
+
+ settings->priv->delete_after_days = delete_after_days;
+
+ g_object_notify (G_OBJECT (settings), "delete-after-days");
+}
+
+/**
+ * camel_pop3_settings_get_delete_expunged:
+ * @settings: a #CamelPOP3Settings
+ *
+ * Returns whether to delete corresponding messages left on the POP3 server
+ * when expunging the local #CamelSettings. The @settings's
+ * #CamelPOP3Settings:keep-on-server property must be %TRUE for this to have
+ * any effect.
+ *
+ * Returns: whether to delete corresponding messages on the server when
+ * expunging the local #CamelSettings
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_pop3_settings_get_delete_expunged (CamelPOP3Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_POP3_SETTINGS (settings), FALSE);
+
+ return settings->priv->delete_expunged;
+}
+
+/**
+ * camel_pop3_settings_set_delete_expunged:
+ * @settings: a #CamelPOP3Settings
+ * @delete_expunged: whether to delete corresponding messages on the
+ * server when expunging the local #CamelSettings
+ *
+ * Sets whether to delete corresponding messages left on the POP3 server
+ * when expunging the local #CamelSettings. The @settings's
+ * #CamelPOP3Settings:keep-on-server property must be %TRUE for this to have
+ * any effect.
+ *
+ * Since: 3.2
+ **/
+void
+camel_pop3_settings_set_delete_expunged (CamelPOP3Settings *settings,
+ gboolean delete_expunged)
+{
+ g_return_if_fail (CAMEL_IS_POP3_SETTINGS (settings));
+
+ if (settings->priv->delete_expunged == delete_expunged)
+ return;
+
+ settings->priv->delete_expunged = delete_expunged;
+
+ g_object_notify (G_OBJECT (settings), "delete-expunged");
+}
+
+/**
+ * camel_pop3_settings_get_disable_extensions:
+ * @settings: a #CamelPOP3Settings
+ *
+ * Returns whether to disable support for POP3 extensions. If %TRUE, the
+ * #CamelPOP3Engine will refrain from issuing a "CAPA" command to the server
+ * upon connection.
+ *
+ * Returns: whether to disable support for POP3 extensions
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_pop3_settings_get_disable_extensions (CamelPOP3Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_POP3_SETTINGS (settings), FALSE);
+
+ return settings->priv->disable_extensions;
+}
+
+/**
+ * camel_pop3_settings_set_disable_extensions:
+ * @settings: a #CamelPOP3Settings
+ * @disable_extensions: whether to disable support for POP3 extensions
+ *
+ * Sets whether to disable support for POP3 extensions. If %TRUE, the
+ * #CamelPOP3Engine will refrain from issuing a "CAPA" command to the server
+ * upon connection.
+ *
+ * Since: 3.2
+ **/
+void
+camel_pop3_settings_set_disable_extensions (CamelPOP3Settings *settings,
+ gboolean disable_extensions)
+{
+ g_return_if_fail (CAMEL_IS_POP3_SETTINGS (settings));
+
+ if (settings->priv->disable_extensions == disable_extensions)
+ return;
+
+ settings->priv->disable_extensions = disable_extensions;
+
+ g_object_notify (G_OBJECT (settings), "disable-extensions");
+}
+
+/**
+ * camel_pop3_settings_get_keep_on_server:
+ * @settings: a #CamelPOP3Settings
+ *
+ * Returns whether to leave messages on the remote POP3 server after
+ * downloading them to the local Inbox.
+ *
+ * Returns: whether to leave messages on the POP3 server
+ *
+ * Since: 3.2
+ **/
+gboolean
+camel_pop3_settings_get_keep_on_server (CamelPOP3Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_POP3_SETTINGS (settings), FALSE);
+
+ return settings->priv->keep_on_server;
+}
+
+/**
+ * camel_pop3_settings_set_keep_on_server:
+ * @settings: a #CamelPOP3Settings
+ * @keep_on_server: whether to leave messages on the POP3 server
+ *
+ * Sets whether to leave messages on the remote POP3 server after
+ * downloading them to the local Inbox.
+ *
+ * Since: 3.2
+ **/
+void
+camel_pop3_settings_set_keep_on_server (CamelPOP3Settings *settings,
+ gboolean keep_on_server)
+{
+ g_return_if_fail (CAMEL_IS_POP3_SETTINGS (settings));
+
+ if (settings->priv->keep_on_server == keep_on_server)
+ return;
+
+ settings->priv->keep_on_server = keep_on_server;
+
+ g_object_notify (G_OBJECT (settings), "keep-on-server");
+}
+
+/**
+ * camel_pop3_settings_get_auto_fetch :
+ * @settings: a #CamelPOP3Settings
+ *
+ * Returns whether to download additional mails that may be downloaded later on
+ *
+ * Returns: whether to download additional mails
+ *
+ * Since: 3.4
+ **/
+gboolean
+camel_pop3_settings_get_auto_fetch (CamelPOP3Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_POP3_SETTINGS (settings), FALSE);
+
+ return settings->priv->auto_fetch;
+}
+
+/**
+ * camel_pop3_settings_set_auto_fetch :
+ * @settings: a #CamelPOP3Settings
+ * @auto_fetch: whether to download additional mails
+ *
+ * Sets whether to download additional mails that may be downloaded later on
+ *
+ * Since: 3.4
+ **/
+void
+camel_pop3_settings_set_auto_fetch (CamelPOP3Settings *settings,
+ gboolean auto_fetch)
+{
+ g_return_if_fail (CAMEL_IS_POP3_SETTINGS (settings));
+
+ if (settings->priv->auto_fetch == auto_fetch)
+ return;
+
+ settings->priv->auto_fetch = auto_fetch;
+
+ g_object_notify (G_OBJECT (settings), "auto-fetch");
+}
+
diff --git a/src/camel/providers/pop3/camel-pop3-settings.h b/src/camel/providers/pop3/camel-pop3-settings.h
new file mode 100644
index 000000000..2f5d2f3c6
--- /dev/null
+++ b/src/camel/providers/pop3/camel-pop3-settings.h
@@ -0,0 +1,86 @@
+/*
+ * camel-pop3-settings.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_POP3_SETTINGS_H
+#define CAMEL_POP3_SETTINGS_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_POP3_SETTINGS \
+ (camel_pop3_settings_get_type ())
+#define CAMEL_POP3_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_POP3_SETTINGS, CamelPOP3Settings))
+#define CAMEL_POP3_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_POP3_SETTINGS, CamelPOP3SettingsClass))
+#define CAMEL_IS_POP3_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_POP3_SETTINGS))
+#define CAMEL_IS_POP3_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_POP3_SETTINGS))
+#define CAMEL_POP3_SETTINGS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_POP3_SETTINGS, CamelPOP3SettingsClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelPOP3Settings CamelPOP3Settings;
+typedef struct _CamelPOP3SettingsClass CamelPOP3SettingsClass;
+typedef struct _CamelPOP3SettingsPrivate CamelPOP3SettingsPrivate;
+
+struct _CamelPOP3Settings {
+ CamelStoreSettings parent;
+ CamelPOP3SettingsPrivate *priv;
+};
+
+struct _CamelPOP3SettingsClass {
+ CamelStoreSettingsClass parent_class;
+};
+
+GType camel_pop3_settings_get_type (void) G_GNUC_CONST;
+gint camel_pop3_settings_get_delete_after_days
+ (CamelPOP3Settings *settings);
+void camel_pop3_settings_set_delete_after_days
+ (CamelPOP3Settings *settings,
+ gint delete_after_days);
+gboolean camel_pop3_settings_get_delete_expunged
+ (CamelPOP3Settings *settings);
+void camel_pop3_settings_set_delete_expunged
+ (CamelPOP3Settings *settings,
+ gboolean delete_expunged);
+gboolean camel_pop3_settings_get_disable_extensions
+ (CamelPOP3Settings *settings);
+void camel_pop3_settings_set_disable_extensions
+ (CamelPOP3Settings *settings,
+ gboolean disable_extensions);
+gboolean camel_pop3_settings_get_keep_on_server
+ (CamelPOP3Settings *settings);
+void camel_pop3_settings_set_keep_on_server
+ (CamelPOP3Settings *settings,
+ gboolean keep_on_server);
+gboolean camel_pop3_settings_get_auto_fetch
+ (CamelPOP3Settings *settings);
+void camel_pop3_settings_set_auto_fetch
+ (CamelPOP3Settings *settings,
+ gboolean auto_fetch);
+
+G_END_DECLS
+
+#endif /* CAMEL_POP3_SETTINGS_H */
diff --git a/src/camel/providers/pop3/camel-pop3-store.c b/src/camel/providers/pop3/camel-pop3-store.c
new file mode 100644
index 000000000..7d9284796
--- /dev/null
+++ b/src/camel/providers/pop3/camel-pop3-store.c
@@ -0,0 +1,1249 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-pop3-store.c : class for a pop3 store
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <ctype.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-pop3-folder.h"
+#include "camel-pop3-settings.h"
+#include "camel-pop3-store.h"
+
+#ifdef G_OS_WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+
+#define CAMEL_POP3_STORE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_POP3_STORE, CamelPOP3StorePrivate))
+
+/* Specified in RFC 1939 */
+#define POP3_PORT 110
+#define POP3S_PORT 995
+
+/* defines the length of the server error message we can display in the error dialog */
+#define POP3_ERROR_SIZE_LIMIT 60
+
+struct _CamelPOP3StorePrivate {
+ GMutex property_lock;
+ CamelDataCache *cache;
+ CamelPOP3Engine *engine;
+};
+
+enum {
+ PROP_0,
+ PROP_CONNECTABLE,
+ PROP_HOST_REACHABLE
+};
+
+extern CamelServiceAuthType camel_pop3_password_authtype;
+extern CamelServiceAuthType camel_pop3_apop_authtype;
+
+/* Forward Declarations */
+static void camel_network_service_init (CamelNetworkServiceInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ CamelPOP3Store,
+ camel_pop3_store,
+ CAMEL_TYPE_STORE,
+ G_IMPLEMENT_INTERFACE (
+ CAMEL_TYPE_NETWORK_SERVICE,
+ camel_network_service_init))
+
+/* returns error message with ': ' as prefix */
+static gchar *
+get_valid_utf8_error (const gchar *text)
+{
+ gchar *tmp = camel_utf8_make_valid (text);
+ gchar *ret = NULL;
+
+ /*TODO If the error message > size limit log it somewhere */
+ if (!tmp || g_utf8_strlen (tmp, -1) > POP3_ERROR_SIZE_LIMIT) {
+ g_free (tmp);
+ return NULL;
+ }
+
+ /* Translators: This is the separator between an error and an explanation */
+ ret = g_strconcat (_(": "), tmp, NULL);
+
+ g_free (tmp);
+ return ret;
+}
+
+static gboolean
+connect_to_server (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelPOP3Store *store = CAMEL_POP3_STORE (service);
+ CamelNetworkSettings *network_settings;
+ CamelNetworkSecurityMethod method;
+ CamelSettings *settings;
+ CamelStream *stream = NULL;
+ CamelPOP3Engine *pop3_engine = NULL;
+ CamelPOP3Command *pc;
+ GIOStream *base_stream;
+ GIOStream *tls_stream;
+ gboolean disable_extensions;
+ gboolean success = TRUE;
+ gchar *host;
+ guint32 flags = 0;
+ gint ret;
+ GError *local_error = NULL;
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ method = camel_network_settings_get_security_method (network_settings);
+
+ disable_extensions = camel_pop3_settings_get_disable_extensions (
+ CAMEL_POP3_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ base_stream = camel_network_service_connect_sync (
+ CAMEL_NETWORK_SERVICE (service), cancellable, error);
+
+ if (base_stream != NULL) {
+ stream = camel_stream_new (base_stream);
+ g_object_unref (base_stream);
+ } else {
+ success = FALSE;
+ goto exit;
+ }
+
+ /* parent class connect initialization */
+ if (CAMEL_SERVICE_CLASS (camel_pop3_store_parent_class)->
+ connect_sync (service, cancellable, error) == FALSE) {
+ g_object_unref (stream);
+ success = FALSE;
+ goto exit;
+ }
+
+ if (disable_extensions)
+ flags |= CAMEL_POP3_ENGINE_DISABLE_EXTENSIONS;
+
+ if (!(pop3_engine = camel_pop3_engine_new (stream, flags, cancellable, &local_error)) ||
+ local_error != NULL) {
+ if (local_error)
+ g_propagate_error (error, local_error);
+ else
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Failed to read a valid greeting from POP server %s"),
+ host);
+ g_object_unref (stream);
+ success = FALSE;
+ goto exit;
+ }
+
+ if (method != CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT) {
+ g_object_unref (stream);
+ goto exit;
+ }
+
+ if (!(pop3_engine->capa & CAMEL_POP3_CAP_STLS)) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Failed to connect to POP server %s in secure mode: %s"),
+ host, _("STLS not supported by server"));
+ goto stls_exception;
+ }
+
+ pc = camel_pop3_engine_command_new (
+ pop3_engine, 0, NULL, NULL,
+ cancellable, error, "STLS\r\n");
+ while (camel_pop3_engine_iterate (pop3_engine, NULL, cancellable, NULL) > 0)
+ ;
+
+ ret = pc->state == CAMEL_POP3_COMMAND_OK;
+ camel_pop3_engine_command_free (pop3_engine, pc);
+
+ if (ret == FALSE) {
+ gchar *tmp;
+
+ tmp = get_valid_utf8_error ((gchar *) pop3_engine->line);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ /* Translators: Last %s is an optional
+ * explanation beginning with ": " separator. */
+ _("Failed to connect to POP server %s in secure mode%s"),
+ host, (tmp != NULL) ? tmp : "");
+ g_free (tmp);
+ goto stls_exception;
+ }
+
+ /* Okay, now toggle SSL/TLS mode */
+ base_stream = camel_stream_ref_base_stream (stream);
+ tls_stream = camel_network_service_starttls (
+ CAMEL_NETWORK_SERVICE (service), base_stream, error);
+ g_object_unref (base_stream);
+
+ if (tls_stream != NULL) {
+ camel_stream_set_base_stream (stream, tls_stream);
+ g_object_unref (tls_stream);
+ } else {
+ g_prefix_error (
+ error,
+ _("Failed to connect to POP server %s in secure mode: "),
+ host);
+ goto stls_exception;
+ }
+
+ g_clear_object (&stream);
+
+ /* rfc2595, section 4 states that after a successful STLS
+ * command, the client MUST discard prior CAPA responses */
+ if (!camel_pop3_engine_reget_capabilities (pop3_engine, cancellable, error))
+ goto exception;
+
+ goto exit;
+
+stls_exception:
+ /* As soon as we send a STLS command, all hope
+ * is lost of a clean QUIT if problems arise. */
+ /* if (clean_quit) {
+ / * try to disconnect cleanly * /
+ pc = camel_pop3_engine_command_new (
+ pop3_engine, 0, NULL, NULL,
+ cancellable, NULL, "QUIT\r\n");
+ while (camel_pop3_engine_iterate (pop3_engine, NULL, cancellable, NULL) > 0)
+ ;
+ camel_pop3_engine_command_free (pop3_engine, pc);
+ }*/
+
+exception:
+ g_clear_object (&stream);
+ g_clear_object (&pop3_engine);
+
+ success = FALSE;
+
+exit:
+ g_free (host);
+
+ g_mutex_lock (&store->priv->property_lock);
+ if (pop3_engine != NULL)
+ store->priv->engine = g_object_ref (pop3_engine);
+ g_mutex_unlock (&store->priv->property_lock);
+
+ g_clear_object (&pop3_engine);
+
+ return success;
+}
+
+static CamelAuthenticationResult
+try_sasl (CamelPOP3Store *store,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelPOP3Engine *pop3_engine;
+ CamelPOP3Stream *pop3_stream;
+ CamelNetworkSettings *network_settings;
+ CamelAuthenticationResult result;
+ CamelSettings *settings;
+ CamelService *service;
+ guchar *line, *resp;
+ CamelSasl *sasl = NULL;
+ gchar *string;
+ gchar *host;
+ guint len;
+ gint ret;
+
+ service = CAMEL_SERVICE (store);
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+
+ g_object_unref (settings);
+
+ pop3_engine = camel_pop3_store_ref_engine (store);
+ if (!pop3_engine) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+
+ pop3_stream = pop3_engine->stream;
+
+ sasl = camel_sasl_new ("pop", mechanism, service);
+ if (sasl == NULL) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_URL_INVALID,
+ _("No support for %s authentication"), mechanism);
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+
+ string = g_strdup_printf ("AUTH %s\r\n", g_strcmp0 (mechanism, "Google") == 0 ? "XOAUTH2" : mechanism);
+ ret = camel_stream_write_string (
+ CAMEL_STREAM (pop3_stream), string, cancellable, error);
+ g_free (string);
+
+ if (ret == -1)
+ goto ioerror;
+
+ while (1) {
+ GError *local_error = NULL;
+
+ if (camel_pop3_stream_line (pop3_stream, &line, &len, cancellable, error) == -1)
+ goto ioerror;
+
+ if (strncmp ((gchar *) line, "+OK", 3) == 0) {
+ result = CAMEL_AUTHENTICATION_ACCEPTED;
+ break;
+ }
+
+ if (strncmp ((gchar *) line, "-ERR", 4) == 0) {
+ result = CAMEL_AUTHENTICATION_REJECTED;
+ break;
+ }
+
+ /* 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 ((gchar *) line, "+ ", 2) != 0
+ || camel_sasl_get_authenticated (sasl)
+ || (resp = (guchar *) camel_sasl_challenge_base64_sync (sasl, (const gchar *) line + 2, cancellable, &local_error)) == NULL) {
+ if (camel_stream_write_string (CAMEL_STREAM (pop3_stream), "*\r\n", cancellable, NULL)) {
+ /* coverity[unchecked_value] */
+ camel_pop3_stream_line (pop3_stream, &line, &len, cancellable, NULL);
+ }
+
+ if (local_error) {
+ g_propagate_error (error, local_error);
+ local_error = NULL;
+ goto ioerror;
+ }
+
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Cannot login to POP server %s: "
+ "SASL Protocol error"), host);
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+
+ string = g_strdup_printf ("%s\r\n", resp);
+ ret = camel_stream_write_string (
+ CAMEL_STREAM (pop3_stream), string, cancellable, error);
+ g_free (string);
+
+ g_free (resp);
+
+ if (ret == -1)
+ goto ioerror;
+
+ }
+
+ goto exit;
+
+ioerror:
+ g_prefix_error (
+ error, _("Failed to authenticate on POP server %s: "), host);
+ result = CAMEL_AUTHENTICATION_ERROR;
+
+exit:
+ if (sasl != NULL)
+ g_object_unref (sasl);
+
+ g_free (host);
+
+ g_clear_object (&pop3_engine);
+
+ return result;
+}
+
+static void
+pop3_store_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONNECTABLE:
+ camel_network_service_set_connectable (
+ CAMEL_NETWORK_SERVICE (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+pop3_store_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONNECTABLE:
+ g_value_take_object (
+ value,
+ camel_network_service_ref_connectable (
+ CAMEL_NETWORK_SERVICE (object)));
+ return;
+
+ case PROP_HOST_REACHABLE:
+ g_value_set_boolean (
+ value,
+ camel_network_service_get_host_reachable (
+ CAMEL_NETWORK_SERVICE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+pop3_store_dispose (GObject *object)
+{
+ CamelPOP3StorePrivate *priv;
+
+ priv = CAMEL_POP3_STORE_GET_PRIVATE (object);
+
+ /* Force disconnect so we dont have it run
+ * later, after we've cleaned up some stuff. */
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (object), TRUE, NULL, NULL);
+
+ g_clear_object (&priv->cache);
+ g_clear_object (&priv->engine);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_pop3_store_parent_class)->dispose (object);
+}
+
+static void
+pop3_store_finalize (GObject *object)
+{
+ CamelPOP3StorePrivate *priv;
+
+ priv = CAMEL_POP3_STORE_GET_PRIVATE (object);
+
+ g_mutex_clear (&priv->property_lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_pop3_store_parent_class)->finalize (object);
+}
+
+static gchar *
+pop3_store_get_name (CamelService *service,
+ gboolean brief)
+{
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ gchar *host;
+ gchar *user;
+ gchar *name;
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ user = camel_network_settings_dup_user (network_settings);
+
+ g_object_unref (settings);
+
+ if (brief)
+ name = g_strdup_printf (
+ _("POP3 server %s"), host);
+ else
+ name = g_strdup_printf (
+ _("POP3 server for %s on %s"), user, host);
+
+ g_free (host);
+ g_free (user);
+
+ return name;
+}
+
+static gboolean
+pop3_store_connect_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelPOP3Store *store = (CamelPOP3Store *) service;
+ CamelPOP3Engine *pop3_engine;
+ CamelSettings *settings;
+ CamelSession *session;
+ const gchar *user_data_dir;
+ gboolean success = TRUE;
+ gchar *mechanism;
+
+ /* Chain up to parent's method. */
+ if (!CAMEL_SERVICE_CLASS (camel_pop3_store_parent_class)->connect_sync (service, cancellable, error))
+ return FALSE;
+
+ session = camel_service_ref_session (service);
+ user_data_dir = camel_service_get_user_data_dir (service);
+
+ settings = camel_service_ref_settings (service);
+
+ mechanism = camel_network_settings_dup_auth_mechanism (
+ CAMEL_NETWORK_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ if (!session || !camel_session_get_online (session)) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ success = FALSE;
+ goto exit;
+ }
+
+ g_mutex_lock (&store->priv->property_lock);
+
+ if (store->priv->cache == NULL) {
+ CamelDataCache *cache;
+
+ cache = camel_data_cache_new (user_data_dir, error);
+ if (cache != NULL) {
+ /* Ensure cache will never expire, otherwise
+ * it causes redownload of messages. */
+ camel_data_cache_set_expire_age (cache, -1);
+ camel_data_cache_set_expire_access (cache, -1);
+
+ store->priv->cache = g_object_ref (cache);
+
+ g_object_unref (cache);
+ }
+ }
+
+ g_mutex_unlock (&store->priv->property_lock);
+
+ success = connect_to_server (service, cancellable, error);
+
+ if (!success)
+ goto exit;
+
+ success = camel_session_authenticate_sync (
+ session, service, mechanism, cancellable, error);
+
+ if (!success)
+ goto exit;
+
+ /* Now that we are in the TRANSACTION state,
+ * try regetting the capabilities */
+ pop3_engine = camel_pop3_store_ref_engine (store);
+ if (pop3_engine) {
+ pop3_engine->state = CAMEL_POP3_ENGINE_TRANSACTION;
+ if (!camel_pop3_engine_reget_capabilities (pop3_engine, cancellable, error))
+ success = FALSE;
+ g_clear_object (&pop3_engine);
+ } else {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ success = FALSE;
+ }
+
+exit:
+ g_free (mechanism);
+
+ g_object_unref (session);
+
+ if (!success) {
+ /* to not leak possible connection to the server */
+ g_mutex_lock (&store->priv->property_lock);
+ g_clear_object (&store->priv->engine);
+ g_mutex_unlock (&store->priv->property_lock);
+ }
+
+ return success;
+}
+
+static gboolean
+pop3_store_disconnect_sync (CamelService *service,
+ gboolean clean,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelServiceClass *service_class;
+ CamelPOP3Store *store = CAMEL_POP3_STORE (service);
+ gboolean success;
+
+ if (clean) {
+ CamelPOP3Engine *pop3_engine;
+ CamelPOP3Command *pc;
+
+ pop3_engine = camel_pop3_store_ref_engine (store);
+
+ if (pop3_engine) {
+ if (camel_pop3_engine_busy_lock (pop3_engine, cancellable, NULL)) {
+ pc = camel_pop3_engine_command_new (
+ pop3_engine, 0, NULL, NULL,
+ cancellable, error, "QUIT\r\n");
+ while (camel_pop3_engine_iterate (pop3_engine, NULL, cancellable, NULL) > 0)
+ ;
+ camel_pop3_engine_command_free (pop3_engine, pc);
+ camel_pop3_engine_busy_unlock (pop3_engine);
+ }
+
+ g_clear_object (&pop3_engine);
+ }
+ }
+
+ /* Chain up to parent's disconnect() method. */
+ service_class = CAMEL_SERVICE_CLASS (camel_pop3_store_parent_class);
+
+ success = service_class->disconnect_sync (service, clean, cancellable, error);
+
+ g_mutex_lock (&store->priv->property_lock);
+ g_clear_object (&store->priv->engine);
+ g_mutex_unlock (&store->priv->property_lock);
+
+ return success;
+}
+
+static CamelAuthenticationResult
+pop3_store_authenticate_sync (CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelPOP3Store *store = CAMEL_POP3_STORE (service);
+ CamelNetworkSettings *network_settings;
+ CamelAuthenticationResult result;
+ CamelSettings *settings;
+ CamelPOP3Command *pcu = NULL;
+ CamelPOP3Command *pcp = NULL;
+ CamelPOP3Engine *pop3_engine;
+ const gchar *password;
+ gchar *host;
+ gchar *user;
+ gint status;
+
+ password = camel_service_get_password (service);
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ user = camel_network_settings_dup_user (network_settings);
+
+ g_object_unref (settings);
+
+ pop3_engine = camel_pop3_store_ref_engine (store);
+ if (!pop3_engine) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+
+ if (!camel_pop3_engine_busy_lock (pop3_engine, cancellable, error)) {
+ g_free (host);
+ g_free (user);
+ g_clear_object (&pop3_engine);
+
+ return CAMEL_AUTHENTICATION_ERROR;
+ }
+
+ if (mechanism == NULL) {
+ if (password == NULL) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Authentication password not available"));
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+
+ /* pop engine will take care of pipelining ability */
+ pcu = camel_pop3_engine_command_new (
+ pop3_engine, 0, NULL, NULL, cancellable, error,
+ "USER %s\r\n", user);
+ if (error && *error) {
+ g_prefix_error (
+ error,
+ _("Unable to connect to POP server %s.\n"
+ "Error sending password: "), host);
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+
+ pcp = camel_pop3_engine_command_new (
+ pop3_engine, 0, NULL, NULL, cancellable, error,
+ "PASS %s\r\n", password);
+
+ if (error && *error) {
+ g_prefix_error (
+ error,
+ _("Unable to connect to POP server %s.\n"
+ "Error sending password: "), host);
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+ } else if (strcmp (mechanism, "+APOP") == 0 && pop3_engine->apop) {
+ gchar *secret, *md5asc, *d;
+ gsize secret_len;
+
+ if (password == NULL) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Authentication password not available"));
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+
+ d = pop3_engine->apop;
+
+ while (*d != '\0') {
+ if (!isascii ((gint) * d)) {
+
+ /* Translators: Do not translate APOP. */
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_URL_INVALID,
+ _("Unable to connect to POP server %s: "
+ "Invalid APOP ID received. Impersonation "
+ "attack suspected. Please contact your admin."),
+ host);
+
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+ d++;
+ }
+
+ secret_len =
+ strlen (pop3_engine->apop) +
+ strlen (password) + 1;
+ secret = g_alloca (secret_len);
+ g_snprintf (
+ secret, secret_len, "%s%s",
+ pop3_engine->apop, password);
+ md5asc = g_compute_checksum_for_string (
+ G_CHECKSUM_MD5, secret, -1);
+ pcp = camel_pop3_engine_command_new (
+ pop3_engine, 0, NULL, NULL, cancellable, error,
+ "APOP %s %s\r\n", user, md5asc);
+ g_free (md5asc);
+
+ } else {
+ GList *link;
+ const gchar *test_mechanism = mechanism;
+
+ if (g_strcmp0 (test_mechanism, "Google") == 0)
+ test_mechanism = "XOAUTH2";
+
+ link = pop3_engine->auth;
+ while (link != NULL) {
+ CamelServiceAuthType *auth = link->data;
+
+ if (g_strcmp0 (auth->authproto, test_mechanism) == 0) {
+ result = try_sasl (
+ store, mechanism,
+ cancellable, error);
+ goto exit;
+ }
+ link = g_list_next (link);
+ }
+
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("No support for %s authentication"), mechanism);
+ result = CAMEL_AUTHENTICATION_ERROR;
+ goto exit;
+ }
+
+ while ((status = camel_pop3_engine_iterate (pop3_engine, pcp, cancellable, error)) > 0)
+ ;
+
+ if (status == -1) {
+ g_prefix_error (
+ error,
+ _("Unable to connect to POP server %s.\n"
+ "Error sending password: "), host);
+ result = CAMEL_AUTHENTICATION_ERROR;
+
+ } else if (pcu && pcu->state != CAMEL_POP3_COMMAND_OK) {
+ gchar *tmp;
+
+ /* Abort authentication if the server rejects the user
+ * name. Reprompting for a password won't do any good. */
+ tmp = get_valid_utf8_error ((gchar *) pop3_engine->line);
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ /* Translators: Last %s is an optional explanation
+ * beginning with ": " separator. */
+ _("Unable to connect to POP server %s.\n"
+ "Error sending username%s"),
+ host, (tmp != NULL) ? tmp : "");
+ g_free (tmp);
+ result = CAMEL_AUTHENTICATION_ERROR;
+
+ } else if (pcp->state != CAMEL_POP3_COMMAND_OK) {
+ result = CAMEL_AUTHENTICATION_REJECTED;
+ } else {
+ result = CAMEL_AUTHENTICATION_ACCEPTED;
+ }
+
+exit:
+ if (pcp != NULL)
+ camel_pop3_engine_command_free (pop3_engine, pcp);
+
+ if (pcu != NULL)
+ camel_pop3_engine_command_free (pop3_engine, pcu);
+
+ g_free (host);
+ g_free (user);
+
+ camel_pop3_engine_busy_unlock (pop3_engine);
+ g_clear_object (&pop3_engine);
+
+ return result;
+}
+
+static GList *
+pop3_store_query_auth_types_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelServiceClass *service_class;
+ CamelPOP3Store *store = CAMEL_POP3_STORE (service);
+ GList *types = NULL;
+ GError *local_error = NULL;
+
+ /* Chain up to parent's query_auth_types() method. */
+ service_class = CAMEL_SERVICE_CLASS (camel_pop3_store_parent_class);
+ types = service_class->query_auth_types_sync (
+ service, cancellable, &local_error);
+
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ return NULL;
+ }
+
+ if (connect_to_server (service, cancellable, error)) {
+ CamelPOP3Engine *pop3_engine;
+
+ pop3_engine = camel_pop3_store_ref_engine (store);
+
+ if (pop3_engine) {
+ types = g_list_concat (types, g_list_copy (pop3_engine->auth));
+ pop3_store_disconnect_sync (service, TRUE, cancellable, NULL);
+
+ g_clear_object (&pop3_engine);
+ }
+ }
+
+ return types;
+}
+
+static gboolean
+pop3_store_can_refresh_folder (CamelStore *store,
+ CamelFolderInfo *info,
+ GError **error)
+{
+ /* any pop3 folder can be refreshed */
+ return TRUE;
+}
+
+static CamelFolder *
+pop3_store_get_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ CamelStoreGetFolderFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (g_ascii_strcasecmp (folder_name, "inbox") != 0) {
+ g_set_error (
+ error, CAMEL_FOLDER_ERROR,
+ CAMEL_FOLDER_ERROR_INVALID,
+ _("No such folder '%s'."), folder_name);
+ return NULL;
+ }
+
+ return camel_pop3_folder_new (store, cancellable, error);
+}
+
+static CamelFolderInfo *
+pop3_store_get_folder_info_sync (CamelStore *store,
+ const gchar *top,
+ CamelStoreGetFolderInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("POP3 stores have no folder hierarchy"));
+
+ return NULL;
+}
+
+static CamelFolder *
+pop3_store_get_trash_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* no-op */
+ return NULL;
+}
+
+static const gchar *
+pop3_store_get_service_name (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method)
+{
+ const gchar *service_name;
+
+ switch (method) {
+ case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
+ service_name = "pop3s";
+ break;
+
+ default:
+ service_name = "pop3";
+ break;
+ }
+
+ return service_name;
+}
+
+static guint16
+pop3_store_get_default_port (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method)
+{
+ guint16 default_port;
+
+ switch (method) {
+ case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
+ default_port = POP3S_PORT;
+ break;
+
+ default:
+ default_port = POP3_PORT;
+ break;
+ }
+
+ return default_port;
+}
+
+static void
+camel_pop3_store_class_init (CamelPOP3StoreClass *class)
+{
+ GObjectClass *object_class;
+ CamelServiceClass *service_class;
+ CamelStoreClass *store_class;
+
+ g_type_class_add_private (class, sizeof (CamelPOP3StorePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = pop3_store_set_property;
+ object_class->get_property = pop3_store_get_property;
+ object_class->dispose = pop3_store_dispose;
+ object_class->finalize = pop3_store_finalize;
+
+ service_class = CAMEL_SERVICE_CLASS (class);
+ service_class->settings_type = CAMEL_TYPE_POP3_SETTINGS;
+ service_class->get_name = pop3_store_get_name;
+ service_class->connect_sync = pop3_store_connect_sync;
+ service_class->disconnect_sync = pop3_store_disconnect_sync;
+ service_class->authenticate_sync = pop3_store_authenticate_sync;
+ service_class->query_auth_types_sync = pop3_store_query_auth_types_sync;
+
+ store_class = CAMEL_STORE_CLASS (class);
+ store_class->can_refresh_folder = pop3_store_can_refresh_folder;
+ store_class->get_folder_sync = pop3_store_get_folder_sync;
+ store_class->get_folder_info_sync = pop3_store_get_folder_info_sync;
+ store_class->get_trash_folder_sync = pop3_store_get_trash_folder_sync;
+
+ /* Inherited from CamelNetworkService. */
+ g_object_class_override_property (
+ object_class,
+ PROP_CONNECTABLE,
+ "connectable");
+
+ /* Inherited from CamelNetworkService. */
+ g_object_class_override_property (
+ object_class,
+ PROP_HOST_REACHABLE,
+ "host-reachable");
+}
+
+static void
+camel_network_service_init (CamelNetworkServiceInterface *iface)
+{
+ iface->get_service_name = pop3_store_get_service_name;
+ iface->get_default_port = pop3_store_get_default_port;
+}
+
+static void
+camel_pop3_store_init (CamelPOP3Store *pop3_store)
+{
+ pop3_store->priv = CAMEL_POP3_STORE_GET_PRIVATE (pop3_store);
+
+ g_mutex_init (&pop3_store->priv->property_lock);
+}
+
+/**
+ * camel_pop3_store_ref_cache:
+ * @store: a #CamelPOP3Store
+ *
+ * Returns the #CamelDataCache for @store.
+ *
+ * The returned #CamelDataCache is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelDataCache
+ **/
+CamelDataCache *
+camel_pop3_store_ref_cache (CamelPOP3Store *store)
+{
+ CamelDataCache *cache = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_POP3_STORE (store), NULL);
+
+ g_mutex_lock (&store->priv->property_lock);
+
+ if (store->priv->cache != NULL)
+ cache = g_object_ref (store->priv->cache);
+
+ g_mutex_unlock (&store->priv->property_lock);
+
+ return cache;
+}
+
+/**
+ * camel_pop3_store_ref_engine:
+ * @store: a #CamelPOP3Store
+ *
+ * Returns the #CamelPOP3Engine for @store.
+ *
+ * The returned #CamelPOP3Engine is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: a #CamelPOP3Store
+ **/
+CamelPOP3Engine *
+camel_pop3_store_ref_engine (CamelPOP3Store *store)
+{
+ CamelPOP3Engine *engine = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_POP3_STORE (store), NULL);
+
+ g_mutex_lock (&store->priv->property_lock);
+
+ if (store->priv->engine != NULL)
+ engine = g_object_ref (store->priv->engine);
+
+ g_mutex_unlock (&store->priv->property_lock);
+
+ return engine;
+}
+
+/**
+ * camel_pop3_store_expunge:
+ * @store: a #CamelPOP3Store
+ * @error: return location for a #GError, or %NULL
+ * @cancellable: optional #GCancellable object, or %NULL
+ *
+ * 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.
+ **/
+gboolean
+camel_pop3_store_expunge (CamelPOP3Store *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelPOP3Command *pc;
+ CamelPOP3Engine *pop3_engine;
+ CamelServiceConnectionStatus status;
+
+ status = camel_service_get_connection_status (CAMEL_SERVICE (store));
+
+ if (status != CAMEL_SERVICE_CONNECTED) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ return FALSE;
+ }
+
+ pop3_engine = camel_pop3_store_ref_engine (store);
+
+ if (!camel_pop3_engine_busy_lock (pop3_engine, cancellable, error)) {
+ g_clear_object (&pop3_engine);
+ return FALSE;
+ }
+
+ pc = camel_pop3_engine_command_new (
+ pop3_engine, 0, NULL, NULL, cancellable, error, "QUIT\r\n");
+
+ while (camel_pop3_engine_iterate (pop3_engine, NULL, cancellable, NULL) > 0)
+ ;
+
+ camel_pop3_engine_command_free (pop3_engine, pc);
+
+ camel_pop3_engine_busy_unlock (pop3_engine);
+ g_clear_object (&pop3_engine);
+
+ return TRUE;
+}
+
+/**
+ * camel_pop3_store_cache_add:
+ * @store: a #CamelPOP3Store
+ * @uid: a message UID
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a cache file for @uid in @store and returns a #CamelStream for it.
+ * If an error occurs in opening the cache file, the function sets @error and
+ * returns %NULL.
+ *
+ * The returned #CamelStream is referenced for thread-safety and must be
+ * unreferenced when finished with it.
+ *
+ * Returns: a #CamelStream, or %NULL
+ **/
+CamelStream *
+camel_pop3_store_cache_add (CamelPOP3Store *store,
+ const gchar *uid,
+ GError **error)
+{
+ CamelDataCache *cache;
+ GIOStream *base_stream;
+ CamelStream *stream = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_POP3_STORE (store), NULL);
+ g_return_val_if_fail (uid != NULL, NULL);
+
+ cache = camel_pop3_store_ref_cache (store);
+ g_return_val_if_fail (cache != NULL, NULL);
+
+ base_stream = camel_data_cache_add (cache, "cache", uid, error);
+ if (base_stream != NULL) {
+ stream = camel_stream_new (base_stream);
+ g_object_unref (base_stream);
+ }
+
+ g_object_unref (cache);
+
+ return stream;
+}
+
+/**
+ * camel_pop3_store_cache_get:
+ * @store: a #CamelPOP3Store
+ * @uid: a message UID
+ * @error: return location for a #GError, or %NULL
+ *
+ * Opens the cache file for @uid in @store and returns a #CamelStream for it.
+ * If no matching cache file exists, the function returns %NULL. If an error
+ * occurs in opening the cache file, the function sets @error and returns
+ * %NULL.
+ *
+ * The returned #CamelStream is referenced for thread-safety and must be
+ * unreferenced when finished with it.
+ *
+ * Returns: a #CamelStream, or %NULL
+ **/
+CamelStream *
+camel_pop3_store_cache_get (CamelPOP3Store *store,
+ const gchar *uid,
+ GError **error)
+{
+ CamelDataCache *cache;
+ GIOStream *base_stream;
+ CamelStream *stream = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_POP3_STORE (store), NULL);
+ g_return_val_if_fail (uid != NULL, NULL);
+
+ cache = camel_pop3_store_ref_cache (store);
+ g_return_val_if_fail (cache != NULL, NULL);
+
+ base_stream = camel_data_cache_get (cache, "cache", uid, error);
+ if (base_stream != NULL) {
+ GInputStream *input_stream;
+ gchar buffer[1];
+ gssize n_bytes;
+
+ input_stream = g_io_stream_get_input_stream (base_stream);
+
+ n_bytes = g_input_stream_read (
+ input_stream, buffer, 1, NULL, error);
+
+ if (n_bytes == 1 && buffer[0] == '#')
+ stream = camel_stream_new (base_stream);
+
+ g_object_unref (base_stream);
+ }
+
+ g_object_unref (cache);
+
+ return stream;
+}
+
+/**
+ * camel_pop3_store_cache_has:
+ * @store: a #CamelPOP3Store
+ * @uid: a message UID
+ *
+ * Returns whether @store has a cached message for @uid.
+ *
+ * Returns: whether @uid is cached
+ **/
+gboolean
+camel_pop3_store_cache_has (CamelPOP3Store *store,
+ const gchar *uid)
+{
+ CamelStream *stream;
+ gboolean uid_is_cached;
+
+ g_return_val_if_fail (CAMEL_IS_POP3_STORE (store), FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+
+ stream = camel_pop3_store_cache_get (store, uid, NULL);
+ uid_is_cached = (stream != NULL);
+ g_clear_object (&stream);
+
+ return uid_is_cached;
+}
+
diff --git a/src/camel/providers/pop3/camel-pop3-store.h b/src/camel/providers/pop3/camel-pop3-store.h
new file mode 100644
index 000000000..58432d6bc
--- /dev/null
+++ b/src/camel/providers/pop3/camel-pop3-store.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-pop3-store.h : class for an pop3 store
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ */
+
+#ifndef CAMEL_POP3_STORE_H
+#define CAMEL_POP3_STORE_H
+
+#include <camel/camel.h>
+
+#include "camel-pop3-engine.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_POP3_STORE \
+ (camel_pop3_store_get_type ())
+#define CAMEL_POP3_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_POP3_STORE, CamelPOP3Store))
+#define CAMEL_POP3_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_POP3_STORE, CamelPOP3StoreClass))
+#define CAMEL_IS_POP3_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_POP3_STORE))
+#define CAMEL_IS_POP3_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_POP3_STORE))
+#define CAMEL_POP3_STORE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_POP3_STORE, CamelPOP3StoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelPOP3Store CamelPOP3Store;
+typedef struct _CamelPOP3StoreClass CamelPOP3StoreClass;
+typedef struct _CamelPOP3StorePrivate CamelPOP3StorePrivate;
+
+struct _CamelPOP3Store {
+ CamelStore parent;
+ CamelPOP3StorePrivate *priv;
+};
+
+struct _CamelPOP3StoreClass {
+ CamelStoreClass parent_class;
+};
+
+GType camel_pop3_store_get_type (void);
+CamelDataCache *
+ camel_pop3_store_ref_cache (CamelPOP3Store *store);
+CamelPOP3Engine *
+ camel_pop3_store_ref_engine (CamelPOP3Store *store);
+gboolean camel_pop3_store_expunge (CamelPOP3Store *store,
+ GCancellable *cancellable,
+ GError **error);
+CamelStream * camel_pop3_store_cache_add (CamelPOP3Store *store,
+ const gchar *uid,
+ GError **error);
+CamelStream * camel_pop3_store_cache_get (CamelPOP3Store *store,
+ const gchar *uid,
+ GError **error);
+gboolean camel_pop3_store_cache_has (CamelPOP3Store *store,
+ const gchar *uid);
+
+G_END_DECLS
+
+#endif /* CAMEL_POP3_STORE_H */
+
diff --git a/src/camel/providers/pop3/camel-pop3-stream.c b/src/camel/providers/pop3/camel-pop3-stream.c
new file mode 100644
index 000000000..69398e5ac
--- /dev/null
+++ b/src/camel/providers/pop3/camel-pop3-stream.c
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/* This is *identical* to the camel-nntp-stream, so should probably
+ * work out a way to merge them */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "camel-pop3-stream.h"
+
+#define dd(x) (camel_debug ("pop3")?(x):0)
+
+#define CAMEL_POP3_STREAM_SIZE (4096)
+#define CAMEL_POP3_STREAM_LINE_SIZE (1024) /* maximum line size */
+
+G_DEFINE_TYPE (CamelPOP3Stream, camel_pop3_stream, CAMEL_TYPE_STREAM)
+
+static void
+pop3_stream_dispose (GObject *object)
+{
+ CamelPOP3Stream *stream = CAMEL_POP3_STREAM (object);
+
+ if (stream->source != NULL) {
+ g_object_unref (stream->source);
+ stream->source = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (camel_pop3_stream_parent_class)->dispose (object);
+}
+
+static void
+pop3_stream_finalize (GObject *object)
+{
+ CamelPOP3Stream *stream = CAMEL_POP3_STREAM (object);
+
+ g_free (stream->buf);
+ g_free (stream->linebuf);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_pop3_stream_parent_class)->finalize (object);
+}
+
+static gint
+stream_fill (CamelPOP3Stream *is,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint left = 0;
+ GError *local_error = NULL;
+
+ if (is->source) {
+ left = is->end - is->ptr;
+ memmove (is->buf, is->ptr, left);
+ is->end = is->buf + left;
+ is->ptr = is->buf;
+ left = camel_stream_read (
+ is->source, (gchar *) is->end,
+ CAMEL_POP3_STREAM_SIZE - (is->end - is->buf),
+ cancellable, &local_error);
+
+ /* It's the End Of Stream marker */
+ if (left == 0 && !local_error) {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE,
+ g_strerror (
+ #ifdef EPIPE
+ EPIPE
+ #else
+ 32 /* Also EPIPE; it should be always available, but just in case it isn't */
+ #endif
+ ));
+ } else if (local_error) {
+ g_propagate_error (error, local_error);
+ left = 0;
+ }
+
+ if (left > 0) {
+ is->end += left;
+ is->end[0] = '\n';
+ return is->end - is->ptr;
+ } else
+ return -1;
+ }
+
+ return 0;
+}
+
+static gssize
+stream_read (CamelStream *stream,
+ gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelPOP3Stream *is = (CamelPOP3Stream *) stream;
+ gchar *o, *oe;
+ guchar *p, *e, c;
+ gint state;
+
+ if (is->mode != CAMEL_POP3_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, cancellable, error) == -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_POP3_STREAM_EOD;
+ is->state = 0;
+ 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, cancellable, error) == -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;
+
+ return o - buffer;
+}
+
+static gssize
+stream_write (CamelStream *stream,
+ const gchar *buffer,
+ gsize n,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelPOP3Stream *is = (CamelPOP3Stream *) stream;
+
+ if (strncmp (buffer, "PASS ", 5) != 0)
+ dd (printf ("POP3_STREAM_WRITE (%d):\n%.*s\n", (gint) n, (gint) n, buffer));
+ else
+ dd (printf ("POP3_STREAM_WRITE (%d):\nPASS xxxxxxxx\n", (gint) n));
+
+ return camel_stream_write (is->source, buffer, n, cancellable, error);
+}
+
+static gint
+stream_close (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* nop? */
+ return 0;
+}
+
+static gint
+stream_flush (CamelStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* nop? */
+ return 0;
+}
+
+static gboolean
+stream_eos (CamelStream *stream)
+{
+ CamelPOP3Stream *is = (CamelPOP3Stream *) stream;
+
+ return is->mode != CAMEL_POP3_STREAM_DATA;
+}
+
+static void
+camel_pop3_stream_class_init (CamelPOP3StreamClass *class)
+{
+ GObjectClass *object_class;
+ CamelStreamClass *stream_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = pop3_stream_dispose;
+ object_class->finalize = pop3_stream_finalize;
+
+ stream_class = CAMEL_STREAM_CLASS (class);
+ stream_class->read = stream_read;
+ stream_class->write = stream_write;
+ stream_class->close = stream_close;
+ stream_class->flush = stream_flush;
+ stream_class->eos = stream_eos;
+}
+
+static void
+camel_pop3_stream_init (CamelPOP3Stream *is)
+{
+ /* +1 is room for appending a 0 if we need to for a line */
+ is->ptr = is->end = is->buf = g_malloc (CAMEL_POP3_STREAM_SIZE + 1);
+ is->lineptr = is->linebuf = g_malloc (CAMEL_POP3_STREAM_LINE_SIZE + 1);
+ is->lineend = is->linebuf + CAMEL_POP3_STREAM_LINE_SIZE;
+
+ /* init sentinal */
+ is->ptr[0] = '\n';
+
+ is->state = 0;
+ is->mode = CAMEL_POP3_STREAM_LINE;
+}
+
+/**
+ * camel_pop3_stream_new:
+ *
+ * Returns a NULL stream. A null stream is always at eof, and
+ * always returns success for all reads and writes.
+ *
+ * Returns: the stream
+ **/
+CamelStream *
+camel_pop3_stream_new (CamelStream *source)
+{
+ CamelPOP3Stream *is;
+
+ is = g_object_new (CAMEL_TYPE_POP3_STREAM, NULL);
+ is->source = g_object_ref (source);
+
+ return (CamelStream *) is;
+}
+
+/* Get one line from the pop3 stream */
+gint
+camel_pop3_stream_line (CamelPOP3Stream *is,
+ guchar **data,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error)
+{
+ register guchar c, *p, *o, *oe;
+ gint newlen, oldlen;
+ guchar *e;
+
+ if (is->mode == CAMEL_POP3_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_POP3_STREAM_DATA) {
+ /* need at least 3 chars in buffer */
+ while (e - p < 3) {
+ is->ptr = p;
+ if (stream_fill (is, cancellable, error) == -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_POP3_STREAM_EOD;
+ *data = is->linebuf;
+ *len = 0;
+ is->linebuf[0] = 0;
+
+ dd (printf ("POP3_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, cancellable, error) == -1)
+ return -1;
+ p = is->ptr;
+ e = is->end;
+ } else {
+ is->ptr = p;
+ *data = is->linebuf;
+ *len = o - is->linebuf;
+ *o = 0;
+
+ dd (printf ("POP3_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;
+}
+
+void
+camel_pop3_stream_set_mode (CamelPOP3Stream *is,
+ camel_pop3_stream_mode_t mode)
+{
+ is->mode = mode;
+}
+
+/* returns -1 on erorr, 0 if last data, >0 if more data left */
+gint
+camel_pop3_stream_getd (CamelPOP3Stream *is,
+ guchar **start,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guchar *p, *e, *s;
+ gint state;
+
+ *len = 0;
+
+ if (is->mode == CAMEL_POP3_STREAM_EOD)
+ return 0;
+
+ if (is->mode == CAMEL_POP3_STREAM_LINE) {
+ g_warning ("pop3_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, cancellable, error) == -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_POP3_STREAM_EOD;
+ is->state = 0;
+
+ 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;
+
+ return 1;
+ }
+ }
+ state = 1;
+ break;
+ 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;
+
+ return 1;
+}
diff --git a/src/camel/providers/pop3/camel-pop3-stream.h b/src/camel/providers/pop3/camel-pop3-stream.h
new file mode 100644
index 000000000..2c4a51a59
--- /dev/null
+++ b/src/camel/providers/pop3/camel-pop3-stream.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ */
+
+/* This is *identical* to the camel-nntp-stream, so should probably
+ * work out a way to merge them */
+
+#ifndef CAMEL_POP3_STREAM_H
+#define CAMEL_POP3_STREAM_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_POP3_STREAM \
+ (camel_pop3_stream_get_type ())
+#define CAMEL_POP3_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_POP3_STREAM, CamelPOP3Stream))
+#define CAMEL_POP3_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_POP3_STREAM, CamelPOP3StreamClass))
+#define CAMEL_IS_POP3_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_POP3_STREAM))
+#define CAMEL_IS_POP3_STREAM_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_POP3_STREAM))
+#define CAMEL_POP3_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_POP3_STREAM, CamelPOP3StreamClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelPOP3Stream CamelPOP3Stream;
+typedef struct _CamelPOP3StreamClass CamelPOP3StreamClass;
+
+typedef enum {
+ CAMEL_POP3_STREAM_LINE,
+ CAMEL_POP3_STREAM_DATA,
+ CAMEL_POP3_STREAM_EOD /* end of data, acts as if end of stream */
+} camel_pop3_stream_mode_t;
+
+struct _CamelPOP3Stream {
+ CamelStream parent;
+
+ CamelStream *source;
+
+ camel_pop3_stream_mode_t mode;
+ gint state;
+
+ guchar *buf, *ptr, *end;
+ guchar *linebuf, *lineptr, *lineend;
+};
+
+struct _CamelPOP3StreamClass {
+ CamelStreamClass parent_class;
+};
+
+GType camel_pop3_stream_get_type (void);
+CamelStream * camel_pop3_stream_new (CamelStream *source);
+void camel_pop3_stream_set_mode (CamelPOP3Stream *is,
+ camel_pop3_stream_mode_t mode);
+gint camel_pop3_stream_line (CamelPOP3Stream *is,
+ guchar **data,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error);
+gint camel_pop3_stream_getd (CamelPOP3Stream *is,
+ guchar **start,
+ guint *len,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* CAMEL_POP3_STREAM_H */
diff --git a/src/camel/providers/pop3/libcamelpop3.urls b/src/camel/providers/pop3/libcamelpop3.urls
new file mode 100644
index 000000000..7fffa4d86
--- /dev/null
+++ b/src/camel/providers/pop3/libcamelpop3.urls
@@ -0,0 +1 @@
+pop
diff --git a/src/camel/providers/sendmail/CMakeLists.txt b/src/camel/providers/sendmail/CMakeLists.txt
new file mode 100644
index 000000000..2c4d6cac0
--- /dev/null
+++ b/src/camel/providers/sendmail/CMakeLists.txt
@@ -0,0 +1,49 @@
+set(SOURCES
+ camel-sendmail-settings.c
+ camel-sendmail-settings.h
+ camel-sendmail-provider.c
+ camel-sendmail-transport.c
+ camel-sendmail-transport.h
+)
+
+set(DEPENDENCIES
+ camel
+)
+
+add_library(camelsendmail MODULE ${SOURCES})
+
+add_dependencies(camelsendmail
+ ${DEPENDENCIES}
+)
+
+target_compile_definitions(camelsendmail PRIVATE
+ -DG_LOG_DOMAIN=\"camel-sendmail-provider\"
+)
+
+target_compile_options(camelsendmail PUBLIC
+ ${CAMEL_CFLAGS}
+)
+
+target_include_directories(camelsendmail PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/camel
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_SOURCE_DIR}/src/camel
+ ${CAMEL_INCLUDE_DIRS}
+)
+
+target_link_libraries(camelsendmail
+ ${DEPENDENCIES}
+ ${CAMEL_LDFLAGS}
+)
+
+install(TARGETS camelsendmail
+ DESTINATION ${camel_providerdir}
+)
+
+install(FILES libcamelsendmail.urls
+ DESTINATION ${camel_providerdir}
+)
+
+unset(SOURCES)
diff --git a/src/camel/providers/sendmail/camel-sendmail-provider.c b/src/camel/providers/sendmail/camel-sendmail-provider.c
new file mode 100644
index 000000000..7a8124a0a
--- /dev/null
+++ b/src/camel/providers/sendmail/camel-sendmail-provider.c
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-sendmail-provider.c: sendmail provider registration code
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors :
+ * Dan Winship <danw@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <camel/camel.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-sendmail-transport.h"
+
+static CamelProvider sendmail_provider = {
+ "sendmail",
+ N_("Sendmail"),
+
+ N_("For delivering mail by passing it to the \"sendmail\" program "
+ "on the local system."),
+
+ "mail",
+
+ 0, /* flags */
+
+ 0, /* url_flags */
+
+ NULL, /* conf entries */
+
+ NULL, /* port entries */
+
+ /* ... */
+};
+
+void
+camel_provider_module_init (void)
+{
+ sendmail_provider.object_types[CAMEL_PROVIDER_TRANSPORT] =
+ CAMEL_TYPE_SENDMAIL_TRANSPORT;
+
+ sendmail_provider.url_hash = camel_url_hash;
+ sendmail_provider.url_equal = camel_url_equal;
+ sendmail_provider.translation_domain = GETTEXT_PACKAGE;
+
+ camel_provider_register (&sendmail_provider);
+}
+
diff --git a/src/camel/providers/sendmail/camel-sendmail-settings.c b/src/camel/providers/sendmail/camel-sendmail-settings.c
new file mode 100644
index 000000000..856d07791
--- /dev/null
+++ b/src/camel/providers/sendmail/camel-sendmail-settings.c
@@ -0,0 +1,515 @@
+/*
+ * camel-sendmail-settings.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-sendmail-settings.h"
+
+#define CAMEL_SENDMAIL_SETTINGS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SENDMAIL_SETTINGS, CamelSendmailSettingsPrivate))
+
+struct _CamelSendmailSettingsPrivate {
+ GMutex property_lock;
+ gchar *custom_binary;
+ gchar *custom_args;
+
+ gboolean use_custom_binary;
+ gboolean use_custom_args;
+ gboolean send_in_offline;
+};
+
+enum {
+ PROP_0,
+ PROP_USE_CUSTOM_BINARY,
+ PROP_USE_CUSTOM_ARGS,
+ PROP_CUSTOM_BINARY,
+ PROP_CUSTOM_ARGS,
+ PROP_SEND_IN_OFFLINE
+};
+
+G_DEFINE_TYPE (CamelSendmailSettings, camel_sendmail_settings, CAMEL_TYPE_SETTINGS)
+
+static void
+sendmail_settings_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_USE_CUSTOM_BINARY:
+ camel_sendmail_settings_set_use_custom_binary (
+ CAMEL_SENDMAIL_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_USE_CUSTOM_ARGS:
+ camel_sendmail_settings_set_use_custom_args (
+ CAMEL_SENDMAIL_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_CUSTOM_BINARY:
+ camel_sendmail_settings_set_custom_binary (
+ CAMEL_SENDMAIL_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_CUSTOM_ARGS:
+ camel_sendmail_settings_set_custom_args (
+ CAMEL_SENDMAIL_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_SEND_IN_OFFLINE:
+ camel_sendmail_settings_set_send_in_offline (
+ CAMEL_SENDMAIL_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+sendmail_settings_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_USE_CUSTOM_BINARY:
+ g_value_set_boolean (
+ value,
+ camel_sendmail_settings_get_use_custom_binary (
+ CAMEL_SENDMAIL_SETTINGS (object)));
+ return;
+
+ case PROP_USE_CUSTOM_ARGS:
+ g_value_set_boolean (
+ value,
+ camel_sendmail_settings_get_use_custom_args (
+ CAMEL_SENDMAIL_SETTINGS (object)));
+ return;
+
+ case PROP_CUSTOM_BINARY:
+ g_value_take_string (
+ value,
+ camel_sendmail_settings_dup_custom_binary (
+ CAMEL_SENDMAIL_SETTINGS (object)));
+ return;
+
+ case PROP_CUSTOM_ARGS:
+ g_value_take_string (
+ value,
+ camel_sendmail_settings_dup_custom_args (
+ CAMEL_SENDMAIL_SETTINGS (object)));
+ return;
+
+ case PROP_SEND_IN_OFFLINE:
+ g_value_set_boolean (
+ value,
+ camel_sendmail_settings_get_send_in_offline (
+ CAMEL_SENDMAIL_SETTINGS (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+sendmail_settings_finalize (GObject *object)
+{
+ CamelSendmailSettingsPrivate *priv;
+
+ priv = CAMEL_SENDMAIL_SETTINGS_GET_PRIVATE (object);
+
+ g_mutex_clear (&priv->property_lock);
+
+ g_free (priv->custom_binary);
+ g_free (priv->custom_args);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_sendmail_settings_parent_class)->finalize (object);
+}
+
+static void
+camel_sendmail_settings_class_init (CamelSendmailSettingsClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (CamelSendmailSettingsPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = sendmail_settings_set_property;
+ object_class->get_property = sendmail_settings_get_property;
+ object_class->finalize = sendmail_settings_finalize;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_CUSTOM_BINARY,
+ g_param_spec_boolean (
+ "use-custom-binary",
+ "Use Custom Binary",
+ "Whether the custom-binary property identifies binary to run",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_CUSTOM_ARGS,
+ g_param_spec_boolean (
+ "use-custom-args",
+ "Use Custom Arguments",
+ "Whether the custom-args property identifies arguments to use",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CUSTOM_BINARY,
+ g_param_spec_string (
+ "custom-binary",
+ "Custom Binary",
+ "Custom binary to run, instead of sendmail",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CUSTOM_ARGS,
+ g_param_spec_string (
+ "custom-args",
+ "Custom Arguments",
+ "Custom arguments to use, instead of default (predefined) arguments",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SEND_IN_OFFLINE,
+ g_param_spec_boolean (
+ "send-in-offline",
+ "Send in offline",
+ "Whether to allow message sending in offline mode",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_sendmail_settings_init (CamelSendmailSettings *settings)
+{
+ settings->priv = CAMEL_SENDMAIL_SETTINGS_GET_PRIVATE (settings);
+ g_mutex_init (&settings->priv->property_lock);
+}
+
+/**
+ * camel_sendmail_settings_get_use_custom_binary:
+ * @settings: a #CamelSendmailSettings
+ *
+ * Returns whether the 'custom-binary' property should be used as binary to run, instead of sendmail.
+ *
+ * Returns: whether the 'custom-binary' property should be used as binary to run, instead of sendmail
+ *
+ * Since: 3.8
+ **/
+gboolean
+camel_sendmail_settings_get_use_custom_binary (CamelSendmailSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_SENDMAIL_SETTINGS (settings), FALSE);
+
+ return settings->priv->use_custom_binary;
+}
+
+/**
+ * camel_sendmail_settings_set_use_custom_binary:
+ * @settings: a #CamelSendmailSettings
+ * @use_custom_binary: whether to use custom binary
+ *
+ * Sets whether to use custom binary, instead of sendmail.
+ *
+ * Since: 3.8
+ **/
+void
+camel_sendmail_settings_set_use_custom_binary (CamelSendmailSettings *settings,
+ gboolean use_custom_binary)
+{
+ g_return_if_fail (CAMEL_IS_SENDMAIL_SETTINGS (settings));
+
+ if (settings->priv->use_custom_binary == use_custom_binary)
+ return;
+
+ settings->priv->use_custom_binary = use_custom_binary;
+
+ g_object_notify (G_OBJECT (settings), "use-custom-binary");
+}
+
+/**
+ * camel_sendmail_settings_get_use_custom_args:
+ * @settings: a #CamelSendmailSettings
+ *
+ * Returns whether the 'custom-args' property should be used as arguments to use, instead of default arguments.
+ *
+ * Returns: whether the 'custom-args' property should be used as arguments to use, instead of default arguments
+ *
+ * Since: 3.8
+ **/
+gboolean
+camel_sendmail_settings_get_use_custom_args (CamelSendmailSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_SENDMAIL_SETTINGS (settings), FALSE);
+
+ return settings->priv->use_custom_args;
+}
+
+/**
+ * camel_sendmail_settings_set_use_custom_args:
+ * @settings: a #CamelSendmailSettings
+ * @use_custom_args: whether to use custom arguments
+ *
+ * Sets whether to use custom arguments, instead of default arguments.
+ *
+ * Since: 3.8
+ **/
+void
+camel_sendmail_settings_set_use_custom_args (CamelSendmailSettings *settings,
+ gboolean use_custom_args)
+{
+ g_return_if_fail (CAMEL_IS_SENDMAIL_SETTINGS (settings));
+
+ if (settings->priv->use_custom_args == use_custom_args)
+ return;
+
+ settings->priv->use_custom_args = use_custom_args;
+
+ g_object_notify (G_OBJECT (settings), "use-custom-args");
+}
+
+/**
+ * camel_sendmail_settings_get_custom_binary:
+ * @settings: a #CamelSendmailSettings
+ *
+ * Returns the custom binary to run, instead of sendmail.
+ *
+ * Returns: the custom binary to run, instead of sendmail, or %NULL
+ *
+ * Since: 3.8
+ **/
+const gchar *
+camel_sendmail_settings_get_custom_binary (CamelSendmailSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_SENDMAIL_SETTINGS (settings), NULL);
+
+ return settings->priv->custom_binary;
+}
+
+/**
+ * camel_sendmail_settings_dup_custom_binary:
+ * @settings: a #CamelSendmailSettings
+ *
+ * Thread-safe variation of camel_sendmail_settings_get_custom_binary().
+ * Use this function when accessing @settings from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelSendmailSettings:custom-binary
+ *
+ * Since: 3.8
+ **/
+gchar *
+camel_sendmail_settings_dup_custom_binary (CamelSendmailSettings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_SENDMAIL_SETTINGS (settings), NULL);
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ protected = camel_sendmail_settings_get_custom_binary (settings);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_sendmail_settings_set_custom_binary:
+ * @settings: a #CamelSendmailSettings
+ * @custom_binary: a custom binary name, or %NULL
+ *
+ * Sets the custom binary name to run, instead of sendmail.
+ *
+ * Since: 3.8
+ **/
+void
+camel_sendmail_settings_set_custom_binary (CamelSendmailSettings *settings,
+ const gchar *custom_binary)
+{
+ g_return_if_fail (CAMEL_IS_SENDMAIL_SETTINGS (settings));
+
+ /* The default namespace is an empty string. */
+ if (custom_binary && !*custom_binary)
+ custom_binary = NULL;
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ if (g_strcmp0 (settings->priv->custom_binary, custom_binary) == 0) {
+ g_mutex_unlock (&settings->priv->property_lock);
+ return;
+ }
+
+ g_free (settings->priv->custom_binary);
+ settings->priv->custom_binary = g_strdup (custom_binary);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ g_object_notify (G_OBJECT (settings), "custom-binary");
+}
+
+/**
+ * camel_sendmail_settings_get_custom_args:
+ * @settings: a #CamelSendmailSettings
+ *
+ * Returns the custom arguments to use, instead of default arguments.
+ *
+ * Returns: the custom arguments to use, instead of default arguments, or %NULL
+ *
+ * Since: 3.8
+ **/
+const gchar *
+camel_sendmail_settings_get_custom_args (CamelSendmailSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_SENDMAIL_SETTINGS (settings), NULL);
+
+ return settings->priv->custom_args;
+}
+
+/**
+ * camel_sendmail_settings_dup_custom_args:
+ * @settings: a #CamelSendmailSettings
+ *
+ * Thread-safe variation of camel_sendmail_settings_get_custom_args().
+ * Use this function when accessing @settings from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: a newly-allocated copy of #CamelSendmailSettings:custom-args
+ *
+ * Since: 3.8
+ **/
+gchar *
+camel_sendmail_settings_dup_custom_args (CamelSendmailSettings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_SENDMAIL_SETTINGS (settings), NULL);
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ protected = camel_sendmail_settings_get_custom_args (settings);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ return duplicate;
+}
+
+/**
+ * camel_sendmail_settings_set_custom_args:
+ * @settings: a #CamelSendmailSettings
+ * @custom_args: a custom arguments, or %NULL
+ *
+ * Sets the custom arguments to use, instead of default arguments.
+ *
+ * Since: 3.8
+ **/
+void
+camel_sendmail_settings_set_custom_args (CamelSendmailSettings *settings,
+ const gchar *custom_args)
+{
+ g_return_if_fail (CAMEL_IS_SENDMAIL_SETTINGS (settings));
+
+ /* The default namespace is an empty string. */
+ if (custom_args && !*custom_args)
+ custom_args = NULL;
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ if (g_strcmp0 (settings->priv->custom_args, custom_args) == 0) {
+ g_mutex_unlock (&settings->priv->property_lock);
+ return;
+ }
+
+ g_free (settings->priv->custom_args);
+ settings->priv->custom_args = g_strdup (custom_args);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ g_object_notify (G_OBJECT (settings), "custom-args");
+}
+
+/**
+ * camel_sendmail_settings_get_send_in_offline:
+ * @settings: a #CamelSendmailSettings
+ *
+ * Returns whether can send messages in offline mode.
+ *
+ * Returns: whether can send messages in offline mode
+ *
+ * Since: 3.10
+ **/
+gboolean
+camel_sendmail_settings_get_send_in_offline (CamelSendmailSettings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_SENDMAIL_SETTINGS (settings), FALSE);
+
+ return settings->priv->send_in_offline;
+}
+
+/**
+ * camel_sendmail_settings_set_send_in_offline:
+ * @settings: a #CamelSendmailSettings
+ * @send_in_offline: whether can send messages in offline mode
+ *
+ * Sets whether can send messages in offline mode.
+ *
+ * Since: 3.10
+ **/
+void
+camel_sendmail_settings_set_send_in_offline (CamelSendmailSettings *settings,
+ gboolean send_in_offline)
+{
+ g_return_if_fail (CAMEL_IS_SENDMAIL_SETTINGS (settings));
+
+ if ((settings->priv->send_in_offline ? 1 : 0) == (send_in_offline ? 1 : 0))
+ return;
+
+ settings->priv->send_in_offline = send_in_offline;
+
+ g_object_notify (G_OBJECT (settings), "send-in-offline");
+}
diff --git a/src/camel/providers/sendmail/camel-sendmail-settings.h b/src/camel/providers/sendmail/camel-sendmail-settings.h
new file mode 100644
index 000000000..5c3d1522b
--- /dev/null
+++ b/src/camel/providers/sendmail/camel-sendmail-settings.h
@@ -0,0 +1,91 @@
+/*
+ * camel-sendmail-settings.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_SENDMAIL_SETTINGS_H
+#define CAMEL_SENDMAIL_SETTINGS_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SENDMAIL_SETTINGS \
+ (camel_sendmail_settings_get_type ())
+#define CAMEL_SENDMAIL_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SENDMAIL_SETTINGS, CamelSendmailSettings))
+#define CAMEL_SENDMAIL_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SENDMAIL_SETTINGS, CamelSendmailSettingsClass))
+#define CAMEL_IS_SENDMAIL_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SENDMAIL_SETTINGS))
+#define CAMEL_IS_SENDMAIL_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SENDMAIL_SETTINGS))
+#define CAMEL_SENDMAIL_SETTINGS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SENDMAIL_SETTINGS))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSendmailSettings CamelSendmailSettings;
+typedef struct _CamelSendmailSettingsClass CamelSendmailSettingsClass;
+typedef struct _CamelSendmailSettingsPrivate CamelSendmailSettingsPrivate;
+
+struct _CamelSendmailSettings {
+ CamelSettings parent;
+ CamelSendmailSettingsPrivate *priv;
+};
+
+struct _CamelSendmailSettingsClass {
+ CamelSettingsClass parent_class;
+};
+
+GType camel_sendmail_settings_get_type
+ (void) G_GNUC_CONST;
+gboolean camel_sendmail_settings_get_use_custom_binary
+ (CamelSendmailSettings *settings);
+void camel_sendmail_settings_set_use_custom_binary
+ (CamelSendmailSettings *settings,
+ gboolean use_custom_binary);
+const gchar * camel_sendmail_settings_get_custom_binary
+ (CamelSendmailSettings *settings);
+gchar * camel_sendmail_settings_dup_custom_binary
+ (CamelSendmailSettings *settings);
+void camel_sendmail_settings_set_custom_binary
+ (CamelSendmailSettings *settings,
+ const gchar *custom_binary);
+gboolean camel_sendmail_settings_get_use_custom_args
+ (CamelSendmailSettings *settings);
+void camel_sendmail_settings_set_use_custom_args
+ (CamelSendmailSettings *settings,
+ gboolean use_custom_args);
+const gchar * camel_sendmail_settings_get_custom_args
+ (CamelSendmailSettings *settings);
+gchar * camel_sendmail_settings_dup_custom_args
+ (CamelSendmailSettings *settings);
+void camel_sendmail_settings_set_custom_args
+ (CamelSendmailSettings *settings,
+ const gchar *custom_args);
+gboolean camel_sendmail_settings_get_send_in_offline
+ (CamelSendmailSettings *settings);
+void camel_sendmail_settings_set_send_in_offline
+ (CamelSendmailSettings *settings,
+ gboolean send_in_offline);
+
+G_END_DECLS
+
+#endif /* CAMEL_SENDMAIL_SETTINGS_H */
diff --git a/src/camel/providers/sendmail/camel-sendmail-transport.c b/src/camel/providers/sendmail/camel-sendmail-transport.c
new file mode 100644
index 000000000..7767945cf
--- /dev/null
+++ b/src/camel/providers/sendmail/camel-sendmail-transport.c
@@ -0,0 +1,381 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-sendmail-transport.c: Sendmail-based transport class
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-sendmail-settings.h"
+#include "camel-sendmail-transport.h"
+
+G_DEFINE_TYPE (
+ CamelSendmailTransport,
+ camel_sendmail_transport, CAMEL_TYPE_TRANSPORT)
+
+static gchar *
+sendmail_get_name (CamelService *service,
+ gboolean brief)
+{
+ if (brief)
+ return g_strdup (_("sendmail"));
+ else
+ return g_strdup (_("Mail delivery via the sendmail program"));
+}
+
+static GPtrArray *
+parse_sendmail_args (const gchar *binary,
+ const gchar *args,
+ const gchar *from_addr,
+ CamelAddress *recipients)
+{
+ GPtrArray *args_arr;
+ gint ii, len, argc = 0;
+ gchar **argv = NULL;
+
+ g_return_val_if_fail (binary != NULL, NULL);
+ g_return_val_if_fail (args != NULL, NULL);
+ g_return_val_if_fail (from_addr != NULL, NULL);
+
+ len = camel_address_length (recipients);
+
+ args_arr = g_ptr_array_new_full (5, g_free);
+ g_ptr_array_add (args_arr, g_strdup (binary));
+
+ if (args && g_shell_parse_argv (args, &argc, &argv, NULL) && argc > 0 && argv) {
+ for (ii = 0; ii < argc; ii++) {
+ const gchar *arg = argv[ii];
+
+ if (g_strcmp0 (arg, "%F") == 0) {
+ g_ptr_array_add (args_arr, g_strdup (from_addr));
+ } else if (g_strcmp0 (arg, "%R") == 0) {
+ gint jj;
+
+ for (jj = 0; jj < len; jj++) {
+ const gchar *addr = NULL;
+
+ if (!camel_internet_address_get (
+ CAMEL_INTERNET_ADDRESS (recipients), jj, NULL, &addr)) {
+
+ /* should not happen, as the array is checked beforehand */
+
+ g_ptr_array_free (args_arr, TRUE);
+ g_strfreev (argv);
+
+ return NULL;
+ }
+
+ g_ptr_array_add (args_arr, g_strdup (addr));
+ }
+ } else {
+ g_ptr_array_add (args_arr, g_strdup (arg));
+ }
+ }
+
+ g_strfreev (argv);
+ }
+
+ g_ptr_array_add (args_arr, NULL);
+
+ return args_arr;
+}
+
+static gboolean
+sendmail_send_to_sync (CamelTransport *transport,
+ CamelMimeMessage *message,
+ CamelAddress *from,
+ CamelAddress *recipients,
+ gboolean *out_sent_message_saved,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct _camel_header_raw *header, *savedbcc, *n, *tail;
+ const gchar *from_addr, *addr;
+ GPtrArray *argv_arr;
+ gint i, len, fd[2], nullfd, wstat;
+ CamelStream *filter;
+ CamelMimeFilter *crlf;
+ sigset_t mask, omask;
+ CamelStream *out;
+ CamelSendmailSettings *settings;
+ const gchar *binary = SENDMAIL_PATH;
+ gchar *custom_binary = NULL, *custom_args = NULL;
+ gboolean success;
+ pid_t pid;
+
+ success = camel_internet_address_get (
+ CAMEL_INTERNET_ADDRESS (from), 0, NULL, &from_addr);
+
+ if (!success) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Failed to read From address"));
+ return FALSE;
+ }
+
+ settings = CAMEL_SENDMAIL_SETTINGS (camel_service_ref_settings (CAMEL_SERVICE (transport)));
+
+ if (!camel_sendmail_settings_get_send_in_offline (settings)) {
+ CamelSession *session;
+ gboolean is_online;
+
+ session = camel_service_ref_session (CAMEL_SERVICE (transport));
+ is_online = session && camel_session_get_online (session);
+ g_clear_object (&session);
+
+ if (!is_online) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("Message send in offline mode is disabled"));
+ return FALSE;
+ }
+ }
+
+ if (camel_sendmail_settings_get_use_custom_binary (settings)) {
+ custom_binary = camel_sendmail_settings_dup_custom_binary (settings);
+ if (custom_binary && *custom_binary)
+ binary = custom_binary;
+ }
+
+ if (camel_sendmail_settings_get_use_custom_args (settings)) {
+ custom_args = camel_sendmail_settings_dup_custom_args (settings);
+ /* means no arguments used */
+ if (!custom_args)
+ custom_args = g_strdup ("");
+ }
+
+ g_object_unref (settings);
+
+ len = camel_address_length (recipients);
+ for (i = 0; i < len; i++) {
+ success = camel_internet_address_get (
+ CAMEL_INTERNET_ADDRESS (recipients), i, NULL, &addr);
+
+ if (!success) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Could not parse recipient list"));
+ g_free (custom_binary);
+ g_free (custom_args);
+
+ return FALSE;
+ }
+ }
+
+ argv_arr = parse_sendmail_args (
+ binary,
+ custom_args ? custom_args : "-i -f %F -- %R",
+ from_addr,
+ recipients);
+
+ if (!argv_arr) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Could not parse arguments"));
+
+ g_free (custom_binary);
+ g_free (custom_args);
+
+ return FALSE;
+ }
+
+ /* 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;
+ }
+
+ if (pipe (fd) == -1) {
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not create pipe to '%s': %s: "
+ "mail not sent"), binary, g_strerror (errno));
+
+ /* restore the bcc headers */
+ header->next = savedbcc;
+ g_free (custom_binary);
+ g_free (custom_args);
+ g_ptr_array_free (argv_arr, TRUE);
+
+ return FALSE;
+ }
+
+ /* Block SIGCHLD so the calling application doesn't notice
+ * sendmail exiting before we do.
+ */
+ sigemptyset (&mask);
+ sigaddset (&mask, SIGCHLD);
+ sigprocmask (SIG_BLOCK, &mask, &omask);
+
+ pid = fork ();
+ switch (pid) {
+ case -1:
+ g_set_error (
+ error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Could not fork '%s': %s: "
+ "mail not sent"), binary, g_strerror (errno));
+ close (fd[0]);
+ close (fd[1]);
+ sigprocmask (SIG_SETMASK, &omask, NULL);
+
+ /* restore the bcc headers */
+ header->next = savedbcc;
+ g_free (custom_binary);
+ g_free (custom_args);
+ g_ptr_array_free (argv_arr, TRUE);
+
+ return FALSE;
+ case 0:
+ /* Child process */
+ nullfd = open ("/dev/null", O_RDWR);
+ dup2 (fd[0], STDIN_FILENO);
+ if (nullfd != -1) {
+ /*dup2 (nullfd, STDOUT_FILENO);
+ dup2 (nullfd, STDERR_FILENO);*/
+ close (nullfd);
+ }
+ close (fd[1]);
+
+ execv (binary, (gchar **) argv_arr->pdata);
+ _exit (255);
+ }
+
+ g_ptr_array_free (argv_arr, TRUE);
+
+ /* Parent process. Write the message out. */
+ close (fd[0]);
+ out = camel_stream_fs_new_with_fd (fd[1]);
+
+ /* XXX Workaround for lame sendmail implementations
+ * that can't handle CRLF eoln sequences. */
+ filter = camel_stream_filter_new (out);
+ crlf = camel_mime_filter_crlf_new (
+ CAMEL_MIME_FILTER_CRLF_DECODE,
+ CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (filter), crlf);
+ g_object_unref (crlf);
+ g_object_unref (out);
+
+ out = (CamelStream *) filter;
+ if (camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (message), out, cancellable, error) == -1
+ || camel_stream_close (out, cancellable, error) == -1) {
+ g_object_unref (out);
+ g_prefix_error (error, _("Could not send message: "));
+
+ /* Wait for sendmail to exit. */
+ while (waitpid (pid, &wstat, 0) == -1 && errno == EINTR)
+ ;
+
+ sigprocmask (SIG_SETMASK, &omask, NULL);
+
+ /* restore the bcc headers */
+ header->next = savedbcc;
+ g_free (custom_binary);
+ g_free (custom_args);
+
+ return FALSE;
+ }
+
+ g_object_unref (out);
+
+ /* Wait for sendmail to exit. */
+ while (waitpid (pid, &wstat, 0) == -1 && errno == EINTR)
+ ;
+
+ sigprocmask (SIG_SETMASK, &omask, NULL);
+
+ /* restore the bcc headers */
+ header->next = savedbcc;
+
+ if (!WIFEXITED (wstat)) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("'%s' exited with signal %s: mail not sent."),
+ binary, g_strsignal (WTERMSIG (wstat)));
+ g_free (custom_binary);
+ g_free (custom_args);
+
+ return FALSE;
+ } else if (WEXITSTATUS (wstat) != 0) {
+ if (WEXITSTATUS (wstat) == 255) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Could not execute '%s': mail not sent."),
+ binary);
+ } else {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("'%s' exited with status %d: "
+ "mail not sent."),
+ binary, WEXITSTATUS (wstat));
+ }
+ g_free (custom_binary);
+ g_free (custom_args);
+
+ return FALSE;
+ }
+
+ g_free (custom_binary);
+ g_free (custom_args);
+
+ return TRUE;
+}
+
+static void
+camel_sendmail_transport_class_init (CamelSendmailTransportClass *class)
+{
+ CamelServiceClass *service_class;
+ CamelTransportClass *transport_class;
+
+ service_class = CAMEL_SERVICE_CLASS (class);
+ service_class->get_name = sendmail_get_name;
+ service_class->settings_type = CAMEL_TYPE_SENDMAIL_SETTINGS;
+
+ transport_class = CAMEL_TRANSPORT_CLASS (class);
+ transport_class->send_to_sync = sendmail_send_to_sync;
+}
+
+static void
+camel_sendmail_transport_init (CamelSendmailTransport *sendmail_transport)
+{
+}
+
diff --git a/src/camel/providers/sendmail/camel-sendmail-transport.h b/src/camel/providers/sendmail/camel-sendmail-transport.h
new file mode 100644
index 000000000..77171cbea
--- /dev/null
+++ b/src/camel/providers/sendmail/camel-sendmail-transport.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-sendmail-transport.h: Sendmail-based transport class
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Dan Winship <danw@ximian.com>
+ */
+
+#ifndef CAMEL_SENDMAIL_TRANSPORT_H
+#define CAMEL_SENDMAIL_TRANSPORT_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SENDMAIL_TRANSPORT \
+ (camel_sendmail_transport_get_type ())
+#define CAMEL_SENDMAIL_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SENDMAIL_TRANSPORT, CamelSendmailTransport))
+#define CAMEL_SENDMAIL_TRANSPORT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SENDMAIL_TRANSPORT, CamelSendmailTransportClass))
+#define CAMEL_IS_SENDMAIL_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SENDMAIL_TRANSPORT))
+#define CAMEL_IS_SENDMAIL_TRANSPORT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SENDMAIL_TRANSPORT))
+#define CAMEL_SENDMAIL_TRANSPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SENDMAIL_TRANSPORT, CamelSendmailTransportClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSendmailTransport CamelSendmailTransport;
+typedef struct _CamelSendmailTransportClass CamelSendmailTransportClass;
+
+struct _CamelSendmailTransport {
+ CamelTransport parent;
+};
+
+struct _CamelSendmailTransportClass {
+ CamelTransportClass parent_class;
+};
+
+GType camel_sendmail_transport_get_type (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_SENDMAIL_TRANSPORT_H */
diff --git a/src/camel/providers/sendmail/libcamelsendmail.urls b/src/camel/providers/sendmail/libcamelsendmail.urls
new file mode 100644
index 000000000..ccad52828
--- /dev/null
+++ b/src/camel/providers/sendmail/libcamelsendmail.urls
@@ -0,0 +1 @@
+sendmail
diff --git a/src/camel/providers/smtp/CMakeLists.txt b/src/camel/providers/smtp/CMakeLists.txt
new file mode 100644
index 000000000..d47fbc080
--- /dev/null
+++ b/src/camel/providers/smtp/CMakeLists.txt
@@ -0,0 +1,47 @@
+set(SOURCES
+ camel-smtp-provider.c
+ camel-smtp-settings.c
+ camel-smtp-settings.h
+ camel-smtp-transport.c
+ camel-smtp-transport.h
+)
+
+set(DEPENDENCIES
+ camel
+)
+
+add_library(camelsmtp MODULE ${SOURCES})
+
+add_dependencies(camelsmtp
+ ${DEPENDENCIES}
+)
+
+target_compile_definitions(camelsmtp PRIVATE
+ -DG_LOG_DOMAIN=\"camel-smtp-provider\"
+)
+
+target_compile_options(camelsmtp PUBLIC
+ ${CAMEL_CFLAGS}
+)
+
+target_include_directories(camelsmtp PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/camel
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_SOURCE_DIR}/src/camel
+ ${CAMEL_INCLUDE_DIRS}
+)
+
+target_link_libraries(camelsmtp
+ ${DEPENDENCIES}
+ ${CAMEL_LDFLAGS}
+)
+
+install(TARGETS camelsmtp
+ DESTINATION ${camel_providerdir}
+)
+
+install(FILES libcamelsmtp.urls
+ DESTINATION ${camel_providerdir}
+)
diff --git a/src/camel/providers/smtp/camel-smtp-provider.c b/src/camel/providers/smtp/camel-smtp-provider.c
new file mode 100644
index 000000000..c7b667cd2
--- /dev/null
+++ b/src/camel/providers/smtp/camel-smtp-provider.c
@@ -0,0 +1,124 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-smtp-provider.c: smtp provider registration code
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <camel/camel.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-smtp-transport.h"
+
+#ifdef G_OS_WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+
+static guint smtp_url_hash (gconstpointer key);
+static gint smtp_url_equal (gconstpointer a, gconstpointer b);
+
+CamelProviderPortEntry smtp_port_entries[] = {
+ { 25, N_("Default SMTP port"), FALSE },
+ { 465, N_("SMTP over TLS"), TRUE },
+ { 587, N_("Message submission port"), FALSE },
+ { 0, NULL, 0 }
+};
+
+static CamelProvider smtp_provider = {
+ "smtp",
+ N_("SMTP"),
+
+ N_("For delivering mail by connecting to a remote mailhub "
+ "using SMTP."),
+
+ "mail",
+
+ CAMEL_PROVIDER_IS_REMOTE | CAMEL_PROVIDER_SUPPORTS_SSL,
+
+ CAMEL_URL_NEED_HOST | CAMEL_URL_ALLOW_AUTH | CAMEL_URL_ALLOW_USER,
+
+ NULL,
+
+ smtp_port_entries,
+
+ /* ... */
+};
+
+void
+camel_provider_module_init (void)
+{
+ smtp_provider.object_types[CAMEL_PROVIDER_TRANSPORT] = camel_smtp_transport_get_type ();
+ smtp_provider.authtypes = g_list_append (camel_sasl_authtype_list (TRUE), camel_sasl_authtype ("LOGIN"));
+ smtp_provider.authtypes = g_list_append (smtp_provider.authtypes, camel_sasl_authtype ("POPB4SMTP"));
+ smtp_provider.url_hash = smtp_url_hash;
+ smtp_provider.url_equal = smtp_url_equal;
+ smtp_provider.translation_domain = GETTEXT_PACKAGE;
+
+ camel_provider_register (&smtp_provider);
+}
+
+static void
+add_hash (guint *hash,
+ gchar *s)
+{
+ if (s)
+ *hash ^= g_str_hash(s);
+}
+
+static guint
+smtp_url_hash (gconstpointer key)
+{
+ const CamelURL *u = (CamelURL *) key;
+ guint hash = 0;
+
+ add_hash (&hash, u->user);
+ add_hash (&hash, u->host);
+ hash ^= u->port;
+
+ return hash;
+}
+
+static gint
+check_equal (gchar *s1,
+ gchar *s2)
+{
+ if (s1 == NULL) {
+ if (s2 == NULL)
+ return TRUE;
+ else
+ return FALSE;
+ }
+
+ if (s2 == NULL)
+ return FALSE;
+
+ return strcmp (s1, s2) == 0;
+}
+
+static gint
+smtp_url_equal (gconstpointer a,
+ gconstpointer b)
+{
+ const CamelURL *u1 = a, *u2 = b;
+
+ return check_equal (u1->protocol, u2->protocol)
+ && check_equal (u1->user, u2->user)
+ && check_equal (u1->host, u2->host)
+ && u1->port == u2->port;
+}
diff --git a/src/camel/providers/smtp/camel-smtp-settings.c b/src/camel/providers/smtp/camel-smtp-settings.c
new file mode 100644
index 000000000..2e99e8abd
--- /dev/null
+++ b/src/camel/providers/smtp/camel-smtp-settings.c
@@ -0,0 +1,171 @@
+/*
+ * camel-smtp-settings.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "camel-smtp-settings.h"
+
+#define CAMEL_SMTP_SETTINGS_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_SMTP_SETTINGS, CamelSmtpSettingsPrivate))
+
+enum {
+ PROP_0,
+ PROP_AUTH_MECHANISM,
+ PROP_HOST,
+ PROP_PORT,
+ PROP_SECURITY_METHOD,
+ PROP_USER
+};
+
+G_DEFINE_TYPE_WITH_CODE (
+ CamelSmtpSettings,
+ camel_smtp_settings,
+ CAMEL_TYPE_SETTINGS,
+ G_IMPLEMENT_INTERFACE (
+ CAMEL_TYPE_NETWORK_SETTINGS, NULL))
+
+static void
+smtp_settings_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTH_MECHANISM:
+ camel_network_settings_set_auth_mechanism (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_HOST:
+ camel_network_settings_set_host (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_PORT:
+ camel_network_settings_set_port (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_uint (value));
+ return;
+
+ case PROP_SECURITY_METHOD:
+ camel_network_settings_set_security_method (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_USER:
+ camel_network_settings_set_user (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+smtp_settings_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTH_MECHANISM:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_auth_mechanism (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_HOST:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_host (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_PORT:
+ g_value_set_uint (
+ value,
+ camel_network_settings_get_port (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_SECURITY_METHOD:
+ g_value_set_enum (
+ value,
+ camel_network_settings_get_security_method (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_USER:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_user (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+camel_smtp_settings_class_init (CamelSmtpSettingsClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = smtp_settings_set_property;
+ object_class->get_property = smtp_settings_get_property;
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_AUTH_MECHANISM,
+ "auth-mechanism");
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_HOST,
+ "host");
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_PORT,
+ "port");
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_SECURITY_METHOD,
+ "security-method");
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_USER,
+ "user");
+}
+
+static void
+camel_smtp_settings_init (CamelSmtpSettings *settings)
+{
+}
+
diff --git a/src/camel/providers/smtp/camel-smtp-settings.h b/src/camel/providers/smtp/camel-smtp-settings.h
new file mode 100644
index 000000000..b37739ad9
--- /dev/null
+++ b/src/camel/providers/smtp/camel-smtp-settings.h
@@ -0,0 +1,61 @@
+/*
+ * camel-smtp-settings.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CAMEL_SMTP_SETTINGS_H
+#define CAMEL_SMTP_SETTINGS_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SMTP_SETTINGS \
+ (camel_smtp_settings_get_type ())
+#define CAMEL_SMTP_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SMTP_SETTINGS, CamelSmtpSettings))
+#define CAMEL_SMTP_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SMTP_SETTINGS, CamelSmtpSettingsClass))
+#define CAMEL_IS_SMTP_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SMTP_SETTINGS))
+#define CAMEL_IS_SMTP_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SMTP_SETTINGS))
+#define CAMEL_SMTP_SETTINGS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SMTP_SETTINGS, CamelSmtpSettingsClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSmtpSettings CamelSmtpSettings;
+typedef struct _CamelSmtpSettingsClass CamelSmtpSettingsClass;
+typedef struct _CamelSmtpSettingsPrivate CamelSmtpSettingsPrivate;
+
+struct _CamelSmtpSettings {
+ CamelSettings parent;
+ CamelSmtpSettingsPrivate *priv;
+};
+
+struct _CamelSmtpSettingsClass {
+ CamelSettingsClass parent_class;
+};
+
+GType camel_smtp_settings_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* CAMEL_SMTP_SETTINGS_H */
diff --git a/src/camel/providers/smtp/camel-smtp-transport.c b/src/camel/providers/smtp/camel-smtp-transport.c
new file mode 100644
index 000000000..44e96bf08
--- /dev/null
+++ b/src/camel/providers/smtp/camel-smtp-transport.c
@@ -0,0 +1,1891 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-smtp-transport.c : class for a smtp transport
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-smtp-settings.h"
+#include "camel-smtp-transport.h"
+
+#ifdef G_OS_WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+
+#undef MIN
+#undef MAX
+
+#define d(x) (camel_debug ("smtp") ? (x) : 0)
+
+/* Specified in RFC 821 */
+#define SMTP_PORT 25
+#define SMTPS_PORT 465
+
+#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)
+
+/* set if we are using authtypes from a broken AUTH= */
+#define CAMEL_SMTP_TRANSPORT_AUTH_EQUAL (1 << 4)
+
+enum {
+ PROP_0,
+ PROP_CONNECTABLE,
+ PROP_HOST_REACHABLE
+};
+
+#define CAMEL_SMTP_TRANSPORT_ERROR camel_smtp_transport_error_quark ()
+
+GQuark camel_smtp_transport_error_quark (void);
+
+G_DEFINE_QUARK (camel-smtp-transport-error-quark, camel_smtp_transport_error)
+
+enum {
+ CAMEL_SMTP_TRANSPORT_ERROR_CONNECTION_LOST
+};
+
+/* support prototypes */
+static GHashTable * esmtp_get_authtypes (const guchar *buffer);
+static gboolean smtp_helo (CamelSmtpTransport *transport,
+ CamelStreamBuffer *istream,
+ CamelStream *ostream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean smtp_mail (CamelSmtpTransport *transport,
+ CamelStreamBuffer *istream,
+ CamelStream *ostream,
+ const gchar *sender,
+ gboolean has_8bit_parts,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean smtp_rcpt (CamelSmtpTransport *transport,
+ CamelStreamBuffer *istream,
+ CamelStream *ostream,
+ const gchar *recipient,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean smtp_data (CamelSmtpTransport *transport,
+ CamelStreamBuffer *istream,
+ CamelStream *ostream,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean smtp_rset (CamelSmtpTransport *transport,
+ CamelStreamBuffer *istream,
+ CamelStream *ostream,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean smtp_quit (CamelSmtpTransport *transport,
+ CamelStreamBuffer *istream,
+ CamelStream *ostream,
+ GCancellable *cancellable,
+ GError **error);
+static void smtp_set_error (CamelSmtpTransport *transport,
+ CamelStreamBuffer *istream,
+ const gchar *respbuf,
+ GCancellable *cancellable,
+ GError **error);
+
+/* Forward Declarations */
+static void camel_network_service_init (CamelNetworkServiceInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ CamelSmtpTransport,
+ camel_smtp_transport,
+ CAMEL_TYPE_TRANSPORT,
+ G_IMPLEMENT_INTERFACE (
+ CAMEL_TYPE_NETWORK_SERVICE,
+ camel_network_service_init))
+
+static CamelStreamBuffer *
+smtp_ref_istream (CamelSmtpTransport *transport)
+{
+ CamelStreamBuffer *stream;
+
+ g_return_val_if_fail (CAMEL_IS_SMTP_TRANSPORT (transport), NULL);
+
+ g_mutex_lock (&transport->stream_lock);
+
+ if (transport->istream)
+ stream = g_object_ref (transport->istream);
+ else
+ stream = NULL;
+
+ g_mutex_unlock (&transport->stream_lock);
+
+ return stream;
+}
+
+static CamelStream *
+smtp_ref_ostream (CamelSmtpTransport *transport)
+{
+ CamelStream *stream;
+
+ g_return_val_if_fail (CAMEL_IS_SMTP_TRANSPORT (transport), NULL);
+
+ g_mutex_lock (&transport->stream_lock);
+
+ if (transport->ostream)
+ stream = g_object_ref (transport->ostream);
+ else
+ stream = NULL;
+
+ g_mutex_unlock (&transport->stream_lock);
+
+ return stream;
+}
+
+static gboolean
+connect_to_server (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
+ CamelNetworkSettings *network_settings;
+ CamelNetworkSecurityMethod method;
+ CamelSettings *settings;
+ CamelStream *stream, *ostream = NULL;
+ CamelStreamBuffer *istream = NULL;
+ GIOStream *base_stream;
+ GIOStream *tls_stream;
+ gchar *respbuf = NULL;
+ gboolean success = TRUE;
+ gchar *host;
+
+ if (!CAMEL_SERVICE_CLASS (camel_smtp_transport_parent_class)->
+ connect_sync (service, cancellable, error))
+ return FALSE;
+
+ /* set some smtp transport defaults */
+ transport->flags = 0;
+ transport->authtypes = NULL;
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ method = camel_network_settings_get_security_method (network_settings);
+
+ g_object_unref (settings);
+
+ base_stream = camel_network_service_connect_sync (
+ CAMEL_NETWORK_SERVICE (service), cancellable, error);
+
+ if (base_stream != NULL) {
+ /* get the localaddr - needed later by smtp_helo */
+ transport->local_address =
+ g_socket_connection_get_local_address (
+ G_SOCKET_CONNECTION (base_stream), NULL);
+
+ stream = camel_stream_new (base_stream);
+ g_object_unref (base_stream);
+ } else {
+ success = FALSE;
+ goto exit;
+ }
+
+ transport->connected = TRUE;
+
+ g_mutex_lock (&transport->stream_lock);
+
+ transport->ostream = stream;
+ transport->istream = CAMEL_STREAM_BUFFER (camel_stream_buffer_new (
+ stream, CAMEL_STREAM_BUFFER_READ));
+
+ istream = g_object_ref (transport->istream);
+ ostream = g_object_ref (transport->ostream);
+
+ g_mutex_unlock (&transport->stream_lock);
+
+ /* Read the greeting, note whether the server is ESMTP or not. */
+ do {
+ /* Check for "220" */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
+ d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
+ if (respbuf == NULL) {
+ g_prefix_error (error, _("Welcome response error: "));
+ transport->connected = FALSE;
+ success = FALSE;
+ goto exit;
+ }
+ if (strncmp (respbuf, "220", 3)) {
+ smtp_set_error (transport, istream, respbuf, cancellable, error);
+ g_prefix_error (error, _("Welcome response error: "));
+ g_free (respbuf);
+ success = FALSE;
+ goto exit;
+ }
+ } 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, istream, ostream, cancellable, error)) {
+ if (!transport->connected) {
+ success = FALSE;
+ goto exit;
+ }
+
+ /* Fall back to HELO */
+ g_clear_error (error);
+ transport->flags &= ~CAMEL_SMTP_TRANSPORT_IS_ESMTP;
+
+ if (!smtp_helo (transport, istream, ostream, cancellable, error)) {
+ success = FALSE;
+ goto exit;
+ }
+ }
+
+ /* Clear any EHLO/HELO exception and assume that
+ * any SMTP errors encountered were non-fatal. */
+ g_clear_error (error);
+
+ if (method != CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT)
+ goto exit; /* we're done */
+
+ if (!(transport->flags & CAMEL_SMTP_TRANSPORT_STARTTLS)) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Failed to connect to SMTP server %s in secure mode: %s"),
+ host, _("STARTTLS not supported"));
+
+ success = FALSE;
+ goto exit;
+ }
+
+ d (fprintf (stderr, "[SMTP] sending: STARTTLS\r\n"));
+ if (camel_stream_write (ostream, "STARTTLS\r\n", 10, cancellable, error) == -1) {
+ g_prefix_error (error, _("STARTTLS command failed: "));
+ success = FALSE;
+ goto exit;
+ }
+
+ respbuf = NULL;
+
+ do {
+ /* Check for "220 Ready for TLS" */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
+ d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
+ if (respbuf == NULL) {
+ g_prefix_error (error, _("STARTTLS command failed: "));
+ transport->connected = FALSE;
+ success = FALSE;
+ goto exit;
+ }
+ if (strncmp (respbuf, "220", 3) != 0) {
+ smtp_set_error (transport, istream, respbuf, cancellable, error);
+ g_prefix_error (error, _("STARTTLS command failed: "));
+ g_free (respbuf);
+ success = FALSE;
+ goto exit;
+ }
+ } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
+
+ /* Okay, now toggle SSL/TLS mode */
+ base_stream = camel_stream_ref_base_stream (stream);
+ tls_stream = camel_network_service_starttls (
+ CAMEL_NETWORK_SERVICE (service), base_stream, error);
+ g_object_unref (base_stream);
+
+ if (tls_stream != NULL) {
+ camel_stream_set_base_stream (stream, tls_stream);
+ g_object_unref (tls_stream);
+ } else {
+ g_prefix_error (
+ error,
+ _("Failed to connect to SMTP server %s in secure mode: "),
+ host);
+ success = FALSE;
+ goto exit;
+ }
+
+ /* We are supposed to re-EHLO after a successful STARTTLS to
+ * re-fetch any supported extensions. */
+ if (!smtp_helo (transport, istream, ostream, cancellable, error)) {
+ success = FALSE;
+ }
+
+exit:
+ g_free (host);
+
+ if (!success) {
+ transport->connected = FALSE;
+
+ g_mutex_lock (&transport->stream_lock);
+
+ g_clear_object (&transport->istream);
+ g_clear_object (&transport->ostream);
+
+ g_mutex_unlock (&transport->stream_lock);
+ }
+
+ g_clear_object (&istream);
+ g_clear_object (&ostream);
+
+ return success;
+}
+
+static void
+authtypes_free (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ g_free (value);
+}
+
+static void
+smtp_transport_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONNECTABLE:
+ camel_network_service_set_connectable (
+ CAMEL_NETWORK_SERVICE (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+smtp_transport_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONNECTABLE:
+ g_value_take_object (
+ value,
+ camel_network_service_ref_connectable (
+ CAMEL_NETWORK_SERVICE (object)));
+ return;
+
+ case PROP_HOST_REACHABLE:
+ g_value_set_boolean (
+ value,
+ camel_network_service_get_host_reachable (
+ CAMEL_NETWORK_SERVICE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+smtp_transport_finalize (GObject *object)
+{
+ CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (object);
+
+ g_mutex_clear (&transport->stream_lock);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_smtp_transport_parent_class)->finalize (object);
+}
+
+static gchar *
+smtp_transport_get_name (CamelService *service,
+ gboolean brief)
+{
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ gchar *host;
+ gchar *name;
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+
+ g_object_unref (settings);
+
+ if (brief)
+ name = g_strdup_printf (
+ _("SMTP server %s"), host);
+ else
+ name = g_strdup_printf (
+ _("SMTP mail delivery via %s"), host);
+
+ g_free (host);
+
+ return name;
+}
+
+static void
+smtp_debug_print_server_name (CamelService *service,
+ const gchar *what)
+{
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ gchar *host;
+ gint port;
+
+ if (d(1) + 0 == 0)
+ return;
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ port = camel_network_settings_get_port (network_settings);
+
+ g_object_unref (settings);
+
+ fprintf (stderr, "[SMTP] %s server %s:%d from account %s\r\n", what, host, port, camel_service_get_uid (service));
+
+ g_free (host);
+}
+
+static gboolean
+smtp_transport_connect_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
+ CamelNetworkSettings *network_settings;
+ CamelSettings *settings;
+ gchar *host;
+ gchar *mechanism;
+ gboolean auth_required;
+ gboolean success = TRUE;
+
+ /* Chain up to parent's method. */
+ if (!CAMEL_SERVICE_CLASS (camel_smtp_transport_parent_class)->connect_sync (service, cancellable, error))
+ return FALSE;
+
+ smtp_debug_print_server_name (service, "Connecting to");
+
+ settings = camel_service_ref_settings (service);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ host = camel_network_settings_dup_host (network_settings);
+ mechanism = camel_network_settings_dup_auth_mechanism (network_settings);
+
+ g_object_unref (settings);
+
+ /* We (probably) need to check popb4smtp before we connect ... */
+ if (g_strcmp0 (mechanism, "POPB4SMTP") == 0) {
+ GByteArray *chal;
+ CamelSasl *sasl;
+
+ sasl = camel_sasl_new ("smtp", "POPB4SMTP", service);
+ chal = camel_sasl_challenge_sync (sasl, NULL, cancellable, error);
+ if (chal != NULL)
+ g_byte_array_free (chal, TRUE);
+
+ if (camel_sasl_get_authenticated (sasl))
+ success = connect_to_server (
+ service, cancellable, error);
+ else
+ success = FALSE;
+
+ g_object_unref (sasl);
+
+ goto exit;
+ }
+
+ success = connect_to_server (service, cancellable, error);
+
+ if (!success)
+ goto exit;
+
+ /* check to see if AUTH is required, if so...then AUTH ourselves */
+ auth_required =
+ (mechanism != NULL) &&
+ (transport->authtypes != NULL) &&
+ (g_hash_table_size (transport->authtypes) > 0) &&
+ (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP);
+
+ if (auth_required) {
+ CamelSession *session;
+
+ session = camel_service_ref_session (service);
+ if (!session) {
+ success = FALSE;
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+ goto exit;
+ }
+
+ if (g_hash_table_lookup (transport->authtypes, g_strcmp0 (mechanism, "Google") == 0 ? "XOAUTH2" : mechanism)) {
+ gint tries = 0;
+ GError *local_error = NULL;
+
+ success = camel_session_authenticate_sync (
+ session, service, mechanism,
+ cancellable, &local_error);
+
+ while (g_error_matches (local_error, CAMEL_SMTP_TRANSPORT_ERROR, CAMEL_SMTP_TRANSPORT_ERROR_CONNECTION_LOST) &&
+ !g_cancellable_is_cancelled (cancellable) && tries < 3) {
+ d (fprintf (stderr, "[SMTP] reconnecting after dropped connection, %d. try\r\n", tries + 1));
+
+ tries++;
+
+ g_clear_error (&local_error);
+
+ transport->connected = FALSE;
+ g_mutex_lock (&transport->stream_lock);
+ g_clear_object (&transport->istream);
+ g_clear_object (&transport->ostream);
+ g_mutex_unlock (&transport->stream_lock);
+
+ success = connect_to_server (service, cancellable, error);
+ if (success)
+ success = camel_session_authenticate_sync (
+ session, service, mechanism,
+ cancellable, &local_error);
+ }
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+ } else {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("SMTP server %s does not support %s "
+ "authentication"), host, mechanism);
+ success = FALSE;
+ }
+
+ g_object_unref (session);
+
+ if (!success)
+ transport->connected = FALSE;
+ }
+
+exit:
+ g_free (host);
+ g_free (mechanism);
+
+ return success;
+}
+
+static gboolean
+smtp_transport_disconnect_sync (CamelService *service,
+ gboolean clean,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelServiceClass *service_class;
+ CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
+ CamelStreamBuffer *istream;
+ CamelStream *ostream;
+
+ istream = smtp_ref_istream (transport);
+ ostream = smtp_ref_ostream (transport);
+
+ if (istream && ostream && clean) {
+ /* send the QUIT command to the SMTP server */
+ smtp_quit (transport, istream, ostream, cancellable, NULL);
+ }
+
+ g_clear_object (&istream);
+ g_clear_object (&ostream);
+
+ /* Chain up to parent's disconnect() method. */
+ service_class = CAMEL_SERVICE_CLASS (camel_smtp_transport_parent_class);
+ if (!service_class->disconnect_sync (service, clean, cancellable, error))
+ return FALSE;
+
+ if (transport->authtypes) {
+ g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
+ g_hash_table_destroy (transport->authtypes);
+ transport->authtypes = NULL;
+ }
+
+ g_mutex_lock (&transport->stream_lock);
+ g_clear_object (&transport->istream);
+ g_clear_object (&transport->ostream);
+ g_mutex_unlock (&transport->stream_lock);
+ g_clear_object (&transport->local_address);
+
+ transport->connected = FALSE;
+
+ return TRUE;
+}
+
+static CamelAuthenticationResult
+smtp_transport_authenticate_sync (CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
+ CamelAuthenticationResult result;
+ CamelSasl *sasl;
+ CamelStreamBuffer *istream;
+ CamelStream *ostream;
+ gchar *cmdbuf, *respbuf = NULL, *challenge;
+ gboolean auth_challenge = FALSE;
+ GError *local_error = NULL;
+
+ if (mechanism == NULL) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("No SASL mechanism was specified"));
+ return CAMEL_AUTHENTICATION_ERROR;
+ }
+
+ sasl = camel_sasl_new ("smtp", mechanism, service);
+ if (sasl == NULL) {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("No support for %s authentication"), mechanism);
+ return CAMEL_AUTHENTICATION_ERROR;
+ }
+
+ challenge = camel_sasl_challenge_base64_sync (
+ sasl, NULL, cancellable, &local_error);
+ if (challenge) {
+ auth_challenge = TRUE;
+ cmdbuf = g_strdup_printf (
+ "AUTH %s %s\r\n", g_strcmp0 (mechanism, "Google") == 0 ? "XOAUTH2" : mechanism, challenge);
+ g_free (challenge);
+ } else if (local_error) {
+ d (fprintf (stderr, "[SMTP] SASL challenge failed: %s", local_error->message));
+ g_propagate_error (error, local_error);
+ g_object_unref (sasl);
+ return CAMEL_AUTHENTICATION_ERROR;
+ } else {
+ cmdbuf = g_strdup_printf (
+ "AUTH %s\r\n", mechanism);
+ }
+
+ istream = smtp_ref_istream (transport);
+ ostream = smtp_ref_ostream (transport);
+
+ if (!istream || !ostream) {
+ g_free (cmdbuf);
+ g_set_error_literal (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_NOT_CONNECTED,
+ _("AUTH command failed: Not connected."));
+ goto lose;
+ }
+
+ d (fprintf (stderr, "[SMTP] sending: %s", cmdbuf));
+ if (camel_stream_write_string (ostream, cmdbuf, cancellable, error) == -1) {
+ g_free (cmdbuf);
+ g_prefix_error (error, _("AUTH command failed: "));
+ goto lose;
+ }
+ g_free (cmdbuf);
+
+ respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
+ d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
+
+ while (!camel_sasl_get_authenticated (sasl)) {
+ if (!respbuf) {
+ /* It's an EOF state on the input stream. */
+ if (error && !*error)
+ g_set_error (error, CAMEL_SMTP_TRANSPORT_ERROR,
+ CAMEL_SMTP_TRANSPORT_ERROR_CONNECTION_LOST, _("Connection cancelled"));
+ g_prefix_error (error, _("AUTH command failed: "));
+ transport->connected = FALSE;
+ goto lose;
+ }
+
+ /* the server may have accepted our initial response */
+ if (strncmp (respbuf, "235", 3) == 0)
+ break;
+
+ /* the server challenge/response should follow a 334 code */
+ if (strncmp (respbuf, "334", 3) != 0) {
+ smtp_set_error (transport, istream, respbuf, cancellable, error);
+ g_prefix_error (error, _("AUTH command failed: "));
+ goto lose;
+ }
+
+ if (FALSE) {
+ broken_smtp_server:
+ d (fprintf (
+ stderr, "[SMTP] Your SMTP server's implementation "
+ "of the %s SASL\nauthentication mechanism is "
+ "broken. Please report this to the\n"
+ "appropriate vendor and suggest that they "
+ "re-read rfc2554 again\nfor the first time "
+ "(specifically Section 4).\n",
+ mechanism));
+ }
+
+ /* eat whtspc */
+ for (challenge = respbuf + 4; isspace (*challenge); challenge++);
+
+ challenge = camel_sasl_challenge_base64_sync (
+ sasl, challenge, cancellable, error);
+ if (challenge == NULL)
+ goto break_and_lose;
+
+ g_free (respbuf);
+ respbuf = NULL;
+
+ /* send our challenge */
+ cmdbuf = g_strdup_printf ("%s\r\n", challenge);
+ g_free (challenge);
+ d (fprintf (stderr, "[SMTP] sending: %s", cmdbuf));
+ if (camel_stream_write_string (ostream, cmdbuf, cancellable, error) == -1) {
+ g_free (cmdbuf);
+ goto lose;
+ }
+ g_free (cmdbuf);
+
+ /* get the server's response */
+ respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
+ d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
+ }
+
+ if (respbuf == NULL)
+ goto lose;
+
+ /* Work around broken SASL implementations. */
+ if (auth_challenge && strncmp (respbuf, "334", 3) == 0)
+ goto broken_smtp_server;
+
+ /* If our authentication data was rejected, destroy the
+ * password so that the user gets prompted to try again. */
+ if (strncmp (respbuf, "535", 3) == 0) {
+ result = CAMEL_AUTHENTICATION_REJECTED;
+
+ /* Read the continuation, if the server returned it. */
+ while (respbuf && respbuf[3] == '-') {
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
+ d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
+ }
+ } else if (strncmp (respbuf, "235", 3) == 0)
+ result = CAMEL_AUTHENTICATION_ACCEPTED;
+ /* Catch any other errors. */
+ else {
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+ _("Bad authentication response from server."));
+ goto lose;
+ }
+
+ goto exit;
+
+break_and_lose:
+ /* Get the server out of "waiting for continuation data" mode. */
+ d (fprintf (stderr, "[SMTP] sending: *\n"));
+ camel_stream_write (ostream, "*\r\n", 3, cancellable, NULL);
+ respbuf = camel_stream_buffer_read_line (istream, cancellable, NULL);
+ d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
+
+lose:
+ result = CAMEL_AUTHENTICATION_ERROR;
+
+exit:
+ g_clear_object (&istream);
+ g_clear_object (&ostream);
+ g_object_unref (sasl);
+ g_free (respbuf);
+
+ return result;
+}
+
+static GList *
+smtp_transport_query_auth_types_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
+ CamelServiceAuthType *authtype;
+ CamelProvider *provider;
+ GList *types, *t, *next;
+
+ if (!connect_to_server (service, cancellable, error))
+ return NULL;
+
+ if (!transport->authtypes) {
+ smtp_transport_disconnect_sync (
+ service, TRUE, cancellable, NULL);
+ return NULL;
+ }
+
+ provider = camel_service_get_provider (service);
+ types = g_list_copy (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_transport_disconnect_sync (service, TRUE, cancellable, NULL);
+
+ return types;
+}
+
+static gboolean
+smtp_transport_send_to_sync (CamelTransport *transport,
+ CamelMimeMessage *message,
+ CamelAddress *from,
+ CamelAddress *recipients,
+ gboolean *out_sent_message_saved,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (transport);
+ CamelInternetAddress *cia;
+ CamelStreamBuffer *istream;
+ CamelStream *ostream;
+ gboolean has_8bit_parts;
+ const gchar *addr;
+ gint i, len;
+
+ smtp_debug_print_server_name (CAMEL_SERVICE (transport), "Sending with");
+
+ istream = smtp_ref_istream (smtp_transport);
+ ostream = smtp_ref_ostream (smtp_transport);
+
+ if (!smtp_transport->connected || !istream || !ostream) {
+ g_clear_object (&istream);
+ g_clear_object (&ostream);
+ g_set_error (
+ error, CAMEL_SERVICE_ERROR,
+ CAMEL_SERVICE_ERROR_NOT_CONNECTED,
+ _("Cannot send message: service not connected."));
+ return FALSE;
+ }
+
+ if (!camel_internet_address_get (CAMEL_INTERNET_ADDRESS (from), 0, NULL, &addr)) {
+ g_clear_object (&istream);
+ g_clear_object (&ostream);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot send message: sender address not valid."));
+ return FALSE;
+ }
+
+ camel_operation_push_message (cancellable, _("Sending message"));
+
+ /* find out if the message has 8bit mime parts */
+ has_8bit_parts = camel_mime_message_has_8bit_parts (message);
+
+ /* If the connection needs a ReSET, then do so */
+ if (smtp_transport->need_rset &&
+ !smtp_rset (smtp_transport, istream, ostream, cancellable, error)) {
+ camel_operation_pop_message (cancellable);
+ g_clear_object (&istream);
+ g_clear_object (&ostream);
+ return FALSE;
+ }
+ smtp_transport->need_rset = FALSE;
+
+ /* 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, istream, ostream, addr, has_8bit_parts, cancellable, error)) {
+ camel_operation_pop_message (cancellable);
+ g_clear_object (&istream);
+ g_clear_object (&ostream);
+ return FALSE;
+ }
+
+ len = camel_address_length (recipients);
+ if (len == 0) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot send message: no recipients defined."));
+ camel_operation_pop_message (cancellable);
+ smtp_transport->need_rset = TRUE;
+ g_clear_object (&istream);
+ g_clear_object (&ostream);
+ return FALSE;
+ }
+
+ cia = CAMEL_INTERNET_ADDRESS (recipients);
+ for (i = 0; i < len; i++) {
+ gchar *enc;
+
+ if (!camel_internet_address_get (cia, i, NULL, &addr)) {
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot send message: "
+ "one or more invalid recipients"));
+ camel_operation_pop_message (cancellable);
+ smtp_transport->need_rset = TRUE;
+ g_clear_object (&istream);
+ g_clear_object (&ostream);
+ return FALSE;
+ }
+
+ enc = camel_internet_address_encode_address (NULL, NULL, addr);
+ if (!smtp_rcpt (smtp_transport, istream, ostream, enc, cancellable, error)) {
+ g_free (enc);
+ camel_operation_pop_message (cancellable);
+ smtp_transport->need_rset = TRUE;
+ g_clear_object (&istream);
+ g_clear_object (&ostream);
+ return FALSE;
+ }
+ g_free (enc);
+ }
+
+ if (!smtp_data (smtp_transport, istream, ostream, message, cancellable, error)) {
+ camel_operation_pop_message (cancellable);
+ smtp_transport->need_rset = TRUE;
+ g_clear_object (&istream);
+ g_clear_object (&ostream);
+ return FALSE;
+ }
+
+ camel_operation_pop_message (cancellable);
+ g_clear_object (&istream);
+ g_clear_object (&ostream);
+
+ return TRUE;
+}
+
+static const gchar *
+smtp_transport_get_service_name (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method)
+{
+ const gchar *service_name;
+
+ switch (method) {
+ case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
+ service_name = "smtps";
+ break;
+
+ default:
+ service_name = "smtp";
+ break;
+ }
+
+ return service_name;
+}
+
+static guint16
+smtp_transport_get_default_port (CamelNetworkService *service,
+ CamelNetworkSecurityMethod method)
+{
+ guint16 default_port;
+
+ switch (method) {
+ case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
+ default_port = SMTPS_PORT;
+ break;
+
+ default:
+ default_port = SMTP_PORT;
+ break;
+ }
+
+ return default_port;
+}
+
+static void
+camel_smtp_transport_class_init (CamelSmtpTransportClass *class)
+{
+ GObjectClass *object_class;
+ CamelServiceClass *service_class;
+ CamelTransportClass *transport_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = smtp_transport_set_property;
+ object_class->get_property = smtp_transport_get_property;
+ object_class->finalize = smtp_transport_finalize;
+
+ service_class = CAMEL_SERVICE_CLASS (class);
+ service_class->settings_type = CAMEL_TYPE_SMTP_SETTINGS;
+ service_class->get_name = smtp_transport_get_name;
+ service_class->connect_sync = smtp_transport_connect_sync;
+ service_class->disconnect_sync = smtp_transport_disconnect_sync;
+ service_class->authenticate_sync = smtp_transport_authenticate_sync;
+ service_class->query_auth_types_sync = smtp_transport_query_auth_types_sync;
+
+ transport_class = CAMEL_TRANSPORT_CLASS (class);
+ transport_class->send_to_sync = smtp_transport_send_to_sync;
+
+ /* Inherited from CamelNetworkService. */
+ g_object_class_override_property (
+ object_class,
+ PROP_CONNECTABLE,
+ "connectable");
+
+ /* Inherited from CamelNetworkService. */
+ g_object_class_override_property (
+ object_class,
+ PROP_HOST_REACHABLE,
+ "host-reachable");
+}
+
+static void
+camel_network_service_init (CamelNetworkServiceInterface *iface)
+{
+ iface->get_service_name = smtp_transport_get_service_name;
+ iface->get_default_port = smtp_transport_get_default_port;
+}
+
+static void
+camel_smtp_transport_init (CamelSmtpTransport *smtp)
+{
+ smtp->flags = 0;
+ smtp->connected = FALSE;
+
+ g_mutex_init (&smtp->stream_lock);
+}
+
+static const gchar *
+smtp_error_string (gint error)
+{
+ /* SMTP error codes grabbed from rfc821 */
+ switch (error) {
+ 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");
+ }
+}
+
+static GHashTable *
+esmtp_get_authtypes (const guchar *buffer)
+{
+ const guchar *start, *end;
+ GHashTable *table = NULL;
+
+ start = buffer;
+
+ /* make sure there is at least one delimiter
+ * character in the AUTH response */
+ if (!isspace ((gint) *start) && *start != '=')
+ return NULL;
+
+ /* advance to the first token */
+ while (isspace ((gint) *start) || *start == '=')
+ start++;
+
+ if (!*start)
+ return NULL;
+
+ table = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (; *start; ) {
+ gchar *type;
+
+ /* advance to the end of the token */
+ end = start;
+ while (*end && !isspace ((gint) *end))
+ end++;
+
+ type = g_strndup ((gchar *) start, end - start);
+ g_hash_table_insert (table, type, type);
+
+ /* advance to the next token */
+ start = end;
+ while (isspace ((gint) *start))
+ start++;
+ }
+
+ return table;
+}
+
+static const gchar *
+smtp_next_token (const gchar *buf)
+{
+ const guchar *token;
+
+ token = (const guchar *) buf;
+ while (*token && !isspace ((gint) *token))
+ token++;
+
+ while (*token && isspace ((gint) *token))
+ token++;
+
+ return (const gchar *) 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 gchar *
+smtp_decode_status_code (const gchar *in,
+ gsize len)
+{
+ guchar *inptr, *outptr;
+ const guchar *inend;
+ gchar *outbuf;
+
+ outbuf = (gchar *) g_malloc (len + 1);
+ outptr = (guchar *) outbuf;
+
+ inptr = (guchar *) 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;
+}
+
+/* converts string str to local encoding, thinking it's in utf8.
+ * If fails, then converts all character greater than 127 to hex values.
+ * Also those under 32, other than \n, \r, \t.
+ * Note that the c is signed character, so all characters above 127 have
+ * negative value.
+*/
+static void
+convert_to_local (GString *str)
+{
+ gchar *buf;
+
+ buf = g_locale_from_utf8 (str->str, str->len, NULL, NULL, NULL);
+
+ if (!buf) {
+ gint i;
+ gchar c;
+ GString *s = g_string_new_len (str->str, str->len);
+
+ g_string_truncate (str, 0);
+
+ for (i = 0; i < s->len; i++) {
+ c = s->str[i];
+
+ if (c < 32 && c != '\n' && c != '\r' && c != '\t')
+ g_string_append_printf (str, "<%X%X>", (c >> 4) & 0xF, c & 0xF);
+ else
+ g_string_append_c (str, c);
+ }
+
+ g_string_free (s, TRUE);
+ } else {
+ g_string_truncate (str, 0);
+ g_string_append (str, buf);
+
+ g_free (buf);
+ }
+}
+
+static void
+smtp_set_error (CamelSmtpTransport *transport,
+ CamelStreamBuffer *istream,
+ const gchar *respbuf,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *token, *rbuf = respbuf;
+ gchar *buffer = NULL;
+ GString *string;
+
+ g_return_if_fail (respbuf != NULL);
+
+ string = g_string_new ("");
+ do {
+ if (transport->flags & CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES)
+ token = smtp_next_token (rbuf + 4);
+ else
+ 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 (istream, cancellable, NULL);
+ d (fprintf (stderr, "[SMTP] received: %s\n", buffer ? buffer : "(null)"));
+ g_string_append_c (string, '\n');
+ } else {
+ g_free (buffer);
+ buffer = NULL;
+ }
+
+ rbuf = buffer;
+ } while (rbuf);
+
+ convert_to_local (string);
+ if (!(transport->flags & CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES) && string->len) {
+ string->str = g_strstrip (string->str);
+ string->len = strlen (string->str);
+
+ if (!string->len) {
+ g_string_free (string, TRUE);
+ goto fake_status_code;
+ }
+
+ g_set_error (
+ error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC,
+ "%s", string->str);
+
+ g_string_free (string, TRUE);
+ } else {
+ buffer = smtp_decode_status_code (string->str, string->len);
+ g_string_free (string, TRUE);
+ if (!buffer)
+ goto fake_status_code;
+
+ g_set_error (
+ error, CAMEL_ERROR,
+ CAMEL_ERROR_GENERIC,
+ "%s", buffer);
+
+ g_free (buffer);
+ }
+
+ return;
+
+fake_status_code:
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ "%s", smtp_error_string (atoi (respbuf)));
+}
+
+static gboolean
+smtp_helo (CamelSmtpTransport *transport,
+ CamelStreamBuffer *istream,
+ CamelStream *ostream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar *name = NULL, *cmdbuf = NULL, *respbuf = NULL;
+ const gchar *token;
+ GResolver *resolver;
+ GInetAddress *address;
+ GError *local_error = 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;
+ }
+
+ resolver = g_resolver_get_default ();
+ address = g_inet_socket_address_get_address (
+ G_INET_SOCKET_ADDRESS (transport->local_address));
+
+ name = g_resolver_lookup_by_address (
+ resolver, address, cancellable, &local_error);
+
+ /* Sanity check. */
+ g_return_val_if_fail (
+ ((name != NULL) && (local_error == NULL)) ||
+ ((name == NULL) && (local_error != NULL)), FALSE);
+
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return FALSE;
+
+ g_clear_error (&local_error);
+
+ if (name == NULL) {
+ GSocketFamily family;
+ gchar *string;
+
+ string = g_inet_address_to_string (address);
+ family = g_inet_address_get_family (address);
+ if (family == G_SOCKET_FAMILY_IPV6)
+ name = g_strdup_printf ("[IPv6:%s]", string);
+ else
+ name = g_strdup_printf ("[%s]", string);
+ g_free (string);
+ }
+
+ camel_operation_push_message (cancellable, _("SMTP Greeting"));
+
+ token = (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) ? "EHLO" : "HELO";
+ cmdbuf = g_strdup_printf ("%s %s\r\n", token, name);
+ g_free (name);
+
+ d (fprintf (stderr, "[SMTP] sending: %s", cmdbuf));
+ if (camel_stream_write_string (ostream, cmdbuf, cancellable, error) == -1) {
+ g_free (cmdbuf);
+ g_prefix_error (error, _("HELO command failed: "));
+ camel_operation_pop_message (cancellable);
+
+ return FALSE;
+ }
+ g_free (cmdbuf);
+
+ do {
+ /* Check for "250" */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
+ d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
+ if (respbuf == NULL) {
+ g_prefix_error (error, _("HELO command failed: "));
+ transport->connected = FALSE;
+ camel_operation_pop_message (cancellable);
+ return FALSE;
+ }
+ if (strncmp (respbuf, "250", 3)) {
+ smtp_set_error (transport, istream, respbuf, cancellable, error);
+ g_prefix_error (error, _("HELO command failed: "));
+ camel_operation_pop_message (cancellable);
+ g_free (respbuf);
+ return FALSE;
+ }
+
+ token = respbuf + 4;
+
+ if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) {
+ if (!g_ascii_strncasecmp (token, "8BITMIME", 8)) {
+ transport->flags |= CAMEL_SMTP_TRANSPORT_8BITMIME;
+ } else if (!g_ascii_strncasecmp (token, "ENHANCEDSTATUSCODES", 19)) {
+ transport->flags |= CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES;
+ } else if (!g_ascii_strncasecmp (token, "STARTTLS", 8)) {
+ transport->flags |= CAMEL_SMTP_TRANSPORT_STARTTLS;
+ } else if (!g_ascii_strncasecmp (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 += 4;
+
+ if (transport->authtypes) {
+ g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
+ g_hash_table_destroy (transport->authtypes);
+ }
+
+ transport->authtypes = esmtp_get_authtypes ((const guchar *) token);
+ }
+ }
+ }
+ } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
+ g_free (respbuf);
+
+ camel_operation_pop_message (cancellable);
+
+ return TRUE;
+}
+
+static gboolean
+smtp_mail (CamelSmtpTransport *transport,
+ CamelStreamBuffer *istream,
+ CamelStream *ostream,
+ const gchar *sender,
+ gboolean has_8bit_parts,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* we gotta tell the smtp server who we are. (our email addy) */
+ gchar *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, "[SMTP] sending: %s", cmdbuf));
+
+ if (camel_stream_write_string (ostream, cmdbuf, cancellable, error) == -1) {
+ g_free (cmdbuf);
+ g_prefix_error (error, _("MAIL FROM command failed: "));
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (transport),
+ FALSE, cancellable, NULL);
+ return FALSE;
+ }
+ g_free (cmdbuf);
+
+ do {
+ /* Check for "250 Sender OK..." */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
+ d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
+ if (respbuf == NULL) {
+ g_prefix_error (error, _("MAIL FROM command failed: "));
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (transport),
+ FALSE, cancellable, NULL);
+ return FALSE;
+ }
+ if (strncmp (respbuf, "250", 3)) {
+ smtp_set_error (transport, istream, respbuf, cancellable, error);
+ g_prefix_error (
+ error, _("MAIL FROM command failed: "));
+ 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,
+ CamelStreamBuffer *istream,
+ CamelStream *ostream,
+ const gchar *recipient,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* we gotta tell the smtp server who we are going to be sending
+ * our email to */
+ gchar *cmdbuf, *respbuf = NULL;
+
+ cmdbuf = g_strdup_printf ("RCPT TO:<%s>\r\n", recipient);
+
+ d (fprintf (stderr, "[SMTP] sending: %s", cmdbuf));
+
+ if (camel_stream_write_string (ostream, cmdbuf, cancellable, error) == -1) {
+ g_free (cmdbuf);
+ g_prefix_error (error, _("RCPT TO command failed: "));
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (transport),
+ FALSE, cancellable, NULL);
+
+ return FALSE;
+ }
+ g_free (cmdbuf);
+
+ do {
+ /* Check for "250 Recipient OK..." */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
+ d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
+ if (respbuf == NULL) {
+ g_prefix_error (
+ error, _("RCPT TO <%s> failed: "), recipient);
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (transport),
+ FALSE, cancellable, NULL);
+ return FALSE;
+ }
+ if (strncmp (respbuf, "250", 3)) {
+ smtp_set_error (transport, istream, respbuf, cancellable, error);
+ g_prefix_error (
+ error, _("RCPT TO <%s> failed: "), recipient);
+ g_free (respbuf);
+
+ return FALSE;
+ }
+ } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
+ g_free (respbuf);
+
+ return TRUE;
+}
+
+static void
+smtp_maybe_update_socket_timeout (CamelStream *strm,
+ gint timeout_seconds)
+{
+ GIOStream *base_strm = camel_stream_ref_base_stream (strm);
+
+ if (G_IS_TLS_CONNECTION (base_strm)) {
+ GIOStream *base_io_stream = NULL;
+
+ g_object_get (G_OBJECT (base_strm), "base-io-stream", &base_io_stream, NULL);
+
+ g_object_unref (base_strm);
+ base_strm = base_io_stream;
+ }
+
+ if (G_IS_SOCKET_CONNECTION (base_strm)) {
+ GSocket *socket;
+
+ socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (base_strm));
+ if (socket) {
+ if (timeout_seconds > g_socket_get_timeout (socket))
+ g_socket_set_timeout (socket, timeout_seconds);
+ }
+ }
+
+ g_clear_object (&base_strm);
+}
+
+static gboolean
+smtp_data (CamelSmtpTransport *transport,
+ CamelStreamBuffer *istream,
+ CamelStream *ostream,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct _camel_header_raw *header, *savedbcc, *n, *tail;
+ CamelBestencEncoding enctype = CAMEL_BESTENC_8BIT;
+ CamelStream *filtered_stream;
+ gchar *cmdbuf, *respbuf = NULL;
+ CamelMimeFilter *filter;
+ CamelStreamNull *null;
+ gint 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, "[SMTP] sending: %s", cmdbuf));
+
+ if (camel_stream_write_string (ostream, cmdbuf, cancellable, error) == -1) {
+ g_free (cmdbuf);
+ g_prefix_error (error, _("DATA command failed: "));
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (transport),
+ FALSE, cancellable, NULL);
+ return FALSE;
+ }
+ g_free (cmdbuf);
+
+ respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
+ d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
+ if (respbuf == NULL) {
+ g_prefix_error (error, _("DATA command failed: "));
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (transport),
+ FALSE, cancellable, NULL);
+ return FALSE;
+ }
+ if (strncmp (respbuf, "354", 3) != 0) {
+ /* We should have gotten instructions on how to use the DATA
+ * command: 354 Enter mail, end with "." on a line by itself
+ */
+ smtp_set_error (transport, istream, respbuf, cancellable, error);
+ g_prefix_error (error, _("DATA command failed: "));
+ g_free (respbuf);
+ return FALSE;
+ }
+
+ g_free (respbuf);
+ respbuf = NULL;
+
+ /* 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;
+ }
+
+ /* find out how large the message is... */
+ null = CAMEL_STREAM_NULL (camel_stream_null_new ());
+ camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (message),
+ CAMEL_STREAM (null), NULL, NULL);
+
+ /* Set the upload timeout to an equal of 512 bytes per second */
+ smtp_maybe_update_socket_timeout (ostream, null->written / 512);
+
+ filtered_stream = camel_stream_filter_new (ostream);
+
+ /* setup progress reporting for message sending... */
+ filter = camel_mime_filter_progress_new (cancellable, null->written);
+ camel_stream_filter_add (
+ CAMEL_STREAM_FILTER (filtered_stream), filter);
+ g_object_unref (filter);
+ g_object_unref (null);
+
+ /* setup LF->CRLF conversion */
+ filter = camel_mime_filter_crlf_new (
+ CAMEL_MIME_FILTER_CRLF_ENCODE,
+ CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
+ camel_stream_filter_add (
+ CAMEL_STREAM_FILTER (filtered_stream), filter);
+ g_object_unref (filter);
+
+ /* write the message */
+ ret = camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (message),
+ filtered_stream, cancellable, error);
+
+ /* restore the bcc headers */
+ header->next = savedbcc;
+
+ if (ret == -1) {
+ g_prefix_error (error, _("DATA command failed: "));
+
+ g_object_unref (filtered_stream);
+
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (transport),
+ FALSE, cancellable, NULL);
+ return FALSE;
+ }
+
+ camel_stream_flush (filtered_stream, cancellable, NULL);
+ g_object_unref (filtered_stream);
+
+ /* terminate the message body */
+
+ d (fprintf (stderr, "[SMTP] sending: \\r\\n.\\r\\n\n"));
+
+ if (camel_stream_write (ostream, "\r\n.\r\n", 5, cancellable, error) == -1) {
+ g_prefix_error (error, _("DATA command failed: "));
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (transport),
+ FALSE, cancellable, NULL);
+ return FALSE;
+ }
+
+ do {
+ /* Check for "250 Sender OK..." */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
+ d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
+ if (respbuf == NULL) {
+ g_prefix_error (error, _("DATA command failed: "));
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (transport),
+ FALSE, cancellable, NULL);
+ return FALSE;
+ }
+ if (strncmp (respbuf, "250", 3) != 0) {
+ smtp_set_error (transport, istream, respbuf, cancellable, error);
+ g_prefix_error (error, _("DATA command failed: "));
+ 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,
+ CamelStreamBuffer *istream,
+ CamelStream *ostream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* we are going to reset the smtp server (just to be nice) */
+ gchar *cmdbuf, *respbuf = NULL;
+
+ cmdbuf = g_strdup ("RSET\r\n");
+
+ d (fprintf (stderr, "[SMTP] sending: %s", cmdbuf));
+
+ if (camel_stream_write_string (ostream, cmdbuf, cancellable, error) == -1) {
+ g_free (cmdbuf);
+ g_prefix_error (error, _("RSET command failed: "));
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (transport),
+ FALSE, cancellable, NULL);
+ return FALSE;
+ }
+ g_free (cmdbuf);
+
+ do {
+ /* Check for "250" */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
+ d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
+ if (respbuf == NULL) {
+ g_prefix_error (error, _("RSET command failed: "));
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (transport),
+ FALSE, cancellable, NULL);
+ return FALSE;
+ }
+ if (strncmp (respbuf, "250", 3) != 0) {
+ smtp_set_error (transport, istream, respbuf, cancellable, error);
+ g_prefix_error (error, _("RSET command failed: "));
+ 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,
+ CamelStreamBuffer *istream,
+ CamelStream *ostream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* we are going to reset the smtp server (just to be nice) */
+ gchar *cmdbuf, *respbuf = NULL;
+
+ cmdbuf = g_strdup ("QUIT\r\n");
+
+ d (fprintf (stderr, "[SMTP] sending: %s", cmdbuf));
+
+ if (camel_stream_write_string (ostream, cmdbuf, cancellable, error) == -1) {
+ g_free (cmdbuf);
+ g_prefix_error (error, _("QUIT command failed: "));
+ return FALSE;
+ }
+ g_free (cmdbuf);
+
+ do {
+ /* Check for "221" */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
+
+ d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
+ if (respbuf == NULL) {
+ g_prefix_error (error, _("QUIT command failed: "));
+ transport->connected = FALSE;
+ return FALSE;
+ }
+ if (strncmp (respbuf, "221", 3) != 0) {
+ smtp_set_error (transport, istream, respbuf, cancellable, error);
+ g_prefix_error (error, _("QUIT command failed: "));
+ g_free (respbuf);
+ return FALSE;
+ }
+ } while (*(respbuf+3) == '-'); /* if we got "221-" then loop again */
+
+ g_free (respbuf);
+
+ return TRUE;
+}
diff --git a/src/camel/providers/smtp/camel-smtp-transport.h b/src/camel/providers/smtp/camel-smtp-transport.h
new file mode 100644
index 000000000..c5a20de34
--- /dev/null
+++ b/src/camel/providers/smtp/camel-smtp-transport.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-smtp-transport.h : class for an smtp transfer
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@stampede.org>
+ */
+
+#ifndef CAMEL_SMTP_TRANSPORT_H
+#define CAMEL_SMTP_TRANSPORT_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SMTP_TRANSPORT \
+ (camel_smtp_transport_get_type ())
+#define CAMEL_SMTP_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SMTP_TRANSPORT, CamelSmtpTransport))
+#define CAMEL_SMTP_TRANSPORT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SMTP_TRANSPORT, CamelSmtpTransportClass))
+#define CAMEL_IS_SMTP_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SMTP_TRANSPORT))
+#define CAMEL_IS_SMTP_TRANSPORT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SMTP_TRANSPORT))
+#define CAMEL_SMTP_TRANSPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SMTP_TRANSPORT, CamelSmtpTransportClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSmtpTransport CamelSmtpTransport;
+typedef struct _CamelSmtpTransportClass CamelSmtpTransportClass;
+
+struct _CamelSmtpTransport {
+ CamelTransport parent;
+
+ GMutex stream_lock;
+ CamelStreamBuffer *istream;
+ CamelStream *ostream;
+ GSocketAddress *local_address;
+
+ guint32 flags;
+
+ gboolean need_rset;
+ gboolean connected;
+
+ GHashTable *authtypes;
+};
+
+struct _CamelSmtpTransportClass {
+ CamelTransportClass parent_class;
+};
+
+GType camel_smtp_transport_get_type (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_SMTP_TRANSPORT_H */
diff --git a/src/camel/providers/smtp/libcamelsmtp.urls b/src/camel/providers/smtp/libcamelsmtp.urls
new file mode 100644
index 000000000..ec2fc0fc1
--- /dev/null
+++ b/src/camel/providers/smtp/libcamelsmtp.urls
@@ -0,0 +1 @@
+smtp
diff --git a/src/camel/tests/CMakeLists.txt b/src/camel/tests/CMakeLists.txt
new file mode 100644
index 000000000..652cfc3de
--- /dev/null
+++ b/src/camel/tests/CMakeLists.txt
@@ -0,0 +1,53 @@
+macro(add_camel_test_one _part _name _src_file)
+ set(_test_ident cameltest-${_part}-${_name})
+
+ add_executable(${_test_ident} EXCLUDE_FROM_ALL ${_src_file})
+
+ target_compile_definitions(${_test_ident} PRIVATE
+ -DG_LOG_DOMAIN=\"${_test_ident}\"
+ -DSOURCEDIR=\"${CMAKE_CURRENT_SOURCE_DIR}\"
+ -DTEST_DATA_DIR=\"${CMAKE_SOURCE_DIR}/src/camel/tests/data\"
+ )
+
+ target_compile_options(${_test_ident} PUBLIC
+ ${CAMEL_CFLAGS}
+ )
+
+ target_include_directories(${_test_ident} PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/camel
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_SOURCE_DIR}/src/camel
+ ${CMAKE_SOURCE_DIR}/src/camel/tests/lib
+ ${CAMEL_INCLUDE_DIRS}
+ )
+
+ target_link_libraries(${_test_ident}
+ camel
+ cameltest
+ cameltest-provider
+ ${CAMEL_LDFLAGS}
+ )
+
+ set_target_properties(${_test_ident} PROPERTIES
+ OUTPUT_NAME ${_name}}
+ )
+
+ add_check_test(${_test_ident})
+endmacro(add_camel_test_one)
+
+macro(add_camel_tests _part _tests)
+ foreach(test IN LISTS ${_tests})
+ if(NOT "${test}" STREQUAL "")
+ add_camel_test_one(${_part} ${test} ${test}.c)
+ endif(NOT "${test}" STREQUAL "")
+ endforeach(test)
+endmacro(add_camel_tests)
+
+add_subdirectory(lib)
+add_subdirectory(message)
+add_subdirectory(folder)
+add_subdirectory(smime)
+add_subdirectory(misc)
+add_subdirectory(mime-filter)
diff --git a/src/camel/tests/README b/src/camel/tests/README
new file mode 100644
index 000000000..681533010
--- /dev/null
+++ b/src/camel/tests/README
@@ -0,0 +1,44 @@
+
+This directory is to contain regression tests that should be run
+before committing anything to camel.
+
+In each subdirectory of tests there is a README containing a
+one-line description of each test file. This README must be kept
+uptodate.
+
+To write a new test: copy an existing one and replace the contents.
+
+See camel-test.h for a number of functions and macros which setup and
+define the test environmet, and help provide meaningful messages when
+something actually fails.
+
+All tests have the following options:
+ -v[vvvv]
+ verbose. more v's more verbose. 2 v's will give you
+ a simple test backtrace of any partially failed tests.
+ No v's give you a simple backtrace of any failed tests.
+ -q
+ quiet. Dont print anything, unless there is a SEGV.
+
+See the other files in lib/* for utility functions that help to
+write the tests (object comparison, creation, etc functions).
+
+Tests may fail and be non-fatal. In this case, you will see "Partial
+success" on the result of each test line. To get more information
+about the test, run the test manually with a -v command line argument.
+The more v's you have the more detail you get (upto about -vvvvv),
+generally use -vv to find out which parts of a partially successful
+test failed, and where.
+
+Note that if writing tests, non-fatal tests (bracketed by a
+camel_test_nonfatal() and camel_test_fatal() pair) should only be
+defined where: 1. The test in question should ideally pass, and 2. The
+code has known limitations currently that stop it passing, but
+otherwise works for nominal input.
+
+To debug tests, set a breakpoint on camel_test_fail, which will be
+called for any failure, even a non-fatal one. Or set it to
+camel_test_break, which will only be called for fatal errors which are
+to print to the screen.
+
+ Michael <notzed@helixcode.com>
diff --git a/src/camel/tests/data/camel-test.gpg.pub b/src/camel/tests/data/camel-test.gpg.pub
new file mode 100644
index 000000000..fdf28a59b
--- /dev/null
+++ b/src/camel/tests/data/camel-test.gpg.pub
@@ -0,0 +1,24 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.0.7 (GNU/Linux)
+
+mQGiBD8qsjARBACkS39GA1e6pDUumC9sQyZaE2PEZW6VEHMGQ9IlJ/Qi2wJBFKgl
+Myl2Cko9Rpj2DmgGkBnoL6fPJNM9aCV0RvclvE15EViURU7mjYzF41uspvVMBg7b
+y3cwAzkkgyhLc/V0Inzm9UpJZ6JvrqYO5OvJUA+ka5SHrhuX4ptmjKLjNwCgwNCx
+CQHx9RIbPggYtfci1w9npm8D/3vks1q+G9r91JkRBqGTboBZ7HI1yU2tHJ38wsKp
+kamloFr3j0hmN0s5PY4wgz4daJ120yTmweJQfIljcN2+aHeI5838u7f3Wk5LJtLG
++0xKBpBeejzqT/FzhxLIOoQl3L8aF+KAY+Oz7Bm/J/5MRPEPdxWGIB1LhnkoDP5F
+5szGA/9POpFU7y34ACid5Grzk77LUVyHMGob+vgqoVITfCNGjfGf7tnXn1LlRf6N
+TE81p1vYDz2K4bpPmOgBXuESmpaTfXvlDy6oW7buslFqgddm/MfzzM0wC2jdx5TV
+NdNApku4PKA4pE2oTSg5EuGgxQLjJG/PHkoiRiK6yG8gign8vrQbTm8gVXNlciA8
+bm8udXNlckBuby5kb21haW4+iFkEExECABkFAj8qsjAECwcDAgMVAgMDFgIBAh4B
+AheAAAoJEOvMhgJ6QMoGIQcAoIweyKELWnksPUk5mmZz26JFsmQVAJ9aTm+OO289
++fdwq95xw/6KH2dEtrkBDQQ/KrIxEAQA26Y+QRaR7RymzyUW7MQlDbshD/vInLtb
+36FusuOWzL5TgYn6fiiu/e/J+qjZO5psucWelnBIbnLTZZJs06EgybLPCeEP34X7
+cSsAPZ2JE+TKPM93pxpQpRdWKhv+ocjJ4xPlwI8LTrQIHKxB3yF2WNCh/TQDi0hm
+6ktEi2RVusMAAwUD/3Wp0UbJ2/bgM9wOMAD8celvbl9V/L+9/UVBJrw4tH87FMZi
+NMNg13TbMrARqJjdpOoHzozMv1GOumC5sE8A0/pwbIdU9oSYA07D3+02dCQq+52a
+qtCGJoRq5okccQelRcs0cZIy/z5lzwsNnbshrmnqNdmuVf7BSdw7DjoFj5OLiEYE
+GBECAAYFAj8qsjEACgkQ68yGAnpAygZaCgCfZu68SOUfcUZC3CWi4ITBvcKGvmUA
+n1wrC5ZGv0SIRo5G1PswsKRZuEpl
+=SWoX
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/camel/tests/data/camel-test.gpg.sec b/src/camel/tests/data/camel-test.gpg.sec
new file mode 100644
index 000000000..f2af11e82
--- /dev/null
+++ b/src/camel/tests/data/camel-test.gpg.sec
@@ -0,0 +1,33 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.0.7 (GNU/Linux)
+
+lQHhBD8qsjARBACkS39GA1e6pDUumC9sQyZaE2PEZW6VEHMGQ9IlJ/Qi2wJBFKgl
+Myl2Cko9Rpj2DmgGkBnoL6fPJNM9aCV0RvclvE15EViURU7mjYzF41uspvVMBg7b
+y3cwAzkkgyhLc/V0Inzm9UpJZ6JvrqYO5OvJUA+ka5SHrhuX4ptmjKLjNwCgwNCx
+CQHx9RIbPggYtfci1w9npm8D/3vks1q+G9r91JkRBqGTboBZ7HI1yU2tHJ38wsKp
+kamloFr3j0hmN0s5PY4wgz4daJ120yTmweJQfIljcN2+aHeI5838u7f3Wk5LJtLG
++0xKBpBeejzqT/FzhxLIOoQl3L8aF+KAY+Oz7Bm/J/5MRPEPdxWGIB1LhnkoDP5F
+5szGA/9POpFU7y34ACid5Grzk77LUVyHMGob+vgqoVITfCNGjfGf7tnXn1LlRf6N
+TE81p1vYDz2K4bpPmOgBXuESmpaTfXvlDy6oW7buslFqgddm/MfzzM0wC2jdx5TV
+NdNApku4PKA4pE2oTSg5EuGgxQLjJG/PHkoiRiK6yG8gign8vv4DAwJKsiNZxDAs
+LGBFwzjlfD8Wpo+OQQSuw8kHCJRhMpjBlkWYXuAIXrPVJBZmr3hYQbaL413EFp0w
+DeQUzP0mIzpEU0FfZmFjdG9yOgAAr2ShZqfqP4RkV02MsarSDNg/K9RXc5f9JiM6
+RFNBX2ZhY3RvcjoAAK9F/Ldv8ksX8Ns3uqh8GEEDq88XZIeh/SYjOkRTQV9mYWN0
+b3I6AACvaOM+Tasvvj5+aQ3E44IHW8xzeXdM5bQbTm8gVXNlciA8bm8udXNlckBu
+by5kb21haW4+iFkEExECABkFAj8qsjAECwcDAgMVAgMDFgIBAh4BAheAAAoJEOvM
+hgJ6QMoGIQcAniz8Z+AESmwwrtQpllkLWaXQhkNMAJ4yqLAKSsfimKf3BDjxUzJ5
+6RKTE50BVwQ/KrIxEAQA26Y+QRaR7RymzyUW7MQlDbshD/vInLtb36FusuOWzL5T
+gYn6fiiu/e/J+qjZO5psucWelnBIbnLTZZJs06EgybLPCeEP34X7cSsAPZ2JE+TK
+PM93pxpQpRdWKhv+ocjJ4xPlwI8LTrQIHKxB3yF2WNCh/TQDi0hm6ktEi2RVusMA
+AwUD/3Wp0UbJ2/bgM9wOMAD8celvbl9V/L+9/UVBJrw4tH87FMZiNMNg13TbMrAR
+qJjdpOoHzozMv1GOumC5sE8A0/pwbIdU9oSYA07D3+02dCQq+52aqtCGJoRq5okc
+cQelRcs0cZIy/z5lzwsNnbshrmnqNdmuVf7BSdw7DjoFj5OL/gMDAkqyI1nEMCws
+YMk7XUGj7+BY1uyYyNpYp50F52HFsGXEffiKYPGIuybuP+Yz3ic4W9aQTJJPcIJg
+ZqUP9b14WfY4mgIm1Tz9JiM6RUxHX2ZhY3RvcjoAAKsH4JdVQugM+lGnuNuGoj7I
+V8O8bIsP/SYjOkVMR19mYWN0b3I6AACrBzvFd1NLrbYGMjsN3SKhgBtuHjGQx/0m
+IzpFTEdfZmFjdG9yOgAAqwR77L22dgo/dV8XO8NThXMbEmQgoY39JiM6RUxHX2Zh
+Y3RvcjoAAKsFRPzjSodiTFMeiwWHgpdgf4e2qzEN/SYjOkVMR19mYWN0b3I6AACr
+BX9hqiLZJKfgpo/hs/0wK8yHq6GXh4hGBBgRAgAGBQI/KrIxAAoJEOvMhgJ6QMoG
+WgoAoKwAHwpedO9qQNBu0REM1jNR9PuuAJ9ZtHyuOiziV/JageUT+hZ8s27U3A==
+=xLRp
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/camel/tests/data/gendoc.pl b/src/camel/tests/data/gendoc.pl
new file mode 100755
index 000000000..732f05a4e
--- /dev/null
+++ b/src/camel/tests/data/gendoc.pl
@@ -0,0 +1,65 @@
+#!/usr/bin/perl
+
+# Generate 'documents' in different encodings, from po files
+
+if ($#ARGV < 0) {
+ print "Usage: gendoc.pl pofile pofile ...\n";
+ exit 1;
+}
+
+$fmt = "| fmt -u ";
+
+sub read_msgstr()
+{
+ my $str = "";
+ while (<IN>) {
+ if (m/^msgstr \"(.*)\"/) {
+ $str = $1;
+ if ($str eq "") {
+ while (<IN>) {
+ if (m/\"(.*)\"/) {
+ $str .= $1;
+ } else {
+ last;
+ }
+ }
+ }
+ return $str;
+ }
+ }
+ return "";
+}
+
+$unknown = "x-unknown-1";
+
+foreach $name (@ARGV) {
+ if ($name =~ m@([^/]*).po$@) {
+ $poname = $1;
+
+ open IN,"<$name";
+
+ $header = read_msgstr;
+ if ($header =~ /Content-Type:.*charset=([-a-zA-Z0-9]*)/i) {
+ $charset = $1;
+ } else {
+ $charset = $unknown++;
+ }
+
+ print "Building $poname.$charset.txt from $name\n";
+
+ open OUT,"$fmt > $poname.$charset.txt";
+ while (!eof(IN)) {
+ $msg = read_msgstr;
+ # de-escape
+ $msg =~ s/\\n/\n/gso;
+ $msg =~ s/\\t/\t/gso;
+ $msg =~ s/\\(.)/$1/gso;
+ print OUT $msg." ";
+ }
+ close OUT;
+ close IN;
+ } else {
+ printf("ignoring $name, probably not intended\n");
+ }
+}
+
diff --git a/src/camel/tests/data/genline.pl b/src/camel/tests/data/genline.pl
new file mode 100755
index 000000000..99ff43c88
--- /dev/null
+++ b/src/camel/tests/data/genline.pl
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+
+# Generate 'documents' in different encodings, from po files
+
+if ($#ARGV < 0) {
+ print "Usage: genline.pl pofile pofile ...\n";
+ exit 1;
+}
+
+sub read_msgstr()
+{
+ my $str = "";
+ while (<IN>) {
+ if (m/^msgstr \"(.*)\"/) {
+ $str = $1;
+ if ($str eq "") {
+ while (<IN>) {
+ if (m/\"(.*)\"/) {
+ $str .= $1;
+ } else {
+ last;
+ }
+ }
+ }
+ return $str;
+ }
+ }
+ return "";
+}
+
+$unknown = "x-unknown-1";
+open OUT, ">test-lines.h";
+
+print OUT <<END;
+struct _l {
+ char *type;
+ char *line;
+} test_lines[] = {
+END
+
+foreach $name (@ARGV) {
+ if ($name =~ m@([^/]*).po$@) {
+ $poname = $1;
+
+ open IN,"<$name";
+
+ $header = read_msgstr;
+ if ($header =~ /Content-Type:.*charset=([-a-zA-Z0-9]*)/i) {
+ $charset = $1;
+ } else {
+ $charset = $unknown++;
+ }
+
+ while (!eof(IN)) {
+ $msg = read_msgstr;
+ if (length($msg) > 60 && length($msg) < 160) {
+ print OUT "\t{ \"$charset\", \"$msg\" },\n";
+ last;
+ }
+# $msg =~ s/\\n/\n/gso;
+# $msg =~ s/\\t/\t/gso;
+# $msg =~ s/\\(.)/$1/gso;
+# print OUT $msg." ";
+ }
+ close IN;
+ } else {
+ printf("ignoring $name, probably not intended\n");
+ }
+}
+
+print OUT "};\n";
+close OUT;
diff --git a/src/camel/tests/data/getaddr.pl b/src/camel/tests/data/getaddr.pl
new file mode 100755
index 000000000..74a8a81f7
--- /dev/null
+++ b/src/camel/tests/data/getaddr.pl
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+
+# get addresses out of messages
+
+if ($#ARGV < 0) {
+ print "Usage: $0 message(s) mbox(es)\n";
+ exit 1;
+}
+
+foreach $name (@ARGV) {
+ open IN,"<$name";
+ while (<IN>) {
+ if (/^From: (.*)/i
+ || /^To: (.*)/i
+ || /^Cc: (.*)/i) {
+ $base = $1;
+ while (<IN>) {
+ if (/^\s+(.*)/) {
+ $base .= " ".$1;
+ } else {
+ last;
+ }
+ }
+ $uniq{$base} = 1;
+ }
+ }
+ close IN;
+}
+
+foreach $key (sort keys %uniq) {
+ print $key."\n";
+}
diff --git a/src/camel/tests/folder/CMakeLists.txt b/src/camel/tests/folder/CMakeLists.txt
new file mode 100644
index 000000000..adaea8fdb
--- /dev/null
+++ b/src/camel/tests/folder/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(TESTS
+ test1
+ test2
+ test3
+ test4
+ test5
+ test6
+ test7
+ test8
+ test9
+ test10
+ test11
+)
+
+add_camel_tests(folder TESTS)
diff --git a/src/camel/tests/folder/README b/src/camel/tests/folder/README
new file mode 100644
index 000000000..1d6b0cf24
--- /dev/null
+++ b/src/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/src/camel/tests/folder/test1.c b/src/camel/tests/folder/test1.c
new file mode 100644
index 000000000..4265aefc4
--- /dev/null
+++ b/src/camel/tests/folder/test1.c
@@ -0,0 +1,60 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* store testing */
+
+#include "camel-test.h"
+#include "camel-test-provider.h"
+#include "folders.h"
+#include "session.h"
+
+static const gchar *local_drivers[] = {
+ "local"
+};
+
+static const gchar *local_providers[] = {
+ "mbox",
+ "mh",
+ "maildir"
+};
+
+gint main (gint argc, gchar **argv)
+{
+ CamelSession *session;
+ gint i;
+ gchar *path;
+
+ camel_test_init (argc, argv);
+ camel_test_provider_init (1, local_drivers);
+
+ /* clear out any camel-test data */
+ system ("/bin/rm -rf /tmp/camel-test");
+
+ session = camel_test_session_new ("/tmp/camel-test");
+
+ /* todo: cross-check everything with folder_info checks as well */
+ /* todo: subscriptions? */
+ /* todo: work out how to do imap/pop/nntp tests */
+ for (i = 0; i < G_N_ELEMENTS (local_providers); i++) {
+ path = g_strdup_printf ("%s:///tmp/camel-test/%s", local_providers[i], local_providers[i]);
+
+ test_folder_basic (session, path, TRUE, FALSE);
+
+ g_free (path);
+ }
+
+ g_object_unref (session);
+
+ return 0;
+}
diff --git a/src/camel/tests/folder/test10.c b/src/camel/tests/folder/test10.c
new file mode 100644
index 000000000..5331df927
--- /dev/null
+++ b/src/camel/tests/folder/test10.c
@@ -0,0 +1,123 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* threaded folder testing */
+
+#include <string.h>
+
+#include "camel-test.h"
+#include "camel-test-provider.h"
+#include "folders.h"
+#include "messages.h"
+#include "session.h"
+
+#define MAX_LOOP (10000)
+#define MAX_THREADS (5)
+
+#define d (x)
+
+static const gchar *local_drivers[] = { "local" };
+static const gchar *local_providers[] = {
+ "mbox",
+ "mh",
+ "maildir"
+};
+
+static gchar *path;
+static CamelSession *session;
+static gint testid;
+
+static gpointer
+worker (gpointer d)
+{
+ gint i;
+ CamelStore *store;
+ CamelService *service;
+ CamelFolder *folder;
+
+ for (i = 0; i < MAX_LOOP; i++) {
+ gchar *uid;
+
+ uid = g_strdup_printf ("test-uid-%d", i);
+ service = camel_session_add_service (
+ session, uid, path, CAMEL_PROVIDER_STORE, NULL);
+ g_free (uid);
+
+ check (CAMEL_IS_STORE (service));
+ store = CAMEL_STORE (service);
+
+ folder = camel_store_get_folder_sync (
+ store, "testbox",
+ CAMEL_STORE_FOLDER_CREATE, NULL, NULL);
+ if (testid == 0) {
+ g_object_unref (folder);
+ g_object_unref (store);
+ } else {
+ g_object_unref (store);
+ g_object_unref (folder);
+ }
+ }
+
+ return NULL;
+}
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ gint i, j;
+ GThread *threads[MAX_THREADS];
+
+ camel_test_init (argc, argv);
+ camel_test_provider_init (1, local_drivers);
+
+ /* clear out any camel-test data */
+ system ("/bin/rm -rf /tmp/camel-test");
+
+ session = camel_test_session_new ("/tmp/camel-test");
+
+ for (testid = 0; testid < 2; testid++) {
+ if (testid == 0)
+ camel_test_start ("store and folder bag torture test, stacked references");
+ else
+ camel_test_start ("store and folder bag torture test, unstacked references");
+
+ for (j = 0; j < G_N_ELEMENTS (local_providers); j++) {
+
+ camel_test_push ("provider %s", local_providers[j]);
+ path = g_strdup_printf ("%s:///tmp/camel-test/%s", local_providers[j], local_providers[j]);
+
+ for (i = 0; i < MAX_THREADS; i++) {
+ GError *error = NULL;
+
+ threads[i] = g_thread_new (NULL, worker, NULL);
+ }
+
+ for (i = 0; i < MAX_THREADS; i++) {
+ if (threads[i])
+ g_thread_join (threads[i]);
+ }
+
+ test_free (path);
+
+ camel_test_pull ();
+ }
+
+ camel_test_end ();
+ }
+
+ g_object_unref (session);
+
+ return 0;
+}
diff --git a/src/camel/tests/folder/test11.c b/src/camel/tests/folder/test11.c
new file mode 100644
index 000000000..372772026
--- /dev/null
+++ b/src/camel/tests/folder/test11.c
@@ -0,0 +1,229 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* threaded folder testing */
+
+#include <string.h>
+
+#include "camel-test.h"
+#include "camel-test-provider.h"
+#include "session.h"
+
+#define MAX_LOOP (10000)
+#define MAX_THREADS (5)
+
+static const gchar *local_drivers[] = { "local" };
+
+static CamelSession *session;
+
+/* FIXME: flags aren't really right yet */
+/* ASCII sorted on full_name */
+static CamelFolderInfo fi_list_1[] = {
+ { NULL, NULL, NULL, ".", "Inbox", CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, ".#evolution/Junk", "Junk", CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, ".#evolution/Trash", "Trash", CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "testbox", "testbox", CAMEL_FOLDER_CHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "testbox/foo", "foo", CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "testbox2", "testbox2", CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+};
+
+static CamelFolderInfo fi_list_2[] = {
+ { NULL, NULL, NULL, ".", "Inbox", CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, ".#evolution/Junk", "Junk", CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, ".#evolution/Trash", "Trash", CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "testbox", "testbox", CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "testbox2", "testbox2", CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+};
+
+static CamelFolderInfo fi_list_3[] = {
+ { NULL, NULL, NULL, "testbox", "testbox", CAMEL_FOLDER_CHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "testbox/foo", "foo", CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+};
+
+static gint
+cmp_fi (gconstpointer a,
+ gconstpointer 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,
+ gint len)
+{
+ GPtrArray *folders = g_ptr_array_new ();
+ gint 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].display_name);
+
+ 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->display_name, list[i].display_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);
+}
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ CamelFolder *f1, *f2;
+ CamelStore *store;
+ CamelService *service;
+ CamelFolderInfo *fi;
+ GError *error = NULL;
+
+ camel_test_init (argc, argv);
+ camel_test_provider_init (1, local_drivers);
+
+ /* clear out any camel-test data */
+ system ("/bin/rm -rf /tmp/camel-test");
+
+ session = camel_test_session_new ("/tmp/camel-test");
+ service = camel_session_add_service (
+ session, "test-uid",
+ "maildir:///tmp/camel-test/maildir",
+ CAMEL_PROVIDER_STORE, NULL);
+ store = CAMEL_STORE (service);
+
+ camel_test_start ("Maildir backward compatability tests");
+
+ camel_test_push ("./ prefix path, one level");
+ f1 = camel_store_get_folder_sync (
+ store, "testbox",
+ CAMEL_STORE_FOLDER_CREATE, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ f2 = camel_store_get_folder_sync (
+ store, "./testbox",
+ CAMEL_STORE_FOLDER_CREATE, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ 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_sync (
+ store, "testbox2",
+ CAMEL_STORE_FOLDER_CREATE, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ f2 = camel_store_get_folder_sync (
+ store, "./testbox2", 0, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ 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_sync (
+ store, "testbox/foo",
+ CAMEL_STORE_FOLDER_CREATE, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ f2 = camel_store_get_folder_sync (
+ store, "./testbox/foo",
+ CAMEL_STORE_FOLDER_CREATE, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ check (f1 == f2);
+ check_unref (f2, 2);
+ check_unref (f1, 1);
+ camel_test_pull ();
+
+ camel_test_push ("'.' == Inbox");
+ f2 = camel_store_get_inbox_folder_sync (store, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ f1 = camel_store_get_folder_sync (store, ".", 0, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ 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_sync (
+ store, "",
+ CAMEL_STORE_FOLDER_INFO_RECURSIVE,
+ NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ check (fi != NULL);
+ check_fi (fi, fi_list_1, G_N_ELEMENTS (fi_list_1));
+ camel_test_pull ();
+
+ camel_test_push ("folder info, flat");
+ fi = camel_store_get_folder_info_sync (store, "", 0, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ check (fi != NULL);
+ check_fi (fi, fi_list_2, G_N_ELEMENTS (fi_list_2));
+ camel_test_pull ();
+
+ camel_test_push ("folder info, recursive, non root");
+ fi = camel_store_get_folder_info_sync (
+ store, "testbox",
+ CAMEL_STORE_FOLDER_INFO_RECURSIVE,
+ NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ check (fi != NULL);
+ check_fi (fi, fi_list_3, G_N_ELEMENTS (fi_list_3));
+ camel_test_pull ();
+
+ check_unref (store, 1);
+ check_unref (session, 1);
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/folder/test2.c b/src/camel/tests/folder/test2.c
new file mode 100644
index 000000000..85b66ff26
--- /dev/null
+++ b/src/camel/tests/folder/test2.c
@@ -0,0 +1,62 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* folder testing */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "camel-test.h"
+#include "camel-test-provider.h"
+#include "messages.h"
+#include "folders.h"
+#include "session.h"
+
+static const gchar *local_drivers[] = { "local" };
+
+static const gchar *stores[] = {
+ "mbox:///tmp/camel-test/mbox",
+ "mh:///tmp/camel-test/mh",
+ "maildir:///tmp/camel-test/maildir"
+};
+
+gint main (gint argc, gchar **argv)
+{
+ CamelSession *session;
+ gint i;
+
+ camel_test_init (argc, argv);
+ camel_test_provider_init (1, local_drivers);
+
+ /* clear out any camel-test data */
+ system ("/bin/rm -rf /tmp/camel-test");
+
+ session = camel_test_session_new ("/tmp/camel-test");
+
+ /* we iterate over all stores we want to test, with indexing or indexing turned on or off */
+ for (i = 0; i < G_N_ELEMENTS (stores); i++) {
+ gchar *name = stores[i];
+
+ test_folder_message_ops (session, name, TRUE, "testbox");
+ }
+
+ /* create a pseudo-spool file, and check that */
+ creat ("/tmp/camel-test/testbox", 0600);
+ test_folder_message_ops (session, "spool:///tmp/camel-test/testbox", TRUE, "INBOX");
+
+ check_unref (session, 1);
+
+ return 0;
+}
diff --git a/src/camel/tests/folder/test3.c b/src/camel/tests/folder/test3.c
new file mode 100644
index 000000000..6562a7ad1
--- /dev/null
+++ b/src/camel/tests/folder/test3.c
@@ -0,0 +1,373 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* folder/index testing */
+
+#include <string.h>
+
+#include "camel-test.h"
+#include "camel-test-provider.h"
+#include "messages.h"
+#include "folders.h"
+#include "session.h"
+
+static void
+test_folder_search_sub (CamelFolder *folder,
+ const gchar *expr,
+ gint expected)
+{
+ GPtrArray *uids;
+ GHashTable *hash;
+ gint i;
+ GError *error = NULL;
+
+ uids = camel_folder_search_by_expression (folder, expr, NULL, &error);
+ check (uids != NULL);
+ check_msg (uids->len == expected, "search %s expected %d got %d", expr, expected, uids->len);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+
+ /* check the uid's are actually unique, too */
+ hash = g_hash_table_new (g_str_hash, g_str_equal);
+ for (i = 0; i < uids->len; i++) {
+ check (g_hash_table_lookup (hash, uids->pdata[i]) == NULL);
+ g_hash_table_insert (hash, uids->pdata[i], uids->pdata[i]);
+ }
+ g_hash_table_destroy (hash);
+
+ camel_folder_search_free (folder, uids);
+}
+
+static void
+test_folder_search (CamelFolder *folder,
+ const gchar *expr,
+ gint expected)
+{
+ gchar *matchall;
+
+#if 0
+ /* FIXME: ??? */
+ camel_test_nonfatal ("most searches require match-all construct");
+ push ("Testing search: %s", expr);
+ test_folder_search_sub (folder, expr, expected);
+ pull ();
+ camel_test_fatal ();
+#endif
+
+ matchall = g_strdup_printf ("(match-all %s)", expr);
+ push ("Testing search: %s", matchall);
+ test_folder_search_sub (folder, matchall, expected);
+ test_free (matchall);
+ pull ();
+}
+
+static struct {
+ gint counts[3];
+ const gchar *expr;
+} searches[] = {
+ { { 1, 1, 0 }, "(header-matches \"subject\" \"Test1 message99 subject\")" },
+
+ { { 100, 50, 0 }, "(header-contains \"subject\" \"subject\")" },
+ { { 100, 50, 0 }, "(header-contains \"subject\" \"Subject\")" },
+
+ { { 100, 50, 0 }, "(body-contains \"content\")" },
+ { { 100, 50, 0 }, "(body-contains \"Content\")" },
+
+ { { 0, 0, 0 }, "(user-flag \"every7\")" },
+ { { 100 / 13 + 1, 50 / 13 + 1, 0 }, "(user-flag \"every13\")" },
+ { { 1, 1, 0 }, "(= \"7tag1\" (user-tag \"every7\"))" },
+ { { 100 / 11 + 1, 50 / 11 + 1, 0 }, "(= \"11tag\" (user-tag \"every11\"))" },
+
+ { { 100 / 13 + 100 / 17 + 1, 50 / 13 + 50 / 17 + 2, 0 }, "(user-flag \"every13\" \"every17\")" },
+ { { 100 / 13 + 100 / 17 + 1, 50 / 13 + 50 / 17 + 2, 0 }, "(or (user-flag \"every13\") (user-flag \"every17\"))" },
+ { { 1, 0, 0 }, "(and (user-flag \"every13\") (user-flag \"every17\"))" },
+
+ { { 0, 0, 0 }, "(and (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"Test2\"))" },
+ /* we get 11 here as the header-contains is a substring match */
+ { { 11, 6, 0 }, "(and (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"subject\"))" },
+ { { 1, 1, 0 }, "(and (header-contains \"subject\" \"Test19\") (header-contains \"subject\" \"subject\"))" },
+ { { 0, 0, 0 }, "(and (header-contains \"subject\" \"Test191\") (header-contains \"subject\" \"subject\"))" },
+ { { 1, 1, 0 }, "(and (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"message99\"))" },
+
+ { { 22, 11, 0 }, "(or (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"Test2\"))" },
+ { { 2, 1, 0 }, "(or (header-contains \"subject\" \"Test16\") (header-contains \"subject\" \"Test99\"))" },
+ { { 1, 1, 0 }, "(or (header-contains \"subject\" \"Test123\") (header-contains \"subject\" \"Test99\"))" },
+ { { 100, 50, 0 }, "(or (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"subject\"))" },
+ { { 11, 6, 0 }, "(or (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"message99\"))" },
+
+ /* 72000 is 24*60*100 == half the 'sent date' of the messages */
+ { { 100 / 2, 50 / 2, 0 }, "(> 72000 (get-sent-date))" },
+ { { 100 / 2 - 1, 50 / 2, 0 }, "(< 72000 (get-sent-date))" },
+ { { 1, 0, 0 }, "(= 72000 (get-sent-date))" },
+ { { 0, 0, 0 }, "(= 72001 (get-sent-date))" },
+
+ { { (100 / 2 - 1) / 17 + 1, (50 / 2 - 1) / 17 + 1, 0 }, "(and (user-flag \"every17\") (< 72000 (get-sent-date)))" },
+ { { (100 / 2 - 1) / 17 + 1, (50 / 2 - 1) / 17, 0 }, "(and (user-flag \"every17\") (> 72000 (get-sent-date)))" },
+ { { (100 / 2 - 1) / 13 + 1, (50 / 2 - 1) / 13 + 1, 0 }, "(and (user-flag \"every13\") (< 72000 (get-sent-date)))" },
+ { { (100 / 2 - 1) / 13 + 1, (50 / 2 - 1) / 13 + 1, 0 }, "(and (user-flag \"every13\") (> 72000 (get-sent-date)))" },
+
+ { { 100 / 2 + 100 / 2 / 17, 50 / 2 + 50 / 2 / 17, 0 }, "(or (user-flag \"every17\") (< 72000 (get-sent-date)))" },
+ { { 100 / 2 + 100 / 2 / 17 + 1, 50 / 2 + 50 / 2 / 17 + 1, 0 }, "(or (user-flag \"every17\") (> 72000 (get-sent-date)))" },
+ { { 100 / 2 + 100 / 2 / 13, 50 / 2 + 50 / 2 / 13 + 1, 0 }, "(or (user-flag \"every13\") (< 72000 (get-sent-date)))" },
+ { { 100 / 2 + 100 / 2 / 13 + 1, 50 / 2 + 50 / 2 / 13 + 1, 0 }, "(or (user-flag \"every13\") (> 72000 (get-sent-date)))" },
+};
+
+static void
+run_search (CamelFolder *folder,
+ gint m)
+{
+ gint i, j = 0;
+
+ check (m == 50 || m == 100 || m == 0);
+
+ /* *shrug* messy, but it'll do */
+ if (m == 50)
+ j = 1;
+ else if (m == 0)
+ j = 2;
+
+ push ("performing searches, expected %d", m);
+ for (i = 0; i < G_N_ELEMENTS (searches); i++) {
+ push ("running search %d: %s", i, searches[i].expr);
+ test_folder_search (folder, searches[i].expr, searches[i].counts[j]);
+ pull ();
+ }
+ pull ();
+}
+
+static const gchar *local_drivers[] = { "local" };
+
+static const gchar *stores[] = {
+ "mbox:///tmp/camel-test/mbox",
+ "mh:///tmp/camel-test/mh",
+ "maildir:///tmp/camel-test/maildir"
+};
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ CamelService *service;
+ CamelSession *session;
+ CamelStore *store;
+ CamelFolder *folder;
+ CamelMimeMessage *msg;
+ gint i, j;
+ gint indexed;
+ GPtrArray *uids;
+ GError *error = NULL;
+
+ camel_test_init (argc, argv);
+ camel_test_provider_init (1, local_drivers);
+
+ /* clear out any camel-test data */
+ system ("/bin/rm -rf /tmp/camel-test");
+
+ session = camel_test_session_new ("/tmp/camel-test");
+
+ /* todo: cross-check everything with folder_info checks as well */
+ /* todo: work out how to do imap/pop/nntp tests */
+
+ /* we iterate over all stores we want to test, with indexing or indexing turned on or off */
+ for (i = 0; i < G_N_ELEMENTS (stores); i++) {
+ const gchar *name = stores[i];
+ for (indexed = 0; indexed < 2; indexed++) {
+ gchar *what = g_strdup_printf ("folder search: %s (%sindexed)", name, indexed?"":"non-");
+ gchar *uid;
+ gint flags;
+
+ camel_test_start (what);
+ test_free (what);
+
+ push ("getting store");
+ uid = g_strdup_printf ("test-uid-%d", i);
+ service = camel_session_add_service (
+ session, uid, stores[i],
+ CAMEL_PROVIDER_STORE, &error);
+ g_free (uid);
+ check_msg (error == NULL, "adding store: %s", error->message);
+ check (CAMEL_IS_STORE (service));
+ store = CAMEL_STORE (service);
+ g_clear_error (&error);
+ 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_sync (
+ store, "testbox", flags, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ check (folder != NULL);
+
+ /* we need an empty folder for this to work */
+ test_folder_counts (folder, 0, 0);
+ g_clear_error (&error);
+ pull ();
+
+ /* append a bunch of messages with specific content */
+ push ("appending 100 test messages");
+ for (j = 0; j < 100; j++) {
+ gchar *content, *subject;
+
+ push ("creating test message");
+ msg = test_message_create_simple ();
+ content = g_strdup_printf ("data%d content\n", j);
+ test_message_set_content_simple (
+ (CamelMimePart *) msg, 0, "text/plain",
+ content, strlen (content));
+ test_free (content);
+ subject = g_strdup_printf ("Test%d message%d subject", j, 100 - j);
+ camel_mime_message_set_subject (msg, subject);
+
+ camel_mime_message_set_date (msg, j * 60 * 24, 0);
+ pull ();
+
+ push ("appending simple message %d", j);
+ camel_folder_append_message_sync (
+ folder, msg, NULL, NULL, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ pull ();
+
+ test_free (subject);
+
+ check_unref (msg, 1);
+ }
+ pull ();
+
+ push ("Setting up some flags &c");
+ uids = camel_folder_get_uids (folder);
+ check (uids->len == 100);
+ for (j = 0; j < 100; j++) {
+ gchar *uid = uids->pdata[j];
+
+ if ((j / 13) * 13 == j) {
+ camel_folder_set_message_user_flag (folder, uid, "every13", TRUE);
+ }
+ if ((j / 17) * 17 == j) {
+ camel_folder_set_message_user_flag (folder, uid, "every17", TRUE);
+ }
+ if ((j / 7) * 7 == j) {
+ gchar *tag = g_strdup_printf ("7tag%d", j / 7);
+ camel_folder_set_message_user_tag (folder, uid, "every7", tag);
+ test_free (tag);
+ }
+ if ((j / 11) * 11 == j) {
+ camel_folder_set_message_user_tag (folder, uid, "every11", "11tag");
+ }
+ }
+ camel_folder_free_uids (folder, uids);
+ pull ();
+
+ camel_test_nonfatal ("Index not guaranteed to be accurate before sync: should be fixed eventually");
+ push ("Search before sync");
+ run_search (folder, 100);
+ pull ();
+ camel_test_fatal ();
+
+ push ("syncing folder, searching");
+ camel_folder_synchronize_sync (
+ folder, FALSE, NULL, NULL);
+ run_search (folder, 100);
+ pull ();
+
+ push ("syncing wiht expunge, search");
+ camel_folder_synchronize_sync (
+ folder, TRUE, NULL, NULL);
+ run_search (folder, 100);
+ pull ();
+
+ push ("deleting every 2nd message");
+ uids = camel_folder_get_uids (folder);
+ check (uids->len == 100);
+ for (j = 0; j < uids->len; j+=2) {
+ camel_folder_delete_message (folder, uids->pdata[j]);
+ }
+ camel_folder_free_uids (folder, uids);
+ run_search (folder, 100);
+
+ push ("syncing");
+ camel_folder_synchronize_sync (
+ folder, FALSE, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ run_search (folder, 100);
+ g_clear_error (&error);
+ pull ();
+
+ push ("expunging");
+ camel_folder_expunge_sync (folder, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ run_search (folder, 50);
+ g_clear_error (&error);
+ pull ();
+
+ pull ();
+
+ push ("closing and re-opening folder");
+ check_unref (folder, 1);
+ folder = camel_store_get_folder_sync (
+ store, "testbox",
+ flags & ~(CAMEL_STORE_FOLDER_CREATE),
+ NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ check (folder != NULL);
+ g_clear_error (&error);
+
+ push ("deleting remaining messages");
+ uids = camel_folder_get_uids (folder);
+ check (uids->len == 50);
+ for (j = 0; j < uids->len; j++) {
+ camel_folder_delete_message (folder, uids->pdata[j]);
+ }
+ camel_folder_free_uids (folder, uids);
+ run_search (folder, 50);
+
+ push ("syncing");
+ camel_folder_synchronize_sync (
+ folder, FALSE, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ run_search (folder, 50);
+ g_clear_error (&error);
+ pull ();
+
+ push ("expunging");
+ camel_folder_expunge_sync (folder, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ run_search (folder, 0);
+ g_clear_error (&error);
+ pull ();
+
+ pull ();
+
+ check_unref (folder, 1);
+ pull ();
+
+ push ("deleting test folder, with no messages in it");
+ camel_store_delete_folder_sync (
+ store, "testbox", NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ pull ();
+
+ check_unref (store, 1);
+ camel_test_end ();
+ }
+ }
+
+ check_unref (session, 1);
+
+ return 0;
+}
diff --git a/src/camel/tests/folder/test4.c b/src/camel/tests/folder/test4.c
new file mode 100644
index 000000000..6b94ed8d9
--- /dev/null
+++ b/src/camel/tests/folder/test4.c
@@ -0,0 +1,61 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* store testing, for remote folders */
+
+#include "camel-test.h"
+#include "camel-test-provider.h"
+#include "folders.h"
+#include "session.h"
+
+static const gchar *imap_drivers[] = { "imap4" };
+
+static const gchar *remote_providers[] = {
+ "IMAP_TEST_URL",
+};
+
+gint main (gint argc, gchar **argv)
+{
+ CamelSession *session;
+ gint i;
+ gchar *path;
+
+ camel_test_init (argc, argv);
+ camel_test_provider_init (1, imap_drivers);
+
+ /* clear out any camel-test data */
+ system ("/bin/rm -rf /tmp/camel-test");
+
+ session = camel_test_session_new ("/tmp/camel-test");
+
+ /* todo: cross-check everything with folder_info checks as well */
+ /* todo: subscriptions? */
+ for (i = 0; i < G_N_ELEMENTS (remote_providers); i++) {
+ path = getenv (remote_providers[i]);
+
+ if (path == NULL) {
+ printf ("Aborted (ignored).\n");
+ printf ("Set '%s', to re-run test.\n", remote_providers[i]);
+ /* tells make check to ignore us in the total count */
+ _exit (77);
+ }
+ /*camel_test_nonfatal("The IMAP code is just rooted");*/
+ test_folder_basic (session, path, FALSE, FALSE);
+ /*camel_test_fatal();*/
+ }
+
+ g_object_unref (session);
+
+ return 0;
+}
diff --git a/src/camel/tests/folder/test5.c b/src/camel/tests/folder/test5.c
new file mode 100644
index 000000000..04f850627
--- /dev/null
+++ b/src/camel/tests/folder/test5.c
@@ -0,0 +1,61 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* store testing, for remote folders */
+
+#include "camel-test.h"
+#include "camel-test-provider.h"
+#include "folders.h"
+#include "session.h"
+
+static const gchar *nntp_drivers[] = { "nntp" };
+
+static const gchar *remote_providers[] = {
+ "NNTP_TEST_URL",
+};
+
+gint main (gint argc, gchar **argv)
+{
+ CamelSession *session;
+ gint i;
+ gchar *path;
+
+ camel_test_init (argc, argv);
+ camel_test_provider_init (1, nntp_drivers);
+
+ /* clear out any camel-test data */
+ system ("/bin/rm -rf /tmp/camel-test");
+
+ session = camel_test_session_new ("/tmp/camel-test");
+
+ /* todo: cross-check everything with folder_info checks as well */
+ /* todo: subscriptions? */
+ for (i = 0; i < G_N_ELEMENTS (remote_providers); i++) {
+ path = getenv (remote_providers[i]);
+
+ if (path == NULL) {
+ printf ("Aborted (ignored).\n");
+ printf ("Set '%s', to re-run test.\n", remote_providers[i]);
+ /* tells make check to ignore us in the total count */
+ _exit (77);
+ }
+ camel_test_nonfatal ("Not sure how many tests apply to NNTP");
+ test_folder_basic (session, path, FALSE, FALSE);
+ camel_test_fatal ();
+ }
+
+ g_object_unref (session);
+
+ return 0;
+}
diff --git a/src/camel/tests/folder/test6.c b/src/camel/tests/folder/test6.c
new file mode 100644
index 000000000..3e8673946
--- /dev/null
+++ b/src/camel/tests/folder/test6.c
@@ -0,0 +1,59 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* folder testing */
+
+#include "camel-test.h"
+#include "camel-test-provider.h"
+#include "folders.h"
+#include "session.h"
+
+static const gchar *imap_drivers[] = { "imap4" };
+static const gchar *remote_providers[] = {
+ "IMAP_TEST_URL",
+};
+
+gint main (gint argc, gchar **argv)
+{
+ CamelSession *session;
+ gint i;
+ gchar *path;
+
+ camel_test_init (argc, argv);
+ camel_test_provider_init (1, imap_drivers);
+
+ /* clear out any camel-test data */
+ system ("/bin/rm -rf /tmp/camel-test");
+
+ session = camel_test_session_new ("/tmp/camel-test");
+
+ for (i = 0; i < G_N_ELEMENTS (remote_providers); i++) {
+ path = getenv (remote_providers[i]);
+
+ if (path == NULL) {
+ printf ("Aborted (ignored).\n");
+ printf ("Set '%s', to re-run test.\n", remote_providers[i]);
+ /* tells make check to ignore us in the total count */
+ _exit (77);
+ }
+ /*camel_test_nonfatal("The IMAP code is just rooted");*/
+ test_folder_message_ops (session, path, FALSE, "testbox");
+ test_folder_message_ops (session, path, FALSE, "INBOX");
+ /*camel_test_fatal();*/
+ }
+
+ check_unref (session, 1);
+
+ return 0;
+}
diff --git a/src/camel/tests/folder/test7.c b/src/camel/tests/folder/test7.c
new file mode 100644
index 000000000..ea2befe19
--- /dev/null
+++ b/src/camel/tests/folder/test7.c
@@ -0,0 +1,59 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* folder testing */
+
+#include "camel-test.h"
+#include "camel-test-provider.h"
+#include "folders.h"
+#include "messages.h"
+#include "session.h"
+
+static const gchar *nntp_drivers[] = { "nntp" };
+static const gchar *remote_providers[] = {
+ "NNTP_TEST_URL",
+};
+
+gint main (gint argc, gchar **argv)
+{
+ CamelSession *session;
+ gint i;
+ gchar *path;
+
+ camel_test_init (argc, argv);
+ camel_test_provider_init (1, nntp_drivers);
+
+ /* clear out any camel-test data */
+ system ("/bin/rm -rf /tmp/camel-test");
+
+ session = camel_test_session_new ("/tmp/camel-test");
+
+ for (i = 0; i < G_N_ELEMENTS (remote_providers); i++) {
+ path = getenv (remote_providers[i]);
+
+ if (path == NULL) {
+ printf ("Aborted (ignored).\n");
+ printf ("Set '%s', to re-run test.\n", remote_providers[i]);
+ /* tells make check to ignore us in the total count */
+ _exit (77);
+ }
+ camel_test_nonfatal ("Dont know how many tests apply to NNTP");
+ test_folder_message_ops (session, path, FALSE, "testbox");
+ camel_test_fatal ();
+ }
+
+ check_unref (session, 1);
+
+ return 0;
+}
diff --git a/src/camel/tests/folder/test8.c b/src/camel/tests/folder/test8.c
new file mode 100644
index 000000000..1a56706ab
--- /dev/null
+++ b/src/camel/tests/folder/test8.c
@@ -0,0 +1,235 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* threaded folder testing */
+
+#include <string.h>
+
+#include "camel-test.h"
+#include "camel-test-provider.h"
+#include "folders.h"
+#include "messages.h"
+#include "session.h"
+
+#define MAX_MESSAGES (100)
+#define MAX_THREADS (10)
+
+static const gchar *local_drivers[] = { "local" };
+
+static const gchar *local_providers[] = {
+ "mbox",
+ "mh",
+ "maildir"
+};
+
+static void
+test_add_message (CamelFolder *folder,
+ gint j)
+{
+ CamelMimeMessage *msg;
+ gchar *content;
+ gchar *subject;
+ GError *error = NULL;
+
+ push ("creating message %d\n", j);
+ msg = test_message_create_simple ();
+ content = g_strdup_printf ("Test message %08x 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 %08x subject", j);
+ camel_mime_message_set_subject (msg, subject);
+ pull ();
+
+ push ("appending simple message %d", j);
+ camel_folder_append_message_sync (
+ folder, msg, NULL, NULL, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ pull ();
+
+ check_unref (msg, 1);
+}
+
+struct _threadinfo {
+ gint id;
+ CamelFolder *folder;
+};
+
+static gpointer
+worker (gpointer d)
+{
+ struct _threadinfo *info = d;
+ gint i, j, id = info->id;
+ gchar *sub, *content;
+ GPtrArray *res;
+ CamelMimeMessage *msg;
+ GError *error = NULL;
+
+ /* we add a message, search for it, twiddle some flags, delete it */
+ /* and flat out */
+ for (i = 0; i < MAX_MESSAGES; i++) {
+ test_add_message (info->folder, id + i);
+
+ sub = g_strdup_printf ("(match-all (header-contains \"subject\" \"message %08x subject\"))", id + i);
+
+ push ("searching for message %d\n\tusing: %s", id + i, sub);
+ res = camel_folder_search_by_expression (info->folder, sub, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ check_msg (res->len == 1, "res->len = %d", res->len);
+ g_clear_error (&error);
+ pull ();
+
+ push ("getting message '%s'", res->pdata[0]);
+ msg = camel_folder_get_message_sync (
+ info->folder, (gchar *) res->pdata[0], NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ pull ();
+
+ content = g_strdup_printf ("Test message %08x contents\n\n", id + i);
+ push ("comparing content '%s': '%s'", res->pdata[0], content);
+ test_message_compare_content (camel_medium_get_content ((CamelMedium *) msg), content, strlen (content));
+ test_free (content);
+ pull ();
+
+ push ("deleting message, cleanup");
+ j = (100.0 * rand () / (RAND_MAX + 1.0));
+ if (j <= 70) {
+ camel_folder_delete_message (info->folder, res->pdata[0]);
+ }
+
+ camel_folder_search_free (info->folder, res);
+ res = NULL;
+ test_free (sub);
+
+ check_unref (msg, 1);
+ pull ();
+
+ /* about 1-in 100 calls will expunge */
+ j = (200.0 * rand () / (RAND_MAX + 1.0));
+ if (j <= 2) {
+ push ("expunging folder");
+ camel_folder_expunge_sync (info->folder, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ pull ();
+ }
+ }
+
+ return info;
+}
+
+gint main (gint argc, gchar **argv)
+{
+ CamelSession *session;
+ gint i, j, index;
+ gchar *path;
+ CamelStore *store;
+ CamelService *service;
+ GThread *threads[MAX_THREADS];
+ struct _threadinfo *info;
+ CamelFolder *folder;
+ GPtrArray *uids;
+ GError *error = NULL;
+
+ camel_test_init (argc, argv);
+ camel_test_provider_init (1, local_drivers);
+
+ /* clear out any camel-test data */
+ system ("/bin/rm -rf /tmp/camel-test");
+
+ session = camel_test_session_new ("/tmp/camel-test");
+
+ for (j = 0; j < G_N_ELEMENTS (local_providers); j++) {
+ for (index = 0; index < 2; index++) {
+ gchar *uid;
+
+ path = g_strdup_printf ("method %s %s", local_providers[j], index?"indexed":"nonindexed");
+ camel_test_start (path);
+ test_free (path);
+
+ push ("trying %s index %d", local_providers[j], index);
+ uid = g_strdup_printf ("test-uid-%d", j);
+ path = g_strdup_printf ("%s:///tmp/camel-test/%s", local_providers[j], local_providers[j]);
+ service = camel_session_add_service (
+ session, uid, path,
+ CAMEL_PROVIDER_STORE, &error);
+ g_free (uid);
+ check_msg (error == NULL, "%s", error->message);
+ check (CAMEL_IS_STORE (service));
+ store = CAMEL_STORE (service);
+ test_free (path);
+
+ if (index == 0)
+ folder = camel_store_get_folder_sync (
+ store, "testbox",
+ CAMEL_STORE_FOLDER_CREATE,
+ NULL, &error);
+ else
+ folder = camel_store_get_folder_sync (
+ store, "testbox",
+ CAMEL_STORE_FOLDER_CREATE |
+ CAMEL_STORE_FOLDER_BODY_INDEX,
+ NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+
+ for (i = 0; i < MAX_THREADS; i++) {
+ GError *error = NULL;
+
+ info = g_malloc (sizeof (*info));
+ info->id = i * MAX_MESSAGES;
+ info->folder = folder;
+
+ threads[i] = g_thread_new (NULL, worker, info);
+ }
+
+ for (i = 0; i < MAX_THREADS; i++) {
+ if (threads[i]) {
+ info = g_thread_join (threads[i]);
+ g_free (info);
+ }
+ }
+ pull ();
+
+ push ("deleting remaining messages");
+ uids = camel_folder_get_uids (folder);
+ for (i = 0; i < uids->len; i++) {
+ camel_folder_delete_message (folder, uids->pdata[i]);
+ }
+ camel_folder_free_uids (folder, uids);
+
+ camel_folder_expunge_sync (folder, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+
+ check_unref (folder, 1);
+
+ camel_store_delete_folder_sync (
+ store, "testbox", NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+
+ check_unref (store, 1);
+
+ pull ();
+
+ camel_test_end ();
+ }
+ }
+
+ g_object_unref (session);
+
+ return 0;
+}
diff --git a/src/camel/tests/folder/test9.c b/src/camel/tests/folder/test9.c
new file mode 100644
index 000000000..1b0141a51
--- /dev/null
+++ b/src/camel/tests/folder/test9.c
@@ -0,0 +1,253 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* folder/index testing */
+
+#include <string.h>
+
+#include "camel-test.h"
+#include "camel-test-provider.h"
+#include "messages.h"
+#include "folders.h"
+#include "session.h"
+
+static const gchar *local_drivers[] = { "local" };
+
+struct {
+ const gchar *name;
+ CamelFolder *folder;
+} mailboxes[] = {
+ { "INBOX", NULL },
+ { "folder1", NULL },
+ { "folder2", NULL },
+ { "folder3", NULL },
+ { "folder4", NULL },
+};
+
+struct {
+ const gchar *name, *match, *action;
+} rules[] = {
+ { "empty1", "(match-all (header-contains \"Frobnitz\"))", "(copy-to \"folder1\")" },
+ { "empty2", "(header-contains \"Frobnitz\")", "(copy-to \"folder2\")" },
+ { "count11", "(and (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"subject\"))", "(move-to \"folder3\")" },
+ { "empty3", "(and (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"subject\"))", "(move-to \"folder4\")" },
+ { "count1", "(body-contains \"data50\")", "(copy-to \"folder1\")" },
+ { "stop", "(body-contains \"data2\")", "(stop)" },
+ { "notreached1", "(body-contains \"data2\")", "(move-to \"folder2\")" },
+ { "count1", "(body-contains \"data3\")", "(move-to \"folder2\")" },
+ { "ustrcasecmp", "(header-matches \"Subject\" \"Test0 message100 subject\")", "(copy-to \"folder2\")" },
+};
+
+/* broken match rules */
+struct {
+ const gchar *name, *match, *action;
+} brokens[] = {
+ { "count1", "(body-contains data50)", "(copy-to \"folder1\")" }, /* non string argument */
+ { "count1", "(body-contains-stuff \"data3\")", "(move-to-folder \"folder2\")" }, /* invalid function */
+ { "count1", "(or (body-contains \"data3\") (foo))", "(move-to-folder \"folder2\")" }, /* invalid function */
+ { "count1", "(or (body-contains \"data3\") (foo)", "(move-to-folder \"folder2\")" }, /* missing ) */
+ { "count1", "(and body-contains \"data3\") (foo)", "(move-to-folder \"folder2\")" }, /* missing ( */
+ { "count1", "body-contains \"data3\")", "(move-to-folder \"folder2\")" }, /* missing ( */
+ { "count1", "body-contains \"data3\"", "(move-to-folder \"folder2\")" }, /* missing ( ) */
+ { "count1", "(body-contains \"data3\" ())", "(move-to-folder \"folder2\")" }, /* extra () */
+ { "count1", "()", "(move-to-folder \"folder2\")" }, /* invalid () */
+ { "count1", "", "(move-to-folder \"folder2\")" }, /* empty */
+};
+
+/* broken action rules */
+struct {
+ const gchar *name, *match, *action;
+} brokena[] = {
+ { "a", "(body-contains \"data2\")", "(body-contains \"help\")" }, /* rule in action */
+ { "a", "(body-contains \"data2\")", "(move-to-folder-name \"folder2\")" }, /* unknown function */
+ { "a", "(body-contains \"data2\")", "(or (move-to-folder \"folder2\")" }, /* missing ) */
+ { "a", "(body-contains \"data2\")", "(or move-to-folder \"folder2\"))" }, /* missing ( */
+ { "a", "(body-contains \"data2\")", "move-to-folder \"folder2\")" }, /* missing ( */
+ { "a", "(body-contains \"data2\")", "(move-to-folder \"folder2\" ())" }, /* invalid () */
+ { "a", "(body-contains \"data2\")", "()" }, /* invalid () */
+ { "a", "(body-contains \"data2\")", "" }, /* empty */
+};
+
+static CamelFolder *
+get_folder (CamelFilterDriver *d,
+ const gchar *uri,
+ gpointer data,
+ GError **error)
+{
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (mailboxes); i++)
+ if (!strcmp (mailboxes[i].name, uri)) {
+ return g_object_ref (mailboxes[i].folder);
+ }
+ return NULL;
+}
+
+gint main (gint argc, gchar **argv)
+{
+ CamelService *service;
+ CamelSession *session;
+ CamelStore *store;
+ CamelFolder *folder;
+ CamelMimeMessage *msg;
+ gint i, j;
+ CamelStream *mbox;
+ CamelFilterDriver *driver;
+ GError *error = NULL;
+
+ camel_test_init (argc, argv);
+ camel_test_provider_init (1, local_drivers);
+
+ /* clear out any camel-test data */
+ system ("/bin/rm -rf /tmp/camel-test");
+
+ camel_test_start ("Simple filtering of mbox");
+
+ session = camel_test_session_new ("/tmp/camel-test");
+
+ /* todo: cross-check everything with folder_info checks as well */
+ /* todo: work out how to do imap/pop/nntp tests */
+
+ push ("getting store");
+ service = camel_session_add_service (
+ session, "test-uid", "mbox:///tmp/camel-test/mbox",
+ CAMEL_PROVIDER_STORE, &error);
+ check_msg (error == NULL, "getting store: %s", error->message);
+ check (CAMEL_IS_STORE (service));
+ store = CAMEL_STORE (service);
+ g_clear_error (&error);
+ pull ();
+
+ push ("Creating output folders");
+ for (i = 0; i < G_N_ELEMENTS (mailboxes); i++) {
+ push ("creating %s", mailboxes[i].name);
+ mailboxes[i].folder = folder = camel_store_get_folder_sync (
+ store, mailboxes[i].name,
+ CAMEL_STORE_FOLDER_CREATE, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ check (folder != NULL);
+
+ /* we need an empty folder for this to work */
+ test_folder_counts (folder, 0, 0);
+ g_clear_error (&error);
+ pull ();
+ }
+ pull ();
+
+ /* append a bunch of messages with specific content */
+ push ("creating 100 test message mbox");
+ mbox = camel_stream_fs_new_with_name ("/tmp/camel-test/inbox", O_WRONLY | O_CREAT | O_EXCL, 0600, NULL);
+ for (j = 0; j < 100; j++) {
+ gchar *content, *subject;
+
+ push ("creating test message");
+ msg = test_message_create_simple ();
+ content = g_strdup_printf ("data%d content\n", j);
+ test_message_set_content_simple (
+ (CamelMimePart *) msg, 0, "text/plain",
+ content, strlen (content));
+ test_free (content);
+ subject = g_strdup_printf ("Test%d message%d subject", j, 100 - j);
+ camel_mime_message_set_subject (msg, subject);
+
+ camel_mime_message_set_date (msg, j * 60 * 24, 0);
+ pull ();
+
+ camel_stream_write_string (mbox, "From \n", NULL, NULL);
+ check (camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (msg), mbox, NULL, NULL) != -1);
+#if 0
+ push ("appending simple message %d", j);
+ camel_folder_append_message (folder, msg, NULL, ex);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ pull ();
+#endif
+ test_free (subject);
+
+ check_unref (msg, 1);
+ }
+ check (camel_stream_close (mbox, NULL, NULL) != -1);
+ check_unref (mbox, 1);
+ pull ();
+
+ push ("Building filters");
+ driver = camel_filter_driver_new (session);
+ camel_filter_driver_set_folder_func (driver, get_folder, NULL);
+ for (i = 0; i < G_N_ELEMENTS (rules); i++) {
+ camel_filter_driver_add_rule (driver, rules[i].name, rules[i].match, rules[i].action);
+ }
+ pull ();
+
+ push ("Executing filters");
+ camel_filter_driver_set_default_folder (driver, mailboxes[0].folder);
+#if 0 /* FIXME We no longer filter mbox files. */
+ camel_filter_driver_filter_mbox (
+ driver, "/tmp/camel-test/inbox", NULL, NULL, &error);
+#endif
+ check_msg (error == NULL, "%s", error->message);
+
+ /* now need to check the folder counts/etc */
+
+ check_unref (driver, 1);
+ g_clear_error (&error);
+ pull ();
+
+ /* this tests that invalid rules are caught */
+ push ("Testing broken match rules");
+ for (i = 0; i < G_N_ELEMENTS (brokens); i++) {
+ push ("rule %s", brokens[i].match);
+ driver = camel_filter_driver_new (session);
+ camel_filter_driver_set_folder_func (driver, get_folder, NULL);
+ camel_filter_driver_add_rule (driver, brokens[i].name, brokens[i].match, brokens[i].action);
+#if 0 /* FIXME We no longer filter mbox files. */
+ camel_filter_driver_filter_mbox (
+ driver, "/tmp/camel-test/inbox", NULL, NULL, &error);
+#endif
+ check (error != NULL);
+ check_unref (driver, 1);
+ g_clear_error (&error);
+ pull ();
+ }
+ pull ();
+
+ push ("Testing broken action rules");
+ for (i = 0; i < G_N_ELEMENTS (brokena); i++) {
+ push ("rule %s", brokena[i].action);
+ driver = camel_filter_driver_new (session);
+ camel_filter_driver_set_folder_func (driver, get_folder, NULL);
+ camel_filter_driver_add_rule (driver, brokena[i].name, brokena[i].match, brokena[i].action);
+#if 0 /* FIXME We no longer filter mbox files. */
+ camel_filter_driver_filter_mbox (
+ driver, "/tmp/camel-test/inbox", NULL, NULL, &error);
+#endif
+ check (error != NULL);
+ check_unref (driver, 1);
+ g_clear_error (&error);
+ pull ();
+ }
+ pull ();
+
+ for (i = 0; i < G_N_ELEMENTS (mailboxes); i++) {
+ check_unref (mailboxes[i].folder, 1);
+ }
+
+ check_unref (store, 1);
+
+ check_unref (session, 1);
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/lib/CMakeLists.txt b/src/camel/tests/lib/CMakeLists.txt
new file mode 100644
index 000000000..f7a8b10d2
--- /dev/null
+++ b/src/camel/tests/lib/CMakeLists.txt
@@ -0,0 +1,72 @@
+set(SOURCES
+ camel-test.c
+ camel-test.h
+ messages.c
+ messages.h
+ addresses.c
+ addresses.h
+ folders.c
+ folders.h
+ session.c
+ session.h
+ address-data.h
+)
+
+add_library(cameltest STATIC EXCLUDE_FROM_ALL
+ ${SOURCES}
+)
+
+add_dependencies(cameltest camel)
+
+target_compile_definitions(cameltest PRIVATE
+ -DG_LOG_DOMAIN=\"camel-test\"
+ -DCAMEL_BUILD_DIR=\"${CMAKE_BINARY_DIR}/src/camel\"
+)
+
+target_compile_options(cameltest PUBLIC
+ ${CAMEL_CFLAGS}
+)
+
+target_include_directories(cameltest PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/camel
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_SOURCE_DIR}/src/camel
+ ${CAMEL_INCLUDE_DIRS}
+)
+
+target_link_libraries(cameltest
+ camel
+ ${CAMEL_LDFLAGS}
+)
+
+add_library(cameltest-provider STATIC EXCLUDE_FROM_ALL
+ camel-test-provider.c
+ camel-test-provider.h
+)
+
+add_dependencies(cameltest-provider camel)
+
+target_compile_definitions(cameltest-provider PRIVATE
+ -DG_LOG_DOMAIN=\"camel-test-provider\"
+ -DCAMEL_BUILD_DIR=\"${CMAKE_BINARY_DIR}/src/camel\"
+)
+
+target_compile_options(cameltest-provider PUBLIC
+ ${CAMEL_CFLAGS}
+)
+
+target_include_directories(cameltest-provider PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/camel
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_SOURCE_DIR}/src/camel
+ ${CAMEL_INCLUDE_DIRS}
+)
+
+target_link_libraries(cameltest-provider
+ camel
+ ${CAMEL_LDFLAGS}
+)
diff --git a/src/camel/tests/lib/address-data.h b/src/camel/tests/lib/address-data.h
new file mode 100644
index 000000000..e1f154f97
--- /dev/null
+++ b/src/camel/tests/lib/address-data.h
@@ -0,0 +1,137 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* BE WARY of editing this file with emacs.
+ * Otherwise it might be smart and try to re-encode everything, which
+ * you really do not want
+*/
+
+static struct _a {
+ gint count;
+ const gchar *addr;
+ const gchar *utf8; /* The utf8 in this table was generated by
+ * Camel itself. As a result I'm making the
+ * assumption it was right when it was created.
+ * It also depends on the format of ::format (),
+ * which is likely to change, to handle other
+ * bugs!*/
+} test_address[] = {
+ { 1, "\"=?ISO-8859-1?Q?David_Guti=E9rrez_Magallanes?=\" <david@iiia.csic.es>", "David GutiĂŠrrez Magallanes <david@iiia.csic.es>" },
+ { 1, "\"=?iso-8859-1?Q?Jos=E9?= Antonio Milke G.\" <gerencia@ovoplus.com>", "JosĂŠ Antonio Milke G. <gerencia@ovoplus.com>" },
+ { 1, "\"=?iso-8859-2?Q?Hi-Fi_Internert_market_=3D_1.Virtu=E1ln=ED_Internetov=E9_H?= =?iso-8859-2?Q?i-Fi_Studio?=\" <hifimarket@atlas.cz>", "Hi-Fi Internert market = 1.VirtuĂĄlnĂ­ InternetovĂŠ Hi-Fi Studio <hifimarket@atlas.cz>" },
+ { 3, "\"James M. Cape\" <jcape@jcinteractive.com>, =?iso-8859-1?Q?Joaqu=EDn_Cuenca_Abela?= <cuenca@ie2.u-psud.fr>, gnome-hackers@nuclecu.unam.mx", "James M. Cape <jcape@jcinteractive.com>, JoaquĂ­n Cuenca Abela <cuenca@ie2.u-psud.fr>, gnome-hackers@nuclecu.unam.mx" },
+ { 1, "=?ISO-8859-1?Q?David_Guti=E9rrez_Magallanes?= <david@iiia.csic.es>", "David GutiĂŠrrez Magallanes <david@iiia.csic.es>" },
+ { 1, "=?ISO-8859-2?Q?Tomasz_K=B3oczko?= <kloczek@rudy.mif.pg.gda.pl>", "Tomasz Kłoczko <kloczek@rudy.mif.pg.gda.pl>" },
+ { 1, "=?ISO-8859-2?Q?Vladim=EDr_Solnick=FD?= <vs@utia.cas.cz>", "VladimĂ­r SolnickĂ˝ <vs@utia.cas.cz>" },
+ { 1, "=?iso-8859-1?Q?=22S=F6rensen=2C_Daniel=22?= <dasar@wmdata.com>", "\"SĂśrensen, Daniel\" <dasar@wmdata.com>" },
+ { 1, "=?iso-8859-1?Q?=C1=C2=AAQ=A7=CA?= <dennys@iim.nctu.edu.tw>", "ÁªQ§Ê <dennys@iim.nctu.edu.tw>" },
+ { 1, "=?iso-8859-1?Q?=C1=C2=AAQ=A7=CA?= <dennys@news.iim.nctu.edu.tw>", "ÁªQ§Ê <dennys@news.iim.nctu.edu.tw>" },
+ { 1, "=?iso-8859-1?Q?=C1kos?= Valentinyi <A.Valentinyi@soton.ac.uk>", "Ákos Valentinyi <A.Valentinyi@soton.ac.uk>" },
+ { 1, "=?iso-8859-1?Q?Joaqu=EDn?= Cuenca Abela <cuenca@ie2.u-psud.fr>", "JoaquĂ­n Cuenca Abela <cuenca@ie2.u-psud.fr>" },
+ { 2, "=?iso-8859-1?Q?Joaqu=EDn?= Cuenca Abela <cuenca@ie2.u-psud.fr>, gnome-hackers@nuclecu.unam.mx", "JoaquĂ­n Cuenca Abela <cuenca@ie2.u-psud.fr>, gnome-hackers@nuclecu.unam.mx" },
+ { 1, "=?iso-8859-1?Q?Joaqu=EDn_Cuenca_Abela?= <cuenca@celium.net>", "JoaquĂ­n Cuenca Abela <cuenca@celium.net>" },
+ { 1, "=?iso-8859-1?Q?Juantom=E1s=20Garc=EDa?= <juantomas@lared.es>", "JuantomĂĄs GarcĂ­a <juantomas@lared.es>" },
+ { 1, "=?iso-8859-1?Q?Kenneth_ll=E9phaane_Christiansen?= <kenneth@ripen.dk>", "Kenneth llĂŠphaane Christiansen <kenneth@ripen.dk>" },
+ { 1, "=?iso-8859-1?Q?Kjell_Tage_=D8hman?= <tage@ohman.no>", "Kjell Tage Øhman <tage@ohman.no>" },
+ { 1, "=?iso-8859-1?Q?Martin_Norb=E4ck?= <d95mback@dtek.chalmers.se>", "Martin Norbäck <d95mback@dtek.chalmers.se>" },
+ { 1, "=?iso-8859-1?Q?P=E5llen?= <pollen@astrakan.hig.se>", "PĂĽllen <pollen@astrakan.hig.se>" },
+ { 1, "=?iso-8859-1?Q?Ville_P=E4tsi?= <drc@gnu.org>", "Ville Pätsi <drc@gnu.org>" },
+ { 1, "=?iso-8859-1?q?Joaqu=EDn?= Cuenca Abela <cuenca@celium.net>", "JoaquĂ­n Cuenca Abela <cuenca@celium.net>" },
+ { 1, "=?iso-8859-2?Q?Dra=BEen_Ka=E8ar?= <dave@srce.hr>", "Dražen Kačar <dave@srce.hr>" },
+ /* yep this is right, this isn't valid so doesn't decode at all */
+ { 1, "=?windows-1250?Q? \"Jaka Mo=E8nik\" ?= <jaka.mocnik@kiss.uni-lj.si>", "=?windows-1250?Q? Jaka Mo=E8nik ?= <jaka.mocnik@kiss.uni-lj.si>" },
+ { 3, "George <jirka@5z.com>, Juantomas =?ISO-8859-1?Q?Garc=C3=83=C2=ADa?= <juantomas@lared.es>, gnome-hackers@gnome.org", "George <jirka@5z.com>, Juantomas García <juantomas@lared.es>, gnome-hackers@gnome.org" },
+ { 7, "Jon Trowbridge <trow@emccta.com>, gnome-1.4-list@gnome.org, gnome-devel-list@gnome.org, gnome-hackers@gnome.org, Dom Lachowicz <cinamod@hotmail.com>, =?iso-8859-1?Q?Joaqu=EDn_Cuenca_Abela?= <cuenca@celium.net>, sam th <sam@uchicago.edu>", "Jon Trowbridge <trow@emccta.com>, gnome-1.4-list@gnome.org, gnome-devel-list@gnome.org, gnome-hackers@gnome.org, Dom Lachowicz <cinamod@hotmail.com>, JoaquĂ­n Cuenca Abela <cuenca@celium.net>, sam th <sam@uchicago.edu>" },
+ { 6, "Jon Trowbridge <trow@emccta.com>, gnome-1.4-list@gnome.org, gnome-devel-list@gnome.org, gnome-hackers@gnome.org, Dom Lachowicz <cinamod@hotmail.com>, =?iso-8859-1?Q?Joaqu=EDn_Cuenca_Abela?= <cuenca@ie2.u-psud.fr>", "Jon Trowbridge <trow@emccta.com>, gnome-1.4-list@gnome.org, gnome-devel-list@gnome.org, gnome-hackers@gnome.org, Dom Lachowicz <cinamod@hotmail.com>, JoaquĂ­n Cuenca Abela <cuenca@ie2.u-psud.fr>" },
+ { 1, "Kai =?iso-8859-1?Q?Gro=DFjohann?= <Kai.Grossjohann@CS.Uni-Dortmund.DE>", "Kai Großjohann <Kai.Grossjohann@CS.Uni-Dortmund.DE>" },
+ { 1, "Kai.Grossjohann@CS.Uni-Dortmund.DE (Kai =?iso-8859-1?q?Gro=DFjohann?=)", "Kai Großjohann <Kai.Grossjohann@CS.Uni-Dortmund.DE>" },
+ { 1, "Rickard =?iso-8859-1?Q?Nordstr=F6m?= <rzi@ebox.tninet.se>", "Rickard NordstrĂśm <rzi@ebox.tninet.se>" },
+ { 1, "Tomasz =?iso-8859-2?q?K=B3oczko?= <kloczek@rudy.mif.pg.gda.pl>", "Tomasz Kłoczko <kloczek@rudy.mif.pg.gda.pl>" },
+ { 1, "VALCKE =?iso-8859-1?Q?C=E9dric?= <cvalcke@freesurf.fr>", "VALCKE CĂŠdric <cvalcke@freesurf.fr>" },
+ { 1, "Ville =?iso-8859-1?q?P=E4tsi?= <drc@gnu.org>", "Ville Pätsi <drc@gnu.org>" },
+ { 1, "david@iiia.csic.es (=?ISO-8859-1?Q?David_Guti=E9rrez_Magallanes?=)", "David GutiĂŠrrez Magallanes <david@iiia.csic.es>" },
+ { 1, "kloczek@rudy.mif.pg.gda.pl (=?ISO-8859-2?Q?Tomasz_K=B3oczko?=)", "Tomasz Kłoczko <kloczek@rudy.mif.pg.gda.pl>" },
+ { 1, "lassehp@imv.aau.dk (Lasse =?ISO-8859-1?Q?Hiller=F8e?= Petersen)", "Lasse Hillerøe Petersen <lassehp@imv.aau.dk>" },
+ { 1, "ysato@etl.go.jp (Yutaka Sato =?ISO-2022-JP?B?GyRAOjRGI0stGyhK?=)", "Yutaka Sato 佐藤豊 <ysato@etl.go.jp>" },
+};
+
+static struct _l {
+ const gchar *type;
+ const gchar *line;
+} test_lines[] = {
+ /* commented out unsupported charsets - FIXME: camel should somehow handle this, although it can't really of course */
+ /*{ "windows-1251", "Ĺäčí ŕďëĺň íĺ îňăîâŕđ˙ íŕ çŕ˙âęŕ çŕ çŕďčń.\nÄŕ ăî ďđĺěŕőíŕ ëč čëč äŕ ďî÷ŕęŕě?" },*/
+ { "iso-8859-1", "Omple les miniatures de la finestra amb contingut de la pantalla" },
+ { "ISO-8859-2", "Správce oken hýbe s okrajem okna\n(AfterStep, Enlightenment, FVWM, IceWM, Sawmill)" },
+ { "ISO-8859-1", "Vindueshĺndtering flytter dekorationsvindue istedet\n(AfterStep, Enlightenment, FVWM, IceWM, Sawfish)" },
+ { "ISO-8859-1", "Vorschaubilder der Fenster mit dem Bildschirminhalt ausfüllen" },
+ { "iso-8859-7", "ĹěöÜíéóç ĺńăáóéţí đďő äĺ öáßíďíôáé óôç ëßóôá đáńáčýńůí (ĐÁŃÁĘÁĚŘÇ-ËÉÓÔÁĐÁŃÁČŐŃŮÍ)" },
+ { "iso-8859-1", "You've chosen to disable the startup hint.\nTo re-enable it, choose \"Startup Hint\"\nin the GNOME Control Centre" },
+ { "iso-8859-1", "El aplique de reloj muestra en su panel la fecha y la hora de forma simple \ny ligero " },
+ { "iso-8859-1", "Applet ei vasta salvestuskäsule.\nKas peaks ta niisama sulgema, vői veel ootama?" },
+ { "iso-8859-1", "Lehio kudeatzaileak lehioaren dekorazaioa mugiarazten\n(AfterStep, Enlightenment, FVWM, IceWM, Sawmill)" },
+ { "iso-8859-15", "Näytä sovellukset, joiden ikkunoista on näkyvillä vain otsikkopalkki" },
+ { "ISO-8859-1", "Afficher les tâches qui ne sont pas dans la liste des fenętres" },
+ { "iso-8859-1", "Níl applet ag tabhair freagra ar iarratas sábháil.\nBain amach an applet nó lean ar fánacht?" },
+ { "iso-8859-1", "Amosa-las tarefas agochadas da lista de fiestras (SKIP-WINLIST)" },
+ { "iso-8859-2", "Az ablakkezelő a dekorációt mozgassa az ablak helyett\n(AfterStep, Enlightenment, FVWM, IceWM, SawMill)" },
+ { "iso-8859-1", "Riempi la finestra delle anteprime con il contenuto dello schermo" },
+ { "euc-jp", "ĽŚĽ¤ĽóĽÉĽŚĽŢĽÍĄźĽ¸Ľă¤Ďžţ¤ęĽŚĽ¤ĽóĽÉĽŚ¤ňĆ°¤Ť¤š\n(AfterStep, Enlightenment, FVWM, IceWM, Sawfish)" },
+ { "euc-kr", "â °ü¸ŽŔÚ°Ą ˛ŮšÎ â ´ë˝Ĺ ŔĚľż\n(AfterStep, Enlightenment, FVWM, IceWM, Sawmill)" },
+ { "iso-8859-13", "Priedas neatsakinëja á prađymŕ iđsisaugoti.\nPađalinti priedŕ ar laukti toliau?" },
+ { "iso-8859-1", "Window manager verplaatst dekoratie\n(AfterStep, Enlightenment, FVWM, IceWM, Sawmill)" },
+ { "iso-8859-1", "Vindushĺndtereren flytter dekorasjonsvinduet i stedet\n(AfterStep, Enlightenment, FVWM, IceWM, Sawfish)" },
+ { "iso-8859-2", "Przemieszczanie dekoracji zamiast okna\n(AfterStep, Enlightenment, FVWM, IceWM, Sawmill)" },
+ { "iso-8859-1", "Este programa é responsável por executar outras aplicaçőes, embeber pequenos applets, a paz no mundo e crashes aleatórios do X." },
+ { "iso-8859-1", "Mostrar tarefas que se escondem da lista de janelas (SKIP-WINLIST)" },
+ { "koi8-r", "÷ŮÓĎÔÁ ŇÁÂĎŢĹÇĎ ÓÔĎĚÁ × ĐĹŇĹËĚŔŢÁÔĹĚĹ ÓĎ×ĐÁÄÁĹÔ Ó ×ŮÓĎÔĎĘ ĐÁÎĹĚÉ" },
+ { "iso-8859-2", "Správca okien presúva okraje okien\n(AfterStep, Enlightenment, FVWM, IceWM, Sawfish)" },
+ { "iso-8859-2", "Kaži posle, ki se skrivajo pred upravljalnik oken (SKIP-WINLIST)" },
+ { "iso-8859-5", "Window ÜŐÝĐÔ×ŐŕŘ ßŢÜŐŕĐ ÔŐÚŢŕĐćŘŢÝŘ ßŕŢ×Ţŕ ăÜŐáâŢ âŢÓa\n(AfterStep, Enlightenment, FVWM, IceWM, Sawmill)" },
+ { "iso-8859-2", "Window menadzeri pomera dekoracioni prozor umesto toga\n(AfterStep, Enlightenment, FVWM, IceWM, Sawmill)" },
+ { "iso-8859-1", "Fönsterhanteraren flyttar dekorationsfönstret istället\n(AfterStep, Enlightenment, FVWM, IceWM, Sawfish)" },
+ /*{ "TSCII", "Ŕ˝˘ě¸źî-şđź¸ňž˘ř ŔĄ÷ě¸ ÓĘÂĄž Ŕ˝˘ě¸źí¸¨Ç ¸ĄÁ˘ (Ŕ˝˘ě¸źî-şđź¸ő-žĹ˘÷)" },*/
+ { "iso-8859-9", "Kaydetme isteđine bir uygulak cevap vermiyor .\nUygulađý sileyim mi , yoksa bekleyeyim mi ?" },
+ { "koi8-u", "đĹŇĹÍŚÝĹÎÎŃ ÄĹËĎŇÁĂŚ§ ÚÁÍŚÓÔŘ ×ŚËÎÁ\n(AfterStep, Enlightenment, FVWM, IceWM, Sawfish)" },
+ { "iso-8859-1", "Cwand on scriftôr est bodjî foű, li scriftôr čt totes\nles apliketes ĺ dvins sont pierdowes. Bodjî ci scriftôr chal?" },
+ { "gb2312", "ǨŇĆľ˝×°Ęδ°żÚšÜŔíłĚĐň(AfterStep, Enlightenment, FVWM, IceWM, SawMill)" },
+ { "big5", "ľřľĄşŢ˛zŞĚĽu˛ž°Ę¸Ëš˘ľřľĄ\n(AfterStep, Enlightenment, FVWM, IceWM, Sawmill)" },
+};
+
+static struct _d {
+ const gchar *name;
+ const gchar *email;
+} test_decode[] = {
+ { NULL, "email@example.com" },
+ { NULL, "your.email@example.com" },
+ { "Your", "email@example.com" },
+ { "Your Email", "email@example.com" },
+ { "Mr Smith Black", "smith@black.com" },
+ { "Mr. Smith Black", "smith@black.com" },
+ { "Mr. Smith O. Black", "smith.o@black.com" },
+ { "Joe d'Magio", "joe.d@example.com" },
+ { "example.com address", "email@example.com" },
+ { "email at example.com", "email@example.com" },
+ { "email.at.example.com", "email@example.com" }
+};
+
+static struct _ldf {
+ const gchar *with_name;
+ const gchar *without_name;
+} line_decode_formats[] = {
+ {"%s <%s>", "%s" },
+ { "%s<%s>", "%s" },
+ { "\"%s\" <%s>", " <%s>"},
+ { "\"%s\"<%s>", "<%s>"}
+};
diff --git a/src/camel/tests/lib/addresses.c b/src/camel/tests/lib/addresses.c
new file mode 100644
index 000000000..085dbc658
--- /dev/null
+++ b/src/camel/tests/lib/addresses.c
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include "addresses.h"
+#include "camel-test.h"
+
+void
+test_address_compare (CamelInternetAddress *addr,
+ CamelInternetAddress *addr2)
+{
+ const gchar *r1, *r2, *a1, *a2;
+ gchar *e1, *e2, *f1, *f2;
+ gint j;
+
+ check (camel_address_length (CAMEL_ADDRESS (addr)) == camel_address_length (CAMEL_ADDRESS (addr2)));
+ for (j = 0; j < camel_address_length (CAMEL_ADDRESS (addr)); j++) {
+
+ check (camel_internet_address_get (addr, j, &r1, &a1) == TRUE);
+ check (camel_internet_address_get (addr2, j, &r2, &a2) == TRUE);
+
+ check (string_equal (r1, r2));
+ check (strcmp (a1, a2) == 0);
+ }
+ check (camel_internet_address_get (addr, j, &r1, &a1) == FALSE);
+ check (camel_internet_address_get (addr2, j, &r2, &a2) == FALSE);
+
+ e1 = camel_address_encode (CAMEL_ADDRESS (addr));
+ e2 = camel_address_encode (CAMEL_ADDRESS (addr2));
+
+ if (camel_address_length (CAMEL_ADDRESS (addr)) == 0)
+ check (e1 == NULL && e2 == NULL);
+ else
+ check (e1 != NULL && e2 != NULL);
+
+ if (e1 != NULL) {
+ check_msg (string_equal (e1, e2), "e1 = '%s' e2 = '%s'", e1, e2);
+ test_free (e1);
+ test_free (e2);
+ }
+
+ f1 = camel_address_format (CAMEL_ADDRESS (addr));
+ f2 = camel_address_format (CAMEL_ADDRESS (addr2));
+
+ if (camel_address_length (CAMEL_ADDRESS (addr)) == 0)
+ check (f1 == NULL && f2 == NULL);
+ else
+ check (f1 != NULL && f2 != NULL);
+
+ if (f1 != NULL) {
+ check_msg (string_equal (f1, f2), "f1 = '%s' f2 = '%s'", f1, f2);
+ test_free (f1);
+ test_free (f2);
+ }
+}
diff --git a/src/camel/tests/lib/addresses.h b/src/camel/tests/lib/addresses.h
new file mode 100644
index 000000000..93dbf4921
--- /dev/null
+++ b/src/camel/tests/lib/addresses.h
@@ -0,0 +1,18 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <camel/camel.h>
+
+/* addresses.c */
+void test_address_compare (CamelInternetAddress *addr, CamelInternetAddress *addr2);
diff --git a/src/camel/tests/lib/camel-test-provider.c b/src/camel/tests/lib/camel-test-provider.c
new file mode 100644
index 000000000..576c81f13
--- /dev/null
+++ b/src/camel/tests/lib/camel-test-provider.c
@@ -0,0 +1,34 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "camel-test-provider.h"
+#include "camel-test.h"
+
+void
+camel_test_provider_init (gint argc,
+ const gchar **argv)
+{
+ gchar *name, *path;
+ gint i;
+ GError *error = NULL;
+
+ for (i = 0; i < argc; i++) {
+ name = g_strdup_printf ("libcamel%s."G_MODULE_SUFFIX, argv[i]);
+ path = g_build_filename (CAMEL_BUILD_DIR, "providers", argv[i], ".libs", name, NULL);
+ g_free (name);
+ camel_provider_load (path, &error);
+ check_msg (error == NULL, "Cannot load provider for '%s', test aborted", argv[i]);
+ g_free (path);
+ }
+}
diff --git a/src/camel/tests/lib/camel-test-provider.h b/src/camel/tests/lib/camel-test-provider.h
new file mode 100644
index 000000000..432130724
--- /dev/null
+++ b/src/camel/tests/lib/camel-test-provider.h
@@ -0,0 +1,22 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CAMEL_TEST_PROVIDER_H
+#define CAMEL_TEST_PROVIDER_H
+
+#include <glib.h>
+
+void camel_test_provider_init (gint argc, const gchar **argv);
+
+#endif
diff --git a/src/camel/tests/lib/camel-test.c b/src/camel/tests/lib/camel-test.c
new file mode 100644
index 000000000..9fdc6e4ea
--- /dev/null
+++ b/src/camel/tests/lib/camel-test.c
@@ -0,0 +1,356 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "camel-test.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+/* well i dunno, doesn't seem to be in the headers but hte manpage mentions it */
+/* a nonportable checking mutex for glibc, not really needed, just validates
+ * the test harness really */
+static GMutex lock;
+#define CAMEL_TEST_LOCK g_mutex_lock(&lock)
+#define CAMEL_TEST_UNLOCK g_mutex_unlock(&lock)
+#define CAMEL_TEST_ID (g_thread_self())
+
+static gint setup;
+static gint ok;
+
+struct _stack {
+ struct _stack *next;
+ gint fatal;
+ gchar *what;
+};
+
+/* per-thread state */
+struct _state {
+ gchar *test;
+ gint nonfatal;
+ struct _stack *state;
+};
+
+static GHashTable *info_table;
+
+gint camel_test_verbose;
+
+static void
+dump_action (GThread *thread,
+ struct _state *s,
+ gpointer d)
+{
+ struct _stack *node;
+
+ printf ("\nThread %p:\n", thread);
+
+ node = s->state;
+ if (node) {
+ printf ("Current action:\n");
+ while (node) {
+ printf ("\t%s%s\n", node->fatal?"":"[nonfatal]", node->what);
+ node = node->next;
+ }
+ }
+ printf ("\tTest: %s\n", s->test);
+}
+
+static void G_GNUC_NORETURN
+die (gint sig)
+{
+ static gint indie = 0;
+
+ if (!indie) {
+ indie = 1;
+ printf ("\n\nReceived fatal signal %d\n", sig);
+ g_hash_table_foreach (info_table, (GHFunc) dump_action, 0);
+
+ if (camel_test_verbose > 2) {
+ printf ("Attach debugger to pid %d to debug\n", getpid ());
+ sleep (1000);
+ }
+ }
+
+ _exit (1);
+}
+
+static struct _state *
+current_state (void)
+{
+ struct _state *info;
+
+ if (info_table == NULL)
+ info_table = g_hash_table_new (0, 0);
+
+ info = g_hash_table_lookup (info_table, CAMEL_TEST_ID);
+ if (info == NULL) {
+ info = g_malloc0 (sizeof (*info));
+ g_hash_table_insert (info_table, CAMEL_TEST_ID, info);
+ }
+ return info;
+}
+
+void
+camel_test_init (gint argc,
+ gchar **argv)
+{
+ struct stat st;
+ gchar *path;
+ gint i;
+
+ setup = 1;
+
+ path = g_strdup_printf ("/tmp/camel-test");
+ if (mkdir (path, 0700) == -1 && errno != EEXIST)
+ abort ();
+
+ if (g_stat (path, &st) == -1)
+ abort ();
+
+ if (!S_ISDIR (st.st_mode) || access (path, R_OK | W_OK | X_OK) == -1)
+ abort ();
+
+ camel_init (path, FALSE);
+ g_free (path);
+
+ info_table = g_hash_table_new (0, 0);
+
+ signal (SIGSEGV, die);
+ signal (SIGABRT, die);
+
+ /* default, just say what, how well we did, unless fail, then abort */
+ camel_test_verbose = 1;
+
+ for (i = 0; i < argc; i++) {
+ if (argv[i][0] == '-') {
+ switch (argv[i][1]) {
+ case 'v':
+ camel_test_verbose = strlen (argv[i]);
+ break;
+ case 'q':
+ camel_test_verbose = 0;
+ break;
+ }
+ }
+ }
+}
+
+void camel_test_start (const gchar *what)
+{
+ struct _state *s;
+
+ CAMEL_TEST_LOCK;
+
+ s = current_state ();
+
+ if (!setup)
+ camel_test_init (0, 0);
+
+ ok = 1;
+
+ s->test = g_strdup (what);
+
+ if (camel_test_verbose > 0) {
+ printf ("Test: %s ... ", what);
+ fflush (stdout);
+ }
+
+ CAMEL_TEST_UNLOCK;
+}
+
+void camel_test_push (const gchar *what, ...)
+{
+ struct _stack *node;
+ va_list ap;
+ gchar *text;
+ struct _state *s;
+
+ CAMEL_TEST_LOCK;
+
+ s = current_state ();
+
+ va_start (ap, what);
+ text = g_strdup_vprintf (what, ap);
+ va_end (ap);
+
+ if (camel_test_verbose > 3)
+ printf ("Start step: %s\n", text);
+
+ node = g_malloc (sizeof (*node));
+ node->what = text;
+ node->next = s->state;
+ node->fatal = 1;
+ s->state = node;
+
+ CAMEL_TEST_UNLOCK;
+}
+
+void camel_test_pull (void)
+{
+ struct _stack *node;
+ struct _state *s;
+
+ CAMEL_TEST_LOCK;
+
+ s = current_state ();
+
+ g_return_if_fail (s->state);
+
+ if (camel_test_verbose > 3)
+ printf ("Finish step: %s\n", s->state->what);
+
+ node = s->state;
+ s->state = node->next;
+ if (!node->fatal)
+ s->nonfatal--;
+ g_free (node->what);
+ g_free (node);
+
+ CAMEL_TEST_UNLOCK;
+}
+
+/* where to set breakpoints */
+void camel_test_break (void);
+
+void camel_test_break (void)
+{
+}
+
+void camel_test_fail (const gchar *why, ...)
+{
+ va_list ap;
+
+ va_start (ap, why);
+ camel_test_failv (why, ap);
+ va_end (ap);
+}
+
+void camel_test_failv (const gchar *why, va_list ap)
+{
+ gchar *text;
+ struct _state *s;
+
+ CAMEL_TEST_LOCK;
+
+ s = current_state ();
+
+ text = g_strdup_vprintf (why, ap);
+
+ if ((s->nonfatal == 0 && camel_test_verbose > 0)
+ || (s->nonfatal && camel_test_verbose > 1)) {
+ printf ("Failed.\n%s\n", text);
+ camel_test_break ();
+ }
+
+ g_free (text);
+
+ if ((s->nonfatal == 0 && camel_test_verbose > 0)
+ || (s->nonfatal && camel_test_verbose > 2)) {
+ g_hash_table_foreach (info_table, (GHFunc) dump_action, 0);
+ }
+
+ if (s->nonfatal == 0) {
+ exit (1);
+ } else {
+ ok = 0;
+ if (camel_test_verbose > 1) {
+ printf ("Known problem (ignored):\n");
+ dump_action (CAMEL_TEST_ID, s, 0);
+ }
+ }
+
+ CAMEL_TEST_UNLOCK;
+}
+
+void camel_test_nonfatal (const gchar *what, ...)
+{
+ struct _stack *node;
+ va_list ap;
+ gchar *text;
+ struct _state *s;
+
+ CAMEL_TEST_LOCK;
+
+ s = current_state ();
+
+ va_start (ap, what);
+ text = g_strdup_vprintf (what, ap);
+ va_end (ap);
+
+ if (camel_test_verbose > 3)
+ printf ("Start nonfatal: %s\n", text);
+
+ node = g_malloc (sizeof (*node));
+ node->what = text;
+ node->next = s->state;
+ node->fatal = 0;
+ s->nonfatal++;
+ s->state = node;
+
+ CAMEL_TEST_UNLOCK;
+}
+
+void camel_test_fatal (void)
+{
+ camel_test_pull ();
+}
+
+void camel_test_end (void)
+{
+ if (camel_test_verbose > 0) {
+ if (ok)
+ printf ("Ok\n");
+ else
+ printf ("Partial success\n");
+ }
+
+ fflush (stdout);
+}
+
+/* compare strings, ignore whitespace though */
+gint string_equal (const gchar *a, const gchar *b)
+{
+ const gchar *ap, *bp;
+
+ ap = a;
+ bp = b;
+
+ while (*ap && *bp) {
+ while (*ap == ' ' || *ap == '\n' || *ap == '\t')
+ ap++;
+ while (*bp == ' ' || *bp == '\n' || *bp == '\t')
+ bp++;
+
+ a = ap;
+ b = bp;
+
+ while (*ap && *ap != ' ' && *ap != '\n' && *ap != '\t')
+ ap++;
+ while (*bp && *bp != ' ' && *bp != '\n' && *bp != '\t')
+ bp++;
+
+ if (ap - a != bp - a
+ && ap - 1 > 0
+ && memcmp (a, b, ap - a) != 0) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
diff --git a/src/camel/tests/lib/camel-test.h b/src/camel/tests/lib/camel-test.h
new file mode 100644
index 000000000..d08c9964c
--- /dev/null
+++ b/src/camel/tests/lib/camel-test.h
@@ -0,0 +1,81 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* some utilities for testing */
+
+#include "evolution-data-server-config.h"
+
+#include <stdlib.h>
+#include <glib/gstdio.h>
+#include <camel/camel.h>
+
+void camel_test_failv (const gchar *why, va_list ap);
+
+/* perform a check assertion */
+#define check(x) do {if (!(x)) { camel_test_fail("%s:%d: %s", __FILE__, __LINE__, #x); } } while (0)
+/* check with message */
+#ifdef __GNUC__
+#define check_msg(x, y, z...) do {if (!(x)) { camel_test_fail("%s:%d: %s\n\t" #y, __FILE__, __LINE__, #x, ##z); } } while (0)
+#else
+static void check_msg (gint truth, gchar *fmt, ...)
+{
+ /* no gcc, we lose the condition that failed, nm */
+ if (!truth) {
+ va_list ap;
+ va_start (ap, fmt);
+ camel_test_failv (fmt, ap);
+ va_end (ap);
+ }
+}
+#endif
+
+#define check_count(object, expected) do { \
+ if (G_OBJECT (object)->ref_count != expected) { \
+ camel_test_fail ("%s->ref_count != %s\n\tref_count = %d", #object, #expected, G_OBJECT (object)->ref_count); \
+ } \
+} while (0)
+
+#define check_unref(object, expected) do { \
+ check_count (object, expected); \
+ g_object_unref (object); \
+ if (expected == 1) { \
+ object = NULL; \
+ } \
+} while (0)
+
+#define test_free(mem) (g_free(mem), mem=NULL)
+
+#define push camel_test_push
+#define pull camel_test_pull
+
+void camel_test_init (gint argc, gchar **argv);
+
+/* start/finish a new test */
+void camel_test_start (const gchar *what);
+void camel_test_end (void);
+
+/* start/finish a new test part */
+void camel_test_push (const gchar *what, ...);
+void camel_test_pull (void);
+
+/* fail a test, with a reason why */
+void camel_test_fail (const gchar *why, ...);
+
+/* Set whether a failed test quits. May be nested, but must be called in nonfatal/fatal pairs */
+void camel_test_nonfatal (const gchar *why, ...);
+void camel_test_fatal (void);
+
+/* utility functions */
+/* compare strings, ignore whitespace though */
+gint string_equal (const gchar *a, const gchar *b);
diff --git a/src/camel/tests/lib/folders.c b/src/camel/tests/lib/folders.c
new file mode 100644
index 000000000..4db8c89ab
--- /dev/null
+++ b/src/camel/tests/lib/folders.c
@@ -0,0 +1,627 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include "camel-test.h"
+#include "folders.h"
+#include "messages.h"
+
+/* check the total/unread is what we think it should be */
+void
+test_folder_counts (CamelFolder *folder,
+ gint total,
+ gint unread)
+{
+ GPtrArray *s;
+ gint i, myunread;
+ CamelMessageInfo *info;
+
+ push ("test folder counts %d total %d unread", total, unread);
+
+ /* 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 (camel_message_info_get_flags (info) & CAMEL_MESSAGE_SEEN)
+ myunread--;
+ }
+ check (unread == myunread);
+ camel_folder_free_summary (folder, s);
+
+ /* 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 (camel_message_info_get_flags (info) & CAMEL_MESSAGE_SEEN)
+ myunread--;
+ camel_message_info_unref (info);
+ }
+ check (unread == myunread);
+ camel_folder_free_uids (folder, s);
+
+ pull ();
+}
+
+static gint
+safe_strcmp (const gchar *a,
+ const gchar *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_get_subject (info), camel_mime_message_get_subject (msg)) == 0,
+ "info->subject = '%s', get_subject () = '%s'", camel_message_info_get_subject (info), camel_mime_message_get_subject (msg));
+
+ /* FIXME: testing from/cc/to, etc is more tricky */
+
+ check (camel_message_info_get_date_sent (info) == 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 gchar *uid)
+{
+ CamelMimeMessage *msg;
+ CamelMessageInfo *info;
+ GPtrArray *s;
+ gint i;
+ gint found;
+ GError *error = NULL;
+
+ 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_get_uid (info), uid) == 0);
+ camel_message_info_unref (info);
+
+ /* then, getting message */
+ msg = camel_folder_get_message_sync (folder, uid, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ check (msg != NULL);
+
+ /* cross check with info */
+ test_message_info (msg, info);
+
+ g_object_unref (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_get_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);
+
+ g_clear_error (&error);
+
+ pull ();
+}
+
+/* check message not present */
+void
+test_folder_not_message (CamelFolder *folder,
+ const gchar *uid)
+{
+ CamelMimeMessage *msg;
+ CamelMessageInfo *info;
+ GPtrArray *s;
+ gint i;
+ gint found;
+ GError *error = NULL;
+
+ 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_sync (folder, uid, NULL, &error);
+ check (error != NULL);
+ check (msg == NULL);
+ g_clear_error (&error);
+ 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_get_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 ();
+
+ g_clear_error (&error);
+
+ pull ();
+}
+
+/* test basic store operations on folders */
+/* TODO: Add subscription stuff */
+void
+test_folder_basic (CamelSession *session,
+ const gchar *storename,
+ gint local,
+ gint spool)
+{
+ CamelStore *store;
+ CamelFolder *folder;
+ CamelService *service;
+ gchar *what = g_strdup_printf ("testing store: %s", storename);
+ GError *error = NULL;
+
+ camel_test_start (what);
+ test_free (what);
+
+ push ("getting store");
+ service = camel_session_add_service (
+ session, storename, storename, CAMEL_PROVIDER_STORE, &error);
+ check_msg (error == NULL, "adding store: %s", error->message);
+ check (CAMEL_IS_STORE (service));
+ store = CAMEL_STORE (service);
+ pull ();
+
+ /* local providers == no inbox */
+ push ("getting inbox folder");
+ folder = camel_store_get_inbox_folder_sync (store, NULL, &error);
+ if (local) {
+ /* Well, maildir can have an inbox */
+ if (folder) {
+ check (error == NULL);
+ check_unref (folder, 1);
+ } else {
+ check (error != NULL);
+ }
+ } else {
+ check_msg (error == NULL, "%s", error->message);
+ check (folder != NULL);
+ check_unref (folder, 2);
+ }
+ g_clear_error (&error);
+ pull ();
+
+ push ("getting a non-existant folder, no create");
+ folder = camel_store_get_folder_sync (
+ store, "unknown", 0, NULL, &error);
+ check (error != NULL);
+ check (folder == NULL);
+ g_clear_error (&error);
+ pull ();
+
+ if (!spool) {
+ push ("getting a non-existant folder, with create");
+ folder = camel_store_get_folder_sync (
+ store, "testbox", CAMEL_STORE_FOLDER_CREATE,
+ NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ check (folder != NULL);
+ if (local)
+ check_unref (folder, 1);
+ else
+ check_unref (folder, 2);
+ g_clear_error (&error);
+ pull ();
+
+ push ("getting an existing folder");
+ folder = camel_store_get_folder_sync (
+ store, "testbox", 0, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ check (folder != NULL);
+ if (local)
+ check_unref (folder, 1);
+ else
+ check_unref (folder, 2);
+ g_clear_error (&error);
+ pull ();
+
+ push ("renaming a non-existant folder");
+ camel_store_rename_folder_sync (
+ store, "unknown1", "unknown2", NULL, &error);
+ check (error != NULL);
+ g_clear_error (&error);
+ pull ();
+
+ push ("renaming an existing folder");
+ camel_store_rename_folder_sync (
+ store, "testbox", "testbox2", NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ pull ();
+
+ push ("opening the old name of a renamed folder");
+ folder = camel_store_get_folder_sync (
+ store, "testbox", 0, NULL, &error);
+ check (error != NULL);
+ check (folder == NULL);
+ g_clear_error (&error);
+ pull ();
+
+ push ("opening the new name of a renamed folder");
+ folder = camel_store_get_folder_sync (
+ store, "testbox2", 0, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ 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_sync (store, "unknown", NULL, &error);
+ check (error != NULL);
+ g_clear_error (&error);
+ pull ();
+
+ if (!spool) {
+ push ("deleting an existing folder");
+ camel_store_delete_folder_sync (
+ store, "testbox2", NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ pull ();
+ }
+
+ push ("opening a folder that has been deleted");
+ folder = camel_store_get_folder_sync (
+ store, "testbox2", 0, NULL, &error);
+ check (error != NULL);
+ check (folder == NULL);
+ g_clear_error (&error);
+ pull ();
+
+ check_unref (store, 1);
+
+ camel_test_end ();
+}
+
+/* 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 gchar *name,
+ gint local,
+ const gchar *mailbox)
+{
+ CamelStore *store;
+ CamelService *service;
+ CamelFolder *folder;
+ CamelMimeMessage *msg;
+ gint j;
+ gint indexed, max;
+ GPtrArray *uids;
+ CamelMessageInfo *info;
+ GError *error = NULL;
+
+ max = local ? 2 : 1;
+
+ for (indexed = 0; indexed < max; indexed++) {
+ gchar *what = g_strdup_printf ("folder ops: %s %s", name, local ? (indexed?"indexed":"non-indexed"):"");
+ gint flags;
+
+ camel_test_start (what);
+ test_free (what);
+
+ push ("getting store");
+ service = camel_session_add_service (
+ session, name, name, CAMEL_PROVIDER_STORE, &error);
+ check_msg (error == NULL, "adding store: %s", error->message);
+ check (CAMEL_IS_STORE (service));
+ store = CAMEL_STORE (service);
+ g_clear_error (&error);
+ 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_sync (
+ store, mailbox, flags, NULL, &error);
+
+ /* 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) {
+ gchar *mbox = g_strdup_printf ("INBOX/%s", mailbox);
+ mailbox = mbox;
+ g_clear_error (&error);
+ folder = camel_store_get_folder_sync (
+ store, mailbox, flags, NULL, &error);
+ }
+
+ check_msg (error == NULL, "%s", error->message);
+ 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++) {
+ gchar *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_sync (
+ folder, msg, NULL, NULL, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+
+#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 (error == NULL, "%s", error->message);
+ 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_get_subject (info), subject) == 0,
+ "info->subject %s", camel_message_info_get_subject (info));
+ camel_message_info_unref (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_sync (
+ store, mailbox, flags, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ check (folder != NULL);
+ g_clear_error (&error);
+
+ /* 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++) {
+ gchar *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_get_subject (info), subject) == 0,
+ "info->subject %s", camel_message_info_get_subject (info));
+ test_free (subject);
+ camel_message_info_unref (info);
+ pull ();
+ }
+
+ push ("deleting first message & expunging");
+ camel_folder_delete_message (folder, uids->pdata[0]);
+ test_folder_counts (folder, 10, 9);
+ camel_folder_expunge_sync (folder, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ 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++) {
+ gchar *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_get_subject (info), subject) == 0,
+ "info->subject %s", camel_message_info_get_subject (info));
+ test_free (subject);
+ camel_message_info_unref (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_sync (folder, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ 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++) {
+ gchar *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_get_subject (info), subject) == 0,
+ "info->subject %s", camel_message_info_get_subject (info));
+ test_free (subject);
+ camel_message_info_unref (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_sync (folder, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ 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_sync (
+ store, mailbox, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ pull ();
+ }
+
+ if (!local) {
+ push ("disconneect service");
+ camel_service_disconnect_sync (
+ CAMEL_SERVICE (store), TRUE, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_clear_error (&error);
+ pull ();
+ }
+
+ check_unref (store, 1);
+ camel_test_end ();
+ }
+}
diff --git a/src/camel/tests/lib/folders.h b/src/camel/tests/lib/folders.h
new file mode 100644
index 000000000..f42c1e1aa
--- /dev/null
+++ b/src/camel/tests/lib/folders.h
@@ -0,0 +1,28 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <camel/camel.h>
+
+/* check the total/unread is what we think it should be, everywhere it can be determined */
+void test_folder_counts (CamelFolder *folder, gint total, gint unread);
+/* cross-check info/msg */
+void test_message_info (CamelMimeMessage *msg, const CamelMessageInfo *info);
+/* check a message is present everywhere it should be */
+void test_folder_message (CamelFolder *folder, const gchar *uid);
+/* check message not present everywhere it shouldn't be */
+void test_folder_not_message (CamelFolder *folder, const gchar *uid);
+/* test basic folder ops on a store */
+void test_folder_basic (CamelSession *session, const gchar *storename, gint local, gint spool);
+/* test basic message operations on a folder */
+void test_folder_message_ops (CamelSession *session, const gchar *storename, gint local, const gchar *foldername);
diff --git a/src/camel/tests/lib/messages.c b/src/camel/tests/lib/messages.c
new file mode 100644
index 000000000..dbe674ba3
--- /dev/null
+++ b/src/camel/tests/lib/messages.c
@@ -0,0 +1,332 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "messages.h"
+#include "camel-test.h"
+
+CamelMimeMessage *
+test_message_create_simple (void)
+{
+ CamelMimeMessage *msg;
+ CamelInternetAddress *addr;
+
+ msg = camel_mime_message_new ();
+
+ addr = camel_internet_address_new ();
+ camel_internet_address_add (addr, "Michael Zucchi", "zed@nowhere.com");
+ camel_mime_message_set_from (msg, addr);
+ camel_address_remove ((CamelAddress *) addr, -1);
+ camel_internet_address_add (addr, "POSTMASTER", "POSTMASTER@somewhere.net");
+ camel_mime_message_set_recipients (msg, CAMEL_RECIPIENT_TYPE_TO, addr);
+ camel_address_remove ((CamelAddress *) addr, -1);
+ camel_internet_address_add (addr, "Michael Zucchi", "zed@nowhere.com");
+ camel_mime_message_set_recipients (msg, CAMEL_RECIPIENT_TYPE_CC, addr);
+
+ check_unref (addr, 1);
+
+ camel_mime_message_set_subject (msg, "Simple message subject");
+ camel_mime_message_set_date (msg, time (0), 930);
+
+ return msg;
+}
+
+static void
+content_weak_notify (GByteArray *ba,
+ GObject *where_the_object_was)
+{
+ g_byte_array_free (ba, TRUE);
+}
+
+void
+test_message_set_content_simple (CamelMimePart *part,
+ gint how,
+ const gchar *type,
+ const gchar *text,
+ gint len)
+{
+ CamelStreamMem *content = NULL;
+ CamelDataWrapper *dw;
+ static GByteArray *ba;
+
+ switch (how) {
+ case 0:
+ camel_mime_part_set_content (part, text, len, type);
+ break;
+ case 1:
+ content = (CamelStreamMem *) camel_stream_mem_new_with_buffer (text, len);
+ break;
+ case 2:
+ content = (CamelStreamMem *) camel_stream_mem_new ();
+ camel_stream_mem_set_buffer (content, text, len);
+ break;
+ case 3:
+ ba = g_byte_array_new ();
+ g_byte_array_append (ba, (guint8 *) text, len);
+
+ content = (CamelStreamMem *) camel_stream_mem_new_with_byte_array (ba);
+ ba = NULL;
+ break;
+ case 4:
+ ba = g_byte_array_new ();
+ g_byte_array_append (ba, (guint8 *) text, len);
+
+ content = (CamelStreamMem *) camel_stream_mem_new ();
+ camel_stream_mem_set_byte_array (content, ba);
+
+ g_object_weak_ref (
+ G_OBJECT (content), (GWeakNotify)
+ content_weak_notify, ba);
+ break;
+ }
+
+ if (content != 0) {
+ dw = camel_data_wrapper_new ();
+ camel_data_wrapper_set_mime_type (dw, type);
+
+ camel_data_wrapper_construct_from_stream_sync (
+ dw, (CamelStream *) content, NULL, NULL);
+ camel_medium_set_content ((CamelMedium *) part, dw);
+
+ check_unref (content, 2);
+ check_unref (dw, 2);
+ }
+}
+
+gint
+test_message_write_file (CamelMimeMessage *msg,
+ const gchar *name)
+{
+ CamelStream *stream;
+ gint ret;
+
+ stream = camel_stream_fs_new_with_name (
+ name, O_CREAT | O_WRONLY, 0600, NULL);
+ camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (msg), stream, NULL, NULL);
+ ret = camel_stream_close (stream, NULL, NULL);
+
+ check (G_OBJECT (stream)->ref_count == 1);
+ g_object_unref (stream);
+
+ return ret;
+}
+
+CamelMimeMessage *
+test_message_read_file (const gchar *name)
+{
+ CamelStream *stream;
+ CamelMimeMessage *msg2;
+
+ stream = camel_stream_fs_new_with_name (name, O_RDONLY, 0, NULL);
+ msg2 = camel_mime_message_new ();
+
+ camel_data_wrapper_construct_from_stream_sync (
+ CAMEL_DATA_WRAPPER (msg2), stream, NULL, NULL);
+ /* stream's refcount may be > 1 if the message is real big */
+ check (G_OBJECT (stream)->ref_count >=1);
+ g_object_unref (stream);
+
+ return msg2;
+}
+
+static void
+hexdump (const guchar *in,
+ gint inlen)
+{
+ const guchar *inptr = in, *start = inptr;
+ const guchar *inend = in + inlen;
+ gint octets;
+
+ while (inptr < inend) {
+ octets = 0;
+ while (inptr < inend && octets < 16) {
+ printf ("%.2X ", *inptr++);
+ octets++;
+ }
+
+ while (octets < 16) {
+ printf (" ");
+ octets++;
+ }
+
+ printf (" ");
+
+ while (start < inptr) {
+ fputc (isprint ((gint) *start) ? *start : '.', stdout);
+ start++;
+ }
+
+ fputc ('\n', stdout);
+ }
+}
+
+gint
+test_message_compare_content (CamelDataWrapper *dw,
+ const gchar *text,
+ gint len)
+{
+ GByteArray *byte_array;
+ CamelStream *stream;
+
+ /* sigh, ok, so i len == 0, dw will probably be 0 too
+ * camel_mime_part_set_content is weird like that */
+ if (dw == 0 && len == 0)
+ return 0;
+
+ byte_array = g_byte_array_new ();
+ stream = camel_stream_mem_new_with_byte_array (byte_array);
+ camel_data_wrapper_decode_to_stream_sync (dw, stream, NULL, NULL);
+
+ if (byte_array->len != len) {
+ printf ("original text:\n");
+ hexdump ((guchar *) text, len);
+
+ printf ("new text:\n");
+ hexdump (byte_array->data, byte_array->len);
+ }
+
+ check_msg (byte_array->len == len, "buffer->len = %d, len = %d", byte_array->len, len);
+ check_msg (memcmp (byte_array->data, text, byte_array->len) == 0, "len = %d", len);
+
+ check_unref (stream, 1);
+
+ return 0;
+}
+
+gint
+test_message_compare (CamelMimeMessage *msg)
+{
+ CamelMimeMessage *msg2;
+ CamelStream *stream1;
+ CamelStream *stream2;
+ GByteArray *byte_array1;
+ GByteArray *byte_array2;
+
+ byte_array1 = g_byte_array_new ();
+ stream1 = camel_stream_mem_new_with_byte_array (byte_array1);
+ check_msg (camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (msg), stream1, NULL, NULL) != -1,
+ "write_to_stream 1 failed", NULL);
+ g_seekable_seek (G_SEEKABLE (stream1), 0, G_SEEK_SET, NULL, NULL);
+
+ msg2 = camel_mime_message_new ();
+ check_msg (camel_data_wrapper_construct_from_stream_sync (
+ CAMEL_DATA_WRAPPER (msg2), stream1, NULL, NULL) != -1,
+ "construct_from_stream 1 failed");
+ g_seekable_seek (G_SEEKABLE (stream1), 0, G_SEEK_SET, NULL, NULL);
+
+ byte_array2 = g_byte_array_new ();
+ stream2 = camel_stream_mem_new_with_byte_array (byte_array2);
+ check_msg (camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (msg2), stream2, NULL, NULL) != -1,
+ "write_to_stream 2 failed");
+ g_seekable_seek (G_SEEKABLE (stream2), 0, G_SEEK_SET, NULL, NULL);
+
+ if (byte_array1->len != byte_array2->len) {
+ CamelDataWrapper *content;
+
+ printf ("stream1 stream:\n%.*s\n", byte_array1->len, byte_array1->data);
+ printf ("stream2 stream:\n%.*s\n\n", byte_array2->len, byte_array2->data);
+
+ printf ("msg1:\n");
+ test_message_dump_structure (msg);
+ printf ("msg2:\n");
+ test_message_dump_structure (msg2);
+
+ content = camel_medium_get_content ((CamelMedium *) msg);
+ }
+
+ check_unref (msg2, 1);
+
+ check_msg (
+ byte_array1->len == byte_array2->len,
+ "byte_array1->len = %d, byte_array2->len = %d",
+ byte_array1->len, byte_array2->len);
+
+ check_msg (memcmp (byte_array1->data, byte_array2->data, byte_array1->len) == 0, "msg/stream compare");
+
+ g_object_unref (stream1);
+ g_object_unref (stream2);
+
+ return 0;
+}
+
+gint
+test_message_compare_header (CamelMimeMessage *m1,
+ CamelMimeMessage *m2)
+{
+ return 0;
+}
+
+gint
+test_message_compare_messages (CamelMimeMessage *m1,
+ CamelMimeMessage *m2)
+{
+ return 0;
+}
+
+static void
+message_dump_rec (CamelMimeMessage *msg,
+ CamelMimePart *part,
+ gint depth)
+{
+ CamelDataWrapper *containee;
+ gint parts, i;
+ gchar *s;
+ gchar *mime_type;
+
+ s = alloca (depth + 1);
+ memset (s, ' ', depth);
+ s[depth] = 0;
+
+ mime_type = camel_data_wrapper_get_mime_type ((CamelDataWrapper *) part);
+ printf ("%sPart <%s>\n", s, G_OBJECT_TYPE_NAME (part));
+ printf ("%sContent-Type: %s\n", s, mime_type);
+ g_free (mime_type);
+ printf ("%s encoding: %s\n", s, camel_transfer_encoding_to_string (((CamelDataWrapper *) part)->encoding));
+ printf ("%s part encoding: %s\n", s, camel_transfer_encoding_to_string (camel_mime_part_get_encoding (part)));
+
+ containee = camel_medium_get_content (CAMEL_MEDIUM (part));
+
+ if (containee == NULL)
+ return;
+
+ mime_type = camel_data_wrapper_get_mime_type (containee);
+ printf ("%sContent <%s>\n", s, G_OBJECT_TYPE_NAME (containee));
+ printf ("%sContent-Type: %s\n", s, mime_type);
+ g_free (mime_type);
+ printf ("%s encoding: %s\n", s, camel_transfer_encoding_to_string (((CamelDataWrapper *) containee)->encoding));
+
+ /* 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);
+
+ message_dump_rec (msg, part, depth + 1);
+ }
+ } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
+ message_dump_rec (msg, (CamelMimePart *) containee, depth + 1);
+ }
+}
+
+void
+test_message_dump_structure (CamelMimeMessage *m)
+{
+ message_dump_rec (m, (CamelMimePart *) m, 0);
+}
diff --git a/src/camel/tests/lib/messages.h b/src/camel/tests/lib/messages.h
new file mode 100644
index 000000000..9974513b4
--- /dev/null
+++ b/src/camel/tests/lib/messages.h
@@ -0,0 +1,31 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <camel/camel.h>
+
+/* how many ways to set the content contents */
+#define SET_CONTENT_WAYS (5)
+
+/* messages.c */
+CamelMimeMessage *test_message_create_simple (void);
+void test_message_set_content_simple (CamelMimePart *part, gint how, const gchar *type, const gchar *text, gint len);
+gint test_message_write_file (CamelMimeMessage *msg, const gchar *name);
+CamelMimeMessage *test_message_read_file (const gchar *name);
+gint test_message_compare_content (CamelDataWrapper *dw, const gchar *text, gint len);
+gint test_message_compare (CamelMimeMessage *msg);
+
+void test_message_dump_structure (CamelMimeMessage *m);
+
+gint test_message_compare_header (CamelMimeMessage *m1, CamelMimeMessage *m2);
+gint test_message_compare_messages (CamelMimeMessage *m1, CamelMimeMessage *m2);
diff --git a/src/camel/tests/lib/session.c b/src/camel/tests/lib/session.c
new file mode 100644
index 000000000..827fc594e
--- /dev/null
+++ b/src/camel/tests/lib/session.c
@@ -0,0 +1,37 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "evolution-data-server-config.h"
+
+#include "session.h"
+
+G_DEFINE_TYPE (CamelTestSession, camel_test_session, CAMEL_TYPE_SESSION)
+
+static void
+camel_test_session_class_init (CamelTestSessionClass *class)
+{
+}
+
+static void
+camel_test_session_init (CamelTestSession *test_session)
+{
+}
+
+CamelSession *
+camel_test_session_new (const gchar *path)
+{
+ return g_object_new (
+ CAMEL_TYPE_TEST_SESSION,
+ "user-data-dir", path, NULL);
+}
diff --git a/src/camel/tests/lib/session.h b/src/camel/tests/lib/session.h
new file mode 100644
index 000000000..30f73e4c5
--- /dev/null
+++ b/src/camel/tests/lib/session.h
@@ -0,0 +1,52 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_TEST_SESSION \
+ (camel_test_session_get_type ())
+#define CAMEL_TEST_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_TEST_SESSION, CamelTestSession))
+#define CAMEL_TEST_SESSION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_TEST_SESSION, CamelTestSessionClass))
+#define CAMEL_IS_TEST_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_TEST_SESSION))
+#define CAMEL_IS_TEST_SESSION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_TEST_SESSION))
+#define CAMEL_TEST_SESSION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_TEST_SESSION, CamelTestSessionClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelTestSession CamelTestSession;
+typedef struct _CamelTestSessionClass CamelTestSessionClass;
+
+struct _CamelTestSession {
+ CamelSession parent;
+};
+
+struct _CamelTestSessionClass {
+ CamelSessionClass parent_class;
+};
+
+GType camel_test_session_get_type (void);
+CamelSession *camel_test_session_new (const gchar *path);
+
+G_END_DECLS
diff --git a/src/camel/tests/message/CMakeLists.txt b/src/camel/tests/message/CMakeLists.txt
new file mode 100644
index 000000000..e9f9bea77
--- /dev/null
+++ b/src/camel/tests/message/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(TESTS
+ test1
+ test2
+ test4
+)
+
+add_camel_tests(message TESTS)
diff --git a/src/camel/tests/message/README b/src/camel/tests/message/README
new file mode 100644
index 000000000..f5624e7c9
--- /dev/null
+++ b/src/camel/tests/message/README
@@ -0,0 +1,8 @@
+
+test1 creating, saving, loading simple messages
+test2 camelinternetaddress tests, internationalised addresses, etc.
+test4 more encompassing mime parser tests that test real-world messages.
+ Note: In order to test this, though, you'll need to fetch
+ http://primates.ximian.com/~fejj/camel-mime-tests.tar.gz and
+ untar it into camel/tests/data/
+
diff --git a/src/camel/tests/message/test1.c b/src/camel/tests/message/test1.c
new file mode 100644
index 000000000..472456ef7
--- /dev/null
+++ b/src/camel/tests/message/test1.c
@@ -0,0 +1,212 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ test1.c
+ *
+ Create a message, save it.
+ *
+ Retrieve message, compare content.
+ *
+ Operations:
+ writing / loading from different types of streams
+ reading / writing different content
+ reading / writing different encodings
+ reading / writing different charsets
+ *
+ Just testing streams:
+ different stream types
+ different file ops
+ seek, eof, etc.
+*/
+
+#include "camel-test.h"
+#include "messages.h"
+
+/* for stat */
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+
+struct _text {
+ gchar *text;
+ gint len;
+};
+
+#define MAX_TEXTS (14)
+struct _text texts[MAX_TEXTS];
+
+static void
+setup (void)
+{
+ gint i, j;
+ gchar *p;
+
+ /* setup various edge and other general cases */
+ texts[0].text = g_strdup ("");
+ texts[0].len = 0;
+ texts[1].text = g_strdup ("");
+ texts[1].len = 1;
+ texts[2].text = g_strdup ("\n");
+ texts[2].len = 1;
+ texts[3].text = g_strdup ("A");
+ texts[3].len = 1;
+ texts[4].text = g_strdup ("This is a test.\n.");
+ texts[4].len = strlen (texts[4].text);
+ texts[5].text = g_strdup ("This is a test.\n\n.\n");
+ texts[5].len = strlen (texts[5].text);
+ texts[6].text = g_malloc0 (1024);
+ texts[6].len = 1024;
+ texts[7].text = g_malloc0 (102400);
+ texts[7].len = 102400;
+ texts[8].text = g_malloc (1024);
+ memset (texts[8].text, '\n', 1024);
+ texts[8].len = 1024;
+ texts[9].text = g_malloc (102400);
+ memset (texts[9].text, '\n', 102400);
+ texts[9].len = 102400;
+ texts[10].text = g_malloc (1024);
+ memset (texts[10].text, ' ', 1024);
+ texts[10].len = 1024;
+ texts[11].text = g_malloc (102400);
+ memset (texts[11].text, ' ', 102400);
+ texts[11].len = 102400;
+
+ srand (42);
+ p = texts[12].text = g_malloc (1024);
+ for (i = 0; i < 1024; i++) {
+ j = rand ();
+ if (j < RAND_MAX / 120)
+ *p++ = '\n';
+ else
+ *p++ = (j % 95) + 32;
+ }
+ texts[12].len = 1024;
+ p = texts[13].text = g_malloc (102400);
+ for (i = 0; i < 102400; i++) {
+ j = rand ();
+ if (j < RAND_MAX / 120)
+ *p++ = '\n';
+ else
+ *p++ = (j % 95) + 32;
+ }
+ texts[13].len = 102400;
+}
+
+static void
+cleanup (void)
+{
+ gint i;
+
+ for (i = 0; i < MAX_TEXTS; i++)
+ g_free (texts[i].text);
+}
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ CamelMimeMessage *msg, *msg2;
+ gint i, j;
+ gchar *text;
+ gint len;
+
+ camel_test_init (argc, argv);
+
+ setup ();
+
+ camel_test_start ("Simple memory-based content creation");
+
+ /* test all ways of setting simple content for a message (i.e. memory based) */
+ for (j = 0; j < MAX_TEXTS; j++) {
+ push ("testing text number %d", j);
+ text = texts[j].text;
+ len = texts[j].len;
+ for (i = 0; i < SET_CONTENT_WAYS; i++) {
+ push ("create simple message %d", i);
+ msg = test_message_create_simple ();
+
+ push ("set simple content");
+ test_message_set_content_simple ((CamelMimePart *) msg, i, "text/plain", text, len);
+ pull ();
+
+ push ("compare original content");
+ test_message_compare_content (camel_medium_get_content ((CamelMedium *) msg), text, len);
+ pull ();
+
+ push ("save message to test1.msg");
+ unlink ("test1.msg");
+ test_message_write_file (msg, "test1.msg");
+ check_unref (msg, 1);
+ pull ();
+
+ push ("read from test1.msg");
+ msg2 = test_message_read_file ("test1.msg");
+ pull ();
+
+ push ("compare read with original content");
+ test_message_compare_content (camel_medium_get_content ((CamelMedium *) msg2), text, len);
+ check_unref (msg2, 1);
+ pull ();
+
+ unlink ("test1.msg");
+ pull ();
+ }
+ pull ();
+ }
+
+ camel_test_end ();
+
+ camel_test_start ("Different encodings");
+ for (j = 0; j < MAX_TEXTS; j++) {
+ push ("testing text number %d", j);
+ text = texts[j].text;
+ len = texts[j].len;
+ for (i = 0; i < CAMEL_TRANSFER_NUM_ENCODINGS; i++) {
+
+ push ("test simple message, encoding %s", camel_transfer_encoding_to_string (i));
+ msg = test_message_create_simple ();
+
+ push ("set simple content");
+ test_message_set_content_simple ((CamelMimePart *) msg, 0, "text/plain", text, len);
+ pull ();
+
+ camel_mime_part_set_encoding ((CamelMimePart *) msg, i);
+
+ push ("save message to test1.msg");
+ unlink ("test1.msg");
+ test_message_write_file (msg, "test1.msg");
+ check_unref (msg, 1);
+ pull ();
+
+ push ("read from test1.msg");
+ msg2 = test_message_read_file ("test1.msg");
+ pull ();
+
+ push ("compare read with original content");
+ test_message_compare_content (camel_medium_get_content ((CamelMedium *) msg2), text, len);
+ check_unref (msg2, 1);
+ pull ();
+
+ unlink ("test1.msg");
+ pull ();
+ }
+ pull ();
+ }
+ camel_test_end ();
+
+ cleanup ();
+
+ return 0;
+}
diff --git a/src/camel/tests/message/test2.c b/src/camel/tests/message/test2.c
new file mode 100644
index 000000000..ada3c1dc6
--- /dev/null
+++ b/src/camel/tests/message/test2.c
@@ -0,0 +1,385 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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 "address-data.h"
+
+static gchar *convert (const gchar *in, const gchar *from, const gchar *to)
+{
+ GIConv ic = g_iconv_open (to, from);
+ gchar *out, *outp;
+ const gchar *inp;
+ gsize inlen, outlen;
+
+ if (ic == (GIConv) -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);
+ g_iconv_close (ic);
+ return g_strdup (in);
+ }
+
+ if (iconv (ic, NULL, 0, &outp, &outlen) == -1) {
+ test_free (out);
+ g_iconv_close (ic);
+ return g_strdup (in);
+ }
+
+ g_iconv_close (ic);
+
+ *outp = 0;
+
+#if 0
+ /* lets see if we can convert back again? */
+ {
+ gchar *nout, *noutp;
+ GIConv ic = iconv_open (from, to);
+
+ if (ic == (GIConv) -1)
+ goto fail;
+
+ 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, g_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);
+ }
+fail:
+#endif
+
+ return out;
+}
+
+static void
+check_address_line_decode (gint i,
+ const gchar *line,
+ const gchar *name,
+ const gchar *email)
+{
+ CamelInternetAddress *addr;
+ const gchar *dname, *demail;
+
+ push ("Testing address line %d '%s'", i, line);
+ dname = NULL;
+ demail = NULL;
+ addr = camel_internet_address_new ();
+ check (camel_address_decode (CAMEL_ADDRESS (addr), line) == 1);
+ check (camel_internet_address_get (CAMEL_INTERNET_ADDRESS (addr), 0, &dname, &demail));
+ check_msg (g_strcmp0 (dname, name) == 0 || (!name && dname && !*dname), "decoded name = '%s', but should be '%s'", dname, name);
+ check_msg (g_strcmp0 (demail, email) == 0, "decoded email = '%s', but should be '%s'", demail, email);
+ check_unref (addr, 1);
+ pull ();
+}
+
+#define to_utf8(in, type) convert(in, type, "utf-8")
+#define from_utf8(in, type) convert(in, "utf-8", type)
+
+gint main (gint argc, gchar **argv)
+{
+ gint i;
+ CamelInternetAddress *addr, *addr2;
+ gchar *name;
+ const gchar *charset;
+ const gchar *real, *where;
+ gchar *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++) {
+ gchar name[16], a[32];
+ g_snprintf (name, sizeof (name), "Zed %d", i);
+ g_snprintf (a, sizeof (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 < G_N_ELEMENTS (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 < G_N_ELEMENTS (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 ();
+
+ camel_test_start ("CamelInternerAddress name & email decoder");
+
+ for (i = 0; i < G_N_ELEMENTS (test_decode); i++) {
+ gchar *line;
+ const gchar *name, *email;
+ gint jj;
+
+ name = test_decode[i].name;
+ email = test_decode[i].email;
+
+ for (jj = 0; jj < G_N_ELEMENTS (line_decode_formats); jj++) {
+ if (line_decode_formats[jj].without_name) {
+ line = g_strdup_printf (line_decode_formats[jj].without_name, email);
+ check_address_line_decode (i, line, NULL, email);
+ g_free (line);
+ }
+
+ if (!name)
+ continue;
+
+ line = g_strdup_printf (line_decode_formats[jj].with_name, name, email);
+ check_address_line_decode (i, line, name, email);
+ g_free (line);
+ }
+ }
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/message/test4.c b/src/camel/tests/message/test4.c
new file mode 100644
index 000000000..16442521f
--- /dev/null
+++ b/src/camel/tests/message/test4.c
@@ -0,0 +1,122 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+
+#include "camel-test.h"
+#include "messages.h"
+
+#if 0
+static void
+dump_mime_struct (CamelMimePart *mime_part,
+ gint depth)
+{
+ CamelDataWrapper *content;
+ gchar *mime_type;
+ gint i = 0;
+
+ while (i < depth) {
+ printf (" ");
+ i++;
+ }
+
+ content = camel_medium_get_content ((CamelMedium *) mime_part);
+
+ mime_type = camel_data_wrapper_get_mime_type (content);
+ printf ("Content-Type: %s\n", mime_type);
+ g_free (mime_type);
+
+ if (CAMEL_IS_MULTIPART (content)) {
+ guint num, index = 0;
+
+ num = camel_multipart_get_number ((CamelMultipart *) content);
+ while (index < num) {
+ mime_part = camel_multipart_get_part ((CamelMultipart *) content, index);
+ dump_mime_struct (mime_part, depth + 1);
+ index++;
+ }
+ } else if (CAMEL_IS_MIME_MESSAGE (content)) {
+ dump_mime_struct ((CamelMimePart *) content, depth + 1);
+ }
+}
+#endif
+
+gint main (gint argc, gchar **argv)
+{
+ struct dirent *dent;
+ DIR *dir;
+ gint fd;
+
+ camel_test_init (argc, argv);
+
+ camel_test_start ("Message Test Suite");
+
+ if (!(dir = opendir ("../data/messages")))
+ return 77;
+
+ while ((dent = readdir (dir)) != NULL) {
+ CamelMimeMessage *message;
+ CamelStream *stream;
+ gchar *filename;
+ struct stat st;
+
+ if (dent->d_name[0] == '.')
+ continue;
+
+ filename = g_strdup_printf ("../data/messages/%s", dent->d_name);
+ if (g_stat (filename, &st) == -1 || !S_ISREG (st.st_mode)) {
+ g_free (filename);
+ continue;
+ }
+
+ if ((fd = open (filename, O_RDONLY)) == -1) {
+ g_free (filename);
+ continue;
+ }
+
+ push ("testing message '%s'", filename);
+ g_free (filename);
+
+ stream = camel_stream_fs_new_with_fd (fd);
+ message = camel_mime_message_new ();
+ camel_data_wrapper_construct_from_stream_sync (
+ CAMEL_DATA_WRAPPER (message), stream, NULL, NULL);
+ g_seekable_seek (
+ G_SEEKABLE (stream), 0, G_SEEK_SET, NULL, NULL);
+
+ /*dump_mime_struct ((CamelMimePart *) message, 0);*/
+ test_message_compare (message);
+
+ g_object_unref (message);
+ g_object_unref (stream);
+
+ pull ();
+ }
+
+ closedir (dir);
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/mime-filter/CMakeLists.txt b/src/camel/tests/mime-filter/CMakeLists.txt
new file mode 100644
index 000000000..303548854
--- /dev/null
+++ b/src/camel/tests/mime-filter/CMakeLists.txt
@@ -0,0 +1,8 @@
+set(TESTS
+ test1
+ test-crlf
+ test-charset
+ test-tohtml
+)
+
+add_camel_tests(mime-filter TESTS)
diff --git a/src/camel/tests/mime-filter/charset-gb2312.0.in b/src/camel/tests/mime-filter/charset-gb2312.0.in
new file mode 100644
index 000000000..82520ac91
--- /dev/null
+++ b/src/camel/tests/mime-filter/charset-gb2312.0.in
@@ -0,0 +1,448 @@
+<html><head>
+<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
+<meta name="GENERATOR" content="Microsoft FrontPage 5.0">
+<meta name="ProgId" content="FrontPage.Editor.Document">
+<title>ҝ՞ʽľÄĂłŇץ˘żâ´ćź°ťáźĆšÜŔíϾͳ An all-in-one solution for trading, inventory, accounting management</title>
+
+
+<style>
+<!--
+#fps0 {font-style: normal}
+#fps1 {background-color: #339933}
+#fps2 {font-family:times new roman;font-size:24pt;color:#0066ff;}
+#fps3 {font-family:times new roman;font-size:18pt;color:#0066ff;}
+#fps4 {font-family:SimSun;font-size:14pt}
+#fps5 {color:#0066ff;font-weight:bold;}
+#fps6 {font-size:10pt;}
+#fps7 {color:#ffffff;}
+#fps8 {font-family:times new roman;color:#ffffff;font-weight:bold;}
+#fps9 {font-family:times new roman;font-size:10pt;}
+#fps10 {font-size:10pt;color:#0066ff;}
+-->
+</style>
+
+</head>
+
+<body topmargin="0" leftmargin="10" bgcolor="#FFFFFF" >
+ <table border=0 cellpadding=0 cellspacing=0 style="border-collapse: collapse" bordercolor=#111111 id=AutoNumber1 height=233>
+ <tr valign=top>
+ <td height=165>
+ <p align=justify>
+
+
+ <em id=fps0>
+ <strong>
+ <span id=fps2>TRADE</span><span id=fps3>dot</span><span id=fps2>NET</span></strong></em><span lang=zh-tw><!----><span id=fps4> </span>
+ </span><font color=#FF0000>
+ <strong><font size=2>&nbsp;<br>
+ </font></strong></font><font face=SimSun size=2>ĘÇҝ՞ʽľÄĂłŇץ˘żâ´ćź°ťáźĆšÜŔíϾͳŁŹÖúÄúžŤźňÖÜśř¸´ĘźľÄ×ĘÁĎĘäČ륢ĘýžÝ´ŚŔíź°¸÷Ŕ๨źŰĄ˘ˇ˘ĆąĄ˘´ć˛ÖĄ˘ČŐźÇŐĘĄ˘´ŤĆąĄ˘ą¨ąíľČËůĐčľÄÎÄźţĐĐŐţš¤×÷ŁŹČĂÄú¸üÓĐЧľŘšÜŔíĂłŇץ˘żâ´ćź°ťáźĆĘÂÎńĄŁ</font></td>
+ <td height=165>
+ <p align=justify>
+
+
+ <em id=fps0>
+ <strong>
+ <span id=fps2>TRADE</span><span id=fps3>dot</span><span id=fps2>NET</span></strong></em><span lang=zh-tw><!----><span id=fps4> </span>
+ </span><font size=2><!----><font color=#FF0000>
+ <strong>&nbsp;<br>
+ <font face="Times New Roman">What's the most annoying part during your
+ work? </font>
+ </strong></font><font face="Times New Roman">Endless repetitive data
+ entry, calculation and paper work on quotation, invoice, inventory
+ management, daily voucher, journal and reporting? Once you have installed<span id=fps5>
+ TRADEdotNET</span>,<span id=fps5> </span>all of these
+ annoying work will never appear in your office again!<span id=fps5><br>
+ TRADEdotNET</span> is an all-in-one solution for trading, inventory,
+ accounting management. In the <span id=fps5>TRADEdotNET</span>&nbsp;
+ environment, all data can be continuously retrieved and reused. This will
+ help you for minimizing time consumed on data entry and enhancing
+ efficiency.</font></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=68><em id=fps0>
+ <strong>
+ <span id=fps2>TRADE</span><span id=fps3>dot</span><span id=fps2>NET</span></strong></em><span lang=zh-tw><!----><span id=fps4> </span>
+ </span>ˇÖąđÓÉ<span lang=zh-tw>Čý</span>×éϾͳ×éłÉŁşĂłŇלŠľĽšÜŔí<font face=SimSun>Ą˘żâ´ć</font>šÜŔíź°ťáźĆϾͳ</td>
+ <td height=68><em id=fps0>
+ <strong>
+ <span id=fps2>TRADE</span><span id=fps3>dot</span><span id=fps2>NET</span></strong></em><span lang=zh-tw><!----><span id=fps4> </span>
+ </span><font face="Times New Roman">Containing Trading Management
+ Module, Inventory Management Module and Accounting Management Module.</font></td>
+ </tr>
+ </table>
+ <p align=center><span id=fps6>
+ <img border=0 src=Flow2.gif width=600 height=420></span></p>
+ <table border=0 cellpadding=0 cellspacing=0 style="border-collapse: collapse" bordercolor=#111111 id=AutoNumber2 height=15>
+ <tr valign=top>
+ <td height=129>
+ <em id=fps0>
+ <strong>
+ <span id=fps2>TRADE</span><span id=fps3>dot</span><span id=fps2>NET</span></strong></em><span lang=zh-tw><!----><span id=fps4> </span>
+ </span><span id=fps6>ľÄÓĹľă</span><span lang=zh-tw><!----><span id=fps6>Łş</span></span><span id=fps6><br>
+ </span>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;źňŇ×ϾͳľÇČë
+ </b> <span id=fps6>
+ <br>
+ </span>
+ </span></span> <span id=fps6> <br>
+ źňľĽŇ×Ă÷ľÄ˛Ů×÷Á÷łĚÍźĎÔĘžŐű¸öϾͳľÄÖ÷ŇŞÁ÷łĚŁŹŇýľźÓĂť§ËłŔűÍęłÉÿҝ¸ö˛˝Ö襣ÓĂť§ÖťĐë°´ĎÂÁ÷łĚÍźľÄÍźĎńŁŹź´ĎľÍłżě˝Ýˇ˝Ę˝ŁŹąăżÉ˝řČëĚŘś¨ľÄšŚÄÜť­Ă楣</span></td>
+ <td height=129>
+
+
+ <p align=justify>
+
+
+ <em id=fps0>
+ <strong>
+ <span id=fps2>TRADE</span><span id=fps3>dot</span><span id=fps2>NET</span></strong></em><span lang=zh-tw><!----><span id=fps4> </span>
+ </span><font size=4 face="Times New Roman">Highlights :</font><span id=fps6><br>
+ </span>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Easy Access
+ </span> <span id=fps6>
+ <br>
+ </span>
+ </span></font> <font size=2> <br>
+ <span id=fps9>A Road Map showing the user-friendly system
+ flow guides users throughout all steps. User can click on the menu icons,
+ which act as shortcuts, and enter into the desired function.</span></font></p>
+ </td>
+ </tr>
+ <tr valign=top>
+ <td height=135>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;ÍęÉƾIJéŃŻËŃŃ°Ć÷
+ </b> <span id=fps6>
+ <br>
+ </span>
+ </span></span> <span id=fps6>
+ <br>
+ ľąÔąš¤ťŘ´đżÍť§˛éŃŻťňˇ˘łöśŠľĽŁŹž­łŁĐčĘąˇ­˛éŇÔÍů˝ťŇןÍÂźŁŹŐâ¸öšýłĚ˛ť˝öŔˡŃĘąźäŁŹÍňŇťťŘ´đ˛ť×źČˇŁŹ¸üżÉÄÜÁîÄú´Ó´ËʧȼŐ⹌šóľÄżÍť§ĄŁ˛éŃŻËŃŃ°Ć÷śŕÖÖ˛ťÍŹľÄʾʹ˛éŃŻšŚÄÜŁŹÄÜżěËŮËŃŃ°ËůĐčľÄĘýžÝźÍÂźŁŹÖťŇŞÓĂť§ĘäČëżÍť§ąŕşĹĄ˘ťőơąŕşĹĄ˘ˇ˘ĆąČŐĆÚľČĆäÖĐŇťĎîËŃŃ°˛ÎĘýŁŹËŃŃ°˝ášűËćź´ĎÔĘžĄŁ </span>
+ </td>
+ <td height=135>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Perfect On-line Enquiry
+ </span> <span id=fps6>
+ <br>
+ </span>
+ </span></font> <font size=2>
+ <br>
+ <span id=fps9>Under many circumstances, staff also need to
+ retrieve past transaction history for handing customer enquiry or placing
+ an order, however they cannot get the required information immediately and
+ this may drive away the customers. On-line Enquiry engine enables staff to
+ check past transaction with searching parameters such as key words, client
+ code, item code, invoice date. </span>
+ </font></td>
+ </tr>
+ <tr valign=top>
+ <td height=83>
+ <span style="background-color: #339933; font-weight:700" id="fps7">&nbsp;ÎÄźţĘäČë </span><span id=fps6>
+ <br>
+ <br>
+ ϾͳľÄ×ÜŐʽӿÚÉčźĆŇÔłŁÓþĴŤĆąÎŞŔśąžŁŹÓĂť§ÎŢĐčˇŃĘąÖŘĐÂĘĘÓŚ˛ťÍŹČŐźÇŐʸńĘ˝ŁŹśřÇŇÄܸüÓĐЧÂʾشŚŔíČŐłŁťáźĆŐĘĎîĘýžÝĘäČëłĚĐňĄŁ</span></td>
+ <td height=83>
+ <font color=#FFFFFF face="Times New Roman">
+ <span style="background-color: #339933; font-weight:700">&nbsp;Input
+ Template </span></font><font size=2>
+ <br>
+ <br>
+ <span id=fps9>The data entry journal interface is similar
+ to paper voucher, so that users can handle their daily accounting
+ transaction as they used to, yet more efficient.</span></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=76>
+ <b>
+ <font color=#FFFFFF><span id=fps1>&nbsp;ÎŢĎŢźÍÂź´˘´ć </span></font>&nbsp;</b><font size=2><br>
+ <br>
+ ϾͳČÝÁżÇż´óŁŹ˛ťÉčźÍÂź´˘´ćÄęĆÚÉĎĎŢŁŹŇň´ËÓĂť§żÉŇÔËćĘą˛éÔÄČÎşÎĘąĆھĽťŇןÍÂźĄŁ
+ </font>
+ </td>
+ <td height=76>
+ <font face="Times New Roman">
+ <b>
+ <font color=#FFFFFF><span id=fps1>&nbsp;Unlimited
+ Data Record </span></font>&nbsp;</b></font><font size=2><br>
+ <br>
+ <font face="Times New Roman">The system can store unlimited number of
+ records, so that users can retrieve data of any time whenever they need
+ it.</font></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=105>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;ϾͳľÇČëźŕżŘ
+ </b> <br>
+ </span></span> <span id=fps6>
+ <br>
+ ĎČ˝řľÄľÇČëźŕżŘϾͳąŁŐĎ(ťúĂÜ)ĘýžÝÖťšŠĘÚȨÓĂť§˛éÔÄŁŹĎľÍłšÜŔíÔąŇŕżÉŇÔŇňÓŚÓĂť§ľÄÖ°źśśřÉ蜨¸öąđÓĂť§ľÄÔÄŔŔȨĎŢĄŁ </span></td>
+ <td height=105>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Login Control
+ </span> <br>
+ </span></font> <font size=2>
+ <br>
+ <span id=fps9>Advanced access control ensures access right
+ is only granted to authorized users. System administrator can also define
+ access level of each user according to their capacity, so that confidential
+ information can only be retrieved and viewed by senior management.</span></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=87>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;×ÔŃĄą¨ąí¸ńĘ˝
+ </b> <br>
+ </span></span> <span id=fps6>
+ <br>
+ Ͼͳą¨ąíŇÔżŞˇĹĘ˝ÉčźĆŁŹÖťĐčŇŞĹäşĎSeagate Crystal Report (Áíšş)ŁŹź´żÉ×ÔĐĐÉčźĆą¨ąí¸ńĘ˝ťňÔöźÓą¨ąíĄŁ</span></td>
+ <td height=87>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Customize Report
+ </span> <br>
+ </span></font> <font size=2>
+ <br>
+ <span id=fps9>The system is designed with an open architecture
+ format. When the system is used with&nbsp; Seagate Crystal Report Software, Users can
+ customize report content and format of the system.</span></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=100>
+ <font color=#FFFFFF><span id=fps1>
+ <b>&nbsp;śŕĎňÁЅną¨ąíš¤žß <br>
+ </b>
+ </span></font> <span id=fps6>
+ <br>
+ łŹšý80%ľÄą¨ąíżÉŇÔMS Excel/Word¸ńĘ˝ĘäłöŁŹ°´ĐčŇŞśřÖĆ×÷ÍźąíšŠĘýžÝˇÖÎöÖŽÓĂĄŁ <br>
+ ą¨ąíżÉŇÔMS Excel/WordľČ¸ńĘ˝śŔÁ˘ĘäłöťňŇÔľçÓʸ˝´ř´ŤË͸řÓĐšŘČËÔąĄŁ </span></td>
+ <td height=100>
+ <font color=#FFFFFF><span id=fps1>
+ <b><font face="Times New Roman">&nbsp;Multi-Report Export Tools </font> <br>
+ </b>
+ </span></font> <font size=2>
+ <br>
+ <font face="Times New Roman">Over 80% of reports can be exported to MS
+ Excel /&nbsp; Word and data can be translated into graphs and tables for
+ further analysis.<br>
+ Reports can also be export to MS Excel / Word format independently or send
+ to email client with attachment directly.</font></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=118>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;šúźĘťĽÁŞÖ§łÖ
+ </b> <br>
+ </span>
+ </span> <span id=fps6>
+ <br>
+ ÎŞÁËĹäşĎ˛ťÉŮĂłŇךŤËžÓÚšúÄÚťňśŤÄĎŃÇÉ賧ťňˇÖšŤËžśřĐčŇŞĘýžÝĘýžÝťĽÍ¨ŁŹ</span><strong><font size=2><span id=fps10>TRADEdotNET</span><font color=#FF0000> </font>
+ </font></strong>
+ <span id=fps6>ĚᚊȍĂ氲ȍľÄŐűşĎƽ̨ŁŹ°˛×°</span><font color=#0066FF><strong><span id=fps10> TRADEdotNET </span></strong></font><span id=fps6>ťĽÁŞÍřÖ§łÖ¸˝źţŁŹÍ¸šýťĽÁŞÍřŁŹÓĂť§ź´żÉËćĘąËćľŘŇÔ°˛×°ÁË </span><strong><font size=2>
+ <span id=fps10>TRADEdotNET</span><font color=#FF0000> </font>
+ </font></strong>
+ <span id=fps6>ľÄąęן MS ˛Ů×÷ϾͳľÇČëĘšÓĂĄŁ</span></td>
+ <td height=118>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Internet Ready
+ </span> <br>
+ </span>
+ </font> <font size=2>
+ <br>
+ <span id=fps9>To facilitate data exchange between trading companies and their offices in
+ the Mainland China or Southeast Asia Country, </span> </font>
+ <font face="Times New Roman"> <span id=fps9>
+ Install and makes use of<strong><font color=#0066FF> TRADEdotNET</font>
+ Internet Kit </strong>&nbsp;to provide a secured platform enabling user
+ to access the data from different locations anytime through the internet
+ with </span><font color=#0066FF><strong><span id=fps10> TRADEdotNET </span></strong></font>
+ </font>
+ <span id=fps9>system. </span></td>
+ </tr>
+ <tr valign=top>
+ <td height=90>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;śŕÖÖĘýžÝżâÖ§łÖ
+ </b> <br>
+ </span>
+ </span> <span id=fps6>
+ <br>
+ ÎŞÁËĹäşĎ˛ťÍŹ´óĐĄšŤËžľÄąžÉíĐčŇŞŁŹ</span><strong><span id=fps10>TRADEdotNET</span></strong><span id=fps6> Ěᚊ˛ťÍŹĘýžÝżâ°ćąžŇÔšŠŃĄÔńĄŁŇŃÍĆłöÓĐ MS AccessĄ˘MS SQLŁŹź´˝ŤÍĆłöÓĐ Oracle ź°ĆäËüĘýžÝżâĄŁ<br>
+ ĄĄ</span></td>
+ <td height=90>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Multi-Database Format
+ </span> <br>
+ </span>
+ </font> <span id=fps6>
+ <br>
+ </span><font face="Times New Roman"><strong><font size=2><!----><font color=#0066FF>TRADEdotNET</font><font color=#FF0000> </font>
+ </font></strong>
+ </font><span id=fps9>enables user to select
+ different database format versions to fit their company requirements. Now
+ users can choose from MS
+ Access and MS SQL version.&nbsp; Oracle and other database formats are coming soon.</span></td>
+ </tr>
+ <tr valign=top>
+ <td height=70>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;ĆäËüÖ§łÖšŚÄÜ
+ </b> <br>
+ </span></span>
+ <span id=fps6> <br>
+ Ö§łÖśŕÓĂť§ŁŹÖ§łÖśŕšŤËžŁŹÖ§łÖśŕťőąŇŁŹÖ§łÖśŕżâ´ćŁŹÖ§łÖśŕÓďŃÔ(ÖĐÎÄ/Ó˘ÎÄ)
+ </span></td>
+ <td height=70>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Multiple Functionality
+ </span> <br>
+ </span></font>
+ <font size=2> <br>
+ <span id=fps9>Support multi-users, multi-companies,
+ multi-currencies, multi-inventory, multi-languages (Chinese / English).</span></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=142>
+ <span id=fps7>
+ <span id=fps1>
+ <b>&nbsp;ϾͳҪÇó
+ </b>
+ <br>
+ </span></span><span id=fps6><br>
+ š¤×÷̨<br>
+ Intel Pentium II (ťňŇÔÉĎ) Processor PC<br>
+ Windows 98, ME, 2000 ťň Windows NT 4.0<br>
+ ×îÉŮÓĂ 64 MB RAMŁŹÍĆźöÓĂ128MB<br>
+ 256ÉŤťňŇÔÉĎÖŽSVGAĎÔĘžĆ÷<br>
+ 50MBÓ˛ĹĚżŐźä<br>
+ Windows 98, ME, 2000 ťň Windows NT 4.0 Ö§łÖÖŽ´ňÓĄťú</span></td>
+ <td height=142>
+ <font color=#FFFFFF>
+ <span id=fps1>
+ <span id=fps8>
+ &nbsp;System
+ Requirements
+
+ </span>
+ <br>
+ </span></font><font size=2><br>
+ <span id=fps9>
+ Workstation<br>
+ Intel Pentium II (or above) Processor PC<br>
+ Windows 98, ME, 2000 or Windows NT 4.0<br>
+ 64MB RAM
+ minimumŁŹ 128MB
+ recommended<br>
+ 256 color SVGA display or higher<br>
+ 50MB free Hard Disk<br>
+ Printers
+ supported by
+ Windows 98, ME, 2000 ťň Windows NT 4.0<br>
+ ĄĄ</span></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=436>
+ <span id=fps7>
+ <span id=fps1>&nbsp;ˇţÎńˇśÎ§
+ </span></span><span id=fps6>
+ <br>
+ <br>
+ Ͼͳʞˇś<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; ĂâˇŃÉĎĂĹ×÷Ͼͳʞˇś
+ ĄŁ<br>
+ <br>
+ Ͼͳ°˛×°<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; Ͼͳ°˛×°ťáÓÉąžšŤËž×¨Ňľš¤łĚĘŚ¸şÔđ
+ ĄŁ<br>
+ <br>
+ ĘýžÝą¸ˇÝź°ťšÔ­<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; ĂâˇŃÎŞżÍť§É蜨ĘýžÝą¸ˇÝź°ťšÔ­°´ĹĽŁŹŇÔĹäşĎżÍť§ÖŽĄŽą¸ˇÝÉ蹸ĄŻĄŁ<br>
+ <br>
+ śČÉ휊×öÎÄźţĘ˝Ńů<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; ÎŞżÍť§śČÉ휊×öÎÄźţĘ˝ŃůŁŹĚá¸ßšŤËžĐÎĎó(ÁíˇŃ)ĄŁ<br>
+ <br>
+ ϾͳִĐĐ<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; ÎŞ¸÷ťúššÉ蜨ϾͳÁ÷łĚąíź°źŕ˛ě¸÷˛żĂĹľÄÖ´ĐĐÇéżö
+ ĄŁ<br>
+ <br>
+ ÓĂť§ĹŕŃľ<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; ÓĂť§ĹŕŃľżÉÉčÔڿ͝§°ěšŤĘŇŁŹśřÇҲťĎŢ´ÎĘý
+ ĄŁ<br>
+ <br>
+ Ͼͳ×ÉŃŻČČĎß<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; ĚᚊÓĐЧֹ˝ÓľÄČČĎß×ÉŃŻˇţÎńŁŹ´ŚŔíϾͳŇÉÄŃ
+ ĄŁ<br>
+ <br>
+ ϾͳąŁŃř<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; °ëÄęĂâˇŃąŁŃř
+ Łť <br>
+ &nbsp;&nbsp;&nbsp;&nbsp; ÓÚąŁŃřĆÚÄÚĂâˇŃÎŞżÍť§×÷ϾͳĚáÉý
+ ĄŁ</span></td>
+ <td height=436>
+ <font color=#FFFFFF face="Times New Roman">
+ <span id=fps1>&nbsp;Services
+ Provided
+ </span></font><font size=2>
+ <br>
+ <br>
+ <span id=fps9>System Demonstration<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; Free-of-charge system demonstration in client's office<br>
+ <br>
+ System Installation<br>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ On-site
+ installation
+ by our
+ professional
+ engineers <br>
+ <br>
+ Backup/Restore<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; Setups of backup and restore icons associated with client's backup device (e.g. Zip Drive, MO)<br>
+ <br>
+ Tailored Format<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; Tailored designs on document formats to highlight and promote company image<br>
+ <br>
+ System Implementation<br>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ Implementation
+ procedures
+ customized for
+ each
+ organization <br>
+ <br>
+ User Training<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; Unlimited training is provided in the client's office
+ <br>
+ <br>
+ Hot-line Enquiry<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; Efficient hot-line support for all system enquiries<br>
+ <br>
+ System Maintenance<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; Half-a-year free maintenance <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Free
+ upgrade
+ services
+ throughout
+ warranty
+ periods </span>
+ </font>
+ </td>
+ </tr>
+ </table>
+ <table border=0 cellpadding=0 cellspacing=0 style="border-collapse: collapse" bordercolor=#111111 width=100% id=AutoNumber3>
+ <tr>
+ <td width=100%><b><font size=6 color=#FFFFFF>
+ <span style="background-color: #0000FF"><br>
+ ťś Ó­ Ô¤ Ôź Ęž ˇś&nbsp;Çëľç : (852) 2591 9377</span></font></b><p>žŤ Ńś šú źĘ ÓĐ
+ ĎŢ šŤ Ëž&nbsp; Superdata International Ltd.<br>
+ Tel: (852) 2591 9377 </td>
+ </tr>
+ </table>
+ <p>
+ <font size=1>
+ <font face="Times New Roman">
+ *All prices,
+ conditions and
+ system
+ specifications
+ are subject to
+ change without
+ prior notice.
+ </font>ËůÓП۸ńŁŹĚőšćź°ĎľÍłšć¸ńÖŽĐ޸ģŹ˝Ť˛ťÁíĐĐ֪ͨĄŁ</font><span id=fps6>
+
+ĄĄ</span> </p>
+
+ </body></html>
diff --git a/src/camel/tests/mime-filter/charset-gb2312.0.out b/src/camel/tests/mime-filter/charset-gb2312.0.out
new file mode 100644
index 000000000..8dcae6bdf
--- /dev/null
+++ b/src/camel/tests/mime-filter/charset-gb2312.0.out
@@ -0,0 +1,448 @@
+<html><head>
+<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
+<meta name="GENERATOR" content="Microsoft FrontPage 5.0">
+<meta name="ProgId" content="FrontPage.Editor.Document">
+<title>一站式的贸易、库存及会计管理系统 An all-in-one solution for trading, inventory, accounting management</title>
+
+
+<style>
+<!--
+#fps0 {font-style: normal}
+#fps1 {background-color: #339933}
+#fps2 {font-family:times new roman;font-size:24pt;color:#0066ff;}
+#fps3 {font-family:times new roman;font-size:18pt;color:#0066ff;}
+#fps4 {font-family:SimSun;font-size:14pt}
+#fps5 {color:#0066ff;font-weight:bold;}
+#fps6 {font-size:10pt;}
+#fps7 {color:#ffffff;}
+#fps8 {font-family:times new roman;color:#ffffff;font-weight:bold;}
+#fps9 {font-family:times new roman;font-size:10pt;}
+#fps10 {font-size:10pt;color:#0066ff;}
+-->
+</style>
+
+</head>
+
+<body topmargin="0" leftmargin="10" bgcolor="#FFFFFF" >
+ <table border=0 cellpadding=0 cellspacing=0 style="border-collapse: collapse" bordercolor=#111111 id=AutoNumber1 height=233>
+ <tr valign=top>
+ <td height=165>
+ <p align=justify>
+
+
+ <em id=fps0>
+ <strong>
+ <span id=fps2>TRADE</span><span id=fps3>dot</span><span id=fps2>NET</span></strong></em><span lang=zh-tw><!----><span id=fps4> </span>
+ </span><font color=#FF0000>
+ <strong><font size=2>&nbsp;<br>
+ </font></strong></font><font face=SimSun size=2>是一站式的贸易、库存及会计管理系统,助您精简周而复始的资料输入、数据处理及各类报价、发票、存仓、日记帐、传票、报表等所需的文件行政工作,让您更有效地管理贸易、库存及会计事务。</font></td>
+ <td height=165>
+ <p align=justify>
+
+
+ <em id=fps0>
+ <strong>
+ <span id=fps2>TRADE</span><span id=fps3>dot</span><span id=fps2>NET</span></strong></em><span lang=zh-tw><!----><span id=fps4> </span>
+ </span><font size=2><!----><font color=#FF0000>
+ <strong>&nbsp;<br>
+ <font face="Times New Roman">What's the most annoying part during your
+ work? </font>
+ </strong></font><font face="Times New Roman">Endless repetitive data
+ entry, calculation and paper work on quotation, invoice, inventory
+ management, daily voucher, journal and reporting? Once you have installed<span id=fps5>
+ TRADEdotNET</span>,<span id=fps5> </span>all of these
+ annoying work will never appear in your office again!<span id=fps5><br>
+ TRADEdotNET</span> is an all-in-one solution for trading, inventory,
+ accounting management. In the <span id=fps5>TRADEdotNET</span>&nbsp;
+ environment, all data can be continuously retrieved and reused. This will
+ help you for minimizing time consumed on data entry and enhancing
+ efficiency.</font></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=68><em id=fps0>
+ <strong>
+ <span id=fps2>TRADE</span><span id=fps3>dot</span><span id=fps2>NET</span></strong></em><span lang=zh-tw><!----><span id=fps4> </span>
+ </span>分别由<span lang=zh-tw>三</span>组系统组成:贸易订单管理<font face=SimSun>、库存</font>管理及会计系统</td>
+ <td height=68><em id=fps0>
+ <strong>
+ <span id=fps2>TRADE</span><span id=fps3>dot</span><span id=fps2>NET</span></strong></em><span lang=zh-tw><!----><span id=fps4> </span>
+ </span><font face="Times New Roman">Containing Trading Management
+ Module, Inventory Management Module and Accounting Management Module.</font></td>
+ </tr>
+ </table>
+ <p align=center><span id=fps6>
+ <img border=0 src=Flow2.gif width=600 height=420></span></p>
+ <table border=0 cellpadding=0 cellspacing=0 style="border-collapse: collapse" bordercolor=#111111 id=AutoNumber2 height=15>
+ <tr valign=top>
+ <td height=129>
+ <em id=fps0>
+ <strong>
+ <span id=fps2>TRADE</span><span id=fps3>dot</span><span id=fps2>NET</span></strong></em><span lang=zh-tw><!----><span id=fps4> </span>
+ </span><span id=fps6>的优点</span><span lang=zh-tw><!----><span id=fps6>:</span></span><span id=fps6><br>
+ </span>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;简易系统登入
+ </b> <span id=fps6>
+ <br>
+ </span>
+ </span></span> <span id=fps6> <br>
+ 简单易明的操作流程图显示整个系统的主要流程,引导用户顺利完成每一个步骤。用户只须按下流程图的图像,即系统快捷方式,便可进入特定的功能画面。</span></td>
+ <td height=129>
+
+
+ <p align=justify>
+
+
+ <em id=fps0>
+ <strong>
+ <span id=fps2>TRADE</span><span id=fps3>dot</span><span id=fps2>NET</span></strong></em><span lang=zh-tw><!----><span id=fps4> </span>
+ </span><font size=4 face="Times New Roman">Highlights :</font><span id=fps6><br>
+ </span>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Easy Access
+ </span> <span id=fps6>
+ <br>
+ </span>
+ </span></font> <font size=2> <br>
+ <span id=fps9>A Road Map showing the user-friendly system
+ flow guides users throughout all steps. User can click on the menu icons,
+ which act as shortcuts, and enter into the desired function.</span></font></p>
+ </td>
+ </tr>
+ <tr valign=top>
+ <td height=135>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;完善的查询搜寻器
+ </b> <span id=fps6>
+ <br>
+ </span>
+ </span></span> <span id=fps6>
+ <br>
+ 当员工回答客户查询或发出订单,经常需时翻查以往交易纪录,这个过程不仅浪费时间,万一回答不准确,更可能令您从此失去这宝贵的客户。查询搜寻器多种不同的实时查询功能,能快速搜寻所需的数据纪录,只要用户输入客户编号、货品编号、发票日期等其中一项搜寻参数,搜寻结果随即显示。 </span>
+ </td>
+ <td height=135>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Perfect On-line Enquiry
+ </span> <span id=fps6>
+ <br>
+ </span>
+ </span></font> <font size=2>
+ <br>
+ <span id=fps9>Under many circumstances, staff also need to
+ retrieve past transaction history for handing customer enquiry or placing
+ an order, however they cannot get the required information immediately and
+ this may drive away the customers. On-line Enquiry engine enables staff to
+ check past transaction with searching parameters such as key words, client
+ code, item code, invoice date. </span>
+ </font></td>
+ </tr>
+ <tr valign=top>
+ <td height=83>
+ <span style="background-color: #339933; font-weight:700" id="fps7">&nbsp;文件输入 </span><span id=fps6>
+ <br>
+ <br>
+ 系统的总帐接口设计以常用的传票为蓝本,用户无需费时重新适应不同日记帐格式,而且能更有效率地处理日常会计帐项数据输入程序。</span></td>
+ <td height=83>
+ <font color=#FFFFFF face="Times New Roman">
+ <span style="background-color: #339933; font-weight:700">&nbsp;Input
+ Template </span></font><font size=2>
+ <br>
+ <br>
+ <span id=fps9>The data entry journal interface is similar
+ to paper voucher, so that users can handle their daily accounting
+ transaction as they used to, yet more efficient.</span></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=76>
+ <b>
+ <font color=#FFFFFF><span id=fps1>&nbsp;无限纪录储存 </span></font>&nbsp;</b><font size=2><br>
+ <br>
+ 系统容量强大,不设纪录储存年期上限,因此用户可以随时查阅任何时期的交易纪录。
+ </font>
+ </td>
+ <td height=76>
+ <font face="Times New Roman">
+ <b>
+ <font color=#FFFFFF><span id=fps1>&nbsp;Unlimited
+ Data Record </span></font>&nbsp;</b></font><font size=2><br>
+ <br>
+ <font face="Times New Roman">The system can store unlimited number of
+ records, so that users can retrieve data of any time whenever they need
+ it.</font></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=105>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;系统登入监控
+ </b> <br>
+ </span></span> <span id=fps6>
+ <br>
+ 先进的登入监控系统保障(机密)数据只供授权用户查阅,系统管理员亦可以因应用户的职级而设定个别用户的阅览权限。 </span></td>
+ <td height=105>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Login Control
+ </span> <br>
+ </span></font> <font size=2>
+ <br>
+ <span id=fps9>Advanced access control ensures access right
+ is only granted to authorized users. System administrator can also define
+ access level of each user according to their capacity, so that confidential
+ information can only be retrieved and viewed by senior management.</span></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=87>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;自选报表格式
+ </b> <br>
+ </span></span> <span id=fps6>
+ <br>
+ 系统报表以开放式设计,只需要配合Seagate Crystal Report (另购),即可自行设计报表格式或增加报表。</span></td>
+ <td height=87>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Customize Report
+ </span> <br>
+ </span></font> <font size=2>
+ <br>
+ <span id=fps9>The system is designed with an open architecture
+ format. When the system is used with&nbsp; Seagate Crystal Report Software, Users can
+ customize report content and format of the system.</span></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=100>
+ <font color=#FFFFFF><span id=fps1>
+ <b>&nbsp;多向列n报表工具 <br>
+ </b>
+ </span></font> <span id=fps6>
+ <br>
+ 超过80%的报表可以MS Excel/Word格式输出,按需要而制作图表供数据分析之用。 <br>
+ 报表可以MS Excel/Word等格式独立输出或以电邮附带传送给有关人员。 </span></td>
+ <td height=100>
+ <font color=#FFFFFF><span id=fps1>
+ <b><font face="Times New Roman">&nbsp;Multi-Report Export Tools </font> <br>
+ </b>
+ </span></font> <font size=2>
+ <br>
+ <font face="Times New Roman">Over 80% of reports can be exported to MS
+ Excel /&nbsp; Word and data can be translated into graphs and tables for
+ further analysis.<br>
+ Reports can also be export to MS Excel / Word format independently or send
+ to email client with attachment directly.</font></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=118>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;国际互联支持
+ </b> <br>
+ </span>
+ </span> <span id=fps6>
+ <br>
+ 为了配合不少贸易公司于国内或东南亚设厂或分公司而需要数据数据互通,</span><strong><font size=2><span id=fps10>TRADEdotNET</span><font color=#FF0000> </font>
+ </font></strong>
+ <span id=fps6>提供全面安全的整合平台,安装</span><font color=#0066FF><strong><span id=fps10> TRADEdotNET </span></strong></font><span id=fps6>互联网支持附件,透过互联网,用户即可随时随地以安装了 </span><strong><font size=2>
+ <span id=fps10>TRADEdotNET</span><font color=#FF0000> </font>
+ </font></strong>
+ <span id=fps6>的标准 MS 操作系统登入使用。</span></td>
+ <td height=118>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Internet Ready
+ </span> <br>
+ </span>
+ </font> <font size=2>
+ <br>
+ <span id=fps9>To facilitate data exchange between trading companies and their offices in
+ the Mainland China or Southeast Asia Country, </span> </font>
+ <font face="Times New Roman"> <span id=fps9>
+ Install and makes use of<strong><font color=#0066FF> TRADEdotNET</font>
+ Internet Kit </strong>&nbsp;to provide a secured platform enabling user
+ to access the data from different locations anytime through the internet
+ with </span><font color=#0066FF><strong><span id=fps10> TRADEdotNET </span></strong></font>
+ </font>
+ <span id=fps9>system. </span></td>
+ </tr>
+ <tr valign=top>
+ <td height=90>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;多种数据库支持
+ </b> <br>
+ </span>
+ </span> <span id=fps6>
+ <br>
+ 为了配合不同大小公司的本身需要,</span><strong><span id=fps10>TRADEdotNET</span></strong><span id=fps6> 提供不同数据库版本以供选择。已推出有 MS Access、MS SQL,即将推出有 Oracle 及其它数据库。<br>
+  </span></td>
+ <td height=90>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Multi-Database Format
+ </span> <br>
+ </span>
+ </font> <span id=fps6>
+ <br>
+ </span><font face="Times New Roman"><strong><font size=2><!----><font color=#0066FF>TRADEdotNET</font><font color=#FF0000> </font>
+ </font></strong>
+ </font><span id=fps9>enables user to select
+ different database format versions to fit their company requirements. Now
+ users can choose from MS
+ Access and MS SQL version.&nbsp; Oracle and other database formats are coming soon.</span></td>
+ </tr>
+ <tr valign=top>
+ <td height=70>
+ <span id=fps7><!----><span id=fps1><b>&nbsp;其它支持功能
+ </b> <br>
+ </span></span>
+ <span id=fps6> <br>
+ 支持多用户,支持多公司,支持多货币,支持多库存,支持多语言(中文/英文)
+ </span></td>
+ <td height=70>
+ <font color=#FFFFFF><span id=fps1>
+ <span id=fps8>&nbsp;Multiple Functionality
+ </span> <br>
+ </span></font>
+ <font size=2> <br>
+ <span id=fps9>Support multi-users, multi-companies,
+ multi-currencies, multi-inventory, multi-languages (Chinese / English).</span></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=142>
+ <span id=fps7>
+ <span id=fps1>
+ <b>&nbsp;系统要求
+ </b>
+ <br>
+ </span></span><span id=fps6><br>
+ 工作台<br>
+ Intel Pentium II (或以上) Processor PC<br>
+ Windows 98, ME, 2000 或 Windows NT 4.0<br>
+ 最少用 64 MB RAM,推荐用128MB<br>
+ 256色或以上之SVGA显示器<br>
+ 50MB硬盘空间<br>
+ Windows 98, ME, 2000 或 Windows NT 4.0 支持之打印机</span></td>
+ <td height=142>
+ <font color=#FFFFFF>
+ <span id=fps1>
+ <span id=fps8>
+ &nbsp;System
+ Requirements
+
+ </span>
+ <br>
+ </span></font><font size=2><br>
+ <span id=fps9>
+ Workstation<br>
+ Intel Pentium II (or above) Processor PC<br>
+ Windows 98, ME, 2000 or Windows NT 4.0<br>
+ 64MB RAM
+ minimum, 128MB
+ recommended<br>
+ 256 color SVGA display or higher<br>
+ 50MB free Hard Disk<br>
+ Printers
+ supported by
+ Windows 98, ME, 2000 或 Windows NT 4.0<br>
+  </span></font></td>
+ </tr>
+ <tr valign=top>
+ <td height=436>
+ <span id=fps7>
+ <span id=fps1>&nbsp;服务范围
+ </span></span><span id=fps6>
+ <br>
+ <br>
+ 系统示范<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; 免费上门作系统示范
+ 。<br>
+ <br>
+ 系统安装<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; 系统安装会由本公司专业工程师负责
+ 。<br>
+ <br>
+ 数据备份及还原<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; 免费为客户设定数据备份及还原按钮,以配合客户之‘备份设备’。<br>
+ <br>
+ 度身订做文件式样<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; 为客户度身订做文件式样,提高公司形象(另费)。<br>
+ <br>
+ 系统执行<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; 为各机构设定系统流程表及监察各部门的执行情况
+ 。<br>
+ <br>
+ 用户培训<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; 用户培训可设在客户办公室,而且不限次数
+ 。<br>
+ <br>
+ 系统咨询热线<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; 提供有效直接的热线咨询服务,处理系统疑难
+ 。<br>
+ <br>
+ 系统保养<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; 半年免费保养
+ ; <br>
+ &nbsp;&nbsp;&nbsp;&nbsp; 于保养期内免费为客户作系统提升
+ 。</span></td>
+ <td height=436>
+ <font color=#FFFFFF face="Times New Roman">
+ <span id=fps1>&nbsp;Services
+ Provided
+ </span></font><font size=2>
+ <br>
+ <br>
+ <span id=fps9>System Demonstration<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; Free-of-charge system demonstration in client's office<br>
+ <br>
+ System Installation<br>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ On-site
+ installation
+ by our
+ professional
+ engineers <br>
+ <br>
+ Backup/Restore<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; Setups of backup and restore icons associated with client's backup device (e.g. Zip Drive, MO)<br>
+ <br>
+ Tailored Format<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; Tailored designs on document formats to highlight and promote company image<br>
+ <br>
+ System Implementation<br>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ Implementation
+ procedures
+ customized for
+ each
+ organization <br>
+ <br>
+ User Training<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; Unlimited training is provided in the client's office
+ <br>
+ <br>
+ Hot-line Enquiry<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; Efficient hot-line support for all system enquiries<br>
+ <br>
+ System Maintenance<br>
+ &nbsp;&nbsp;&nbsp;&nbsp; Half-a-year free maintenance <br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Free
+ upgrade
+ services
+ throughout
+ warranty
+ periods </span>
+ </font>
+ </td>
+ </tr>
+ </table>
+ <table border=0 cellpadding=0 cellspacing=0 style="border-collapse: collapse" bordercolor=#111111 width=100% id=AutoNumber3>
+ <tr>
+ <td width=100%><b><font size=6 color=#FFFFFF>
+ <span style="background-color: #0000FF"><br>
+ 欢 迎 预 约 示 范&nbsp;请电 : (852) 2591 9377</span></font></b><p>精 讯 国 际 有
+ 限 公 司&nbsp; Superdata International Ltd.<br>
+ Tel: (852) 2591 9377 </td>
+ </tr>
+ </table>
+ <p>
+ <font size=1>
+ <font face="Times New Roman">
+ *All prices,
+ conditions and
+ system
+ specifications
+ are subject to
+ change without
+ prior notice.
+ </font>所有价格,条规及系统规格之修改,将不另行通知。</font><span id=fps6>
+
+ </span> </p>
+
+ </body></html>
diff --git a/src/camel/tests/mime-filter/charset-iso-2022-jp.0.in b/src/camel/tests/mime-filter/charset-iso-2022-jp.0.in
new file mode 100644
index 000000000..8c23b208c
--- /dev/null
+++ b/src/camel/tests/mime-filter/charset-iso-2022-jp.0.in
@@ -0,0 +1,5 @@
+$B$($m$$#t#y$&$H$&$#$&$$$(#y$F#r$A$&$(#r#y$H#y#r$H$($D$*#3#9#4#5#0#9#8#0#9#4$($$#r(B
+$B$H$($D$*$($&$H$($k$H$&#t(B
+
+Thanks & Regards,
+Sures
diff --git a/src/camel/tests/mime-filter/charset-iso-2022-jp.0.out b/src/camel/tests/mime-filter/charset-iso-2022-jp.0.out
new file mode 100644
index 000000000..c66c5f79c
--- /dev/null
+++ b/src/camel/tests/mime-filter/charset-iso-2022-jp.0.out
@@ -0,0 +1,5 @@
+えろいtyうとうぃういえyてrちうえryとyrとえつお3945098094えいr
+とえつおえうとえるとうt
+
+Thanks & Regards,
+Sures
diff --git a/src/camel/tests/mime-filter/crlf-1.in b/src/camel/tests/mime-filter/crlf-1.in
new file mode 100644
index 000000000..d98703c72
--- /dev/null
+++ b/src/camel/tests/mime-filter/crlf-1.in
@@ -0,0 +1,19 @@
+This is some text to filter and stuff. Hopefully that . will not become '..'
+when the filter is run on this text. It should, however, '..' the next line
+. The previous . should become .. in the output file, or so I hope...
+
+.
+..
+...
+....
+
+Once this text is decoded again, the above set of dots should look like:
+
+ .
+ ..
+ ...
+ ....
+
+Only it shouldn't be indented, obviously.
+
+Jeff \ No newline at end of file
diff --git a/src/camel/tests/mime-filter/crlf-1.out b/src/camel/tests/mime-filter/crlf-1.out
new file mode 100644
index 000000000..c0b688f33
--- /dev/null
+++ b/src/camel/tests/mime-filter/crlf-1.out
@@ -0,0 +1,19 @@
+This is some text to filter and stuff. Hopefully that . will not become '..'
+when the filter is run on this text. It should, however, '..' the next line
+.. The previous . should become .. in the output file, or so I hope...
+
+..
+...
+....
+.....
+
+Once this text is decoded again, the above set of dots should look like:
+
+ .
+ ..
+ ...
+ ....
+
+Only it shouldn't be indented, obviously.
+
+Jeff \ No newline at end of file
diff --git a/src/camel/tests/mime-filter/data/html.0.in b/src/camel/tests/mime-filter/data/html.0.in
new file mode 100644
index 000000000..217f85b4f
--- /dev/null
+++ b/src/camel/tests/mime-filter/data/html.0.in
@@ -0,0 +1,10 @@
+
+College of Engineering Students,
+
+We are contacting you to request that you consider nominating a College
+of Engineering faculty member for the College's 2003 Excellence in
+Teaching Award. The award criteria and nomination form are attached.
+They can also be found on the web at:
+
+ <http://www.ce.udel.edu/teaching_award.html>
+ <http://www.ce.udel.edu/nomination_form.html>
diff --git a/src/camel/tests/mime-filter/data/html.0.out b/src/camel/tests/mime-filter/data/html.0.out
new file mode 100644
index 000000000..3f19310dd
--- /dev/null
+++ b/src/camel/tests/mime-filter/data/html.0.out
@@ -0,0 +1,10 @@
+
+College of Engineering Students,
+
+We are contacting you to request that you consider nominating a College
+of Engineering faculty member for the College's 2003 Excellence in
+Teaching Award. The award criteria and nomination form are attached.
+They can also be found on the web at:
+
+&#9;&lt;<a href="http://www.ce.udel.edu/teaching_award.html">http://www.ce.udel.edu/teaching_award.html</a>&gt;
+&#9;&lt;<a href="http://www.ce.udel.edu/nomination_form.html">http://www.ce.udel.edu/nomination_form.html</a>&gt;
diff --git a/src/camel/tests/mime-filter/data/html.1.in b/src/camel/tests/mime-filter/data/html.1.in
new file mode 100644
index 000000000..8dece6113
--- /dev/null
+++ b/src/camel/tests/mime-filter/data/html.1.in
@@ -0,0 +1,10 @@
+
+College of Engineering Students,
+
+We are contacting you to request that you consider nominating a College
+of Engineering faculty member for the College's 2003 Excellence in
+Teaching Award. The award criteria and nomination form are attached.
+They can also be found on the web at:
+
+ <http://www.ce.udel.edu/teaching_award.html>
+ <http://www.ce.udel.edu/nomination_form.html> \ No newline at end of file
diff --git a/src/camel/tests/mime-filter/data/html.1.out b/src/camel/tests/mime-filter/data/html.1.out
new file mode 100644
index 000000000..71a977e7f
--- /dev/null
+++ b/src/camel/tests/mime-filter/data/html.1.out
@@ -0,0 +1,10 @@
+
+College of Engineering Students,
+
+We are contacting you to request that you consider nominating a College
+of Engineering faculty member for the College's 2003 Excellence in
+Teaching Award. The award criteria and nomination form are attached.
+They can also be found on the web at:
+
+&#9;&lt;<a href="http://www.ce.udel.edu/teaching_award.html">http://www.ce.udel.edu/teaching_award.html</a>&gt;
+&#9;&lt;<a href="http://www.ce.udel.edu/nomination_form.html">http://www.ce.udel.edu/nomination_form.html</a>&gt; \ No newline at end of file
diff --git a/src/camel/tests/mime-filter/test-charset.c b/src/camel/tests/mime-filter/test-charset.c
new file mode 100644
index 000000000..c490396d4
--- /dev/null
+++ b/src/camel/tests/mime-filter/test-charset.c
@@ -0,0 +1,208 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * test-crlf.c
+ *
+ * Test the CamelMimeFilterCharset class
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "camel-test.h"
+
+#define d(x)
+
+#define CHUNK_SIZE 4096
+
+static void
+test_case (const gchar *basename)
+{
+ GFileInputStream *source_stream;
+ GFileInputStream *correct_stream;
+ GInputStream *filter_stream;
+ CamelMimeFilter *filter;
+ GFile *file;
+ gssize comp_progress, comp_correct_chunk, comp_filter_chunk;
+ gchar comp_correct[CHUNK_SIZE], comp_filter[CHUNK_SIZE];
+ gchar *filename, *charset, *work;
+ const gchar *ext;
+ gint i, test = 0;
+ GError *local_error = NULL;
+
+ ext = strrchr (basename, '.');
+ if (ext == NULL)
+ return;
+
+ if (!g_str_has_prefix (basename, "charset-"))
+ return;
+
+ if (!g_str_has_suffix (basename, ".in"))
+ return;
+
+ work = g_strdup_printf (
+ "Charset filter, test case %d (%s)", test++, basename);
+ camel_test_start (work);
+ g_free (work);
+
+ filename = g_strdup_printf ("%s/%s", SOURCEDIR, basename);
+
+ file = g_file_new_for_path (filename);
+ source_stream = g_file_read (file, NULL, &local_error);
+ g_object_unref (file);
+
+ /* Sanity check. */
+ g_warn_if_fail (
+ ((source_stream != NULL) && (local_error == NULL)) ||
+ ((source_stream == NULL) && (local_error != NULL)));
+
+ if (local_error != NULL) {
+ camel_test_fail (
+ "Failed to open input case in \"%s\": %s",
+ filename, local_error->message);
+ g_error_free (local_error);
+ g_free (filename);
+ return;
+ }
+ g_free (filename);
+
+ filename = g_strdup_printf (
+ "%s/%.*s.out", SOURCEDIR, ext - basename, basename);
+
+ file = g_file_new_for_path (filename);
+ correct_stream = g_file_read (file, NULL, &local_error);
+ g_object_unref (file);
+
+ /* Sanity check. */
+ g_warn_if_fail (
+ ((correct_stream != NULL) && (local_error == NULL)) ||
+ ((correct_stream == NULL) && (local_error != NULL)));
+
+ if (local_error != NULL) {
+ camel_test_fail (
+ "Failed to open correct output in \"%s\": %s",
+ filename, local_error->message);
+ g_error_free (local_error);
+ g_free (filename);
+ return;
+ }
+ g_free (filename);
+
+ charset = g_strdup (basename + 8);
+ ext = strchr (charset, '.');
+ *((gchar *) ext) = '\0';
+
+ filter = camel_mime_filter_charset_new (charset, "UTF-8");
+ if (filter == NULL) {
+ camel_test_fail ("Couldn't create CamelMimeFilterCharset??");
+ g_free (charset);
+ return;
+ }
+ filter_stream = camel_filter_input_stream_new (
+ G_INPUT_STREAM (source_stream), filter);
+ g_clear_object (&filter);
+
+ g_free (charset);
+
+ camel_test_push ("Running filter and comparing to correct result");
+
+ comp_progress = 0;
+
+ while (1) {
+ comp_correct_chunk = g_input_stream_read (
+ G_INPUT_STREAM (correct_stream),
+ comp_correct, CHUNK_SIZE, NULL, NULL);
+ comp_filter_chunk = 0;
+
+ if (comp_correct_chunk == 0)
+ break;
+
+ while (comp_filter_chunk < comp_correct_chunk) {
+ gssize delta;
+
+ delta = g_input_stream_read (
+ filter_stream,
+ comp_filter + comp_filter_chunk,
+ CHUNK_SIZE - comp_filter_chunk,
+ NULL, NULL);
+
+ if (delta == 0) {
+ camel_test_fail (
+ "Chunks are different sizes: "
+ "correct is %d, "
+ "filter is %d, "
+ "%d bytes into stream",
+ comp_correct_chunk,
+ comp_filter_chunk,
+ comp_progress);
+ }
+
+ comp_filter_chunk += delta;
+ }
+
+ for (i = 0; i < comp_filter_chunk; i++) {
+ if (comp_correct[i] != comp_filter[i]) {
+ camel_test_fail ("Difference: correct is %c, filter is %c, "
+ "%d bytes into stream",
+ comp_correct[i],
+ comp_filter[i],
+ comp_progress + i);
+ }
+ }
+
+ comp_progress += comp_filter_chunk;
+ }
+
+ camel_test_pull ();
+
+ camel_test_push ("Cleaning up");
+ g_object_unref (correct_stream);
+ g_object_unref (source_stream);
+ g_object_unref (filter_stream);
+ camel_test_pull ();
+
+ camel_test_end ();
+}
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ GDir *dir;
+ const gchar *basename;
+ GError *local_error = NULL;
+
+ camel_test_init (argc, argv);
+
+ dir = g_dir_open (SOURCEDIR, 0, &local_error);
+
+ /* Sanity check. */
+ g_warn_if_fail (
+ ((dir != NULL) && (local_error == NULL)) ||
+ ((dir == NULL) && (local_error != NULL)));
+
+ if (local_error != NULL) {
+ g_error ("%s", local_error->message);
+ }
+
+ while ((basename = g_dir_read_name (dir)) != NULL)
+ test_case (basename);
+
+ g_dir_close (dir);
+
+ return 0;
+}
diff --git a/src/camel/tests/mime-filter/test-crlf.c b/src/camel/tests/mime-filter/test-crlf.c
new file mode 100644
index 000000000..a61aa647a
--- /dev/null
+++ b/src/camel/tests/mime-filter/test-crlf.c
@@ -0,0 +1,197 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ test - crlf.c
+ *
+ Test the CamelMimeFilterCrlf class
+*/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-test.h"
+
+#define d(x)
+
+#define CHUNK_SIZE 4096
+
+enum {
+ CRLF_ENCODE,
+ CRLF_DECODE,
+ CRLF_DONE
+};
+
+static void
+test_case (gint test_num)
+{
+ GFileInputStream *source_stream;
+ GFileInputStream *correct_stream;
+ GInputStream *filter_stream;
+ CamelMimeFilter *filter;
+ CamelMimeFilterCRLFDirection direction;
+ GFile *file;
+ gssize comp_progress, comp_correct_chunk, comp_filter_chunk;
+ gint comp_i;
+ gchar comp_correct[CHUNK_SIZE], comp_filter[CHUNK_SIZE];
+ gchar *infile = NULL, *outfile = NULL;
+ GError *local_error = NULL;
+
+ switch (test_num) {
+ case CRLF_ENCODE:
+ camel_test_push ("Test of the encoder");
+ direction = CAMEL_MIME_FILTER_CRLF_ENCODE;
+ infile = g_strdup_printf ("%s/crlf-%d.in", SOURCEDIR, 1);
+ outfile = g_strdup_printf ("%s/crlf-%d.out", SOURCEDIR, 1);
+ break;
+ case CRLF_DECODE:
+ camel_test_push ("Test of the decoder");
+ direction = CAMEL_MIME_FILTER_CRLF_DECODE;
+ infile = g_strdup_printf ("%s/crlf-%d.out", SOURCEDIR, 1);
+ outfile = g_strdup_printf ("%s/crlf-%d.in", SOURCEDIR, 1);
+ break;
+ default:
+ break;
+ }
+
+ camel_test_push ("Initializing objects");
+
+ file = g_file_new_for_path (infile);
+ source_stream = g_file_read (file, NULL, &local_error);
+ g_object_unref (file);
+
+ /* Sanity check. */
+ g_warn_if_fail (
+ ((source_stream != NULL) && (local_error == NULL)) ||
+ ((source_stream == NULL) && (local_error != NULL)));
+
+ if (local_error != NULL) {
+ camel_test_fail (
+ "Failed to open input case in \"%s\": %s",
+ infile, local_error->message);
+ g_free (infile);
+ return;
+ }
+ g_free (infile);
+
+ file = g_file_new_for_path (outfile);
+ correct_stream = g_file_read (file, NULL, &local_error);
+ g_object_unref (file);
+
+ /* Sanity check. */
+ g_warn_if_fail (
+ ((correct_stream != NULL) && (local_error == NULL)) ||
+ ((correct_stream == NULL) && (local_error != NULL)));
+
+ if (local_error != NULL) {
+ camel_test_fail (
+ "Failed to open correct output in \"%s\": %s",
+ outfile, local_error->message);
+ g_free (outfile);
+ return;
+ }
+ g_free (outfile);
+
+ filter = camel_mime_filter_crlf_new (
+ direction, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
+ filter_stream = camel_filter_input_stream_new (
+ G_INPUT_STREAM (source_stream), filter);
+ g_object_unref (filter);
+
+ camel_test_pull ();
+
+ camel_test_push ("Running filter and comparing to correct result");
+
+ comp_progress = 0;
+
+ while (1) {
+ comp_correct_chunk = g_input_stream_read (
+ G_INPUT_STREAM (correct_stream),
+ comp_correct, CHUNK_SIZE, NULL, NULL);
+ comp_filter_chunk = 0;
+
+ if (comp_correct_chunk == 0)
+ break;
+
+ while (comp_filter_chunk < comp_correct_chunk) {
+ gssize delta;
+
+ delta = g_input_stream_read (
+ filter_stream,
+ comp_filter + comp_filter_chunk,
+ CHUNK_SIZE - comp_filter_chunk,
+ NULL, NULL);
+
+ if (delta == 0) {
+ camel_test_fail (
+ "Chunks are different sizes: "
+ "correct is %d, "
+ "filter is %d, "
+ "%d bytes into stream",
+ comp_correct_chunk,
+ comp_filter_chunk,
+ comp_progress);
+ }
+
+ comp_filter_chunk += delta;
+ }
+
+ for (comp_i = 0; comp_i < comp_filter_chunk; comp_i++) {
+ if (comp_correct[comp_i] != comp_filter[comp_i]) {
+ camel_test_fail (
+ "Difference: "
+ "correct is %c, "
+ "filter is %c, "
+ "%d bytes into stream",
+ comp_correct[comp_i],
+ comp_filter[comp_i],
+ comp_progress + comp_i);
+ }
+ }
+
+ comp_progress += comp_filter_chunk;
+ }
+
+ camel_test_pull ();
+
+ /* inefficient */
+ camel_test_push ("Cleaning up");
+ g_object_unref (correct_stream);
+ g_object_unref (source_stream);
+ g_object_unref (filter_stream);
+ camel_test_pull ();
+
+ camel_test_pull ();
+}
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ gchar *work;
+ gint ii;
+
+ camel_test_init (argc, argv);
+
+ work = g_strdup_printf ("CRLF/DOT filter, test case %d", 0);
+ camel_test_start (work);
+ g_free (work);
+
+ for (ii = CRLF_ENCODE; ii < CRLF_DONE; ii++)
+ test_case (ii);
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/mime-filter/test-tohtml.c b/src/camel/tests/mime-filter/test-tohtml.c
new file mode 100644
index 000000000..946bd3024
--- /dev/null
+++ b/src/camel/tests/mime-filter/test-tohtml.c
@@ -0,0 +1,199 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * test - html.c
+ *
+ * Test the CamelMimeFilterToHTML class
+ */
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "camel-test.h"
+
+#define d(x)
+
+#define CHUNK_SIZE 4096
+
+static void
+test_filter (CamelMimeFilter *filter,
+ GFile *infile,
+ GFile *outfile)
+{
+ GFileInputStream *indisk;
+ GFileInputStream *outdisk;
+ GOutputStream *in;
+ GOutputStream *out;
+ GInputStream *in_filter_stream;
+ GOutputStream *out_filter_stream;
+ gchar *in_data;
+ gsize in_size;
+ gchar *out_data;
+ gsize out_size;
+
+ camel_test_push ("setup");
+
+ indisk = g_file_read (infile, NULL, NULL);
+ check (indisk != NULL);
+ outdisk = g_file_read (outfile, NULL, NULL);
+ check (outdisk != NULL);
+
+ out = g_memory_output_stream_new_resizable ();
+ check (g_output_stream_splice (
+ out, G_INPUT_STREAM (outdisk),
+ G_OUTPUT_STREAM_SPLICE_NONE, NULL, NULL) > 0);
+
+ camel_test_pull ();
+
+ camel_test_push ("reading through filter stream");
+
+ in = g_memory_output_stream_new_resizable ();
+
+ in_filter_stream = camel_filter_input_stream_new (
+ G_INPUT_STREAM (indisk), filter);
+
+ /* Leave the base stream open so we can re-read it. */
+ g_filter_input_stream_set_close_base_stream (
+ G_FILTER_INPUT_STREAM (in_filter_stream), FALSE);
+
+ check_count (indisk, 2);
+ check_count (filter, 2);
+
+ check (g_output_stream_splice (
+ in, in_filter_stream,
+ G_OUTPUT_STREAM_SPLICE_NONE, NULL, NULL) > 0);
+
+ in_data = g_memory_output_stream_get_data (
+ G_MEMORY_OUTPUT_STREAM (in));
+ in_size = g_memory_output_stream_get_data_size (
+ G_MEMORY_OUTPUT_STREAM (in));
+
+ out_data = g_memory_output_stream_get_data (
+ G_MEMORY_OUTPUT_STREAM (out));
+ out_size = g_memory_output_stream_get_data_size (
+ G_MEMORY_OUTPUT_STREAM (out));
+
+ check_msg (
+ in_size == out_size &&
+ memcmp (in_data, out_data, in_size) == 0,
+ "Buffer content mismatch: "
+ "%d != %d, in = '%.*s' != out = '%.*s'",
+ in_size, out_size,
+ in_size, in_data,
+ out_size, out_data);
+
+ camel_test_pull ();
+
+ camel_mime_filter_reset (filter);
+
+ check_unref (in_filter_stream, 1);
+ check_count (indisk, 1);
+ check_count (filter, 1);
+ check_unref (in, 1);
+
+ check (g_seekable_seek (
+ G_SEEKABLE (indisk), 0, G_SEEK_SET, NULL, NULL));
+
+ camel_test_push ("writing through filter stream");
+
+ in = g_memory_output_stream_new_resizable ();
+
+ out_filter_stream = camel_filter_output_stream_new (in, filter);
+ check_count (in, 2);
+ check_count (filter, 2);
+
+ check (g_output_stream_splice (
+ out_filter_stream, G_INPUT_STREAM (indisk),
+ G_OUTPUT_STREAM_SPLICE_NONE, NULL, NULL) > 0);
+ check (g_output_stream_flush (out_filter_stream, NULL, NULL));
+
+ in_data = g_memory_output_stream_get_data (
+ G_MEMORY_OUTPUT_STREAM (in));
+ in_size = g_memory_output_stream_get_data_size (
+ G_MEMORY_OUTPUT_STREAM (in));
+
+ out_data = g_memory_output_stream_get_data (
+ G_MEMORY_OUTPUT_STREAM (out));
+ out_size = g_memory_output_stream_get_data_size (
+ G_MEMORY_OUTPUT_STREAM (out));
+
+ check_msg (
+ in_size == out_size &&
+ memcmp (in_data, out_data, in_size) == 0,
+ "Buffer content mismatch: "
+ "%d != %d, in = '%.*s' != out = '%.*s'",
+ in_size, out_size,
+ in_size, in_data,
+ out_size, out_data);
+
+ check_unref (out_filter_stream, 1);
+ check_unref (in, 1);
+ check_unref (indisk, 1);
+ check_unref (outdisk, 1);
+ check_unref (out, 1);
+
+ camel_test_pull ();
+}
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ gint i;
+
+ camel_test_init (argc, argv);
+
+ camel_test_start ("HTML Stream filtering");
+
+ for (i = 0; i < 100; i++) {
+ gchar inname[32], outname[32];
+ CamelMimeFilter *filter;
+ GFile *infile;
+ GFile *outfile;
+ struct stat st;
+
+ g_snprintf (inname, sizeof (inname), "data/html.%d.in", i);
+ g_snprintf (outname, sizeof (outname), "data/html.%d.out", i);
+
+ if (g_stat (inname, &st) == -1)
+ break;
+
+ filter = camel_mime_filter_tohtml_new (
+ CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0);
+
+ infile = g_file_new_for_path (inname);
+ outfile = g_file_new_for_path (outname);
+
+ camel_test_push ("Data file '%s'", inname);
+
+ test_filter (filter, infile, outfile);
+
+ camel_test_pull ();
+
+ g_object_unref (infile);
+ g_object_unref (outfile);
+
+ check_unref (filter, 1);
+ }
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/mime-filter/test1.c b/src/camel/tests/mime-filter/test1.c
new file mode 100644
index 000000000..32e0e4119
--- /dev/null
+++ b/src/camel/tests/mime-filter/test1.c
@@ -0,0 +1,139 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * test-crlf.c
+ *
+ * Test the CamelMimeFilterCanon class
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-test.h"
+
+#define d (x)
+
+#define NUM_CASES 1
+#define CHUNK_SIZE 4096
+
+struct {
+ gint flags;
+ const gchar *in;
+ const gchar *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" },
+};
+
+static void
+test_case (gint test_num,
+ gsize chunk_size)
+{
+ GOutputStream *memory_stream;
+ GOutputStream *filter_stream;
+ CamelMimeFilter *filter;
+ const gchar *p;
+ gchar *data;
+ gsize size;
+
+ filter = camel_mime_filter_canon_new (tests[test_num].flags);
+ memory_stream = g_memory_output_stream_new_resizable ();
+ filter_stream = camel_filter_output_stream_new (memory_stream, filter);
+ check_unref (filter, 2);
+
+ p = tests[test_num].in;
+ while (*p) {
+ gint w = MIN (strlen (p), chunk_size);
+
+ check (g_output_stream_write (
+ filter_stream, p, w, NULL, NULL) == w);
+ p += w;
+ }
+ g_output_stream_flush (filter_stream, NULL, NULL);
+
+ data = g_memory_output_stream_get_data (
+ G_MEMORY_OUTPUT_STREAM (memory_stream));
+ size = g_memory_output_stream_get_data_size (
+ G_MEMORY_OUTPUT_STREAM (memory_stream));
+
+ check_msg (
+ size == strlen (tests[test_num].out),
+ "Buffer length mismatch: "
+ "expected %d got %d\n or '%s' got '%.*s'",
+ strlen (tests[test_num].out), size,
+ tests[test_num].out, size, data);
+ check_msg (
+ memcmp (data, tests[test_num].out, size) == 0,
+ "Buffer mismatch: expected '%s' got '%.*s'",
+ tests[test_num].out, size, data);
+
+ check_unref (filter_stream, 1);
+ check_unref (memory_stream, 1);
+}
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ gint ii;
+
+ camel_test_init (argc, argv);
+
+ camel_test_start ("canonicalisation filter tests");
+
+ for (ii = 0; ii < G_N_ELEMENTS (tests); ii++) {
+ gsize chunk_size;
+
+ camel_test_push ("Data test %d '%s'\n", ii, tests[ii].in);
+
+ /* try all write sizes */
+ for (chunk_size = 1; chunk_size < 20; chunk_size++) {
+ camel_test_push ("Chunk size %d\n", chunk_size);
+ test_case (ii, chunk_size);
+ camel_test_pull ();
+ }
+
+ camel_test_pull ();
+ }
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/misc/CMakeLists.txt b/src/camel/tests/misc/CMakeLists.txt
new file mode 100644
index 000000000..7c0b02ac8
--- /dev/null
+++ b/src/camel/tests/misc/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(TESTS
+ test1
+ test2
+ url
+ url-scan
+ utf7
+ split
+ rfc2047
+)
+
+add_camel_tests(misc TESTS)
diff --git a/src/camel/tests/misc/README b/src/camel/tests/misc/README
new file mode 100644
index 000000000..891bbb233
--- /dev/null
+++ b/src/camel/tests/misc/README
@@ -0,0 +1,6 @@
+
+test1 references header decoding
+test2 rfc2184 multipart/i18n parameters
+url URL parsing
+utf7 UTF7 and UTF8 processing
+split word splitting for searching
diff --git a/src/camel/tests/misc/rfc2047.c b/src/camel/tests/misc/rfc2047.c
new file mode 100644
index 000000000..0d57ac038
--- /dev/null
+++ b/src/camel/tests/misc/rfc2047.c
@@ -0,0 +1,102 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@novell.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "camel-test.h"
+
+struct {
+ const gchar *encoded;
+ const gchar *decoded;
+ gint dummy;
+} test1[] = {
+ /* the first half are rfc compliant cases (which are the most important) */
+ { "=?iso-8859-1?q?this=20is=20some=20text?=", "this is some text", 0 },
+ { "this =?iso-8859-1?q?is_some?= text", "this is some text", 0 },
+ { "=?iso-8859-1?q?th?= =?iso-8859-1?q?is?= is some text", "this is some text", 0 },
+ { "=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=",
+ "If you can read this you understand the example.", 0 },
+#if 0
+ /* And oddly enough, camel fails on these, removed for now */
+
+ /* second half: brokenly encoded rfc2047 words */
+ { "foo=?UTF-8?Q?bar?=baz", "foobarbaz", 0 },
+ { "foo =?UTF-8?Q?bar?=baz", "foo barbaz", 0 },
+ { "foo=?UTF-8?Q?bar?= baz", "foobar baz", 0 },
+ { "=?UTF-8?Q?foo bar?=baz", "foo barbaz", 0 },
+ { "foo=?UTF-8?Q?bar baz?=", "foobar baz", 0 },
+ { "=?UTF-8?Q?foo?==?UTF-8?Q?bar baz?=", "foobar baz", 0 },
+ { "=?UTF-8?Q?foo?= =?UTF-8?Q?bar baz?=", "foobar baz", 0 },
+ { "=?UTF-8?Q?foo?= bar =?UTF-8?Q?baz?=", "foo bar baz", 0 },
+ { "=?foo=?UTF-8?Q?bar baz?=", "=?foobar baz", 0 },
+ { "=?foo?=?UTF-8?Q?bar baz?=", "=?foo?bar baz", 0 },
+ { "=?foo?Q=?UTF-8?Q?bar baz?=", "=?foo?Qbar baz", 0 },
+ { "=?foo?Q?=?UTF-8?Q?bar baz?=", "=?foo?Q?bar baz", 0 },
+ { "=?UTF-8?Q?bar ? baz?=", "=?UTF-8?Q?bar ? baz?=", 0 },
+#endif
+};
+
+struct {
+ const gchar *encoded;
+ const gchar *decoded;
+ gint dummy;
+} test2[] = {
+ /* ctext tests */
+ { "Test of ctext (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)", "Test of ctext (ab)", 1 },
+ { "ctext with \\\"quoted\\\" pairs", "ctext with \"quoted\" pairs", 1 }
+};
+
+gint main (gint argc, gchar ** argv)
+{
+ gchar *decoded;
+ gint i;
+
+ camel_test_init (argc, argv);
+
+ camel_test_start ("rfc2047 decoding");
+
+ for (i = 0; i < G_N_ELEMENTS (test1); i++) {
+ camel_test_push ("rfc2047 decoding[%d] '%s'", i, test1[i].encoded);
+ decoded = camel_header_decode_string (test1[i].encoded, "iso-8859-1");
+ check (decoded && strcmp (decoded, test1[i].decoded) == 0);
+ g_free (decoded);
+ camel_test_pull ();
+ }
+
+ camel_test_end ();
+
+ camel_test_start ("rfc2047 ctext decoding");
+
+ for (i = 0; i < G_N_ELEMENTS (test2); i++) {
+ camel_test_push ("rfc2047 ctext decoding[%d] '%s'", i, test2[i].encoded);
+ decoded = camel_header_format_ctext (test2[i].encoded, "iso-8859-1");
+ check (decoded && strcmp (decoded, test2[i].decoded) == 0);
+ g_free (decoded);
+ camel_test_pull ();
+ }
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/misc/split.c b/src/camel/tests/misc/split.c
new file mode 100644
index 000000000..eac95483a
--- /dev/null
+++ b/src/camel/tests/misc/split.c
@@ -0,0 +1,125 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <camel/camel-search-private.h>
+
+#include "camel-test.h"
+
+/* TODO: should put utf8 stuff here too */
+
+static struct {
+ const gchar *word;
+ gint count;
+ struct {
+ const gchar *word;
+ gint type;
+ } splits[5];
+} split_tests[] = {
+ { "simple", 1, { { "simple", CAMEL_SEARCH_WORD_SIMPLE } } },
+ { "two words", 2, { { "two", CAMEL_SEARCH_WORD_SIMPLE }, {"words" , CAMEL_SEARCH_WORD_SIMPLE } } },
+ { "compl;ex", 1, { { "compl;ex", CAMEL_SEARCH_WORD_COMPLEX } } },
+ { "compl;ex simple", 2, { { "compl;ex", CAMEL_SEARCH_WORD_COMPLEX} , {"simple", CAMEL_SEARCH_WORD_SIMPLE} } },
+ { "\"quoted\"", 1, { { "quoted", CAMEL_SEARCH_WORD_SIMPLE } } },
+ { "\"quoted double\"", 1, { { "quoted double", CAMEL_SEARCH_WORD_COMPLEX } } },
+ { "\"quoted double\" compl;ex", 2, { { "quoted double", CAMEL_SEARCH_WORD_COMPLEX }, { "compl;ex", CAMEL_SEARCH_WORD_COMPLEX } } },
+ { "\"quoted gdouble \\\" escaped\"", 1, { { "quoted gdouble \" escaped", CAMEL_SEARCH_WORD_COMPLEX } } },
+ { "\"quoted\\\"double\" \\\" escaped\\\"", 3, { { "quoted\"double", CAMEL_SEARCH_WORD_COMPLEX }, {"\"", CAMEL_SEARCH_WORD_COMPLEX}, { "escaped\"", CAMEL_SEARCH_WORD_COMPLEX } } },
+ { "\\\"escaped", 1, { { "\"escaped", CAMEL_SEARCH_WORD_COMPLEX } } },
+
+};
+
+static struct {
+ const gchar *word;
+ gint count;
+ struct {
+ const gchar *word;
+ gint type;
+ } splits[5];
+} simple_tests[] = {
+ { "simple", 1, { {"simple", CAMEL_SEARCH_WORD_SIMPLE } } },
+ { "simpleCaSe", 1, { { "simplecase", CAMEL_SEARCH_WORD_SIMPLE } } },
+ { "two words", 2, { { "two", CAMEL_SEARCH_WORD_SIMPLE }, { "words", CAMEL_SEARCH_WORD_SIMPLE } } },
+ { "two wordscAsE", 2, { { "two", CAMEL_SEARCH_WORD_SIMPLE} , { "wordscase", CAMEL_SEARCH_WORD_SIMPLE } } },
+ { "compl;ex", 2, { { "compl", CAMEL_SEARCH_WORD_SIMPLE }, { "ex", CAMEL_SEARCH_WORD_SIMPLE } } },
+ { "compl;ex simple", 3, { { "compl", CAMEL_SEARCH_WORD_SIMPLE }, { "ex", CAMEL_SEARCH_WORD_SIMPLE }, { "simple", CAMEL_SEARCH_WORD_SIMPLE } } },
+ { "\"quoted compl;ex\" simple", 4, { { "quoted", CAMEL_SEARCH_WORD_SIMPLE}, { "compl", CAMEL_SEARCH_WORD_SIMPLE }, { "ex", CAMEL_SEARCH_WORD_SIMPLE }, { "simple", CAMEL_SEARCH_WORD_SIMPLE } } },
+ { "\\\" \"quoted\"compl;ex\" simple", 4, { { "quoted", CAMEL_SEARCH_WORD_SIMPLE}, { "compl", CAMEL_SEARCH_WORD_SIMPLE }, { "ex", CAMEL_SEARCH_WORD_SIMPLE }, { "simple", CAMEL_SEARCH_WORD_SIMPLE } } },
+};
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ gint i, j;
+ struct _camel_search_words *words, *tmp;
+
+ camel_test_init (argc, argv);
+
+ camel_test_start ("Search splitting");
+
+ for (i = 0; i < G_N_ELEMENTS (split_tests); i++) {
+ camel_test_push ("split %d '%s'", i, split_tests[i].word);
+
+ words = camel_search_words_split (split_tests[i].word);
+ check (words != NULL);
+ check_msg (words->len == split_tests[i].count, "words->len = %d, count = %d", words->len, split_tests[i].count);
+
+ for (j = 0; j < words->len; j++) {
+ check_msg (
+ strcmp (split_tests[i].splits[j].word, words->words[j]->word) == 0,
+ "'%s' != '%s'", split_tests[i].splits[j].word, words->words[j]->word);
+ check (split_tests[i].splits[j].type == words->words[j]->type);
+ }
+
+ camel_search_words_free (words);
+ camel_test_pull ();
+ }
+
+ camel_test_end ();
+
+ camel_test_start ("Search splitting - simple");
+
+ for (i = 0; i < G_N_ELEMENTS (simple_tests); i++) {
+ camel_test_push ("simple split %d '%s'", i, simple_tests[i].word);
+
+ tmp = camel_search_words_split (simple_tests[i].word);
+ check (tmp != NULL);
+
+ words = camel_search_words_simple (tmp);
+ check (words != NULL);
+ check_msg (words->len == simple_tests[i].count, "words->len = %d, count = %d", words->len, simple_tests[i].count);
+
+ for (j = 0; j < words->len; j++) {
+ check_msg (
+ strcmp (simple_tests[i].splits[j].word, words->words[j]->word) == 0,
+ "'%s' != '%s'", simple_tests[i].splits[j].word, words->words[j]->word);
+ check (simple_tests[i].splits[j].type == words->words[j]->type);
+ }
+
+ camel_search_words_free (words);
+ camel_search_words_free (tmp);
+ camel_test_pull ();
+ }
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/misc/test1.c b/src/camel/tests/misc/test1.c
new file mode 100644
index 000000000..eb944c42d
--- /dev/null
+++ b/src/camel/tests/misc/test1.c
@@ -0,0 +1,69 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "camel-test.h"
+
+struct {
+ const gchar *header;
+ const gchar *values[5];
+} test1[] = {
+ { "<test@camel.host>", { "test@camel.host" } },
+ { "(this is a comment) <test@camel.host>", { "test@camel.host" } },
+ { "<test@camel.host> (this is a comment)", { "test@camel.host" } },
+ { "<test@camel.host> This is total rubbish!", { "test@camel.host" } },
+ { " << test.groupwise@bug.novell>@novell>", { "test.groupwise@bug.novell" } },
+ { " << test.groupwise@bug.novell>@novell> <test@camel.host>",
+ { "test@camel.host", "test.groupwise@bug.novell" } },
+ { "<test@camel.host> <<test.groupwise@bug.novell>@novell> <test@camel.host>",
+ { "test@camel.host", "test.groupwise@bug.novell", "test@camel.host" } },
+ { " << test.groupwise@bug.novell>@novell> <test@camel.host> <<test.groupwise@bug.novell>@novell>",
+ { "test.groupwise@bug.novell", "test@camel.host", "test.groupwise@bug.novell" } },
+};
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ gint i, j;
+
+ camel_test_init (argc, argv);
+
+ camel_test_start ("references decoding");
+
+ for (i = 0; i < G_N_ELEMENTS (test1); i++) {
+ GSList *list;
+
+ camel_test_push ("references decoding[%d] '%s'", i, test1[i].header);
+ list = camel_header_references_decode (test1[i].header);
+ for (j = 0; test1[i].values[j]; j++) {
+ check_msg (list != NULL, "didn't find all references");
+ check (strcmp (test1[i].values[j], list->data) == 0);
+ list = g_slist_next (list);
+ }
+ check_msg (list == NULL, "found more references than should have");
+ g_slist_free_full (list, g_free);
+ camel_test_pull ();
+ }
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/misc/test2.c b/src/camel/tests/misc/test2.c
new file mode 100644
index 000000000..2ca8ac375
--- /dev/null
+++ b/src/camel/tests/misc/test2.c
@@ -0,0 +1,133 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "camel-test.h"
+
+/* NB: We know which order the params will be decoded in, plain in the order they come,
+ * and rfc2184 encoded following those, sorted lexigraphically */
+struct {
+ const gchar *list;
+ gint count;
+ const gchar *params[8];
+} test1[] = {
+ { "; charset=\"iso-8859-1\"",
+ 1,
+ { "charset", "iso-8859-1" }, },
+ { "; charset=iso-8859-1",
+ 1,
+ { "charset", "iso-8859-1" }, },
+ { "; charset=\"iso-8859-1\"; boundary=\"foo\"",
+ 2,
+ { "charset", "iso-8859-1",
+ "boundary", "foo" }, },
+ { "; charset*1 = 8859; charset*0=\"iso-8859-1'en'iso-\";charset*2=\"-1\" ",
+ 1,
+ { "charset", "iso-8859-1" }, },
+ { "; charset*1 = 8859; boundary=foo; charset*0=\"iso-8859-1'en'iso-\";charset*2=\"-1\" ",
+ 2,
+ { "boundary", "foo",
+ "charset", "iso-8859-1", }, },
+ { "; charset*1 = 8859; boundary*0=f; charset*0=\"iso-8859-1'en'iso-\"; boundary*2=\"o\" ; charset*2=\"-1\"; boundary*1=o ",
+ 2,
+ { "boundary", "foo",
+ "charset", "iso-8859-1", }, },
+ { "; charset*1 = 8859; boundary*0=\"iso-8859-1'en'f\"; charset*0=\"iso-8859-1'en'iso-\"; boundary*2=\"o\" ; charset*2=\"-1\"; boundary*1=o ",
+ 2,
+ { "boundary", "foo",
+ "charset", "iso-8859-1", }, },
+};
+
+struct {
+ gint count;
+ const gchar *params[8];
+ const gchar *list;
+} test2[] = {
+ { 1,
+ { "name", "Doul\xC3\xADk01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123457890123456789123456789" },
+ ";\n"
+ "\tname*0*=ISO-8859-1''Doul%EDk012345678901234567890123456789012345678901234;\n"
+ "\tname*1*=56789012345678901234567890123456789012345678901234567890123457890;\n"
+ "\tname*2*=123456789123456789" },
+ { 1,
+ { "name", "\"%$#@ special chars?;; !" },
+ "; name=\"\\\"%$#@ special chars?;; !\"" },
+ { 1,
+ { "name", "\"%$#@ special chars?;; !\xC3\xAD" },
+ "; name*=ISO-8859-1''%22%25$#%40%20special%20chars%3F%3B%3B%20!%ED" },
+};
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ gint i, j;
+
+ camel_test_init (argc, argv);
+
+ camel_test_start ("Param list decoding");
+
+ for (i = 0; i < G_N_ELEMENTS (test1); i++) {
+ struct _camel_header_param *head, *node;
+
+ camel_test_push ("param decoding[%d] '%s'", i, test1[i].list);
+ head = camel_header_param_list_decode (test1[i].list);
+ check (head != NULL);
+ node = head;
+ for (j = 0; j < test1[i].count; j++) {
+ check_msg (node != NULL, "didn't find all params");
+ check (strcmp (node->name, test1[i].params[j * 2]) == 0);
+ check (strcmp (node->value, test1[i].params[j * 2 + 1]) == 0);
+ node = node->next;
+ }
+ check_msg (node == NULL, "found more params than should have");
+ camel_header_param_list_free (head);
+ camel_test_pull ();
+ }
+
+ camel_test_end ();
+
+ camel_test_start ("Param list encoding");
+
+ for (i = 0; i < G_N_ELEMENTS (test2); i++) {
+ struct _camel_header_param *head = NULL, *scan;
+ gchar *text;
+
+ camel_test_push ("param encoding[%d]", i);
+
+ for (j = 0; j < test2[i].count; j++)
+ camel_header_set_param (&head, test2[i].params[j * 2], test2[i].params[j * 2 + 1]);
+ scan = head;
+ for (j = 0; scan; j++)
+ scan = scan->next;
+ check (j == test2[i].count);
+
+ text = camel_header_param_list_format (head);
+ check (strcmp (text, test2[i].list) == 0);
+ camel_header_param_list_free (head);
+
+ camel_test_pull ();
+ }
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/misc/url-scan.c b/src/camel/tests/misc/url-scan.c
new file mode 100644
index 000000000..5539db91e
--- /dev/null
+++ b/src/camel/tests/misc/url-scan.c
@@ -0,0 +1,125 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "camel-test.h"
+
+struct {
+ const gchar *text, *url;
+} url_tests[] = {
+ { "bob@foo.com", "mailto:bob@foo.com" },
+ { "Ends with bob@foo.com", "mailto:bob@foo.com" },
+ { "bob@foo.com at start", "mailto:bob@foo.com" },
+ { "bob@foo.com.", "mailto:bob@foo.com" },
+ { "\"bob@foo.com\"", "mailto:bob@foo.com" },
+ { "<bob@foo.com>", "mailto:bob@foo.com" },
+ { "(bob@foo.com)", "mailto:bob@foo.com" },
+ { "bob@foo.com, 555-9999", "mailto:bob@foo.com" },
+ { "|bob@foo.com|555-9999|", "mailto:bob@foo.com" },
+ { "bob@ no match bob@", NULL },
+ { "@foo.com no match @foo.com", NULL },
+ { "\"bob\"@foo.com", NULL },
+ { "M@ke money fast!", NULL },
+ { "ASCII art @_@ @>->-", NULL },
+
+ { "http://www.foo.com", "http://www.foo.com" },
+ { "Ends with http://www.foo.com", "http://www.foo.com" },
+ { "http://www.foo.com at start", "http://www.foo.com" },
+ { "http://www.foo.com.", "http://www.foo.com" },
+ { "http://www.foo.com/.", "http://www.foo.com/" },
+ { "<http://www.foo.com>", "http://www.foo.com" },
+ { "(http://www.foo.com)", "http://www.foo.com" },
+ { "http://www.foo.com, 555-9999", "http://www.foo.com" },
+ { "|http://www.foo.com|555-9999|", "http://www.foo.com" },
+ { "foo http://www.foo.com/ bar", "http://www.foo.com/" },
+ { "foo http://www.foo.com/index.html bar", "http://www.foo.com/index.html" },
+ { "foo http://www.foo.com/q?99 bar", "http://www.foo.com/q?99" },
+ { "foo http://www.foo.com/;foo=bar&baz=quux bar", "http://www.foo.com/;foo=bar&baz=quux" },
+ { "foo http://www.foo.com/index.html#anchor bar", "http://www.foo.com/index.html#anchor" },
+ { "http://www.foo.com/index.html; foo", "http://www.foo.com/index.html" },
+ { "http://www.foo.com/index.html: foo", "http://www.foo.com/index.html" },
+ { "http://www.foo.com/index.html-- foo", "http://www.foo.com/index.html" },
+ { "http://www.foo.com/index.html?", "http://www.foo.com/index.html" },
+ { "http://www.foo.com/index.html!", "http://www.foo.com/index.html" },
+ { "\"http://www.foo.com/index.html\"", "http://www.foo.com/index.html" },
+ { "'http://www.foo.com/index.html'", "http://www.foo.com/index.html" },
+ { "http://bob@www.foo.com/bar/baz/", "http://bob@www.foo.com/bar/baz/" },
+ { "http no match http", NULL },
+ { "http: no match http:", NULL },
+ { "http:// no match http://", NULL },
+ { "unrecognized://bob@foo.com/path", "mailto:bob@foo.com" },
+
+ { "src/www.c", NULL },
+ { "Ewwwwww.Gross.", NULL },
+
+};
+
+static gint num_url_tests = G_N_ELEMENTS (url_tests);
+
+gint main (gint argc, gchar **argv)
+{
+ gchar *html, *url, *p;
+ gint i, errors = 0;
+ guint32 flags;
+
+ camel_test_init (argc, argv);
+
+ camel_test_start ("URL scanning");
+
+ flags = CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES;
+ for (i = 0; i < num_url_tests; i++) {
+ camel_test_push ("'%s' => '%s'", url_tests[i].text, url_tests[i].url ? url_tests[i].url : "None");
+
+ html = camel_text_to_html (url_tests[i].text, flags, 0);
+
+ url = strstr (html, "href=\"");
+ if (url) {
+ url += 6;
+ p = strchr (url, '"');
+ if (p)
+ *p = '\0';
+
+ while ((p = strstr (url, "&amp;")))
+ memmove (p + 1, p + 5, strlen (p + 5) + 1);
+ }
+
+ if ((url && (!url_tests[i].url || strcmp (url, url_tests[i].url) != 0)) ||
+ (!url && url_tests[i].url)) {
+ printf (
+ "FAILED on \"%s\" -> %s\n (got %s)\n\n",
+ url_tests[i].text,
+ url_tests[i].url ? url_tests[i].url : "(nothing)",
+ url ? url : "(nothing)");
+ errors++;
+ }
+
+ g_free (html);
+ }
+
+ printf ("\n%d errors\n", errors);
+
+ camel_test_end ();
+
+ return errors;
+}
diff --git a/src/camel/tests/misc/url.c b/src/camel/tests/misc/url.c
new file mode 100644
index 000000000..0f519fde1
--- /dev/null
+++ b/src/camel/tests/misc/url.c
@@ -0,0 +1,137 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "camel-test.h"
+
+const gchar *base = "http://a/b/c/d;p?q#f";
+
+struct {
+ const gchar *url_string, *result;
+} tests[] = {
+ { "g:h", "g:h" },
+ { "g", "http://a/b/c/g" },
+ { "./g", "http://a/b/c/g" },
+ { "g/", "http://a/b/c/g/" },
+ { "/g", "http://a/g" },
+ { "//g", "http://g" },
+ { "?y", "http://a/b/c/d;p?y" },
+ { "g?y", "http://a/b/c/g?y" },
+ { "g?y/./x", "http://a/b/c/g?y/./x" },
+ { "#s", "http://a/b/c/d;p?q#s" },
+ { "g#s", "http://a/b/c/g#s" },
+ { "g#s/./x", "http://a/b/c/g#s/./x" },
+ { "g?y#s", "http://a/b/c/g?y#s" },
+ { ";x", "http://a/b/c/d;x" },
+ { "g;x", "http://a/b/c/g;x" },
+ { "g;x?y#s", "http://a/b/c/g;x?y#s" },
+ { ".", "http://a/b/c/" },
+ { "./", "http://a/b/c/" },
+ { "..", "http://a/b/" },
+ { "../", "http://a/b/" },
+ { "../g", "http://a/b/g" },
+ { "../..", "http://a/" },
+ { "../../", "http://a/" },
+ { "../../g", "http://a/g" },
+ { "", "http://a/b/c/d;p?q#f" },
+ { "../../../g", "http://a/../g" },
+ { "../../../../g", "http://a/../../g" },
+ { "/./g", "http://a/./g" },
+ { "/../g", "http://a/../g" },
+ { "g.", "http://a/b/c/g." },
+ { ".g", "http://a/b/c/.g" },
+ { "g..", "http://a/b/c/g.." },
+ { "..g", "http://a/b/c/..g" },
+ { "./../g", "http://a/b/g" },
+ { "./g/.", "http://a/b/c/g/" },
+ { "g/./h", "http://a/b/c/g/h" },
+ { "g/../h", "http://a/b/c/h" },
+ { "http:g", "http:g" },
+ { "http:", "http:" },
+
+ /* (not from rfc 1808) */
+ { "sendmail:", "sendmail:" },
+ { "mbox:/var/mail/user", "mbox:/var/mail/user" },
+ { "pop://user@host", "pop://user@host" },
+ { "pop://user@host:99", "pop://user@host:99" },
+ { "pop://user:password@host", "pop://user:password@host" },
+ { "pop://user:password@host:99", "pop://user:password@host:99" },
+ { "pop://user;auth=APOP@host", "pop://user;auth=APOP@host" },
+ { "pop://user@host/;keep_on_server", "pop://user@host/;keep_on_server" },
+ { "pop://user@host/;keep_on_server=1", "pop://user@host/;keep_on_server=1" },
+ { "pop://us%65r@host", "pop://user@host" },
+ { "pop://us%40r@host", "pop://us%40r@host" },
+ { "pop://us%3ar@host", "pop://us%3ar@host" },
+ { "pop://us%2fr@host", "pop://us%2fr@host" }
+};
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ CamelURL *base_url, *url;
+ gchar *url_string;
+ gint i;
+ GError *error = NULL;
+
+ camel_test_init (argc, argv);
+
+ camel_test_start ("URL parsing");
+
+ camel_test_push ("base URL parsing");
+ base_url = camel_url_new (base, &error);
+ if (!base_url) {
+ camel_test_fail (
+ "Could not parse %s: %s\n",
+ base, error->message);
+ }
+ camel_test_pull ();
+
+ camel_test_push ("base URL unparsing");
+ url_string = camel_url_to_string (base_url, 0);
+ if (strcmp (url_string, base) != 0) {
+ camel_test_fail (
+ "URL <%s> unparses to <%s>\n",
+ base, url_string);
+ }
+ camel_test_pull ();
+ g_free (url_string);
+
+ for (i = 0; i < G_N_ELEMENTS (tests); i++) {
+ camel_test_push ("<%s> + <%s> = <%s>?", base, tests[i].url_string, tests[i].result);
+ url = camel_url_new_with_base (base_url, tests[i].url_string);
+ if (!url) {
+ camel_test_fail ("could not parse");
+ camel_test_pull ();
+ continue;
+ }
+
+ url_string = camel_url_to_string (url, 0);
+ if (strcmp (url_string, tests[i].result) != 0)
+ camel_test_fail ("got <%s>!", url_string);
+ g_free (url_string);
+ camel_test_pull ();
+ }
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/misc/utf7.c b/src/camel/tests/misc/utf7.c
new file mode 100644
index 000000000..b79cf10d2
--- /dev/null
+++ b/src/camel/tests/misc/utf7.c
@@ -0,0 +1,118 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "camel-test.h"
+
+static struct {
+ const gchar *utf8;
+ const gchar *utf7;
+ guint32 unicode[200];
+} tests[] = {
+ /* the escape gchar */
+ { "&", "&-",
+ { 0x0026, } },
+ /* part of set D */
+ { "+", "+",
+ { 0x002b, } },
+ { "plain ascii text", "plain ascii text",
+ { 0x0070, 0x006c, 0x0061, 0x0069, 0x006e, 0x0020, 0x0061, 0x0073, 0x0063, 0x0069, 0x0069, 0x0020, 0x0074, 0x0065, 0x0078, 0x0074, } },
+ /* part of set O */
+ { "'(),-./:?", "'(),-./:?",
+ { 0x0027, 0x0028, 0x0029, 0x002c, 0x002d, 0x002e, 0x002f, 0x003a, 0x003f, } },
+ { "!\"#$%*+-;<=>@[]^_`{|}", "!\"#$%*+-;<=>@[]^_`{|}",
+ { 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x002a, 0x002b, 0x002d, 0x003b, 0x003c, 0x003d, 0x003e, 0x0040, 0x005b, 0x005d, 0x005e, 0x005f, 0x0060, 0x007b, 0x007c, 0x007d, } },
+ /* example strings from rfc1642 (modified for imap utf7) */
+ { "A\xe2\x89\xa2\xce\x91" ".", "A&ImIDkQ-.",
+ { 0x0041, 0x2262, 0x0391, 0x002e, } },
+ { "Hi Mum \xe2\x98\xba!", "Hi Mum &Jjo-!",
+ { 0x0048, 0x0069, 0x0020, 0x004d, 0x0075, 0x006d, 0x0020, 0x263a, 0x0021, } },
+ { "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e", "&ZeVnLIqe-",
+ { 0x65e5, 0x672c, 0x8a9e, } },
+ { "Item 3 is \xc2\xa3" "1.", "Item 3 is &AKM-1.",
+ { 0x0049, 0x0074, 0x0065, 0x006d, 0x0020, 0x0033, 0x0020, 0x0069, 0x0073, 0x0020, 0x00a3, 0x0031, 0x002e, } },
+ { "\"The sayings of Confucius,\" James R. Ware, trans. \xe5\x8f\xb0\xe5\x8c\x97:\xe6\x96\x87\xe8\x87\xb4\xe5\x87\xba\xe7\x89\x88\xe7\xa4\xbe, 1980. (Chinese text with English translation)\xe5\x9b\x9b\xe6\x9b\xb8\xe4\xba\x94\xe7\xb6\x93, \xe5\xae\x8b\xe5\x85\x83\xe4\xba\xba\xe6\xb3\xa8, \xe5\x8c\x97\xe4\xba\xac: \xe4\xb8\xad\xe5\x9c\x8b\xe6\x9b\xb8\xe5\xba\x97, 1990.",
+ "\"The sayings of Confucius,\" James R. Ware, trans. &U,BTFw-:&ZYeB9FH6ckh5Pg-, 1980. (Chinese text with English translation)&Vttm+E6UfZM-, &W4tRQ066bOg-, &UxdOrA-: &Ti1XC2b4Xpc-, 1990.",
+ { 0x0022, 0x0054, 0x0068, 0x0065, 0x0020, 0x0073, 0x0061, 0x0079, 0x0069, 0x006e, 0x0067, 0x0073, 0x0020, 0x006f, 0x0066, 0x0020, 0x0043, 0x006f, 0x006e, 0x0066, 0x0075, 0x0063, 0x0069, 0x0075, 0x0073, 0x002c, 0x0022, 0x0020, 0x004a, 0x0061, 0x006d, 0x0065, 0x0073, 0x0020, 0x0052, 0x002e, 0x0020, 0x0057, 0x0061, 0x0072, 0x0065, 0x002c, 0x0020, 0x0074, 0x0072, 0x0061, 0x006e, 0x0073, 0x002e, 0x0020, 0x0020, 0x53f0, 0x5317, 0x003a, 0x6587, 0x81f4, 0x51fa, 0x7248, 0x793e, 0x002c, 0x0020, 0x0031, 0x0039, 0x0038, 0x0030, 0x002e, 0x0020, 0x0020, 0x0028, 0x0043, 0x0068, 0x0069, 0x006e, 0x0065, 0x0073, 0x0065, 0x0020, 0x0074, 0x0065, 0x0078, 0x0074, 0x0020, 0x0077, 0x0069, 0x0074, 0x0068, 0x0020, 0x0045, 0x006e, 0x0067, 0x006c, 0x0069, 0x0073, 0x0068, 0x0020, 0x0074, 0x0072, 0x0061, 0x006e, 0x0073, 0x006c, 0x0061, 0x0074, 0x0069, 0x006f, 0x006e, 0x0029, 0x56db, 0x66f8, 0x4e94, 0x7d93, 0x002c, 0x0020, 0x5b8b, 0x5143, 0x4eba, 0x6ce8, 0x002c, 0x0020, 0x5317, 0x4eac, 0x003a, 0x0020, 0x0020, 0x4e2d, 0x570b, 0x66f8, 0x5e97, 0x002c, 0x0020, 0x0031, 0x0039, 0x0039, 0x0030, 0x002e, } },
+};
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ const gchar *p;
+ gchar *utf8, *utf7;
+ gint i, j;
+ guint32 u;
+ gchar utf8enc[256];
+ GString *out;
+
+ camel_test_init (argc, argv);
+
+ out = g_string_new ("");
+
+ camel_test_start ("UTF8, UTF7 RFC1642+RFC2060");
+
+ for (i = 0; i < G_N_ELEMENTS (tests); i++) {
+
+ camel_test_push ("%2d: %s utf8 decode", i, tests[i].utf7);
+ p = tests[i].utf8;
+ j = 0;
+ do {
+ u = camel_utf8_getc ((const guchar **) &p);
+ check (u == tests[i].unicode[j]);
+ j++;
+ } while (u);
+ camel_test_pull ();
+
+ camel_test_push ("%2d: %s utf7->utf8", i, tests[i].utf7);
+ utf8 = camel_utf7_utf8 (tests[i].utf7);
+ check_msg (strcmp (utf8, tests[i].utf8) == 0, "utf8 = '%s'", utf8);
+ camel_test_pull ();
+
+ camel_test_push ("%2d: %s utf7->utf8->utf7", i, tests[i].utf7);
+ utf7 = camel_utf8_utf7 (utf8);
+ check_msg (strcmp (utf7, tests[i].utf7) == 0, "utf7 = '%s'", utf7);
+ camel_test_pull ();
+
+ g_free (utf7);
+ g_free (utf8);
+
+ camel_test_push ("%2d: %s utf8 encode", i, tests[i].utf7);
+
+ g_string_truncate (out, 0);
+ p = utf8enc;
+ j = 0;
+ do {
+ u = tests[i].unicode[j++];
+ camel_utf8_putc ((guchar **) &p, u);
+ g_string_append_u (out, u);
+ } while (u);
+
+ check (strcmp (utf8enc, out->str) == 0);
+ check (strcmp (utf8enc, tests[i].utf8) == 0);
+
+ camel_test_pull ();
+ }
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/smime/CMakeLists.txt b/src/camel/tests/smime/CMakeLists.txt
new file mode 100644
index 000000000..3d122e636
--- /dev/null
+++ b/src/camel/tests/smime/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(TESTS
+ pgp
+# pgp-mime
+# pkcs7
+)
+
+add_camel_tests(smime TESTS)
diff --git a/src/camel/tests/smime/README b/src/camel/tests/smime/README
new file mode 100644
index 000000000..baad91ba0
--- /dev/null
+++ b/src/camel/tests/smime/README
@@ -0,0 +1,2 @@
+pgp PGP test suite
+
diff --git a/src/camel/tests/smime/pgp-mime.c b/src/camel/tests/smime/pgp-mime.c
new file mode 100644
index 000000000..eb440509c
--- /dev/null
+++ b/src/camel/tests/smime/pgp-mime.c
@@ -0,0 +1,200 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#include "camel-test.h"
+#include "session.h"
+
+static gchar test_msg[] = "Since we need to make sure that\nFrom lines work okay, we should test that"
+"as well as test 8bit chars and other fun stuff? 8bit chars: Dražen Kačar\n\nOkay, I guess that covers"
+"the basics at least...\n";
+
+#define CAMEL_PGP_SESSION_TYPE (camel_pgp_session_get_type ())
+#define CAMEL_PGP_SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), CAMEL_PGP_SESSION_TYPE, CamelPgpSession))
+#define CAMEL_PGP_SESSION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), CAMEL_PGP_SESSION_TYPE, CamelPgpSessionClass))
+#define CAMEL_PGP_IS_SESSION(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), CAMEL_PGP_SESSION_TYPE))
+
+typedef struct _CamelPgpSession {
+ CamelSession parent_object;
+
+} CamelPgpSession;
+
+typedef struct _CamelPgpSessionClass {
+ CamelSessionClass parent_class;
+
+} CamelPgpSessionClass;
+
+static gchar *get_password (CamelSession *session, const gchar *prompt,
+ guint32 flags,
+ CamelService *service, const gchar *item,
+ GError **error);
+
+static void
+init (CamelPgpSession *session)
+{
+ ;
+}
+
+static void
+class_init (CamelPgpSessionClass *camel_pgp_session_class)
+{
+ CamelSessionClass *camel_session_class =
+ CAMEL_SESSION_CLASS (camel_pgp_session_class);
+
+ camel_session_class->get_password = get_password;
+}
+
+static GType
+camel_pgp_session_get_type (void)
+{
+ static GType type = G_TYPE_INVALID;
+
+ if (G_UNLIKELY (type == G_TYPE_INVALID))
+ type = camel_type_register (
+ CAMEL_TYPE_TEST_SESSION,
+ "CamelPgpSession",
+ sizeof (CamelPgpSession),
+ sizeof (CamelPgpSessionClass),
+ (GClassInitFunc) class_init,
+ NULL,
+ (GInstanceInitFunc) init,
+ NULL);
+
+ return type;
+}
+
+static gchar *
+get_password (CamelSession *session,
+ const gchar *prompt,
+ guint32 flags,
+ CamelService *service,
+ const gchar *item,
+ GError **error)
+{
+ return g_strdup ("no.secret");
+}
+
+static CamelSession *
+camel_pgp_session_new (const gchar *path)
+{
+ CamelSession *session;
+
+ session = g_object_new (CAMEL_TYPE_PGP_SESSION, NULL);
+ camel_session_construct (session, path);
+
+ return session;
+}
+
+gint main (gint argc, gchar **argv)
+{
+ CamelSession *session;
+ CamelCipherContext *ctx;
+ GError **error;
+ CamelCipherValidity *valid;
+ CamelMimePart *mime_part;
+ CamelMultipartSigned *mps;
+ CamelMultipartEncrypted *mpe;
+ GPtrArray *recipients;
+ gint ret;
+
+ camel_test_init (argc, argv);
+
+ /* clear out any camel-test data */
+ system ("/bin/rm -rf /tmp/camel-test");
+ system ("/bin/mkdir /tmp/camel-test");
+ setenv ("GNUPGHOME", "/tmp/camel-test/.gnupg", 1);
+
+ /* import the gpg keys */
+ if ((ret = system ("gpg < /dev/null > /dev/null 2>&1")) == -1)
+ return 77;
+ else if (WEXITSTATUS (ret) == 127)
+ return 77;
+
+ system ("gpg --import ../data/camel-test.gpg.pub > /dev/null 2>&1");
+ system ("gpg --import ../data/camel-test.gpg.sec > /dev/null 2>&1");
+
+ session = camel_pgp_session_new ("/tmp/camel-test");
+
+ ex = camel_exception_new ();
+
+ ctx = camel_gpg_context_new (session);
+ camel_gpg_context_set_always_trust (CAMEL_GPG_CONTEXT (ctx), TRUE);
+
+ camel_test_start ("Test of PGP/MIME functions");
+
+ mime_part = camel_mime_part_new ();
+ camel_mime_part_set_content (mime_part, test_msg, strlen (test_msg), "text/plain");
+ camel_mime_part_set_description (mime_part, "Test of PGP/MIME multipart/signed stuff");
+
+ camel_test_push ("PGP/MIME signing");
+ mps = camel_multipart_signed_new ();
+ camel_multipart_signed_sign (mps, ctx, mime_part, "no.user@no.domain", CAMEL_CIPHER_HASH_SHA1, ex);
+ check_msg (!camel_exception_is_set (ex), "%s", camel_exception_get_description (ex));
+ camel_test_pull ();
+
+ g_object_unref (mime_part);
+ camel_exception_clear (ex);
+
+ camel_test_push ("PGP/MIME verify");
+ valid = camel_multipart_signed_verify (mps, ctx, ex);
+ check_msg (!camel_exception_is_set (ex), "%s", camel_exception_get_description (ex));
+ check_msg (camel_cipher_validity_get_valid (valid), "%s", camel_cipher_validity_get_description (valid));
+ camel_cipher_validity_free (valid);
+ camel_test_pull ();
+
+ g_object_unref (mps);
+ camel_exception_clear (ex);
+
+ mime_part = camel_mime_part_new ();
+ camel_mime_part_set_content (mime_part, test_msg, strlen (test_msg), "text/plain");
+ camel_mime_part_set_description (mime_part, "Test of PGP/MIME multipart/encrypted stuff");
+
+ camel_test_push ("PGP/MIME encrypt");
+ recipients = g_ptr_array_new ();
+ g_ptr_array_add (recipients, "no.user@no.domain");
+
+ mpe = camel_multipart_encrypted_new ();
+ camel_multipart_encrypted_encrypt (mpe, mime_part, ctx, "no.user@no.domain", recipients, ex);
+ check_msg (!camel_exception_is_set (ex), "%s", camel_exception_get_description (ex));
+ g_ptr_array_free (recipients, TRUE);
+ camel_test_pull ();
+
+ camel_exception_clear (ex);
+ g_object_unref (mime_part);
+
+ camel_test_push ("PGP/MIME decrypt");
+ mime_part = camel_multipart_encrypted_decrypt (mpe, ctx, ex);
+ check_msg (!camel_exception_is_set (ex), "%s", camel_exception_get_description (ex));
+ g_object_unref (mime_part);
+ g_object_unref (mpe);
+ camel_test_pull ();
+
+ g_object_unref (ctx);
+ g_object_unref (session);
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/smime/pgp.c b/src/camel/tests/smime/pgp.c
new file mode 100644
index 000000000..05f77711a
--- /dev/null
+++ b/src/camel/tests/smime/pgp.c
@@ -0,0 +1,231 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#include "camel-test.h"
+#include "session.h"
+
+#define CAMEL_TYPE_PGP_SESSION (camel_pgp_session_get_type ())
+#define CAMEL_PGP_SESSION (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CAMEL_TYPE_PGP_SESSION, CamelPgpSession))
+#define CAMEL_PGP_SESSION_CLASS (k) (G_TYPE_CHECK_CLASS_CAST ((k), CAMEL_TYPE_PGP_SESSION, CamelPgpSessionClass))
+#define CAMEL_PGP_IS_SESSION (o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CAMEL_TYPE_PGP_SESSION))
+
+typedef struct _CamelPgpSession {
+ CamelSession parent_object;
+
+} CamelPgpSession;
+
+typedef struct _CamelPgpSessionClass {
+ CamelSessionClass parent_class;
+
+} CamelPgpSessionClass;
+
+GType camel_pgp_session_get_type (void);
+
+G_DEFINE_TYPE (CamelPgpSession, camel_pgp_session, camel_test_session_get_type ())
+
+static gchar *
+pgp_session_get_password (CamelSession *session,
+ CamelService *service,
+ const gchar *domain,
+ const gchar *prompt,
+ const gchar *item,
+ guint32 flags,
+ GError **error)
+{
+ return g_strdup ("no.secret");
+}
+
+static void
+camel_pgp_session_class_init (CamelPgpSessionClass *class)
+{
+ CamelSessionClass *session_class;
+
+ session_class = CAMEL_SESSION_CLASS (class);
+ session_class->get_password = pgp_session_get_password;
+}
+
+static void
+camel_pgp_session_init (CamelPgpSession *session)
+{
+}
+
+static CamelSession *
+camel_pgp_session_new (const gchar *path)
+{
+ return g_object_new (
+ CAMEL_TYPE_PGP_SESSION,
+ "user-data-dir", path, NULL);
+}
+
+gint main (gint argc, gchar **argv)
+{
+ CamelSession *session;
+ CamelCipherContext *ctx;
+ CamelCipherValidity *valid;
+ CamelStream *stream1, *stream2;
+ GByteArray *buffer1, *buffer2;
+ struct _CamelMimePart *sigpart, *conpart, *encpart, *outpart;
+ CamelDataWrapper *dw;
+ GPtrArray *recipients;
+ gchar *before, *after;
+ gint ret;
+ GError *error = NULL;
+
+ if (getenv ("CAMEL_TEST_GPG") == NULL)
+ return 77;
+
+ camel_test_init (argc, argv);
+
+ /* clear out any camel-test data */
+ system ("/bin/rm -rf /tmp/camel-test");
+ system ("/bin/mkdir /tmp/camel-test");
+ setenv ("GNUPGHOME", "/tmp/camel-test/.gnupg", 1);
+
+ /* import the gpg keys */
+ if ((ret = system ("gpg < /dev/null > /dev/null 2>&1")) == -1)
+ return 77;
+ else if (WEXITSTATUS (ret) == 127)
+ return 77;
+
+ g_message ("gpg --import " TEST_DATA_DIR "/camel-test.gpg.pub > /dev/null 2>&1");
+ system ("gpg --import " TEST_DATA_DIR "/camel-test.gpg.pub > /dev/null 2>&1");
+ g_message ("gpg --import " TEST_DATA_DIR "/camel-test.gpg.sec > /dev/null 2>&1");
+ system ("gpg --import " TEST_DATA_DIR "/camel-test.gpg.sec > /dev/null 2>&1");
+
+ session = camel_pgp_session_new ("/tmp/camel-test");
+
+ ctx = camel_gpg_context_new (session);
+ camel_gpg_context_set_always_trust (CAMEL_GPG_CONTEXT (ctx), TRUE);
+
+ camel_test_start ("Test of PGP functions");
+
+ stream1 = camel_stream_mem_new ();
+ camel_stream_write (
+ stream1, "Hello, I am a test stream.\n", 27, NULL, NULL);
+ g_seekable_seek (G_SEEKABLE (stream1), 0, G_SEEK_SET, NULL, NULL);
+
+ conpart = camel_mime_part_new ();
+ dw = camel_data_wrapper_new ();
+ camel_data_wrapper_construct_from_stream_sync (
+ dw, stream1, NULL, NULL);
+ camel_medium_set_content ((CamelMedium *) conpart, dw);
+ g_object_unref (stream1);
+ g_object_unref (dw);
+
+ sigpart = camel_mime_part_new ();
+
+ camel_test_push ("PGP signing");
+ camel_cipher_context_sign_sync (
+ ctx, "no.user@no.domain", CAMEL_CIPHER_HASH_SHA1,
+ conpart, sigpart, NULL, &error);
+ if (error != NULL) {
+ printf ("PGP signing failed assuming non-functional environment\n%s", error->message);
+ camel_test_pull ();
+ return 77;
+ }
+ camel_test_pull ();
+
+ g_clear_error (&error);
+
+ camel_test_push ("PGP verify");
+ valid = camel_cipher_context_verify_sync (ctx, sigpart, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ check_msg (camel_cipher_validity_get_valid (valid), "%s", camel_cipher_validity_get_description (valid));
+ camel_cipher_validity_free (valid);
+ camel_test_pull ();
+
+ g_object_unref (conpart);
+ g_object_unref (sigpart);
+
+ stream1 = camel_stream_mem_new ();
+ camel_stream_write (
+ stream1, "Hello, I am a test of encryption/decryption.",
+ 44, NULL, NULL);
+ g_seekable_seek (G_SEEKABLE (stream1), 0, G_SEEK_SET, NULL, NULL);
+
+ conpart = camel_mime_part_new ();
+ dw = camel_data_wrapper_new ();
+ g_seekable_seek (G_SEEKABLE (stream1), 0, G_SEEK_SET, NULL, NULL);
+ camel_data_wrapper_construct_from_stream_sync (
+ dw, stream1, NULL, NULL);
+ camel_medium_set_content ((CamelMedium *) conpart, dw);
+ g_object_unref (stream1);
+ g_object_unref (dw);
+
+ encpart = camel_mime_part_new ();
+
+ g_clear_error (&error);
+
+ camel_test_push ("PGP encrypt");
+ recipients = g_ptr_array_new ();
+ g_ptr_array_add (recipients, (guint8 *) "no.user@no.domain");
+ camel_cipher_context_encrypt_sync (
+ ctx, "no.user@no.domain", recipients,
+ conpart, encpart, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ g_ptr_array_free (recipients, TRUE);
+ camel_test_pull ();
+
+ g_clear_error (&error);
+
+ camel_test_push ("PGP decrypt");
+ outpart = camel_mime_part_new ();
+ valid = camel_cipher_context_decrypt_sync (
+ ctx, encpart, outpart, NULL, &error);
+ check_msg (error == NULL, "%s", error->message);
+ check_msg (valid->encrypt.status == CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED, "%s", valid->encrypt.description);
+
+ buffer1 = g_byte_array_new ();
+ stream1 = camel_stream_mem_new_with_byte_array (buffer1);
+ buffer2 = g_byte_array_new ();
+ stream2 = camel_stream_mem_new_with_byte_array (buffer2);
+
+ camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (conpart), stream1, NULL, NULL);
+ camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (outpart), stream2, NULL, NULL);
+
+ before = g_strndup ((gchar *) buffer1->data, buffer1->len);
+ after = g_strndup ((gchar *) buffer2->data, buffer2->len);
+ check_msg (string_equal (before, after), "before = '%s', after = '%s'", before, after);
+ g_free (before);
+ g_free (after);
+
+ g_object_unref (stream1);
+ g_object_unref (stream2);
+ g_object_unref (conpart);
+ g_object_unref (encpart);
+ g_object_unref (outpart);
+
+ camel_test_pull ();
+
+ g_object_unref (ctx);
+ g_object_unref (session);
+
+ camel_test_end ();
+
+ return 0;
+}
diff --git a/src/camel/tests/smime/pkcs7.c b/src/camel/tests/smime/pkcs7.c
new file mode 100644
index 000000000..3f1d845a3
--- /dev/null
+++ b/src/camel/tests/smime/pkcs7.c
@@ -0,0 +1,190 @@
+/*
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "camel-test.h"
+
+#define CAMEL_TEST_SESSION_TYPE (camel_test_session_get_type ())
+#define CAMEL_TEST_SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), CAMEL_TEST_SESSION_TYPE, CamelTestSession))
+#define CAMEL_TEST_SESSION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), CAMEL_TEST_SESSION_TYPE, CamelTestSessionClass))
+#define CAMEL_TEST_IS_SESSION(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), CAMEL_TEST_SESSION_TYPE))
+
+typedef struct _CamelTestSession {
+ CamelSession parent_object;
+
+} CamelTestSession;
+
+typedef struct _CamelTestSessionClass {
+ CamelSessionClass parent_class;
+
+} CamelTestSessionClass;
+
+static gchar *get_password (CamelSession *session, const gchar *prompt,
+ guint32 flags, CamelService *service,
+ const gchar *item, GError **error);
+
+static void
+init (CamelTestSession *session)
+{
+ ;
+}
+
+static void
+class_init (CamelTestSessionClass *camel_test_session_class)
+{
+ CamelSessionClass *camel_session_class =
+ CAMEL_SESSION_CLASS (camel_test_session_class);
+
+ camel_session_class->get_password = get_password;
+}
+
+static GType
+camel_test_session_get_type (void)
+{
+ static GType type = G_TYPE_INVALID;
+
+ if (G_UNLIKELY (type == G_TYPE_INVALID))
+ type = camel_type_register (
+ CAMEL_TEST_SESSION_TYPE,
+ "CamelTestSession",
+ sizeof (CamelTestSession),
+ sizeof (CamelTestSessionClass),
+ (GClassInitFunc) class_init,
+ NULL,
+ (GInstanceInitFunc) init,
+ NULL);
+
+ return type;
+}
+
+static gchar *
+get_password (CamelSession *session,
+ const gchar *prompt,
+ guint32 flags,
+ CamelService *service,
+ const gchar *item,
+ GError **error)
+{
+ return g_strdup ("S/MIME v3 is rfc263x, now go and read them.");
+}
+
+static CamelSession *
+camel_test_session_new (const gchar *path)
+{
+ CamelSession *session;
+
+ session = g_object_new (CAMEL_TYPE_TEST_SESSION, NULL);
+ camel_session_construct (session, path);
+
+ return session;
+}
+
+gint main (gint argc, gchar **argv)
+{
+ CamelSession *session;
+ CamelSMimeContext *ctx;
+ GError **error;
+ CamelCipherValidity *valid;
+ CamelStream *stream1, *stream2, *stream3;
+ GPtrArray *recipients;
+ GByteArray *buf;
+ gchar *before, *after;
+
+ 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");
+
+ ctx = camel_smime_context_new (session);
+
+ camel_test_start ("Test of S/MIME PKCS7 functions");
+
+ stream1 = camel_stream_mem_new ();
+ camel_stream_write (stream1, "Hello, I am a test stream.", 25);
+ g_seekable_seek (G_SEEKABLE (stream1), 0, G_SEEK_SET, NULL, NULL);
+
+ stream2 = camel_stream_mem_new ();
+
+ camel_test_push ("PKCS7 signing");
+ camel_smime_sign (
+ ctx, "smime@xtorshun.org", CAMEL_CIPHER_HASH_SHA1,
+ stream1, stream2, ex);
+ check_msg (!camel_exception_is_set (ex), "%s", camel_exception_get_description (ex));
+ camel_test_pull ();
+
+ camel_exception_clear (ex);
+
+ camel_test_push ("PKCS7 verify");
+ g_seekable_seek (G_SEEKABLE (stream1), 0, G_SEEK_SET, NULL, NULL);
+ g_seekable_seek (G_SEEKABLE (stream2), 0, G_SEEK_SET, NULL, NULL);
+ valid = camel_smime_verify (ctx, CAMEL_CIPHER_HASH_SHA1, stream1, stream2, ex);
+ check_msg (!camel_exception_is_set (ex), "%s", camel_exception_get_description (ex));
+ check_msg (camel_cipher_validity_get_valid (valid), "%s", camel_cipher_validity_get_description (valid));
+ camel_cipher_validity_free (valid);
+ camel_test_pull ();
+
+ g_object_unref (stream1);
+ g_object_unref (stream2);
+
+ stream1 = camel_stream_mem_new ();
+ stream2 = camel_stream_mem_new ();
+ stream3 = camel_stream_mem_new ();
+
+ camel_stream_write (stream1, "Hello, I am a test of encryption/decryption.", 44);
+ g_seekable_seek (G_SEEKABLE (stream1), 0, G_SEEK_SET, NULL, NULL);
+
+ camel_exception_clear (ex);
+
+ camel_test_push ("PKCS7 encrypt");
+ recipients = g_ptr_array_new ();
+ g_ptr_array_add (recipients, "smime@xtorshun.org");
+ camel_smime_encrypt (
+ ctx, FALSE, "smime@xtorshun.org", recipients,
+ stream1, stream2, ex);
+ check_msg (!camel_exception_is_set (ex), "%s", camel_exception_get_description (ex));
+ g_ptr_array_free (recipients, TRUE);
+ camel_test_pull ();
+
+ g_seekable_seek (G_SEEKABLE (stream2), 0, G_SEEK_SET, NULL, NULL);
+ camel_exception_clear (ex);
+
+ camel_test_push ("PKCS7 decrypt");
+ camel_smime_decrypt (ctx, stream2, stream3, ex);
+ check_msg (!camel_exception_is_set (ex), "%s", camel_exception_get_description (ex));
+ buf = CAMEL_STREAM_MEM (stream1)->buffer;
+ before = g_strndup (buf->data, buf->len);
+ buf = CAMEL_STREAM_MEM (stream3)->buffer;
+ after = g_strndup (buf->data, buf->len);
+ check_msg (string_equal (before, after), "before = '%s', after = '%s'", before, after);
+ g_free (before);
+ g_free (after);
+ camel_test_pull ();
+
+ g_object_unref (ctx);
+ g_object_unref (session);
+
+ camel_test_end ();
+
+ return 0;
+}