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