Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / providers / nntp / camel-nntp-folder.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-nntp-folder.c : Class for a news folder
3  *
4  * Authors : Chris Toshok <toshok@ximian.com> 
5  *           Michael Zucchi <notzed@ximian.com>
6  *
7  * Copyright (C) 2001-2003 Ximian, Inc. (www.ximian.com)
8  *
9  * This program is free software; you can redistribute it and/or 
10  * modify it under the terms of version 2 of the GNU Lesser General Public 
11  * License as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
21  * USA
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <errno.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <sys/types.h>
33
34 #include <glib/gi18n-lib.h>
35
36 #include <libedataserver/e-data-server-util.h>
37
38 #include "camel/camel-data-cache.h"
39 #include "camel/camel-data-wrapper.h"
40 #include "camel/camel-exception.h"
41 #include "camel/camel-file-utils.h"
42 #include "camel/camel-folder-search.h"
43 #include "camel/camel-mime-filter-crlf.h"
44 #include "camel/camel-mime-message.h"
45 #include "camel/camel-mime-message.h"
46 #include "camel/camel-mime-part.h"
47 #include "camel/camel-multipart.h"
48 #include "camel/camel-private.h"
49 #include "camel/camel-session.h"
50 #include "camel/camel-stream-buffer.h"
51 #include "camel/camel-stream-filter.h"
52 #include "camel/camel-stream-mem.h"
53
54 #include "camel-nntp-folder.h"
55 #include "camel-nntp-private.h"
56 #include "camel-nntp-store.h"
57 #include "camel-nntp-store.h"
58 #include "camel-nntp-summary.h"
59
60 static CamelFolderClass *folder_class = NULL;
61 static CamelDiscoFolderClass *parent_class = NULL;
62
63 /* Returns the class for a CamelNNTPFolder */
64 #define CNNTPF_CLASS(so) CAMEL_NNTP_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
65 #define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
66 #define CNNTPS_CLASS(so) CAMEL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
67
68 void
69 camel_nntp_folder_selected(CamelNNTPFolder *folder, char *line, CamelException *ex)
70 {
71         camel_nntp_summary_check((CamelNNTPSummary *)((CamelFolder *)folder)->summary,
72                                  (CamelNNTPStore *)((CamelFolder *)folder)->parent_store,
73                                  line, folder->changes, ex);
74 }
75
76 static void
77 nntp_folder_refresh_info_online (CamelFolder *folder, CamelException *ex)
78 {
79         CamelNNTPStore *nntp_store;
80         CamelFolderChangeInfo *changes = NULL;
81         CamelNNTPFolder *nntp_folder;
82         char *line;
83
84         nntp_store = (CamelNNTPStore *) folder->parent_store;
85         nntp_folder = (CamelNNTPFolder *) folder;
86         
87         CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
88
89         camel_nntp_command(nntp_store, ex, nntp_folder, &line, NULL);
90
91         if (camel_folder_change_info_changed(nntp_folder->changes)) {
92                 changes = nntp_folder->changes;
93                 nntp_folder->changes = camel_folder_change_info_new();
94         }
95         
96         CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
97         
98         if (changes) {
99                 camel_object_trigger_event ((CamelObject *) folder, "folder_changed", changes);
100                 camel_folder_change_info_free (changes);
101         }
102 }
103
104 static void
105 nntp_folder_sync_online (CamelFolder *folder, CamelException *ex)
106 {
107         CAMEL_SERVICE_REC_LOCK(folder->parent_store, connect_lock);
108         camel_folder_summary_save (folder->summary);
109         CAMEL_SERVICE_REC_UNLOCK(folder->parent_store, connect_lock);
110 }
111
112 static void
113 nntp_folder_sync_offline (CamelFolder *folder, CamelException *ex)
114 {
115         CAMEL_SERVICE_REC_LOCK(folder->parent_store, connect_lock);
116         camel_folder_summary_save (folder->summary);
117         CAMEL_SERVICE_REC_UNLOCK(folder->parent_store, connect_lock);
118 }
119
120 static gboolean
121 nntp_folder_set_message_flags (CamelFolder *folder, const char *uid, guint32 flags, guint32 set)
122 {
123         return ((CamelFolderClass *) folder_class)->set_message_flags (folder, uid, flags, set);
124 }
125
126 static CamelStream *
127 nntp_folder_download_message (CamelNNTPFolder *nntp_folder, const char *id, const char *msgid, CamelException *ex)
128 {
129         CamelNNTPStore *nntp_store = (CamelNNTPStore *) ((CamelFolder *) nntp_folder)->parent_store;
130         CamelStream *stream = NULL;
131         int ret;
132         char *line;
133
134         ret = camel_nntp_command (nntp_store, ex, nntp_folder, &line, "article %s", id);
135         if (ret == 220) {
136                 stream = camel_data_cache_add (nntp_store->cache, "cache", msgid, NULL);
137                 if (stream) {
138                         if (camel_stream_write_to_stream ((CamelStream *) nntp_store->stream, stream) == -1)
139                                 goto fail;
140                         if (camel_stream_reset (stream) == -1)
141                                 goto fail;
142                 } else {
143                         stream = (CamelStream *) nntp_store->stream;
144                         camel_object_ref (stream);
145                 }
146         } else if (ret == 423 || ret == 430) {
147                 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID, _("Cannot get message %s: %s"), msgid, line);
148         } else if (ret != -1) {
149                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), msgid, line);
150         }
151         
152         return stream;
153         
154  fail:
155         if (errno == EINTR)
156                 camel_exception_setv (ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
157         else
158                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), msgid, g_strerror (errno));
159         
160         return NULL;
161 }
162
163
164 static void
165 nntp_folder_cache_message (CamelDiscoFolder *disco_folder, const char *uid, CamelException *ex)
166 {
167         CamelNNTPStore *nntp_store = (CamelNNTPStore *)((CamelFolder *) disco_folder)->parent_store;
168         CamelStream *stream;
169         char *article, *msgid;
170
171         article = alloca(strlen(uid)+1);
172         strcpy(article, uid);
173         msgid = strchr(article, ',');
174         if (!msgid) {
175                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
176                                       _("Internal error: UID in invalid format: %s"), uid);
177                 return;
178         }
179         *msgid++ = 0;
180         
181         CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
182         
183         stream = nntp_folder_download_message ((CamelNNTPFolder *) disco_folder, article, msgid, ex);
184         if (stream)
185                 camel_object_unref (stream);
186
187         CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
188 }
189
190 static CamelMimeMessage *
191 nntp_folder_get_message (CamelFolder *folder, const char *uid, CamelException *ex)
192 {
193         CamelMimeMessage *message = NULL;
194         CamelNNTPStore *nntp_store;
195         CamelFolderChangeInfo *changes;
196         CamelNNTPFolder *nntp_folder;
197         CamelStream *stream = NULL;
198         char *article, *msgid;
199
200         nntp_store = (CamelNNTPStore *) folder->parent_store;
201         nntp_folder = (CamelNNTPFolder *) folder;
202         
203         article = alloca(strlen(uid)+1);
204         strcpy(article, uid);
205         msgid = strchr (article, ',');
206         if (msgid == NULL) {
207                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
208                                       _("Internal error: UID in invalid format: %s"), uid);
209                 return NULL;
210         }
211         *msgid++ = 0;
212
213         CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
214         
215         /* Lookup in cache, NEWS is global messageid's so use a global cache path */
216         stream = camel_data_cache_get (nntp_store->cache, "cache", msgid, NULL);
217         if (stream == NULL) {
218                 if (camel_disco_store_status ((CamelDiscoStore *) nntp_store) == CAMEL_DISCO_STORE_OFFLINE) {
219                         camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
220                                              _("This message is not currently available"));
221                         goto fail;
222                 }
223
224                 stream = nntp_folder_download_message (nntp_folder, article, msgid, ex);
225                 if (stream == NULL)
226                         goto fail;
227         }
228         
229         message = camel_mime_message_new ();
230         if (camel_data_wrapper_construct_from_stream ((CamelDataWrapper *) message, stream) == -1) {
231                 if (errno == EINTR)
232                         camel_exception_setv (ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
233                 else
234                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), uid, g_strerror (errno));
235                 camel_object_unref(message);
236                 message = NULL;
237         }
238
239         camel_object_unref (stream);
240 fail:
241         if (camel_folder_change_info_changed (nntp_folder->changes)) {
242                 changes = nntp_folder->changes;
243                 nntp_folder->changes = camel_folder_change_info_new ();
244         } else {
245                 changes = NULL;
246         }
247         
248         CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
249         
250         if (changes) {
251                 camel_object_trigger_event ((CamelObject *) folder, "folder_changed", changes);
252                 camel_folder_change_info_free (changes);
253         }
254                 
255         return message;
256 }
257
258 static GPtrArray*
259 nntp_folder_search_by_expression (CamelFolder *folder, const char *expression, CamelException *ex)
260 {
261         CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
262         GPtrArray *matches;
263         
264         CAMEL_NNTP_FOLDER_LOCK(nntp_folder, search_lock);
265         
266         if (nntp_folder->search == NULL)
267                 nntp_folder->search = camel_folder_search_new ();
268         
269         camel_folder_search_set_folder (nntp_folder->search, folder);
270         matches = camel_folder_search_search(nntp_folder->search, expression, NULL, ex);
271         
272         CAMEL_NNTP_FOLDER_UNLOCK(nntp_folder, search_lock);
273         
274         return matches;
275 }
276
277 static GPtrArray *
278 nntp_folder_search_by_uids (CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex)
279 {
280         CamelNNTPFolder *nntp_folder = (CamelNNTPFolder *) folder;
281         GPtrArray *matches;
282
283         if (uids->len == 0)
284                 return g_ptr_array_new();
285         
286         CAMEL_NNTP_FOLDER_LOCK(folder, search_lock);
287         
288         if (nntp_folder->search == NULL)
289                 nntp_folder->search = camel_folder_search_new ();
290         
291         camel_folder_search_set_folder (nntp_folder->search, folder);
292         matches = camel_folder_search_search(nntp_folder->search, expression, uids, ex);
293         
294         CAMEL_NNTP_FOLDER_UNLOCK(folder, search_lock);
295         
296         return matches;
297 }
298
299 static void
300 nntp_folder_search_free (CamelFolder *folder, GPtrArray *result)
301 {
302         CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
303         
304         camel_folder_search_free_result (nntp_folder->search, result);
305 }
306
307 static void           
308 nntp_folder_append_message_online (CamelFolder *folder, CamelMimeMessage *mime_message,
309                                    const CamelMessageInfo *info, char **appended_uid,
310                                    CamelException *ex)
311 {
312         CamelNNTPStore *nntp_store = (CamelNNTPStore *) folder->parent_store;
313         CamelStream *stream = (CamelStream*)nntp_store->stream;
314         CamelStreamFilter *filtered_stream;
315         CamelMimeFilter *crlffilter;
316         int ret;
317         unsigned int u;
318         struct _camel_header_raw *header, *savedhdrs, *n, *tail;
319         char *group, *line;
320         
321         CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
322         
323         /* send 'POST' command */
324         ret = camel_nntp_command (nntp_store, ex, NULL, &line, "post");
325         if (ret != 340) {
326                 if (ret == 440)
327                         camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION,
328                                               _("Posting failed: %s"), line);
329                 else if (ret != -1)
330                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
331                                               _("Posting failed: %s"), line);
332                 CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
333                 return;
334         }
335         
336         /* the 'Newsgroups: ' header */
337         group = g_strdup_printf ("Newsgroups: %s\r\n", folder->full_name);
338         
339         /* setup stream filtering */
340         crlffilter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
341         filtered_stream = camel_stream_filter_new_with_stream (stream);
342         camel_stream_filter_add (filtered_stream, crlffilter);
343         camel_object_unref (crlffilter);
344         
345         /* remove mail 'To', 'CC', and 'BCC' headers */
346         savedhdrs = NULL;
347         tail = (struct _camel_header_raw *) &savedhdrs;
348         
349         header = (struct _camel_header_raw *) &CAMEL_MIME_PART (mime_message)->headers;
350         n = header->next;
351         while (n != NULL) {
352                 if (!g_ascii_strcasecmp (n->name, "To") || !g_ascii_strcasecmp (n->name, "Cc") || !g_ascii_strcasecmp (n->name, "Bcc")) {
353                         header->next = n->next;
354                         tail->next = n;
355                         n->next = NULL;
356                         tail = n;
357                 } else {
358                         header = n;
359                 }
360                 
361                 n = header->next;
362         }
363         
364         /* write the message */
365         if (camel_stream_write(stream, group, strlen(group)) == -1
366             || camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (mime_message), CAMEL_STREAM (filtered_stream)) == -1
367             || camel_stream_flush (CAMEL_STREAM (filtered_stream)) == -1
368             || camel_stream_write (stream, "\r\n.\r\n", 5) == -1
369             || (ret = camel_nntp_stream_line (nntp_store->stream, (unsigned char **)&line, &u)) == -1) {
370                 if (errno == EINTR)
371                         camel_exception_setv (ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
372                 else
373                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Posting failed: %s"), g_strerror (errno));
374         } else if (atoi(line) != 240) {
375                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Posting failed: %s"), line);
376         }
377
378         camel_object_unref (filtered_stream);
379         g_free(group);
380         header->next = savedhdrs;
381
382         CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
383         
384         return;
385 }
386
387 static void
388 nntp_folder_append_message_offline (CamelFolder *folder, CamelMimeMessage *mime_message,
389                                     const CamelMessageInfo *info, char **appended_uid,
390                                     CamelException *ex)
391 {
392         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
393                               _("You cannot post NNTP messages while working offline!"));       
394 }
395
396 /* I do not know what to do this exactly. Looking at the IMAP implementation for this, it
397    seems to assume the message is copied to a folder on the same store. In that case, an
398    NNTP implementation doesn't seem to make any sense. */
399 static void
400 nntp_folder_transfer_message (CamelFolder *source, GPtrArray *uids, CamelFolder *dest,
401                               GPtrArray **transferred_uids, gboolean delete_orig, CamelException *ex)
402 {
403         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
404                               _("You cannot copy messages from a NNTP folder!"));
405 }
406
407 static void           
408 nntp_folder_init (CamelNNTPFolder *nntp_folder, CamelNNTPFolderClass *klass)
409 {
410         struct _CamelNNTPFolderPrivate *p;
411         
412         nntp_folder->changes = camel_folder_change_info_new ();
413         p = nntp_folder->priv = g_malloc0 (sizeof (*nntp_folder->priv));
414         p->search_lock = g_mutex_new ();
415         p->cache_lock = g_mutex_new ();
416 }
417
418 static void           
419 nntp_folder_finalise (CamelNNTPFolder *nntp_folder)
420 {
421         struct _CamelNNTPFolderPrivate *p;
422         
423         camel_folder_summary_save (((CamelFolder*) nntp_folder)->summary);
424         
425         p = nntp_folder->priv;
426         g_mutex_free (p->search_lock);
427         g_mutex_free (p->cache_lock);
428         g_free (p);
429 }
430
431 static void
432 nntp_folder_class_init (CamelNNTPFolderClass *camel_nntp_folder_class)
433 {
434         CamelDiscoFolderClass *camel_disco_folder_class = CAMEL_DISCO_FOLDER_CLASS (camel_nntp_folder_class);
435         CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS (camel_nntp_folder_class);
436         
437         parent_class = CAMEL_DISCO_FOLDER_CLASS (camel_type_get_global_classfuncs (camel_disco_folder_get_type ()));
438         folder_class = CAMEL_FOLDER_CLASS (camel_type_get_global_classfuncs (camel_folder_get_type ()));
439         
440         /* virtual method definition */
441         
442         /* virtual method overload */
443         camel_disco_folder_class->sync_online = nntp_folder_sync_online;
444         camel_disco_folder_class->sync_resyncing = nntp_folder_sync_offline;
445         camel_disco_folder_class->sync_offline = nntp_folder_sync_offline;        
446         camel_disco_folder_class->cache_message = nntp_folder_cache_message;
447         camel_disco_folder_class->append_online = nntp_folder_append_message_online;
448         camel_disco_folder_class->append_resyncing = nntp_folder_append_message_online;
449         camel_disco_folder_class->append_offline = nntp_folder_append_message_offline;
450         camel_disco_folder_class->transfer_online = nntp_folder_transfer_message;
451         camel_disco_folder_class->transfer_resyncing = nntp_folder_transfer_message;
452         camel_disco_folder_class->transfer_offline = nntp_folder_transfer_message;
453         camel_disco_folder_class->refresh_info_online = nntp_folder_refresh_info_online;
454         
455         camel_folder_class->set_message_flags = nntp_folder_set_message_flags;
456         camel_folder_class->get_message = nntp_folder_get_message;
457         camel_folder_class->search_by_expression = nntp_folder_search_by_expression;
458         camel_folder_class->search_by_uids = nntp_folder_search_by_uids;
459         camel_folder_class->search_free = nntp_folder_search_free;
460 }
461
462 CamelType
463 camel_nntp_folder_get_type (void)
464 {
465         static CamelType camel_nntp_folder_type = CAMEL_INVALID_TYPE;
466         
467         if (camel_nntp_folder_type == CAMEL_INVALID_TYPE)       {
468                 camel_nntp_folder_type = camel_type_register (CAMEL_DISCO_FOLDER_TYPE, "CamelNNTPFolder",
469                                                               sizeof (CamelNNTPFolder),
470                                                               sizeof (CamelNNTPFolderClass),
471                                                               (CamelObjectClassInitFunc) nntp_folder_class_init,
472                                                               NULL,
473                                                               (CamelObjectInitFunc) nntp_folder_init,
474                                                               (CamelObjectFinalizeFunc) nntp_folder_finalise);
475         }
476         
477         return camel_nntp_folder_type;
478 }
479
480 CamelFolder *
481 camel_nntp_folder_new (CamelStore *parent, const char *folder_name, CamelException *ex)
482 {
483         CamelFolder *folder;
484         CamelNNTPFolder *nntp_folder;
485         char *root;
486         CamelService *service;
487         CamelStoreInfo *si;
488         gboolean subscribed = TRUE;
489         
490         service = (CamelService *) parent;
491         root = camel_session_get_storage_path (service->session, service, ex);
492         if (root == NULL)
493                 return NULL;
494         
495         /* If this doesn't work, stuff wont save, but let it continue anyway */
496         g_mkdir_with_parents (root, 0777);
497         
498         folder = (CamelFolder *) camel_object_new (CAMEL_NNTP_FOLDER_TYPE);
499         nntp_folder = (CamelNNTPFolder *)folder;
500         
501         camel_folder_construct (folder, parent, folder_name, folder_name);
502         folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY|CAMEL_FOLDER_HAS_SEARCH_CAPABILITY;
503         
504         nntp_folder->storage_path = g_build_filename (root, folder->full_name, NULL);
505         g_free (root);
506         
507         root = g_strdup_printf ("%s.cmeta", nntp_folder->storage_path);
508         camel_object_set(nntp_folder, NULL, CAMEL_OBJECT_STATE_FILE, root, NULL);
509         camel_object_state_read(nntp_folder);
510         g_free(root);
511
512         root = g_strdup_printf("%s.ev-summary", nntp_folder->storage_path);
513         folder->summary = (CamelFolderSummary *) camel_nntp_summary_new (folder, root);
514         g_free(root);
515         camel_folder_summary_load (folder->summary);
516         
517         si = camel_store_summary_path ((CamelStoreSummary *) ((CamelNNTPStore*) parent)->summary, folder_name);
518         if (si) {
519                 subscribed = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
520                 camel_store_summary_info_free ((CamelStoreSummary *) ((CamelNNTPStore*) parent)->summary, si);
521         }
522         
523         if (subscribed) {
524                 camel_folder_refresh_info(folder, ex);
525                 if (camel_exception_is_set(ex)) {
526                         camel_object_unref (folder);
527                         folder = NULL;
528                 }
529         }
530         
531         return folder;
532 }