support image/x-eps format via pdf_viewer
[claws.git] / src / plugins / archive / libarchive_archive.c
blobb477d82e9e945adfa9491f35f9342d1c140564ba
1 /*
2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2024 Michael Rasmussen and the Claws Mail Team
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.
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.
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/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #include <glib.h>
25 #include <glib/gi18n.h>
26 #include <glib/gstdio.h>
28 #include "libarchive_archive.h"
30 #ifndef DEBUG_ARCHIVE
31 # include "archiver.h"
32 # include "utils.h"
33 # include "mainwindow.h"
34 # include "folder.h"
35 #endif
36 #ifdef DEBUG_ARCHIVE
37 #include "procmsg.h"
38 #endif
40 #include <sys/types.h>
41 #include <sys/stat.h>
43 #include <archive.h>
44 #include <archive_entry.h>
45 #include <fcntl.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <unistd.h>
50 #include <libgen.h>
52 #define READ_BLOCK_SIZE 10240
54 struct file_info {
55 char* path;
56 char* name;
59 static GSList* msg_trash_list = NULL;
60 static GSList* file_list = NULL;
61 static gboolean stop_action = FALSE;
63 #ifdef DEBUG_ARCHIVE
64 static int permissions = 0;
65 #endif
67 static void free_msg_trash(MsgTrash* trash) {
68 if (trash) {
69 gchar *name = folder_item_get_name(trash->item);
71 debug_print("Freeing files in %s\n", name);
72 g_free(name);
73 if (trash->msgs) {
74 g_slist_free(trash->msgs);
76 g_free(trash);
80 MsgTrash* new_msg_trash(FolderItem* item) {
81 MsgTrash* msg_trash;
82 FolderType type;
84 g_return_val_if_fail(item != NULL, NULL);
86 /* FolderType must be F_MH, F_MBOX, F_MAILDIR or F_IMAP */
87 type = item->folder->klass->type;
88 if (!(type == F_MH || type == F_MBOX ||
89 type == F_MAILDIR || type == F_IMAP))
90 return NULL;
91 msg_trash = g_new0(MsgTrash, 1);
92 msg_trash->item = item;
93 msg_trash->msgs = NULL;
94 msg_trash_list = g_slist_prepend(msg_trash_list, msg_trash);
96 return msg_trash;
99 void archive_free_archived_files() {
100 MsgTrash* mt = NULL;
101 gint res;
102 GSList* l = NULL;
104 for (l = msg_trash_list; l; l = g_slist_next(l)) {
105 gchar *name;
107 mt = (MsgTrash *) l->data;
108 name = folder_item_get_name(mt->item);
109 debug_print("Trashing messages in folder: %s\n",
110 name);
111 g_free(name);
112 res = folder_item_remove_msgs(mt->item, mt->msgs);
113 debug_print("Result was %d\n", res);
114 free_msg_trash(mt);
116 g_slist_free(msg_trash_list);
117 msg_trash_list = NULL;
120 void archive_add_msg_mark(MsgTrash* trash, MsgInfo* msg) {
121 g_return_if_fail(trash != NULL || msg != NULL);
122 debug_print("Marking msg #%d for removal\n", msg->msgnum);
123 trash->msgs = g_slist_prepend(trash->msgs, msg);
126 static void free_all(GDate* date, gchar** parts) {
127 if (date)
128 g_date_free(date);
129 if (parts)
130 g_strfreev(parts);
133 static gboolean is_iso_string(gchar** items) {
134 int i = -1;
135 gchar* item;
137 while (*items) {
138 i++;
139 item = *items++;
140 debug_print("Date part %d: %s\n", i, item);
141 switch(i) {
142 case 0:
143 if (strlen(item) != 4)
144 return FALSE;
145 break;
146 case 1:
147 case 2:
148 if (strlen(item) != 2)
149 return FALSE;
150 break;
151 default:
152 return FALSE;
155 debug_print("Leaving\n");
156 return (i == 2);
159 static GDate* iso2GDate(const gchar* date) {
160 GDate* gdate;
161 gchar** parts;
162 int i;
164 g_return_val_if_fail(date != NULL, NULL);
166 gdate = g_date_new();
167 parts = g_strsplit(date, "-", 3);
168 if (!parts)
169 return NULL;
170 if (! is_iso_string(parts)) {
171 free_all(gdate, parts);
172 return NULL;
174 for (i = 0; i < 3; i++) {
175 int t = atoi(parts[i]);
176 switch (i) {
177 case 0:
178 if (t < 1 || t > 9999) {
179 free_all(gdate, parts);
180 return NULL;
182 g_date_set_year(gdate, t);
183 break;
184 case 1:
185 if (t < 1 || t > 12) {
186 free_all(gdate, parts);
187 return NULL;
189 g_date_set_month(gdate, t);
190 break;
191 case 2:
192 if (t < 1 || t > 31) {
193 free_all(gdate, parts);
194 return NULL;
196 g_date_set_day(gdate, t);
197 break;
200 g_strfreev(parts);
201 return gdate;
204 gboolean before_date(time_t msg_mtime, const gchar* before) {
205 gchar* pos = NULL;
206 GDate* date;
207 GDate* file_t;
208 gboolean res;
210 debug_print("Cut-off date: %s\n", before);
211 if ((date = iso2GDate(before)) == NULL) {
212 g_warning("bad date format: %s", before);
213 return FALSE;
216 file_t = g_date_new();
217 g_date_set_time_t(file_t, msg_mtime);
219 if (debug_get_mode()) {
220 pos = g_new0(char, 100);
221 g_date_strftime(pos, 100, "%F", file_t);
222 fprintf(stderr, "File date: %s\n", pos);
223 g_free(pos);
226 if (! g_date_valid(file_t)) {
227 g_warning("invalid msg date");
228 return FALSE;
231 res = (g_date_compare(file_t, date) >= 0) ? FALSE : TRUE;
232 g_date_free(file_t);
233 return res;
236 static void archive_free_file_info(struct file_info* file) {
237 if (! file)
238 return;
239 if (file->path)
240 g_free(file->path);
241 if (file->name)
242 g_free(file->name);
243 g_free(file);
244 file = NULL;
247 void stop_archiving(void) {
248 debug_print("stop action set to true\n");
249 stop_action = TRUE;
252 void archive_free_file_list(gboolean md5, gboolean rename) {
253 struct file_info* file = NULL;
254 gchar* path = NULL;
256 debug_print("freeing file list\n");
257 if (! file_list)
258 return;
259 while (file_list) {
260 file = (struct file_info *) file_list->data;
261 if (!rename && md5 && g_str_has_suffix(file->name, ".md5")) {
262 path = g_strdup_printf("%s/%s", file->path, file->name);
263 debug_print("unlinking %s\n", path);
264 if (g_unlink(path) < 0)
265 FILE_OP_ERROR(path, "g_unlink");
266 g_free(path);
268 if (rename) {
269 path = g_strdup_printf("%s/%s", file->path, file->name);
270 debug_print("unlinking %s\n", path);
271 if (g_unlink(path) < 0)
272 FILE_OP_ERROR(path, "g_unlink");
273 g_free(path);
275 archive_free_file_info(file);
276 file_list->data = NULL;
277 file_list = g_slist_next(file_list);
279 if (file_list) {
280 g_slist_free(file_list);
281 file_list = NULL;
285 static struct file_info* archive_new_file_info() {
286 struct file_info* new_file_info = malloc(sizeof(struct file_info));
288 new_file_info->path = NULL;
289 new_file_info->name = NULL;
290 return new_file_info;
293 static void archive_add_to_list(struct file_info* file) {
294 if (! file)
295 return;
296 file_list = g_slist_prepend(file_list, (gpointer) file);
299 static gchar* strip_leading_dot_slash(gchar* path) {
300 if (path && strlen(path) > 1 && path[0] == '.' && path[1] == '/')
301 return g_strdup(&(path[2]));
303 return g_strdup(path);
306 static gchar* get_full_path(struct file_info* file) {
307 char* path = malloc(PATH_MAX);
309 if (file->path && *(file->path))
310 sprintf(path, "%s/%s", file->path, file->name);
311 else
312 sprintf(path, "%s", file->name);
313 return path;
316 #ifdef DEBUG_ARCHIVE
317 static gchar* strip_leading_slash(gchar* path) {
318 gchar* stripped = path;
319 gchar* result = NULL;
321 if (stripped && stripped[0] == '/') {
322 ++stripped;
323 result = g_strdup(stripped);
325 else
326 result = g_strdup(path);
327 return result;
330 static int archive_get_permissions() {
331 return permissions;
335 void archive_set_permissions(int perm) {
336 permissions = perm;
339 static int archive_copy_data(struct archive* in, struct archive* out) {
340 const void* buf;
341 size_t size;
342 off_t offset;
343 int res = ARCHIVE_OK;
345 while (res == ARCHIVE_OK) {
346 res = archive_read_data_block(in, &buf, &size, &offset);
347 if (res == ARCHIVE_OK) {
348 res = archive_write_data_block(out, buf, size, offset);
351 return (res == ARCHIVE_EOF) ? ARCHIVE_OK : res;
353 #endif
355 void archive_add_file(gchar* path) {
356 struct file_info* file;
357 gchar* filename = NULL;
359 g_return_if_fail(path != NULL);
361 #ifndef DEBUG_ARCHIVE
362 debug_print("add %s to list\n", path);
363 #endif
364 filename = g_strrstr_len(path, strlen(path), "/");
365 if (! filename)
366 g_warning("no filename in path '%s'", path);
367 g_return_if_fail(filename != NULL);
369 filename++;
370 file = archive_new_file_info();
371 file->name = g_strdup(filename);
372 file->path = strip_leading_dot_slash(dirname(path));
373 archive_add_to_list(file);
376 GSList* archive_get_file_list() {
377 return file_list;
380 #ifdef DEBUG_ARCHIVE
381 const gchar* archive_extract(const char* archive_name, int flags) {
382 struct archive* in;
383 struct archive* out;
384 struct archive_entry* entry;
385 int res = ARCHIVE_OK;
386 const char* result = NULL;
388 g_return_val_if_fail(archive_name != NULL, NULL);
390 fprintf(stdout, "%s: extracting\n", archive_name);
391 in = archive_read_new();
392 if ((res = archive_read_support_format_tar(in)) == ARCHIVE_OK) {
393 if ((res = archive_read_support_compression_gzip(in)) == ARCHIVE_OK) {
394 #if ARCHIVE_VERSION_NUMBER < 3000000
395 if ((res = archive_read_open_file(
396 #else
397 if ((res = archive_read_open_filename(
398 #endif
399 in, archive_name, READ_BLOCK_SIZE)) != ARCHIVE_OK) {
400 g_warning("%s: %s", archive_name, archive_error_string(in));
401 result = archive_error_string(in);
403 else {
404 out = archive_write_disk_new();
405 if ((res = archive_write_disk_set_options(
406 out, flags)) == ARCHIVE_OK) {
407 res = archive_read_next_header(in, &entry);
408 while (res == ARCHIVE_OK) {
409 res = archive_write_header(out, entry);
410 if (res != ARCHIVE_OK) {
411 g_warning("%s", archive_error_string(out));
412 /* skip this file an continue */
413 res = ARCHIVE_OK;
415 else {
416 res = archive_copy_data(in, out);
417 if (res != ARCHIVE_OK) {
418 g_warning("%s", archive_error_string(in));
419 /* skip this file an continue */
420 res = ARCHIVE_OK;
422 else
423 res = archive_read_next_header(in, &entry);
426 if (res == ARCHIVE_EOF)
427 res = ARCHIVE_OK;
428 if (res != ARCHIVE_OK) {
429 const gchar *e = archive_error_string(in);
430 g_warning("%s: %s", archive_name, e? e: "unknown error");
431 result = e;
434 else
435 result = archive_error_string(out);
436 archive_read_close(in);
438 #if ARCHIVE_VERSION_NUMBER < 3000000
439 archive_read_finish(in);
440 #else
441 archive_read_free(in);
442 #endif
444 else
445 result = archive_error_string(in);
447 else
448 result = archive_error_string(in);
449 return result;
451 #endif
453 const gchar* archive_create(const char* archive_name, GSList* files,
454 COMPRESS_METHOD method, ARCHIVE_FORMAT format) {
455 struct archive* arch;
457 #ifndef DEBUG_ARCHIVE
458 gint num = 0;
459 gint total = g_slist_length (files);
460 #endif
462 g_return_val_if_fail(files != NULL, "No files for archiving");
464 debug_print("File: %s\n", archive_name);
465 arch = archive_write_new();
466 switch (method) {
467 case GZIP:
468 #if ARCHIVE_VERSION_NUMBER < 3000000
469 if (archive_write_set_compression_gzip(arch) != ARCHIVE_OK)
470 #else
471 if (archive_write_add_filter_gzip(arch) != ARCHIVE_OK)
472 #endif
473 return archive_error_string(arch);
474 break;
475 case BZIP2:
476 #if ARCHIVE_VERSION_NUMBER < 3000000
477 if (archive_write_set_compression_bzip2(arch) != ARCHIVE_OK)
478 #else
479 if (archive_write_add_filter_bzip2(arch) != ARCHIVE_OK)
480 #endif
481 return archive_error_string(arch);
482 break;
483 case COMPRESS:
484 #if ARCHIVE_VERSION_NUMBER < 3000000
485 if (archive_write_set_compression_compress(arch) != ARCHIVE_OK)
486 #else
487 if (archive_write_add_filter_compress(arch) != ARCHIVE_OK)
488 #endif
489 return archive_error_string(arch);
490 break;
491 #if ARCHIVE_VERSION_NUMBER >= 2006990
492 case LZMA:
493 #if ARCHIVE_VERSION_NUMBER < 3000000
494 if (archive_write_set_compression_lzma(arch) != ARCHIVE_OK)
495 #else
496 if (archive_write_add_filter_lzma(arch) != ARCHIVE_OK)
497 #endif
498 return archive_error_string(arch);
499 break;
500 case XZ:
501 #if ARCHIVE_VERSION_NUMBER < 3000000
502 if (archive_write_set_compression_xz(arch) != ARCHIVE_OK)
503 #else
504 if (archive_write_add_filter_xz(arch) != ARCHIVE_OK)
505 #endif
506 return archive_error_string(arch);
507 break;
508 #endif
509 #if ARCHIVE_VERSION_NUMBER >= 3000000
510 case LZIP:
511 if (archive_write_add_filter_lzip(arch) != ARCHIVE_OK)
512 return archive_error_string(arch);
513 break;
514 #endif
515 #if ARCHIVE_VERSION_NUMBER >= 3001000
516 case LRZIP:
517 if (archive_write_add_filter_lrzip(arch) != ARCHIVE_OK)
518 return archive_error_string(arch);
519 break;
520 case LZOP:
521 if (archive_write_add_filter_lzop(arch) != ARCHIVE_OK)
522 return archive_error_string(arch);
523 break;
524 case GRZIP:
525 if (archive_write_add_filter_grzip(arch) != ARCHIVE_OK)
526 return archive_error_string(arch);
527 break;
528 #endif
529 #if ARCHIVE_VERSION_NUMBER >= 3001900
530 case LZ4:
531 if (archive_write_add_filter_lz4(arch) != ARCHIVE_OK)
532 return archive_error_string(arch);
533 break;
534 #endif
535 case NO_COMPRESS:
536 #if ARCHIVE_VERSION_NUMBER < 3000000
537 if (archive_write_set_compression_none(arch) != ARCHIVE_OK)
538 #else
539 if (archive_write_add_filter_none(arch) != ARCHIVE_OK)
540 #endif
541 return archive_error_string(arch);
542 break;
544 switch (format) {
545 case TAR:
546 if (archive_write_set_format_ustar(arch) != ARCHIVE_OK)
547 return archive_error_string(arch);
548 break;
549 case SHAR:
550 if (archive_write_set_format_shar(arch) != ARCHIVE_OK)
551 return archive_error_string(arch);
552 break;
553 case PAX:
554 if (archive_write_set_format_pax(arch) != ARCHIVE_OK)
555 return archive_error_string(arch);
556 break;
557 case CPIO:
558 if (archive_write_set_format_cpio(arch) != ARCHIVE_OK)
559 return archive_error_string(arch);
560 break;
561 case NO_FORMAT:
562 return "Missing archive format";
564 #if ARCHIVE_VERSION_NUMBER < 3000000
565 if (archive_write_open_file(arch, archive_name) != ARCHIVE_OK)
566 #else
567 if (archive_write_open_filename(arch, archive_name) != ARCHIVE_OK)
568 #endif
569 return archive_error_string(arch);
571 while (files && ! stop_action) {
572 struct file_info* file;
573 gchar* filename = NULL;
575 #ifndef DEBUG_ARCHIVE
576 set_progress_print_all(num++, total, 30);
577 #endif
578 file = (struct file_info *) files->data;
579 if (!file)
580 continue;
581 filename = get_full_path(file);
582 /* libarchive will crash if instructed to add archive to it self */
583 if (g_utf8_collate(archive_name, filename) == 0) {
584 g_warning("%s: not dumping to '%s'", archive_name, filename);
585 #ifndef DEBUG_ARCHIVE
586 debug_print("%s: not dumping to '%s'\n", archive_name, filename);
587 #endif
589 else {
590 struct archive_entry* entry;
591 char* buf = NULL;
592 ssize_t len;
593 GError* err = NULL;
594 GStatBuf st;
595 int fd;
597 #ifndef DEBUG_ARCHIVE
598 gchar* msg = NULL;
600 debug_print("Adding: %s\n", filename);
601 msg = g_strdup_printf("%s", filename);
602 set_progress_file_label(msg);
603 g_free(msg);
604 #endif
605 if ((fd = g_open(filename, O_RDONLY, 0)) == -1) {
606 FILE_OP_ERROR(filename, "g_open");
608 else {
609 if (g_stat(filename, &st) == -1) {
610 FILE_OP_ERROR(filename, "g_stat");
611 } else {
612 entry = archive_entry_new();
613 archive_entry_copy_stat(entry, &st);
614 archive_entry_set_pathname(entry, filename);
615 if (S_ISLNK(st.st_mode)) {
617 buf = g_file_read_link(filename, &err);
618 if (err) {
619 FILE_OP_ERROR(filename, "g_file_read_link");
620 g_clear_error(&err);
621 } else {
622 archive_entry_set_symlink(entry, buf);
623 g_free(buf);
624 archive_entry_set_size(entry, 0);
625 archive_write_header(arch, entry);
628 else {
629 if (archive_write_header(arch, entry) != ARCHIVE_OK)
630 g_warning("%s", archive_error_string(arch));
631 if ((buf = malloc(READ_BLOCK_SIZE)) != NULL) {
632 len = read(fd, buf, READ_BLOCK_SIZE);
633 while (len > 0) {
634 if (archive_write_data(arch, buf, len) == -1)
635 g_warning("%s", archive_error_string(arch));
636 memset(buf, 0, READ_BLOCK_SIZE);
637 len = read(fd, buf, READ_BLOCK_SIZE);
639 g_free(buf);
642 archive_entry_free(entry);
644 if (!g_close(fd, &err) || err) {
645 FILE_OP_ERROR(filename, "g_close");
646 if (err)
647 g_clear_error(&err);
651 g_free(filename);
652 files = g_slist_next(files);
654 #ifndef DEBUG_ARCHIVE
655 if (stop_action)
656 unlink(archive_name);
657 stop_action = FALSE;
658 #endif
659 archive_write_close(arch);
660 #if ARCHIVE_VERSION_NUMBER < 3000000
661 archive_write_finish(arch);
662 #else
663 archive_write_free(arch);
664 #endif
665 return NULL;
668 #ifdef DEBUG_ARCHIVE
669 void archive_scan_folder(const char* dir) {
670 GStatBuf st;
671 GDir* root;
672 const gchar* ent;
673 gchar cwd[PATH_MAX];
674 gchar path[PATH_MAX];
675 GError *error = NULL;
677 getcwd(cwd, PATH_MAX);
679 if (g_stat(dir, &st) == -1)
680 return;
681 if (! S_ISDIR(st.st_mode))
682 return;
683 if (!(root = g_dir_open(dir, 0, &error))) {
684 debug_print("opening '%s' failed: %d (%s)\n", dir, error->code, error->message);
685 return;
687 chdir(dir);
689 while ((ent = g_dir_read_name(root)) != NULL) {
690 if (strcmp(".", ent) == 0 || strcmp("..", ent) == 0)
691 continue;
692 if (g_stat(ent, &st) == -1) {
693 FILE_OP_ERROR(ent, "g_stat");
694 continue;
696 sprintf(path, "%s/%s", dir, ent);
697 if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
698 archive_add_file(path);
700 else if (S_ISDIR(st.st_mode)) {
701 archive_scan_folder(path);
704 chdir(cwd);
705 g_dir_close(root);
708 int main(int argc, char** argv) {
709 char* archive = NULL;
710 char buf[PATH_MAX];
711 int pid;
712 int opt;
713 int perm = ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME |
714 ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_SECURE_SYMLINKS;
715 gchar cwd[PATH_MAX];
716 gboolean remove = FALSE;
717 const char *p = NULL;
718 const gchar* res;
720 getcwd(cwd, PATH_MAX);
722 while (*++argv && **argv == '-') {
723 p = *argv + 1;
725 while ((opt = *p++) != '\0') {
726 switch(opt) {
727 case 'a':
728 if (*p != '\0')
729 archive = (char *) p;
730 else
731 archive = *++argv;
732 p += strlen(p);
733 break;
734 case 'r':
735 remove = TRUE;
736 break;
740 if (! archive) {
741 fprintf(stderr, "Missing archive name!\n");
742 return EXIT_FAILURE;
744 if (!*argv) {
745 fprintf(stderr, "Expected arguments after options!\n");
746 return EXIT_FAILURE;
749 while (*argv) {
750 archive_scan_folder(*argv++);
751 res = archive_create(archive, file_list, GZIP, TAR);
752 if (res != NULL) {
753 fprintf(stderr, "%s: Creating archive failed [%s]\n", archive, res);
754 return EXIT_FAILURE;
757 pid = (int) getpid();
758 sprintf(buf, "/tmp/%d", pid);
759 fprintf(stdout, "Creating: %s\n", buf);
760 mkdir(buf, 0700);
761 chdir(buf);
762 if (strcmp(dirname(archive), ".") == 0)
763 sprintf(buf, "%s/%s", cwd, basename(archive));
764 else
765 sprintf(buf, "%s", archive);
766 archive_extract(buf, perm);
767 chdir(cwd);
768 if (remove) {
769 sprintf(buf, "rm -rf /tmp/%d", pid);
770 fprintf(stdout, "Executing: %s\n", buf);
771 system(buf);
773 if (file_list != NULL)
774 g_slist_free(file_list);
775 return EXIT_SUCCESS;
777 #endif
779 void archiver_set_tooltip(GtkWidget* widget, gchar* text) {
780 gtk_widget_set_tooltip_text(widget, text);
781 g_free(text);