Merge branch 'master' of ssh+git://git.claws-mail.org/home/git/claws
[claws.git] / src / plugins / archive / libarchive_archive.c
1 /* vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: */
2
3 /*
4  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
5  * Copyright (C) 1999-2008 Michael Rasmussen and the Claws Mail Team
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
19  * 
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #include "claws-features.h"
25 #endif
26
27 #include <glib.h>
28 #include <glib/gi18n.h>
29
30 #include "libarchive_archive.h"
31
32 #ifndef _TEST
33 #       include "archiver.h"
34 #       include "utils.h"
35 #       include "mainwindow.h"
36 #       include "folder.h"
37 #endif
38
39 #include <sys/types.h>
40 #include <sys/stat.h>
41
42 #include <archive.h>
43 #include <archive_entry.h>
44 #include <fcntl.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 #include <dirent.h>
50 #include <glib.h>
51 #include <libgen.h>
52
53 #define READ_BLOCK_SIZE 10240
54
55 struct file_info {
56         char* path;
57         char* name;
58 };
59
60 static GSList* msg_trash_list = NULL;
61 static GSList* file_list = NULL;
62 static gboolean stop_action = FALSE;
63
64 #ifdef _TEST
65 static int permissions = 0;
66 #endif
67
68 static void free_msg_trash(MsgTrash* trash) {
69     if (trash) {
70         debug_print("Freeing files in %s\n", folder_item_get_name(trash->item));
71         if (trash->msgs) {
72             g_slist_free(trash->msgs);
73         }
74         g_free(trash);
75     }
76 }
77
78 MsgTrash* new_msg_trash(FolderItem* item) {
79     MsgTrash* msg_trash;
80     FolderType  type;
81
82     g_return_val_if_fail(item != NULL, NULL);
83
84     /* FolderType must be F_MH, F_MBOX, F_MAILDIR or F_IMAP */
85     type = item->folder->klass->type;
86     if (!(type == F_MH || type == F_MBOX || 
87             type == F_MAILDIR || type == F_IMAP))
88        return NULL; 
89     msg_trash = g_new0(MsgTrash, 1);
90     msg_trash->item = item;
91     msg_trash->msgs = NULL;
92     msg_trash_list = g_slist_prepend(msg_trash_list, msg_trash);
93     
94     return msg_trash;
95     }
96
97 void archive_free_archived_files() {
98     MsgTrash* mt = NULL;
99     gint    res;
100     GSList* l = NULL;
101    
102     for (l = msg_trash_list; l; l = g_slist_next(l)) {
103         mt = (MsgTrash *) l->data;
104         debug_print("Trashing messages in folder: %s\n", 
105                 folder_item_get_name(mt->item));
106         res = folder_item_remove_msgs(mt->item, mt->msgs);
107         debug_print("Result was %d\n", res);
108         free_msg_trash(mt);
109     }
110     g_slist_free(msg_trash_list);
111     msg_trash_list = NULL;
112 }
113
114 void archive_add_msg_mark(MsgTrash* trash, MsgInfo* msg) {
115     g_return_if_fail(trash != NULL || msg != NULL);
116     debug_print("Marking msg #%d for removal\n", msg->msgnum);
117     trash->msgs = g_slist_prepend(trash->msgs, msg);
118 }
119
120 static void free_all(GDate* date, gchar** parts) {
121     if (date)
122         g_date_free(date);
123     if (parts)
124         g_strfreev(parts);
125 }
126
127 static gboolean is_iso_string(gchar** items) {
128     int i = -1;
129     gchar* item;
130
131     while (*items) {
132         i++;
133         item = *items++;
134         debug_print("Date part %d: %s\n", i, item);
135         switch(i) {
136             case 0:
137                 if (strlen(item) != 4)
138                     return FALSE;
139                 break;
140             case 1:
141             case 2:
142                 if (strlen(item) != 2)
143                     return FALSE;
144                 break;
145             default:
146                 return FALSE;
147         }
148     }
149     debug_print("Leaving\n");
150     return (i == 2);
151 }
152
153 static GDate* iso2GDate(const gchar* date) {
154     GDate*  gdate;
155     gchar** parts = NULL;
156     int     i;
157
158     g_return_val_if_fail(date != NULL, NULL);
159
160     gdate = g_date_new();
161     parts = g_strsplit(date, "-", 3);
162     if (! is_iso_string(parts))
163         return NULL;
164     if (!parts)
165         return NULL;
166     for (i = 0; i < 3; i++) {
167         int t = atoi(parts[i]);
168         switch (i) {
169             case 0: 
170                 if (t < 1 || t > 9999) {
171                     free_all(gdate, parts);
172                     return NULL;
173                 }
174                 g_date_set_year(gdate, t);
175                 break;
176             case 1:
177                 if (t < 1 || t > 12) {
178                     free_all(gdate, parts);
179                     return NULL;
180                 }
181                 g_date_set_month(gdate, t);
182                 break;
183             case 2:
184                 if (t < 1 || t > 31) {
185                     free_all(gdate, parts);
186                     return NULL;
187                 }
188                 g_date_set_day(gdate, t);
189                 break;
190         }
191     }
192     g_strfreev(parts);
193     return gdate;
194 }
195
196 gboolean before_date(time_t msg_mtime, const gchar* before) {
197     gchar*      pos = NULL;
198     GDate*      date;
199     GDate*      file_t;
200     gboolean    res;
201
202     debug_print("Cut-off date: %s\n", before);
203     if ((date = iso2GDate(before)) == NULL) {
204         g_warning("Bad date format: %s\n", before);
205         return FALSE;
206     }
207
208     file_t = g_date_new();
209     g_date_set_time_t(file_t, msg_mtime);
210
211     if (debug_get_mode()) {
212         pos = g_new0(char, 100);
213         g_date_strftime(pos, 100, "%F", file_t);
214         fprintf(stderr, "File date: %s\n", pos);
215         g_free(pos);
216     }
217
218     if (! g_date_valid(file_t)) {
219         g_warning("Invalid msg date\n");
220         return FALSE;
221     }
222
223     res = (g_date_compare(file_t, date) >= 0) ? FALSE : TRUE;
224     g_date_free(file_t);
225     return res;
226 }
227    
228 static void archive_free_file_info(struct file_info* file) {
229         if (! file)
230                 return;
231         if (file->path)
232                 g_free(file->path);
233         if (file->name)
234                 g_free(file->name);
235         g_free(file);
236         file = NULL;
237 }
238
239 void stop_archiving() {
240         debug_print("stop action set to true\n");
241         stop_action = TRUE;
242 }
243
244 void archive_free_file_list(gboolean md5, gboolean rename) {
245         struct file_info* file = NULL;
246         gchar* path = NULL;
247
248         debug_print("freeing file list\n");
249         if (! file_list)
250                 return;
251         while (file_list) {
252                 file = (struct file_info *) file_list->data;
253                 if (!rename && md5 && g_str_has_suffix(file->name, ".md5")) {
254                         path = g_strdup_printf("%s/%s", file->path, file->name);
255                         debug_print("unlinking %s\n", path);
256                         g_unlink(path);
257                         g_free(path);
258                 }
259                 if (rename) {
260                         path = g_strdup_printf("%s/%s", file->path, file->name);
261                         debug_print("unlinking %s\n", path);
262                         g_unlink(path);
263                         g_free(path);
264                 }
265                 archive_free_file_info(file);
266                 file_list->data = NULL;
267                 file_list = g_slist_next(file_list);
268         }
269         if (file_list) {
270                 g_slist_free(file_list);
271                 file_list = NULL;
272         }
273 }
274
275 static struct file_info* archive_new_file_info() {
276         struct file_info* new_file_info = malloc(sizeof(struct file_info));
277
278         new_file_info->path = NULL;
279         new_file_info->name = NULL;
280         return new_file_info;
281 }
282
283 static void archive_add_to_list(struct file_info* file) {
284         if (! file)
285                 return;
286         file_list = g_slist_prepend(file_list, (gpointer) file);
287 }
288
289 static gchar* strip_leading_dot_slash(gchar* path) {
290         gchar* stripped = path;
291         gchar* result = NULL;
292
293         if (stripped && stripped[0] == '.') {
294                 ++stripped;
295         if (stripped && stripped[0] == '/')
296                 ++stripped;
297                 result = g_strdup(stripped);
298         }
299         else
300                 result = g_strdup(path);
301         return result;
302 }
303
304 static gchar* get_full_path(struct file_info* file) {
305         char* path = malloc(PATH_MAX);
306
307         if (file->path && *(file->path))
308                 sprintf(path, "%s/%s", file->path, file->name);
309         else
310                 sprintf(path, "%s", file->name);
311         return path;
312 }
313
314 #ifdef _TEST
315 static gchar* strip_leading_slash(gchar* path) {
316         gchar* stripped = path;
317         gchar* result = NULL;
318
319         if (stripped && stripped[0] == '/') {
320                 ++stripped;
321                 result = g_strdup(stripped);
322         }
323         else
324                 result = g_strdup(path);
325         return result;
326 }
327
328 static int archive_get_permissions() {
329         return permissions;
330 }
331
332
333 void archive_set_permissions(int perm) {
334         permissions = perm;
335 }
336
337 static int archive_copy_data(struct archive* in, struct archive* out) {
338         const void* buf;
339         size_t size;
340         off_t offset;
341         int res = ARCHIVE_OK;
342
343         while (res == ARCHIVE_OK) {
344                 res = archive_read_data_block(in, &buf, &size, &offset);
345                 if (res == ARCHIVE_OK) {
346                         res = archive_write_data_block(out, buf, size, offset);
347                 }
348         }
349         return (res == ARCHIVE_EOF) ? ARCHIVE_OK : res;
350 }
351 #endif
352
353 void archive_add_file(gchar* path) {
354         struct file_info* file = archive_new_file_info();
355         gchar* filename = NULL;
356
357         g_return_if_fail(path != NULL);
358
359 #ifndef _TEST
360         debug_print("add %s to list\n", path);
361 #endif
362         filename = g_strrstr_len(path, strlen(path), "/");
363         if (! filename)
364                 g_warning("%s\n", path);
365         g_return_if_fail(filename != NULL);
366
367         filename++;
368         file->name = g_strdup(filename);
369         file->path = strip_leading_dot_slash(dirname(path));
370         archive_add_to_list(file);
371 }
372
373 GSList* archive_get_file_list() {
374         return file_list;
375 }
376
377 #ifdef _TEST
378 const gchar* archive_extract(const char* archive_name, int flags) {
379         struct archive* in;
380         struct archive* out;
381         struct archive_entry* entry;
382         int res = ARCHIVE_OK;
383         gchar* buf = NULL;
384         const char* result == NULL;
385
386         g_return_val_if_fail(archive_name != NULL, ARCHIVE_FATAL);
387
388         fprintf(stdout, "%s: extracting\n", archive_name);
389         in = archive_read_new();
390         if ((res = archive_read_support_format_tar(in)) == ARCHIVE_OK) {
391                 if ((res = archive_read_support_compression_gzip(in)) == ARCHIVE_OK) {
392 #if ARCHIVE_VERSION_NUMBER < 3000000
393                         if ((res = archive_read_open_file(
394 #else
395                         if ((res = archive_read_open_filename(
396 #endif
397                                 in, archive_name, READ_BLOCK_SIZE)) != ARCHIVE_OK) {
398                                 buf = g_strdup_printf(
399                                                 "%s: %s\n", archive_name, archive_error_string(in));
400                                 g_warning("%s\n", buf);
401                                 g_free(buf);
402                                 result = archive_error_string(in);
403                         }
404                         else {
405                                 out = archive_write_disk_new();
406                                 if ((res = archive_write_disk_set_options(
407                                                                 out, flags)) == ARCHIVE_OK) {
408                                         res = archive_read_next_header(in, &entry);
409                                         while (res == ARCHIVE_OK) {
410                                                 fprintf(stdout, "%s\n", archive_entry_pathname(entry));
411                                                 res = archive_write_header(out, entry);
412                                                 if (res != ARCHIVE_OK) {
413                                                         buf = g_strdup_printf("%s\n", 
414                                                                                         archive_error_string(out));
415                                                         g_warning("%s\n", buf);
416                                                         g_free(buf);
417                                                         /* skip this file an continue */
418                                                         res = ARCHIVE_OK;
419                                                 }
420                                                 else {
421                                                         res = archive_copy_data(in, out);
422                                                         if (res != ARCHIVE_OK) {
423                                                                 buf = g_strdup_printf("%s\n", 
424                                                                                                 archive_error_string(in));
425                                                                 g_warning("%s\n", buf);
426                                                                 g_free(buf);
427                                                                 /* skip this file an continue */
428                                                                 res = ARCHIVE_OK;
429                                                         }
430                                                         else
431                                                                 res = archive_read_next_header(in, &entry);
432                                                 }
433                                         }
434                                         if (res == ARCHIVE_EOF)
435                                                 res = ARCHIVE_OK;
436                                         if (res != ARCHIVE_OK) {
437                                                 buf = g_strdup_printf("%s\n", archive_error_string(in));
438                                                 if (*buf == '\n') {
439                                                         g_free(buf);
440                                                         buf = g_strdup_printf("%s: Unknown error\n", archive_name);
441                                                 }
442                                                 g_warning("%s\n", buf);
443                                                 g_free(buf);
444                                                 result = archive_error_string(in);
445                                         }
446                                 }
447                                 else
448                                         result = archive_error_string(out);
449                                 archive_read_close(in);
450                         }
451 #if ARCHIVE_VERSION_NUMBER < 3000000
452                         archive_read_finish(in);
453 #else
454                         archive_read_free(in);
455 #endif
456                 }
457                 else
458                         result = archive_error_string(in);
459         }
460         else
461                 result = archive_error_string(in);
462         return result;
463 }
464 #endif
465
466 const gchar* archive_create(const char* archive_name, GSList* files,
467                         COMPRESS_METHOD method, ARCHIVE_FORMAT format) {
468         struct archive* arch;
469         struct archive_entry* entry;
470         char* buf = NULL;
471         ssize_t len;
472         int fd;
473         struct stat st;
474         struct file_info* file;
475         gchar* filename = NULL;
476         gchar* msg = NULL;
477
478 #ifndef _TEST
479         gint num = 0;
480         gint total = g_slist_length (files);
481 #endif
482
483         g_return_val_if_fail(files != NULL, "No files for archiving");
484
485         debug_print("File: %s\n", archive_name);
486         arch = archive_write_new();
487         switch (method) {
488                 case ZIP:
489 #if ARCHIVE_VERSION_NUMBER < 3000000
490                         if (archive_write_set_compression_gzip(arch) != ARCHIVE_OK)
491 #else
492                         if (archive_write_add_filter_gzip(arch) != ARCHIVE_OK)
493 #endif
494                                 return archive_error_string(arch);
495                         break;
496                 case BZIP2:
497 #if ARCHIVE_VERSION_NUMBER < 3000000
498                         if (archive_write_set_compression_bzip2(arch) != ARCHIVE_OK)
499 #else
500                         if (archive_write_add_filter_bzip2(arch) != ARCHIVE_OK)
501 #endif
502                                 return archive_error_string(arch);
503                         break;
504 #if NEW_ARCHIVE_API
505                 case COMPRESS:
506 #if ARCHIVE_VERSION_NUMBER < 3000000
507                         if (archive_write_set_compression_compress(arch) != ARCHIVE_OK)
508 #else
509                         if (archive_write_add_filter_compress(arch) != ARCHIVE_OK)
510 #endif
511                                 return archive_error_string(arch);
512                         break;
513 #endif
514                 case NO_COMPRESS:
515 #if ARCHIVE_VERSION_NUMBER < 3000000
516                         if (archive_write_set_compression_none(arch) != ARCHIVE_OK)
517 #else
518                         if (archive_write_add_filter_none(arch) != ARCHIVE_OK)
519 #endif
520                                 return archive_error_string(arch);
521                         break;
522         }
523         switch (format) {
524                 case TAR:
525                         if (archive_write_set_format_ustar(arch) != ARCHIVE_OK)
526                                 return archive_error_string(arch);
527                         break;
528                 case SHAR:
529                         if (archive_write_set_format_shar(arch) != ARCHIVE_OK)
530                                 return archive_error_string(arch);
531                         break;
532                 case PAX:
533                         if (archive_write_set_format_pax(arch) != ARCHIVE_OK)
534                                 return archive_error_string(arch);
535                         break;
536                 case CPIO:
537                         if (archive_write_set_format_cpio(arch) != ARCHIVE_OK)
538                                 return archive_error_string(arch);
539                         break;
540                 case NO_FORMAT:
541                         return "Missing archive format";
542         }
543 #if ARCHIVE_VERSION_NUMBER < 3000000
544         if (archive_write_open_file(arch, archive_name) != ARCHIVE_OK)
545 #else
546         if (archive_write_open_filename(arch, archive_name) != ARCHIVE_OK)
547 #endif
548                 return archive_error_string(arch);
549
550         while (files && ! stop_action) {
551 #ifndef _TEST
552                 set_progress_print_all(num++, total, 30);
553 #endif
554                 file = (struct file_info *) files->data;
555                 if (!file)
556                         continue;
557                 filename = get_full_path(file);
558                 /* libarchive will crash if instructed to add archive to it self */
559                 if (g_utf8_collate(archive_name, filename) == 0) {
560                         buf = NULL;
561                         buf = g_strdup_printf(
562                                                 "%s: Not dumping to %s", archive_name, filename);
563                         g_warning("%s\n", buf);
564 #ifndef _TEST
565                         debug_print("%s\n", buf);
566 #endif
567                         g_free(buf);
568                 }
569                 else {
570 #ifndef _TEST
571                         debug_print("Adding: %s\n", filename);
572                         msg = g_strdup_printf("%s", filename);
573                         set_progress_file_label(msg);
574                         g_free(msg);
575 #endif
576                         entry = archive_entry_new();
577                         lstat(filename, &st);
578                         if ((fd = open(filename, O_RDONLY)) == -1) {
579                                 perror("open file");
580                         }
581                         else {
582                                 archive_entry_copy_stat(entry, &st);
583                                 archive_entry_set_pathname(entry, filename);
584                                 if (S_ISLNK(st.st_mode)) {
585                                         buf = NULL;
586                                         buf = malloc(PATH_MAX + 1);
587                                         if ((len = readlink(filename, buf, PATH_MAX)) < 0)
588                                                 perror("error in readlink");
589                                         else
590                                                 buf[len] = '\0';
591                                         archive_entry_set_symlink(entry, buf);
592                                         g_free(buf);
593                                         archive_entry_set_size(entry, 0);
594                                         archive_write_header(arch, entry);
595                                 }
596                                 else {
597                                         if (archive_write_header(arch, entry) != ARCHIVE_OK)
598                                                 g_warning("%s", archive_error_string(arch));
599                                         buf = NULL;
600                                         buf = malloc(READ_BLOCK_SIZE);
601                                         len = read(fd, buf, READ_BLOCK_SIZE);
602                                         while (len > 0) {
603                                                 if (archive_write_data(arch, buf, len) == -1)
604                                                         g_warning("%s", archive_error_string(arch));
605                                                 memset(buf, 0, READ_BLOCK_SIZE);
606                                                 len = read(fd, buf, READ_BLOCK_SIZE);
607                                         }
608                                         g_free(buf);
609                                 }
610                                 close(fd);
611                                 archive_entry_free(entry);
612                         }
613                 }
614                 g_free(filename);
615                 files = g_slist_next(files);
616         }
617 #ifndef _TEST
618         if (stop_action)
619                 unlink(archive_name);
620         stop_action = FALSE;
621 #endif
622         archive_write_close(arch);
623 #if ARCHIVE_VERSION_NUMBER < 3000000
624         archive_write_finish(arch);
625 #else
626         archive_write_free(arch);
627 #endif
628         return NULL;
629 }
630
631 #ifdef _TEST
632 void archive_scan_folder(const char* dir) {
633         struct stat st;
634         DIR* root;
635         struct dirent* ent;
636         gchar cwd[PATH_MAX];
637         gchar path[PATH_MAX];
638         
639         getcwd(cwd, PATH_MAX);
640
641         if (g_stat(dir, &st) == -1)
642                 return;
643         if (! S_ISDIR(st.st_mode))
644                 return;
645         if (!(root = opendir(dir)))
646                 return;
647         chdir(dir);
648
649         while ((ent = readdir(root)) != NULL) {
650                 if (strcmp(".", ent->d_name) == 0 || strcmp("..", ent->d_name) == 0)
651                         continue;
652                 g_stat(ent->d_name, &st);
653                 sprintf(path, "%s/%s", dir, ent->d_name);
654                 if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
655                         archive_add_file(path);
656                 }
657                 else if (S_ISDIR(st.st_mode)) {
658                         archive_scan_folder(path);
659                 }
660         }
661         chdir(cwd);
662         closedir(root);
663 }
664
665 int main(int argc, char** argv) {
666         char* archive = NULL;
667         char buf[PATH_MAX];
668         int pid;
669         int opt;
670         int perm = ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME |
671                 ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_SECURE_SYMLINKS;
672         gchar cwd[PATH_MAX];
673         gboolean remove = FALSE;
674         const char *p = NULL;
675         int res;
676
677         getcwd(cwd, PATH_MAX);
678
679         while (*++argv && **argv == '-') {
680                 p = *argv + 1;
681
682                 while ((opt = *p++) != '\0') {
683                         switch(opt) {
684                                 case 'a':
685                                         if (*p != '\0')
686                                                 archive = (char *) p;
687                                         else
688                                                 archive = *++argv;
689                                         p += strlen(p);
690                                         break;
691                                 case 'r':
692                                         remove = TRUE;
693                                         break;
694                         }
695                 }
696         }
697         if (! archive) {
698                 fprintf(stderr, "Missing archive name!\n");
699                 return EXIT_FAILURE;
700         }
701         if (!*argv) {
702                 fprintf(stderr, "Expected arguments after options!\n");
703                 return EXIT_FAILURE;
704         }
705         
706         while (*argv) {
707                 archive_scan_folder(*argv++);
708                 res = archive_create(archive, file_list);
709                 if (res != ARCHIVE_OK) {
710                         fprintf(stderr, "%s: Creating archive failed\n", archive);
711                         return EXIT_FAILURE;
712                 }
713         }
714         pid = (int) getpid();
715         sprintf(buf, "/tmp/%d", pid);
716         fprintf(stdout, "Creating: %s\n", buf);
717         mkdir(buf, 0700);
718         chdir(buf);
719         if (strcmp(dirname(archive), ".") == 0) 
720                 sprintf(buf, "%s/%s", cwd, basename(archive));
721         else
722                 sprintf(buf, "%s", archive);
723         archive_extract(buf, perm);
724         chdir(cwd);
725         if (remove) {
726                 sprintf(buf, "rm -rf /tmp/%d", pid);
727                 fprintf(stdout, "Executing: %s\n", buf);
728                 system(buf);
729         }
730         archive_free_list(file_list);
731         return EXIT_SUCCESS;
732 }
733 #endif