CVE-2023-51698: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (CWE-78)
value: Atril is a simple multi-page document viewer. Atril is vulnerable to a critical Command Injection Vulnerability. This vulnerability gives the attacker immediate access to the target system when the target user opens a crafted document or clicks on a crafted link/URL using a maliciously crafted CBT document which is a TAR archive. A patch is available at commit ce41df6.

CWE description: The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
CVSS v3 base score: 8.8
Published: 2024-01-12T21:15Z
Repository: https://github.com/mate-desktop/atril
Language: C
Files changed: backend/comics/Makefile.am, backend/comics/comics-document.c, backend/comics/comics-document.h, backend/comics/ev-archive.c, backend/comics/ev-archive.h, configure.ac, libdocument/ev-document.h
Commit date: 2024-01-07 22:17:05 +0000
Commit message: comics: Use libarchive to unpack documents

This commit eliminates the use of external commands for opening
comic documents, and uses libarchive instead.

Diff: diff --git a/backend/comics/Makefile.am b/backend/comics/Makefile.am
index b27f9b8..77f3ded 100644
--- a/backend/comics/Makefile.am
+++ b/backend/comics/Makefile.am
@@ -10,16 +10,19 @@ AM_CPPFLAGS =                      		\
 
 backend_LTLIBRARIES = libcomicsdocument.la
 
 libcomicsdocument_la_SOURCES = \
 	comics-document.c      \
-	comics-document.h
+	comics-document.h      \
+	ev-archive.c     \
+	ev-archive.h
 
 libcomicsdocument_la_LDFLAGS = $(BACKEND_LIBTOOL_FLAGS)
 libcomicsdocument_la_LIBADD =				\
 	$(top_builddir)/libdocument/libatrildocument.la	\
 	$(BACKEND_LIBS)					\
+	$(COMICS_LIBS)					\
 	$(LIB_LIBS)
 
 backend_in_files = comicsdocument.atril-backend.desktop.in
 backend_DATA = $(backend_in_files:.atril-backend.desktop.in=.atril-backend)
 $(backend_DATA): $(backend_in_files)
diff --git a/backend/comics/comics-document.c b/backend/comics/comics-document.c
index ed02a35..1da1eee 100644
--- a/backend/comics/comics-document.c
+++ b/backend/comics/comics-document.c
@@ -28,430 +28,294 @@
 #include <glib.h>
 #include <glib/gi18n-lib.h>
 #include <glib/gstdio.h>
 #include <gio/gio.h>
 
-#include <sys/wait.h>
-
 #include "comics-document.h"
 #include "ev-document-misc.h"
 #include "ev-document-thumbnails.h"
 #include "ev-file-helpers.h"
+#include "ev-archive.h"
+#include <archive.h>
+#include <archive_entry.h>
 
 #define EV_EOL "\n"
 
-typedef enum
-{
-	RARLABS,
-	GNAUNRAR,
-	UNZIP,
-	P7ZIP,
-	TAR,
-	UNARCHIVER
-} ComicBookDecompressType;
+#define BLOCK_SIZE 10240
 
 typedef struct _ComicsDocumentClass ComicsDocumentClass;
 
 struct _ComicsDocumentClass
 {
 	EvDocumentClass parent_class;
 };
 
 struct _ComicsDocument
 {
-	EvDocument parent_instance;
-
-	gchar    *archive, *dir;
-	GPtrArray *page_names;
-	gchar    *selected_command, *alternative_command;
-	gchar    *extract_command, *list_command, *decompress_tmp;
-	gboolean regex_arg;
-	gint     offset;
-	ComicBookDecompressType command_usage;
+	EvDocument     parent_instance;
+	EvArchive     *archive;
+	gchar         *archive_path;
+	gchar         *archive_uri;
+	GPtrArray     *page_names; /* elem: char * */
+	GHashTable    *page_positions; /* key: char *, value: uint + 1 */
+
 };
 
-#define OFFSET_7Z 53
-#define OFFSET_ZIP 2
-#define NO_OFFSET 0
-
-/* For perfomance reasons of 7z* we've choosen to decompress on the temporary
- * directory instead of decompressing on the stdout */
-
-/**
- * @extract: command line arguments to pass to extract a file from the archive
- *   to stdout.
- * @list: command line arguments to list the archive contents
- * @decompress_tmp: command line arguments to pass to extract the archive
- *   into a directory.
- * @regex_arg: whether the command can accept regex expressions
- * @offset: the position offset of the filename on each line in the output of
- *   running the @list command
- */
-typedef struct {
-        char *extract;
-        char *list;
-        char *decompress_tmp;
-        gboolean regex_arg;
-        gint offset;
-} ComicBookDecompressCommand;
+static void       
+comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface);
+EV_BACKEND_REGISTER_WITH_CODE (ComicsDocument, comics_document,
+    {
+        EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS,
+                        comics_document_document_thumbnails_iface_init);
+    } );
+
+#define FORMAT_UNKNOWN     0
+#define FORMAT_SUPPORTED   1
+#define FORMAT_UNSUPPORTED 2
+
+/* Returns a GHashTable of:
+ * <key>: file extensions
+ * <value>: degree of support in gdk-pixbuf */
+static GHashTable *
+get_image_extensions(void)
+{
+	GHashTable *extensions;
+	GSList *formats = gdk_pixbuf_get_formats ();
+	GSList *l;
+	guint i;
+	const char *known_image_formats[] = {
+		"png",
+		"jpg",
+		"jpeg",
+		"webp"
+	};
+
+	extensions = g_hash_table_new_full (g_str_hash, g_str_equal,
+					    g_free, NULL);
+	for (l = formats; l != NULL; l = l->next) {
+		int i;
+		gchar **ext = gdk_pixbuf_format_get_extensions (l->data);
+
+		for (i = 0; ext[i] != NULL; i++) {
+			g_hash_table_insert (extensions,
+					     g_strdup (ext[i]),
+					     GINT_TO_POINTER (FORMAT_SUPPORTED));
+		}
 
-static const ComicBookDecompressCommand command_usage_def[] = {
-        /* RARLABS unrar */
-	{"%s p -c- -ierr --", "%s vb -c- -- %s", NULL             , FALSE, NO_OFFSET},
+		g_strfreev (ext);
+	}
+	g_slist_free (formats);
 
-        /* GNA! unrar */
-	{NULL               , "%s t %s"        , "%s -xf %s %s"   , FALSE, NO_OFFSET},
+	/* Add known image formats that aren't supported by gdk-pixbuf */
+	for (i = 0; i < G_N_ELEMENTS (known_image_formats); i++) {
+		if (!g_hash_table_lookup (extensions, known_image_formats[i])) {
+			g_hash_table_insert (extensions,
+					     g_strdup (known_image_formats[i]),
+					     GINT_TO_POINTER (FORMAT_UNSUPPORTED));
+		}
+	}
 
-        /* unzip */
-	{"%s -p -C --"      , "%s %s"          , NULL             , TRUE , OFFSET_ZIP},
+	return extensions;
+}
 
-        /* 7zip */
-	{NULL               , "%s l -- %s"     , "%s x -y %s -o%s", FALSE, OFFSET_7Z},
+static int
+has_supported_extension (const char *name,
+			 GHashTable *supported_extensions)
+{
+	gboolean ret = FALSE;
+	gchar *suffix;
+	suffix = g_strrstr (name, ".");
+	if (!suffix)
+		return ret;
 
-        /* tar */
-	{"%s -xOf"          , "%s -tf %s"      , NULL             , FALSE, NO_OFFSET},
+	suffix = g_ascii_strdown (suffix + 1, -1);
+	ret = GPOINTER_TO_INT (g_hash_table_lookup (supported_extensions, suffix));
+	g_free (suffix);
 
-	/* UNARCHIVER */
-	{"unar -o -"	    , "%s %s"	       , NULL		  , FALSE, NO_OFFSET}
-};
+	return ret;
+}
 
-static void       comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface);
+#define APPLE_DOUBLE_PREFIX "._"
+static gboolean
+is_apple_double (const char *name)
+{
+char *basename;
+	gboolean ret = FALSE;
 
-static GSList*    get_supported_image_extensions (void);
-static void       get_page_size_area_prepared_cb (GdkPixbufLoader *loader,
-						  gpointer data);
-static void       render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
-						  gint width,
-						  gint height,
-						  gpointer data);
-static char**     extract_argv                   (EvDocument *document,
-						  gint page);
+	basename = g_path_get_basename (name);
+	if (basename == NULL) {
+		g_debug ("Filename '%s' doesn't have a basename?", name);
+		return ret;
+	}
+	ret = g_str_has_prefix (basename, APPLE_DOUBLE_PREFIX);
+	g_free (basename);
 
-EV_BACKEND_REGISTER_WITH_CODE (ComicsDocument, comics_document,
-	{
-		EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS,
-						comics_document_document_thumbnails_iface_init);
-	} );
-
-/**
- * comics_regex_quote:
- * @unquoted_string: a literal string
- *
- * Quotes a string so unzip will not interpret the regex expressions of
- * @unquoted_string. Basically, this functions uses [] to disable regex
- * expressions. The return value must be freed with * g_free()
- *
- * Return value: quoted and disabled-regex string
- **/
-static gchar *
-comics_regex_quote (const gchar *unquoted_string)
+	return ret;
+}
+
+static gboolean
+archive_reopen_if_needed (ComicsDocument  *comics_document,
+			  const char      *page_wanted,
+			  GError         **error)
 {
-	const gchar *p;
-	GString *dest;
-
-	dest = g_string_new ("'");
-
-	p = unquoted_string;
-
-	while (*p) {
-		switch (*p) {
-			/* * matches a sequence of 0 or more characters */
-			case ('*'):
-			/* ? matches exactly 1 charactere */
-			case ('?'):
-			/* [...]  matches any single character found inside
-			 * the brackets. Disabling the first bracket is enough.
-			 */
-			case ('['):
-				g_string_append (dest, "[");
-				g_string_append_c (dest, *p);
-				g_string_append (dest, "]");
-				break;
-			/* Because \ escapes regex expressions that we are
-			 * disabling for unzip, we need to disable \ too */
-			case ('\\'):
-				g_string_append (dest, "[\\\\]");
-				break;
-			/* Escape single quote inside the string */
-			case ('\''):
-				g_string_append (dest, "'\\''");
-				break;
-			default:
-				g_string_append_c (dest, *p);
-				break;
+	const char *current_page;
+	guint current_page_idx, page_wanted_idx;
+
+	if (ev_archive_at_entry (comics_document->archive)) {
+		current_page = ev_archive_get_entry_pathname (comics_document->archive);
+		if (current_page) {
+			current_page_idx = GPOINTER_TO_UINT (g_hash_table_lookup (comics_document->page_positions, current_page));
+			page_wanted_idx = GPOINTER_TO_UINT (g_hash_table_lookup (comics_document->page_positions, page_wanted));
+
+			if (current_page_idx != 0 &&
+			    page_wanted_idx != 0 &&
+			    page_wanted_idx > current_page_idx)
+				return TRUE;
 		}
-		++p;
+
+		ev_archive_reset (comics_document->archive);
 	}
-	g_string_append_c (dest, '\'');
-	return g_string_free (dest, FALSE);
+return ev_archive_open_filename (comics_document->archive, comics_document->archive_path, error);
 }
 
-/* This function manages the command for decompressing a comic book */
-static gboolean
-comics_decompress_temp_dir (const gchar *command_decompress_tmp,
-			    const gchar *command,
-			    GError      **error)
+static GPtrArray *
+comics_document_list (ComicsDocument  *comics_document,
+		      GError         **error)
 {
-	gboolean success;
-	gchar *std_out, *basename;
-	GError *err = NULL;
-	gint retval;
-
-	success = g_spawn_command_line_sync (command_decompress_tmp, &std_out,
-					     NULL, &retval, &err);
-	basename = g_path_get_basename (command);
-	if (!success) {
-		g_set_error (error,
-			     EV_DOCUMENT_ERROR,
-			     EV_DOCUMENT_ERROR_INVALID,
-			     _("Error launching the command “%s” in order to "
-			     "decompress the comic book: %s"),
-			     basename,
-			     err->message);
-		g_error_free (err);
-	} else if (WIFEXITED (retval)) {
-		if (WEXITSTATUS (retval) == EXIT_SUCCESS) {
-			g_free (std_out);
-			g_free (basename);
-			return TRUE;
-		} else {
-			g_set_error (error,
+GPtrArray *array = NULL;
+	gboolean has_encrypted_files, has_unsupported_images, has_archive_errors;
+	GHashTable *supported_extensions = NULL;
+
+	if (!ev_archive_open_filename (comics_document->archive, comics_document->archive_path, error)) {
+		if (*error != NULL) {
+			g_warning ("Fatal error handling archive (%s): %s", G_STRFUNC, (*error)->message);
+			g_clear_error (error);
+		}
+
+		g_set_error_literal (error,
 				     EV_DOCUMENT_ERROR,
 				     EV_DOCUMENT_ERROR_INVALID,
-				     _("The command “%s” failed at "
-				     "decompressing the comic book."),
-				     basename);
-			g_free (std_out);
+			     _("File is corrupted"));
+		goto out;
+	}
+
+	supported_extensions = get_image_extensions ();
+
+	has_encrypted_files = FALSE;
+	has_unsupported_images = FALSE;
+	has_archive_errors = FALSE;
+	array = g_ptr_array_sized_new (64);
+
+	while (1) {
+		const char *name;
+		int supported;
+
+		if (!ev_archive_read_next_header (comics_document->archive, error)) {
+			if (*error != NULL) {
+				g_debug ("Fatal error handling archive (%s): %s", G_STRFUNC, (*error)->message);
+				g_clear_error (error);
+				has_archive_errors = TRUE;
+				goto out;
+			}
+			break;
 		}
-	} else {
-		g_set_error (error,
-			     EV_DOCUMENT_ERROR,
-			     EV_DOCUMENT_ERROR_INVALID,
-			     _("The command “%s” did not end normally."),
-			     basename);
-		g_free (std_out);
+
+		name = ev_archive_get_entry_pathname (comics_document->archive);
+		/* Ignore https://en.wikipedia.org/wiki/AppleSingle_and_AppleDouble_formats */
+		if (is_apple_double (name)) {
+			g_debug ("Not adding AppleDouble file '%s' to the list of files in the comics", name);
+			continue;
+		}
+
+		supported = has_supported_extension (name, supported_extensions);
+		if (supported == FORMAT_UNKNOWN) {
+			g_debug ("Not adding unsupported file '%s' to the list of files in the comics", name);
+			continue;
+		} else if (supported == FORMAT_UNSUPPORTED) {
+			g_debug ("Not adding unsupported image '%s' to the list of files in the comics", name);
+			has_unsupported_images = TRUE;
+			continue;
+		}
+
+		if (ev_archive_get_entry_is_encrypted (comics_document->archive)) {
+			g_debug ("Not adding encrypted file '%s' to the list of files in the comics", name);
+			has_encrypted_files = TRUE;
+			continue;
+		}
+
+		g_debug ("Adding '%s' to the list of files in the comics", name);
+		g_ptr_array_add (array, g_strdup (name));
 	}
-	g_free (basename);
-	return FALSE;
+out:
+	if (array->len == 0) {
+		g_ptr_array_free (array, TRUE);
+		array = NULL;
+
+		if (has_encrypted_files) {
+			g_set_error_literal (error,
+					     EV_DOCUMENT_ERROR,
+					     EV_DOCUMENT_ERROR_ENCRYPTED,
+					     _("Archive is encrypted"));
+		} else if (has_unsupported_images) {
+			g_set_error_literal (error,
+					     EV_DOCUMENT_ERROR,
+					     EV_DOCUMENT_ERROR_UNSUPPORTED_CONTENT,
+					     _("No supported images in archive"));
+		} else if (has_archive_errors) {
+			g_set_error_literal (error,
+					     EV_DOCUMENT_ERROR,
+					     EV_DOCUMENT_ERROR_INVALID,
+					     _("File is corrupted"));
+		} else {
+			g_set_error_literal (error,
+					     EV_DOCUMENT_ERROR,
+					     EV_DOCUMENT_ERROR_INVALID,
+					     _("No files in archive"));
+		}
+	}
+
+	if (supported_extensions)
+		g_hash_table_destroy (supported_extensions);
+	ev_archive_reset (comics_document->archive);
+	return array;
 }
 
-/* This function shows how to use the choosen command for decompressing a
- * comic book file. It modifies fields of the ComicsDocument struct with
- * this information */
-static gboolean
-comics_generate_command_lines (ComicsDocument *comics_document,
-			       GError         **error)
+static GHashTable *
+save_positions (GPtrArray *page_names)
 {
-	gchar *quoted_file, *quoted_file_aux;
-	gchar *quoted_command;
-	ComicBookDecompressType type;
-
-	type = comics_document->command_usage;
-	comics_document->regex_arg = command_usage_def[type].regex_arg;
-	quoted_command = g_shell_quote (comics_document->selected_command);
-	if (comics_document->regex_arg) {
-		quoted_file = comics_regex_quote (comics_document->archive);
-		quoted_file_aux = g_shell_quote (comics_document->archive);
-		comics_document->list_command =
-			   g_strdup_printf (command_usage_def[type].list,
-			                    comics_document->alternative_command,
-			                    quoted_file_aux);
-		g_free (quoted_file_aux);
-	} else {
-		quoted_file = g_shell_quote (comics_document->archive);
-		comics_document->list_command =
-				g_strdup_printf (command_usage_def[type].list,
-				                 quoted_command, quoted_file);
-	}
-	comics_document->extract_command =
-			    g_strdup_printf (command_usage_def[type].extract,
-				             quoted_command);
-	comics_document->offset = command_usage_def[type].offset;
-	if (command_usage_def[type].decompress_tmp) {
-		comics_document->dir = ev_mkdtemp ("atril-comics-XXXXXX", error);
-                if (comics_document->dir == NULL)
-                        return FALSE;
-
-		/* unrar-free can't create directories, but ev_mkdtemp already created the dir */
-
-		comics_document->decompress_tmp =
-			g_strdup_printf (command_usage_def[type].decompress_tmp,
-					 quoted_command, quoted_file,
-					 comics_document->dir);
-		g_free (quoted_file);
-		g_free (quoted_command);
-
-		if (!comics_decompress_temp_dir (comics_document->decompress_tmp,
-		    comics_document->selected_command, error))
-			return FALSE;
-		else
-			return TRUE;
-	} else {
-		g_free (quoted_file);
-		g_free (quoted_command);
-		return TRUE;
-	}
+	guint i;
+	GHashTable *ht;
 
+	ht = g_hash_table_new (g_str_hash, g_str_equal);
+	for (i = 0; i < page_names->len; i++)
+		g_hash_table_insert (ht, page_names->pdata[i], GUINT_TO_POINTER(i + 1));
+	return ht;
 }
 
-/* This function chooses an external command for decompressing a comic
- * book based on its mime tipe. */
+/*This function chooses the archive decompression support
+ * book based on its mime type. */
 static gboolean
-comics_check_decompress_command	(gchar          *mime_type,
+comics_check_decompress_support	(gchar          *mime_type,
 				 ComicsDocument *comics_document,
 				 GError         **error)
 {
-	gboolean success;
-	gchar *std_out, *std_err;
-	gint retval;
-	GError *err = NULL;
-
-	/* FIXME, use proper cbr/cbz mime types once they're
-	 * included in shared-mime-info */
-
 	if (g_content_type_is_a (mime_type, "application/x-cbr") ||
 	    g_content_type_is_a (mime_type, "application/x-rar")) {
-	        /* The RARLAB provides a no-charge proprietary (freeware)
-	        * decompress-only client for Linux called unrar. Another
-		* option is a GPLv2-licensed command-line tool developed by
-		* the Gna! project. Confusingly enough, the free software RAR
-		* decoder is also named unrar. For this reason we need to add
-		* some lines for disambiguation. Sorry for the added the
-		* complexity but it's life :)
-		* Finally, some distributions, like Debian, rename this free
-		* option as unrar-free.
-		* */
-		comics_document->selected_command =
-					g_find_program_in_path ("unrar");
-		if (comics_document->selected_command) {
-			/* We only use std_err to avoid printing useless error
-			 * messages on the terminal */
-			success =
-				g_spawn_command_line_sync (
-				              comics_document->selected_command,
-							   &std_out, &std_err,
-							   &retval, &err);
-			if (!success) {
-				g_propagate_error (error, err);
-				g_error_free (err);
-				return FALSE;
-			/* I don't check retval status because RARLAB unrar
-			 * doesn't have a way to return 0 without involving an
-			 * operation with a file*/
-			} else if (WIFEXITED (retval)) {
-				if (g_strrstr (std_out,"freeware") != NULL)
-					/* The RARLAB freeware client */
-					comics_document->command_usage = RARLABS;
-				else
-					/* The Gna! free software client */
-					comics_document->command_usage = GNAUNRAR;
-
-				g_free (std_out);
-				g_free (std_err);
-				return TRUE;
-			}
-		}
-		/* The Gna! free software client with Debian naming convention */
-		comics_document->selected_command =
-				g_find_program_in_path ("unrar-free");
-		if (comics_document->selected_command) {
-			comics_document->command_usage = GNAUNRAR;
-			return TRUE;
-		}
-		comics_document->selected_command =
-				g_find_program_in_path ("lsar");
-		if (comics_document->selected_command) {
-			comics_document->command_usage = UNARCHIVER;
-			return TRUE;
-		}
-		comics_document->selected_command =
-				g_find_program_in_path ("bsdtar");
-		if (comics_document->selected_command) {
-			comics_document->command_usage = TAR;
+		if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_RAR))
 			return TRUE;
-		}
-
 	} else if (g_content_type_is_a (mime_type, "application/x-cbz") ||
 		   g_content_type_is_a (mime_type, "application/zip")) {
-		/* InfoZIP's unzip program */
-		comics_document->selected_command =
-				g_find_program_in_path ("unzip");
-		comics_document->alternative_command =
-				g_find_program_in_path ("zipnote");
-		if (comics_document->selected_command &&
-		    comics_document->alternative_command) {
-			comics_document->command_usage = UNZIP;
+		if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_ZIP))
 			return TRUE;
-		}
-		comics_document->selected_command =
-				g_find_program_in_path ("bsdtar");
-		if (comics_document->selected_command) {
-			comics_document->command_usage = TAR;
-			return TRUE;
-		}
-		comics_document->selected_command =
-				g_find_program_in_path ("lsar");
-		if (comics_document->selected_command) {
-			comics_document->command_usage = UNARCHIVER;
-			return TRUE;
-		}
-
 	} else if (g_content_type_is_a (mime_type, "application/x-cb7") ||
 		   g_content_type_is_a (mime_type, "application/x-7z-compressed")) {
-		/* 7zr, 7za and 7z are the commands from the p7zip project able
-		 * to decompress .7z files */
-		comics_document->selected_command =
-			g_find_program_in_path ("7zr");
-		if (comics_document->selected_command) {
-			comics_document->command_usage = P7ZIP;
+		if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_7Z))
 			return TRUE;
-		}
-		comics_document->selected_command =
-			g_find_program_in_path ("7za");
-		if (comics_document->selected_command) {
-			comics_document->command_usage = P7ZIP;
-			return TRUE;
-		}
-		comics_document->selected_command =
-			g_find_program_in_path ("7z");
-		if (comics_document->selected_command) {
-			comics_document->command_usage = P7ZIP;
-			return TRUE;
-		}
-		comics_document->selected_command =
-				g_find_program_in_path ("bsdtar");
-		if (comics_document->selected_command) {
-			comics_document->command_usage = TAR;
-			return TRUE;
-		}
-		comics_document->selected_command =
-				g_find_program_in_path ("lsar");
-		if (comics_document->selected_command) {
-			comics_document->command_usage = UNARCHIVER;
-			return TRUE;
-		}
 	} else if (g_content_type_is_a (mime_type, "application/x-cbt") ||
 		   g_content_type_is_a (mime_type, "application/x-tar")) {
-		/* tar utility (Tape ARchive) */
-		comics_document->selected_command =
-				g_find_program_in_path ("tar");
-		if (comics_document->selected_command) {
-			comics_document->command_usage = TAR;
+	if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_TAR))
 			return TRUE;
-		}
-		comics_document->selected_command =
-				g_find_program_in_path ("bsdtar");
-		if (comics_document->selected_command) {
-			comics_document->command_usage = TAR;
-			return TRUE;
-		}
-		comics_document->selected_command =
-				g_find_program_in_path ("lsar");
-		if (comics_document->selected_command) {
-			comics_document->command_usage = UNARCHIVER;
-			return TRUE;
-		}
 	} else {
 		g_set_error (error,
 			     EV_DOCUMENT_ERROR,
 			     EV_DOCUMENT_ERROR_INVALID,
 			     _("Not a comic book MIME type: %s"),
@@ -459,167 +323,71 @@ comics_check_decompress_command	(gchar          *mime_type,
 			     return FALSE;
 	}
 	g_set_error_literal (error,
 			     EV_DOCUMENT_ERROR,
 			     EV_DOCUMENT_ERROR_INVALID,
-			     _("Can't find an appropriate command to "
-			     "decompress this type of comic book"));
+		             _("libarchive lacks support for this comic book’s "
+			     "compression, please contact your distributor"));
+
 	return FALSE;
 }
 
 static int
 sort_page_names (gconstpointer a,
                  gconstpointer b)
 {
-	const char *name_1, *name_2;
-	gchar *key_1, *key_2;
-	gboolean sort_last_1, sort_last_2;
-	int compare;
-
-	name_1 = * (const char **) a;
-	name_2 = * (const char **) b;
-
-	#define SORT_LAST_CHAR1 '.'
-	#define SORT_LAST_CHAR2 '#'
-
-	sort_last_1 = name_1[0] == SORT_LAST_CHAR1 || name_1[0] == SORT_LAST_CHAR2;
-	sort_last_2 = name_2[0] == SORT_LAST_CHAR1 || name_2[0] == SORT_LAST_CHAR2;
-
-	#undef SORT_LAST_CHAR1
-	#undef SORT_LAST_CHAR2
-
-	if (sort_last_1 && !sort_last_2)
-	{
-		compare = +1;
-	}
-	else if (!sort_last_1 && sort_last_2)
-	{
-		compare = -1;
-	}
-	else
-	{
-		key_1 = g_utf8_collate_key_for_filename (name_1, -1);
-		key_2 = g_utf8_collate_key_for_filename (name_2, -1);
-
-		compare = strcmp (key_1, key_2);
-
-		g_free (key_1);
-		g_free (key_2);
-	}
-
-	return compare;
+	gchar *temp1, *temp2;
+	gint ret;
+	temp1 = g_utf8_collate_key_for_filename (* (const char **) a, -1);
+	temp2 = g_utf8_collate_key_for_filename (* (const char **) b, -1);
+	ret = strcmp (temp1, temp2);
+
+	g_free (temp1);
+	g_free (temp2);
+	return ret;
 }
 
 static gboolean
 comics_document_load (EvDocument *document,
 		      const char *uri,
 		      GError    **error)
 {
 	ComicsDocument *comics_document = COMICS_DOCUMENT (document);
-	GSList *supported_extensions;
-	gchar *std_out;
 	gchar *mime_type;
-	gchar **cb_files, *cb_file;
-	gboolean success;
-	int i, retval;
-	GError *err = NULL;
-
-	comics_document->archive = g_filename_from_uri (uri, NULL, error);
-	if (!comics_document->archive)
-		return FALSE;
-
-	mime_type = ev_file_get_mime_type (uri, FALSE, &err);
-	if (!mime_type) {
-		if (err) {
-			g_propagate_error (error, err);
-		} else {
-			g_set_error_literal (error,
-					     EV_DOCUMENT_ERROR,
-					     EV_DOCUMENT_ERROR_INVALID,
-					     _("Unknown MIME Type"));
-		}
-
-		return FALSE;
-	}
-
-	if (!comics_check_decompress_command (mime_type, comics_document,
-	error)) {
-		g_free (mime_type);
-		return FALSE;
-	} else if (!comics_generate_command_lines (comics_document, error)) {
-		   g_free (mime_type);
-		return FALSE;
-	}
+	GFile *file;
+	file = g_file_new_for_uri (uri);
+	comics_document->archive_path = g_file_get_path (file);
+	g_object_unref (file);
 
-	g_free (mime_type);
-
-	/* Get list of files in archive */
-	success = g_spawn_command_line_sync (comics_document->list_command,
-					     &std_out, NULL, &retval, error);
-
-	if (!success) {
-		return FALSE;
-	} else if (!WIFEXITED(retval) || WEXITSTATUS(retval) != EXIT_SUCCESS) {
+	if (!comics_document->archive_path) {
 		g_set_error_literal (error,
                                      EV_DOCUMENT_ERROR,
                                      EV_DOCUMENT_ERROR_INVALID,
                                      _("File corrupted"));
 		return FALSE;
 	}
 
-	/* FIXME: is this safe against filenames containing \n in the archive ? */
-	cb_files = g_strsplit (std_out, EV_EOL, 0);
+	comics_document->archive_uri = g_strdup (uri);
+	mime_type = ev_file_get_mime_type (uri, FALSE, error);
 
-	g_free (std_out);
+	if (mime_type == NULL)
+		return FALSE;
 
-	if (!cb_files) {
-		g_set_error_literal (error,
-				     EV_DOCUMENT_ERROR,
-				     EV_DOCUMENT_ERROR_INVALID,
-				     _("No files in archive"));
+	if (!comics_check_decompress_support (mime_type, comics_document, error)) {
+		g_free (mime_type);
 		return FALSE;
 	}
 
-        comics_document->page_names = g_ptr_array_sized_new (64);
-
-	supported_extensions = get_supported_image_extensions ();
-	for (i = 0; cb_files[i] != NULL; i++) {
-		if (comics_document->offset != NO_OFFSET) {
-			if (g_utf8_strlen (cb_files[i],-1) >
-			    comics_document->offset) {
-				cb_file =
-					g_utf8_offset_to_pointer (cb_files[i],
-						       comics_document->offset);
-			} else {
-				continue;
-			}
-		} else {
-			cb_file = cb_files[i];
-		}
-		gchar *suffix = g_strrstr (cb_file, ".");
-		if (!suffix)
-			continue;
-		suffix = g_ascii_strdown (suffix + 1, -1);
-		if (g_slist_find_custom (supported_extensions, suffix,
-					 (GCompareFunc) strcmp) != NULL) {
-                        g_ptr_array_add (comics_document->page_names,
-                                         g_strstrip (g_strdup (cb_file)));
-		}
-		g_free (suffix);
-	}
-	g_strfreev (cb_files);
-	g_slist_foreach (supported_extensions, (GFunc) g_free, NULL);
-	g_slist_free (supported_extensions);
+	g_free (mime_type);
 
-	if (comics_document->page_names->len == 0) {
-		g_set_error (error,
-			     EV_DOCUMENT_ERROR,
-			     EV_DOCUMENT_ERROR_INVALID,
-			     _("No images found in archive %s"),
-			     uri);
+	/* Get list of files in archive */
+	comics_document->page_names = comics_document_list (comics_document, error);
+	if (!comics_document->page_names)
 		return FALSE;
-	}
+
+	/* Keep an index */
+	comics_document->page_positions = save_positions (comics_document->page_names);
 
         /* Now sort the pages */
         g_ptr_array_sort (comics_document->page_names, sort_page_names);
 
 	return TRUE;
@@ -630,11 +398,11 @@ comics_document_save (EvDocument *document,
 		      const char *uri,
 		      GError    **error)
 {
 	ComicsDocument *comics_document = COMICS_DOCUMENT (document);
 
-	return ev_xfer_uri_simple (comics_document->archive, uri, error);
+	return ev_xfer_uri_simple (comics_document->archive_uri, uri, error);
 }
 
 static int
 comics_document_get_n_pages (EvDocument *document)
 {
@@ -644,155 +412,186 @@ comics_document_get_n_pages (EvDocument *document)
                 return 0;
 
 	return comics_document->page_names->len;
 }
 
+typedef struct {
+	gboolean got_info;
+	int height;
+	int width;
+} PixbufInfo;
+
+static void
+get_page_size_prepared_cb (GdkPixbufLoader *loader,
+			   int              width,
+			   int              height,
+			   PixbufInfo      *info)
+{
+	info->got_info = TRUE;
+	info->height = height;
+	info->width = width;
+}
+
 static void
 comics_document_get_page_size (EvDocument *document,
 			       EvPage     *page,
 			       double     *width,
 			       double     *height)
 {
 	GdkPixbufLoader *loader;
-	char **argv;
-	guchar buf[1024];
-	gboolean success, got_size = FALSE;
-	gint outpipe = -1;
-	GPid child_pid;
-	gssize bytes;
-	GdkPixbuf *pixbuf;
-	gchar *filename;
+
 	ComicsDocument *comics_document = COMICS_DOCUMENT (document);
 
-	if (!comics_document->decompress_tmp) {
-		argv = extract_argv (document, page->index);
-		success = g_spawn_async_with_pipes (NULL, argv, NULL,
-						    G_SPAWN_SEARCH_PATH |
-						    G_SPAWN_STDERR_TO_DEV_NULL,
-						    NULL, NULL,
-						    &child_pid,
-						    NULL, &outpipe, NULL, NULL);
-		g_strfreev (argv);
-		g_return_if_fail (success == TRUE);
-
-		loader = gdk_pixbuf_loader_new ();
-		g_signal_connect (loader, "area-prepared",
-				  G_CALLBACK (get_page_size_area_prepared_cb),
-				  &got_size);
-
-		while (outpipe >= 0) {
-			bytes = read (outpipe, buf, 1024);
-
-			if (bytes > 0)
-			gdk_pixbuf_loader_write (loader, buf, bytes, NULL);
-			if (bytes <= 0 || got_size) {
-				close (outpipe);
-				outpipe = -1;
-				gdk_pixbuf_loader_close (loader, NULL);
+	const char *page_path;
+	PixbufInfo info;
+	GError *error = NULL;
+
+	page_path = g_ptr_array_index (comics_document->page_names, page->index);
+
+	if (!archive_reopen_if_needed (comics_document, page_path, &error)) {
+		g_warning ("Fatal error opening archive: %s", error->message);
+		g_error_free (error);
+		return;
+	}
+
+	loader = gdk_pixbuf_loader_new ();
+	info.got_info = FALSE;
+	g_signal_connect (loader, "size-prepared",
+			  G_CALLBACK (get_page_size_prepared_cb),
+			  &info);
+
+	while (1) {
+		const char *name;
+		GError *error = NULL;
+
+		if (!ev_archive_read_next_header (comics_document->archive, &error)) {
+			if (error != NULL) {
+				g_warning ("Fatal error handling archive (%s): %s", G_STRFUNC, error->message);
+				g_error_free (error);
 			}
+			break;
 		}
-		pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
-		if (pixbuf) {
-			if (width)
-				*width = gdk_pixbuf_get_width (pixbuf);
-			if (height)
-				*height = gdk_pixbuf_get_height (pixbuf);
-		}
-		g_spawn_close_pid (child_pid);
-		g_object_unref (loader);
-	} else {
-		filename = g_build_filename (comics_document->dir,
-                                             (char *) comics_document->page_names->pdata[page->index],
-					     NULL);
-		pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
-		if (pixbuf) {
-			if (width)
-				*width = gdk_pixbuf_get_width (pixbuf);
-			if (height)
-				*height = gdk_pixbuf_get_height (pixbuf);
-			g_object_unref (pixbuf);
+
+		name = ev_archive_get_entry_pathname (comics_document->archive);
+		if (g_strcmp0 (name, page_path) == 0) {
+			char buf[BLOCK_SIZE];
+			gssize read;
+			gint64 left;
+
+			left = ev_archive_get_entry_size (comics_document->archive);
+			read = ev_archive_read_data (comics_document->archive, buf,
+						     MIN(BLOCK_SIZE, left), &error);
+			while (read > 0 && !info.got_info) {
+				if (!gdk_pixbuf_loader_write (loader, (guchar *) buf, read, &error)) {
+					read = -1;
+					break;
+				}
+				left -= read;
+				read = ev_archive_read_data (comics_document->archive, buf,
+							     MIN(BLOCK_SIZE, left), &error);
+			}
+			if (read < 0) {
+				g_warning ("Fatal error reading '%s' in archive: %s", name, error->message);
+				g_error_free (error);
+			}
+			break;
 		}
-		g_free (filename);
+	}
+
+	gdk_pixbuf_loader_close (loader, NULL);
+	g_object_unref (loader);
+
+	if (info.got_info) {
+		if (width)
+			*width = info.width;
+		if (height)
+			*height = info.height;
 	}
 }
 
 static void
-get_page_size_area_prepared_cb (GdkPixbufLoader *loader,
-				gpointer         data)
+render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
+				gint             width,
+				gint             height,
+				EvRenderContext *rc)
 {
-	gboolean *got_size = data;
-	*got_size = TRUE;
+	// int scaled_width, scaled_height;
+	double scale = rc->scale;
+	int w = (width  * scale + 0.5);
+	int h = (height * scale + 0.5);
+
+	// ev_render_context_compute_scaled_size (rc, width, height, &scaled_width, &scaled_height);
+	gdk_pixbuf_loader_set_size (loader, w, h);
 }
 
 static GdkPixbuf *
 comics_document_render_pixbuf (EvDocument      *document,
 			       EvRenderContext *rc)
 {
 	GdkPixbufLoader *loader;
-	GdkPixbuf *rotated_pixbuf, *tmp_pixbuf;
-	char **argv;
-	guchar buf[4096];
-	gboolean success;
-	gint outpipe = -1;
-	GPid child_pid;
-	gssize bytes;
-	gint width, height;
-	gchar *filename;
+	GdkPixbuf *tmp_pixbuf;
+	GdkPixbuf *rotated_pixbuf = NULL;
 	ComicsDocument *comics_document = COMICS_DOCUMENT (document);
+	const char *page_path;
+	GError *error = NULL;
+
+	page_path = g_ptr_array_index (comics_document->page_names, rc->page->index);
+
+	if (!archive_reopen_if_needed (comics_document, page_path, &error)) {
+		g_warning ("Fatal error opening archive: %s", error->message);
+		g_error_free (error);
+		return NULL;
+	}
 
-	if (!comics_document->decompress_tmp) {
-		argv = extract_argv (document, rc->page->index);
-		success = g_spawn_async_with_pipes (NULL, argv, NULL,
-						    G_SPAWN_SEARCH_PATH |
-						    G_SPAWN_STDERR_TO_DEV_NULL,
-						    NULL, NULL,
-						    &child_pid,
-						    NULL, &outpipe, NULL, NULL);
-		g_strfreev (argv);
-		g_return_val_if_fail (success == TRUE, NULL);
-
-		loader = gdk_pixbuf_loader_new ();
-		g_signal_connect (loader, "size-prepared",
-				  G_CALLBACK (render_pixbuf_size_prepared_cb),
-				  &rc->scale);
-
-		while (outpipe >= 0) {
-			bytes = read (outpipe, buf, 4096);
-
-			if (bytes > 0) {
-				gdk_pixbuf_loader_write (loader, buf, bytes,
-				NULL);
+	loader = gdk_pixbuf_loader_new ();
+	g_signal_connect (loader, "size-prepared",
+			  G_CALLBACK (render_pixbuf_size_prepared_cb),
+			  rc);
+
+	while (1) {
+		const char *name;
+
+		if (!ev_archive_read_next_header (comics_document->archive, &error)) {
+			if (error != NULL) {
+				g_warning ("Fatal error handling archive (%s): %s", G_STRFUNC, error->message);
+				g_error_free (error);
+			}
+			break;
+		}
+
+		name = ev_archive_get_entry_pathname (comics_document->archive);
+		if (g_strcmp0 (name, page_path) == 0) {
+			size_t size = ev_archive_get_entry_size (comics_document->archive);
+			char *buf;
+			ssize_t read;
+
+			buf = g_malloc (size);
+			read = ev_archive_read_data (comics_document->archive, buf, size, &error);
+			if (read <= 0) {
+				if (read < 0) {
+					g_warning ("Fatal error reading '%s' in archive: %s", name, error->message);
+					g_error_free (error);
+				} else {
+					g_warning ("Read an empty file from the archive");
+				}
 			} else {
-				close (outpipe);
-				gdk_pixbuf_loader_close (loader, NULL);
-				outpipe = -1;
+				gdk_pixbuf_loader_write (loader, (guchar *) buf, size, NULL);
 			}
+			g_free (buf);
+			gdk_pixbuf_loader_close (loader, NULL);
+			break;
 		}
-		tmp_pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
-		rotated_pixbuf =
-			gdk_pixbuf_rotate_simple (tmp_pixbuf,
-						  360 - rc->rotation);
-		g_spawn_close_pid (child_pid);
-		g_object_unref (loader);
-	} else {
-		filename =
-			g_build_filename (comics_document->dir,
-                                          (char *) comics_document->page_names->pdata[rc->page->index],
-					  NULL);
-
-		gdk_pixbuf_get_file_info (filename, &width, &height);
-
-		tmp_pixbuf =
-			gdk_pixbuf_new_from_file_at_size (
-				    filename, width * (rc->scale) + 0.5,
-				    height * (rc->scale) + 0.5, NULL);
-		rotated_pixbuf =
-			gdk_pixbuf_rotate_simple (tmp_pixbuf,
-						  360 - rc->rotation);
-		g_free (filename);
-		g_object_unref (tmp_pixbuf);
 	}
+	tmp_pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+	if (tmp_pixbuf) {
+		if ((rc->rotation % 360) == 0)
+			rotated_pixbuf = g_object_ref (tmp_pixbuf);
+		else
+			rotated_pixbuf = gdk_pixbuf_rotate_simple (tmp_pixbuf,
+								   360 - rc->rotation);
+	}
+	g_object_unref (loader);
 	return rotated_pixbuf;
 }
 
 static cairo_surface_t *
 comics_document_render (EvDocument      *document,
@@ -800,83 +599,30 @@ comics_document_render (EvDocument      *document,
 {
 	GdkPixbuf       *pixbuf;
 	cairo_surface_t *surface;
 
 	pixbuf = comics_document_render_pixbuf (document, rc);
+	if (!pixbuf)
+		return NULL;
 	surface = ev_document_misc_surface_from_pixbuf (pixbuf);
-	g_object_unref (pixbuf);
-
+	g_clear_object (&pixbuf);
 	return surface;
 }
 
-static void
-render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
-				gint             width,
-				gint             height,
-				gpointer         data)
-{
-	double *scale = data;
-	int w = (width  * (*scale) + 0.5);
-	int h = (height * (*scale) + 0.5);
-
-	gdk_pixbuf_loader_set_size (loader, w, h);
-}
-
-/**
- * comics_remove_dir: Removes a directory recursively.
- * Returns:
- *   	0 if it was successfully deleted,
- * 	-1 if an error occurred
- */
-static int
-comics_remove_dir (gchar *path_name)
-{
-	GDir  *content_dir;
-	const gchar *filename;
-	gchar *filename_with_path;
-
-	if (g_file_test (path_name, G_FILE_TEST_IS_DIR)) {
-		content_dir = g_dir_open  (path_name, 0, NULL);
-		filename  = g_dir_read_name (content_dir);
-		while (filename) {
-			filename_with_path =
-				g_build_filename (path_name,
-						  filename, NULL);
-			comics_remove_dir (filename_with_path);
-			g_free (filename_with_path);
-			filename = g_dir_read_name (content_dir);
-		}
-		g_dir_close (content_dir);
-	}
-	/* Note from g_remove() documentation: on Windows, it is in general not
-	 * possible to remove a file that is open to some process, or mapped
-	 * into memory.*/
-	return (g_remove (path_name));
-}
-
 static void
 comics_document_finalize (GObject *object)
 {
 	ComicsDocument *comics_document = COMICS_DOCUMENT (object);
 
-	if (comics_document->decompress_tmp) {
-		if (comics_remove_dir (comics_document->dir) == -1)
-			g_warning (_("There was an error deleting “%s”."),
-				   comics_document->dir);
-		g_free (comics_document->dir);
-	}
-
 	if (comics_document->page_names) {
                 g_ptr_array_foreach (comics_document->page_names, (GFunc) g_free, NULL);
                 g_ptr_array_free (comics_document->page_names, TRUE);
 	}
-
-	g_free (comics_document->archive);
-	g_free (comics_document->selected_command);
-	g_free (comics_document->alternative_command);
-	g_free (comics_document->extract_command);
-	g_free (comics_document->list_command);
+	g_clear_pointer (&comics_document->page_positions, g_hash_table_destroy);
+	g_clear_object (&comics_document->archive);
+	g_free (comics_document->archive_path);
+	g_free (comics_document->archive_uri);
 
 	G_OBJECT_CLASS (comics_document_parent_class)->finalize (object);
 }
 
 static void
@@ -895,37 +641,11 @@ comics_document_class_init (ComicsDocumentClass *klass)
 }
 
 static void
 comics_document_init (ComicsDocument *comics_document)
 {
-	comics_document->archive = NULL;
-	comics_document->page_names = NULL;
-	comics_document->extract_command = NULL;
-}
-
-/* Returns a list of file extensions supported by gdk-pixbuf */
-static GSList*
-get_supported_image_extensions(void)
-{
-	GSList *extensions = NULL;
-	GSList *formats = gdk_pixbuf_get_formats ();
-	GSList *l;
-
-	for (l = formats; l != NULL; l = l->next) {
-		int i;
-		gchar **ext = gdk_pixbuf_format_get_extensions (l->data);
-
-		for (i = 0; ext[i] != NULL; i++) {
-			extensions = g_slist_append (extensions,
-						     g_strdup (ext[i]));
-		}
-
-		g_strfreev (ext);
-	}
-
-	g_slist_free (formats);
-	return extensions;
+	comics_document->archive = ev_archive_new ();
 }
 
 static GdkPixbuf *
 comics_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document,
 					  EvRenderContext      *rc,
@@ -969,50 +689,5 @@ static void
 comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface)
 {
 	iface->get_thumbnail = comics_document_thumbnails_get_thumbnail;
 	iface->get_dimensions = comics_document_thumbnails_get_dimensions;
 }
-
-static char**
-extract_argv (EvDocument *document, gint page)
-{
-	ComicsDocument *comics_document = COMICS_DOCUMENT (document);
-	char **argv;
-	char *command_line, *quoted_archive, *quoted_filename;
-	GError *err = NULL;
-
-	if (g_strrstr (comics_document->page_names->pdata[page], "--checkpoint-action="))
-	{
-		g_warning ("File unsupported\n");
-		gtk_main_quit ();
-	}
-
-        if (page >= comics_document->page_names->len)
-                return NULL;
-
-	if (comics_document->regex_arg) {
-		quoted_archive = g_shell_quote (comics_document->archive);
-		quoted_filename =
-			comics_regex_quote (comics_document->page_names->pdata[page]);
-	} else {
-		quoted_archive = g_shell_quote (comics_document->archive);
-		quoted_filename = g_shell_quote (comics_document->page_names->pdata[page]);
-	}
-
-	command_line = g_strdup_printf ("%s %s %s",
-					comics_document->extract_command,
-					quoted_archive,
-					quoted_filename);
-	g_free (quoted_archive);
-	g_free (quoted_filename);
-
-	g_shell_parse_argv (command_line, NULL, &argv, &err);
-	g_free (command_line);
-
-	if (err) {
-		g_warning (_("Error %s"), err->message);
-		g_error_free (err);
-		return NULL;
-	}
-
-	return argv;
-}
diff --git a/backend/comics/comics-document.h b/backend/comics/comics-document.h
index f6a4b44..4417a69 100644
--- a/backend/comics/comics-document.h
+++ b/backend/comics/comics-document.h
@@ -14,13 +14,13 @@
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
-#ifndef __COMICS_DOCUMENT_H__
-#define __COMICS_DOCUMENT_H__
+#pragma once
 
+#include "ev-macros.h"
 #include "ev-document.h"
 
 G_BEGIN_DECLS
 
 #define COMICS_TYPE_DOCUMENT    (comics_document_get_type ())
@@ -28,11 +28,10 @@ G_BEGIN_DECLS
 #define COMICS_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), COMICS_TYPE_DOCUMENT))
 
 typedef struct _ComicsDocument ComicsDocument;
 
 GType                 comics_document_get_type (void) G_GNUC_CONST;
+GType                 register_atril_backend  (GTypeModule *module);
 
-G_MODULE_EXPORT GType register_atril_backend  (GTypeModule *module);
 
 G_END_DECLS
 
-#endif /* __COMICS_DOCUMENT_H__ */
diff --git a/backend/comics/ev-archive.c b/backend/comics/ev-archive.c
new file mode 100644
index 0000000..568e162
--- /dev/null
+++ b/backend/comics/ev-archive.c
@@ -0,0 +1,323 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
+/*
+ * Copyright (C) 2017, Bastien Nocera <hadess@hadess.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+#include "ev-archive.h"
+
+#include <archive.h>
+#include <archive_entry.h>
+#include <gio/gio.h>
+
+#define BUFFER_SIZE (64 * 1024)
+
+struct _EvArchive {
+	GObject parent_instance;
+	EvArchiveType type;
+
+	/* libarchive */
+	struct archive *libar;
+	struct archive_entry *libar_entry;
+};
+
+G_DEFINE_TYPE(EvArchive, ev_archive, G_TYPE_OBJECT);
+
+static void
+ev_archive_finalize (GObject *object)
+{
+	EvArchive *archive = EV_ARCHIVE (object);
+
+	switch (archive->type) {
+	case EV_ARCHIVE_TYPE_RAR:
+	case EV_ARCHIVE_TYPE_ZIP:
+	case EV_ARCHIVE_TYPE_7Z:
+	case EV_ARCHIVE_TYPE_TAR:
+		g_clear_pointer (&archive->libar, archive_free);
+		break;
+	default:
+		break;
+	}
+
+	G_OBJECT_CLASS (ev_archive_parent_class)->finalize (object);
+}
+
+static void
+ev_archive_class_init (EvArchiveClass *klass)
+{
+        GObjectClass *object_class = (GObjectClass *) klass;
+
+        object_class->finalize = ev_archive_finalize;
+}
+
+EvArchive *
+ev_archive_new (void)
+{
+	return g_object_new (EV_TYPE_ARCHIVE, NULL);
+}
+
+static void
+libarchive_set_archive_type (EvArchive *archive,
+			     EvArchiveType archive_type)
+{
+	archive->type = archive_type;
+	archive->libar = archive_read_new ();
+
+	if (archive_type == EV_ARCHIVE_TYPE_ZIP)
+		archive_read_support_format_zip (archive->libar);
+	else if (archive_type == EV_ARCHIVE_TYPE_7Z)
+		archive_read_support_format_7zip (archive->libar);
+	else if (archive_type == EV_ARCHIVE_TYPE_TAR)
+		archive_read_support_format_tar (archive->libar);
+	else if (archive_type == EV_ARCHIVE_TYPE_RAR) {
+		archive_read_support_format_rar (archive->libar);
+		archive_read_support_format_rar5 (archive->libar);
+	} else
+		g_assert_not_reached ();
+}
+
+EvArchiveType
+ev_archive_get_archive_type (EvArchive *archive)
+{
+	g_return_val_if_fail (EV_IS_ARCHIVE (archive), EV_ARCHIVE_TYPE_NONE);
+
+	return archive->type;
+}
+
+gboolean
+ev_archive_set_archive_type (EvArchive *archive,
+			     EvArchiveType archive_type)
+{
+	g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE);
+	g_return_val_if_fail (archive->type == EV_ARCHIVE_TYPE_NONE, FALSE);
+
+	switch (archive_type) {
+	case EV_ARCHIVE_TYPE_RAR:
+	case EV_ARCHIVE_TYPE_ZIP:
+	case EV_ARCHIVE_TYPE_7Z:
+	case EV_ARCHIVE_TYPE_TAR:
+		libarchive_set_archive_type (archive, archive_type);
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+
+	return TRUE;
+}
+
+gboolean
+ev_archive_open_filename (EvArchive   *archive,
+			  const char  *path,
+			  GError     **error)
+{
+	int r;
+
+	g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE);
+	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, FALSE);
+	g_return_val_if_fail (path != NULL, FALSE);
+
+	switch (archive->type) {
+	case EV_ARCHIVE_TYPE_NONE:
+		g_assert_not_reached ();
+	case EV_ARCHIVE_TYPE_RAR:
+	case EV_ARCHIVE_TYPE_ZIP:
+	case EV_ARCHIVE_TYPE_7Z:
+	case EV_ARCHIVE_TYPE_TAR:
+		r = archive_read_open_filename (archive->libar, path, BUFFER_SIZE);
+		if (r != ARCHIVE_OK) {
+			g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+				     "Error opening archive: %s", archive_error_string (archive->libar));
+			return FALSE;
+		}
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+libarchive_read_next_header (EvArchive *archive,
+			     GError   **error)
+{
+	while (1) {
+		int r;
+
+		r = archive_read_next_header (archive->libar, &archive->libar_entry);
+		if (r != ARCHIVE_OK) {
+			if (r != ARCHIVE_EOF)
+				g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+					     "Error reading archive: %s", archive_error_string (archive->libar));
+			return FALSE;
+		}
+
+		if (archive_entry_filetype (archive->libar_entry) != AE_IFREG) {
+			g_debug ("Skipping '%s' as it's not a regular file",
+				 archive_entry_pathname (archive->libar_entry));
+			continue;
+		}
+
+		g_debug ("At header for file '%s'", archive_entry_pathname (archive->libar_entry));
+
+		break;
+	}
+
+	return TRUE;
+}
+
+gboolean
+ev_archive_read_next_header (EvArchive *archive,
+			     GError   **error)
+{
+	g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE);
+	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, FALSE);
+
+	switch (archive->type) {
+	case EV_ARCHIVE_TYPE_NONE:
+		g_assert_not_reached ();
+	case EV_ARCHIVE_TYPE_RAR:
+	case EV_ARCHIVE_TYPE_ZIP:
+	case EV_ARCHIVE_TYPE_7Z:
+	case EV_ARCHIVE_TYPE_TAR:
+		return libarchive_read_next_header (archive, error);
+	}
+
+	return FALSE;
+}
+
+gboolean
+ev_archive_at_entry (EvArchive *archive)
+{
+	g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE);
+	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, FALSE);
+
+	return (archive->libar_entry != NULL);
+}
+
+const char *
+ev_archive_get_entry_pathname (EvArchive *archive)
+{
+	g_return_val_if_fail (EV_IS_ARCHIVE (archive), NULL);
+	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, NULL);
+
+	switch (archive->type) {
+	case EV_ARCHIVE_TYPE_NONE:
+		g_assert_not_reached ();
+	case EV_ARCHIVE_TYPE_RAR:
+	case EV_ARCHIVE_TYPE_ZIP:
+	case EV_ARCHIVE_TYPE_7Z:
+	case EV_ARCHIVE_TYPE_TAR:
+		g_return_val_if_fail (archive->libar_entry != NULL, NULL);
+		return archive_entry_pathname (archive->libar_entry);
+	}
+
+	return NULL;
+}
+
+gint64
+ev_archive_get_entry_size (EvArchive *archive)
+{
+	g_return_val_if_fail (EV_IS_ARCHIVE (archive), -1);
+	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, -1);
+
+	switch (archive->type) {
+	case EV_ARCHIVE_TYPE_NONE:
+		g_assert_not_reached ();
+	case EV_ARCHIVE_TYPE_RAR:
+	case EV_ARCHIVE_TYPE_ZIP:
+	case EV_ARCHIVE_TYPE_7Z:
+	case EV_ARCHIVE_TYPE_TAR:
+		g_return_val_if_fail (archive->libar_entry != NULL, -1);
+		return archive_entry_size (archive->libar_entry);
+	}
+
+	return -1;
+}
+
+gboolean
+ev_archive_get_entry_is_encrypted (EvArchive *archive)
+{
+	g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE);
+	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, FALSE);
+
+	switch (archive->type) {
+	case EV_ARCHIVE_TYPE_NONE:
+		g_assert_not_reached ();
+	case EV_ARCHIVE_TYPE_RAR:
+	case EV_ARCHIVE_TYPE_ZIP:
+	case EV_ARCHIVE_TYPE_7Z:
+	case EV_ARCHIVE_TYPE_TAR:
+		g_return_val_if_fail (archive->libar_entry != NULL, -1);
+		return archive_entry_is_encrypted (archive->libar_entry);
+	}
+
+	return FALSE;
+}
+
+gssize
+ev_archive_read_data (EvArchive *archive,
+		      void      *buf,
+		      gsize      count,
+		      GError   **error)
+{
+	gssize r = -1;
+
+	g_return_val_if_fail (EV_IS_ARCHIVE (archive), -1);
+	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, -1);
+
+	switch (archive->type) {
+	case EV_ARCHIVE_TYPE_NONE:
+		g_assert_not_reached ();
+	case EV_ARCHIVE_TYPE_RAR:
+	case EV_ARCHIVE_TYPE_ZIP:
+	case EV_ARCHIVE_TYPE_7Z:
+	case EV_ARCHIVE_TYPE_TAR:
+		g_return_val_if_fail (archive->libar_entry != NULL, -1);
+		r = archive_read_data (archive->libar, buf, count);
+		if (r < 0) {
+			g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+				     "Failed to decompress data: %s", archive_error_string (archive->libar));
+		}
+		break;
+	}
+
+	return r;
+}
+
+void
+ev_archive_reset (EvArchive *archive)
+{
+	g_return_if_fail (EV_IS_ARCHIVE (archive));
+	g_return_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE);
+
+	switch (archive->type) {
+	case EV_ARCHIVE_TYPE_RAR:
+	case EV_ARCHIVE_TYPE_ZIP:
+	case EV_ARCHIVE_TYPE_7Z:
+	case EV_ARCHIVE_TYPE_TAR:
+		g_clear_pointer (&archive->libar, archive_free);
+		libarchive_set_archive_type (archive, archive->type);
+		archive->libar_entry = NULL;
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+}
+
+static void
+ev_archive_init (EvArchive *archive)
+{
+}
diff --git a/backend/comics/ev-archive.h b/backend/comics/ev-archive.h
new file mode 100644
index 0000000..b4e1399
--- /dev/null
+++ b/backend/comics/ev-archive.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
+/*
+ * Copyright (C) 2017, Bastien Nocera <hadess@hadess.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EV_TYPE_ARCHIVE ev_archive_get_type ()
+G_DECLARE_FINAL_TYPE (EvArchive, ev_archive, EV, ARCHIVE, GObject)
+
+typedef enum {
+	EV_ARCHIVE_TYPE_NONE = 0,
+	EV_ARCHIVE_TYPE_RAR,
+	EV_ARCHIVE_TYPE_ZIP,
+	EV_ARCHIVE_TYPE_7Z,
+	EV_ARCHIVE_TYPE_TAR
+} EvArchiveType;
+
+EvArchive     *ev_archive_new                (void);
+gboolean       ev_archive_set_archive_type   (EvArchive     *archive,
+					      EvArchiveType  archive_type);
+EvArchiveType  ev_archive_get_archive_type   (EvArchive     *archive);
+gboolean       ev_archive_open_filename      (EvArchive     *archive,
+					      const char    *path,
+					      GError       **error);
+gboolean       ev_archive_read_next_header   (EvArchive     *archive,
+					      GError       **error);
+gboolean       ev_archive_at_entry           (EvArchive     *archive);
+const char    *ev_archive_get_entry_pathname (EvArchive     *archive);
+gint64         ev_archive_get_entry_size     (EvArchive     *archive);
+gboolean       ev_archive_get_entry_is_encrypted (EvArchive *archive);
+gssize         ev_archive_read_data          (EvArchive     *archive,
+					      void          *buf,
+					      gsize          count,
+					      GError       **error);
+void           ev_archive_reset              (EvArchive     *archive);
+
+G_END_DECLS
diff --git a/configure.ac b/configure.ac
index c037706..86bc28d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -579,12 +579,15 @@ AC_ARG_ENABLE(comics,
     [AS_HELP_STRING([--enable-comics],
                     [Compile with support for comic book archives])],
     [enable_comics=$enableval],
     [enable_comics=yes])
 
+COMICS_DEPS="libarchive"
 if test "x$enable_comics" = "xyes"; then
     AC_DEFINE([ENABLE_COMICS], [1], [Enable support for comics.])
+    PKG_CHECK_MODULES([COMICS], [$COMICS_DEPS])
+    AC_SUBST(COMICS_LIBS)
 fi
 AM_CONDITIONAL(ENABLE_COMICS, test x$enable_comics = xyes)
 
 dnl ================== End of comic book checks ============================================
 
diff --git a/libdocument/ev-document.h b/libdocument/ev-document.h
index 744d0f9..ea0016c 100644
--- a/libdocument/ev-document.h
+++ b/libdocument/ev-document.h
@@ -54,10 +54,11 @@ typedef struct _EvDocumentPrivate EvDocumentPrivate;
 #define EV_DOC_MUTEX_UNLOCK (ev_document_doc_mutex_unlock ())
 
 typedef enum
 {
         EV_DOCUMENT_ERROR_INVALID,
+        EV_DOCUMENT_ERROR_UNSUPPORTED_CONTENT,
         EV_DOCUMENT_ERROR_ENCRYPTED
 } EvDocumentError;
 
 typedef struct {
         double x;

Vulnerable code: 	comics-document.h
#include <sys/wait.h>

typedef enum
{
	RARLABS,
	GNAUNRAR,
	UNZIP,
	P7ZIP,
	TAR,
	UNARCHIVER
} ComicBookDecompressType;
	EvDocument parent_instance;

	gchar    *archive, *dir;
	GPtrArray *page_names;
	gchar    *selected_command, *alternative_command;
	gchar    *extract_command, *list_command, *decompress_tmp;
	gboolean regex_arg;
	gint     offset;
	ComicBookDecompressType command_usage;
#define OFFSET_7Z 53
#define OFFSET_ZIP 2
#define NO_OFFSET 0

/* For perfomance reasons of 7z* we've choosen to decompress on the temporary
 * directory instead of decompressing on the stdout */

/**
 * @extract: command line arguments to pass to extract a file from the archive
 *   to stdout.
 * @list: command line arguments to list the archive contents
 * @decompress_tmp: command line arguments to pass to extract the archive
 *   into a directory.
 * @regex_arg: whether the command can accept regex expressions
 * @offset: the position offset of the filename on each line in the output of
 *   running the @list command
 */
typedef struct {
        char *extract;
        char *list;
        char *decompress_tmp;
        gboolean regex_arg;
        gint offset;
} ComicBookDecompressCommand;
static const ComicBookDecompressCommand command_usage_def[] = {
        /* RARLABS unrar */
	{"%s p -c- -ierr --", "%s vb -c- -- %s", NULL             , FALSE, NO_OFFSET},
        /* GNA! unrar */
	{NULL               , "%s t %s"        , "%s -xf %s %s"   , FALSE, NO_OFFSET},
        /* unzip */
	{"%s -p -C --"      , "%s %s"          , NULL             , TRUE , OFFSET_ZIP},
        /* 7zip */
	{NULL               , "%s l -- %s"     , "%s x -y %s -o%s", FALSE, OFFSET_7Z},
        /* tar */
	{"%s -xOf"          , "%s -tf %s"      , NULL             , FALSE, NO_OFFSET},
	/* UNARCHIVER */
	{"unar -o -"	    , "%s %s"	       , NULL		  , FALSE, NO_OFFSET}
};
static void       comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface);
static GSList*    get_supported_image_extensions (void);
static void       get_page_size_area_prepared_cb (GdkPixbufLoader *loader,
						  gpointer data);
static void       render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
						  gint width,
						  gint height,
						  gpointer data);
static char**     extract_argv                   (EvDocument *document,
						  gint page);
EV_BACKEND_REGISTER_WITH_CODE (ComicsDocument, comics_document,
	{
		EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS,
						comics_document_document_thumbnails_iface_init);
	} );

/**
 * comics_regex_quote:
 * @unquoted_string: a literal string
 *
 * Quotes a string so unzip will not interpret the regex expressions of
 * @unquoted_string. Basically, this functions uses [] to disable regex
 * expressions. The return value must be freed with * g_free()
 *
 * Return value: quoted and disabled-regex string
 **/
static gchar *
comics_regex_quote (const gchar *unquoted_string)
	const gchar *p;
	GString *dest;

	dest = g_string_new ("'");

	p = unquoted_string;

	while (*p) {
		switch (*p) {
			/* * matches a sequence of 0 or more characters */
			case ('*'):
			/* ? matches exactly 1 charactere */
			case ('?'):
			/* [...]  matches any single character found inside
			 * the brackets. Disabling the first bracket is enough.
			 */
			case ('['):
				g_string_append (dest, "[");
				g_string_append_c (dest, *p);
				g_string_append (dest, "]");
				break;
			/* Because \ escapes regex expressions that we are
			 * disabling for unzip, we need to disable \ too */
			case ('\\'):
				g_string_append (dest, "[\\\\]");
				break;
			/* Escape single quote inside the string */
			case ('\''):
				g_string_append (dest, "'\\''");
				break;
			default:
				g_string_append_c (dest, *p);
				break;
		++p;
	g_string_append_c (dest, '\'');
	return g_string_free (dest, FALSE);
/* This function manages the command for decompressing a comic book */
static gboolean
comics_decompress_temp_dir (const gchar *command_decompress_tmp,
			    const gchar *command,
			    GError      **error)
	gboolean success;
	gchar *std_out, *basename;
	GError *err = NULL;
	gint retval;

	success = g_spawn_command_line_sync (command_decompress_tmp, &std_out,
					     NULL, &retval, &err);
	basename = g_path_get_basename (command);
	if (!success) {
		g_set_error (error,
			     EV_DOCUMENT_ERROR,
			     EV_DOCUMENT_ERROR_INVALID,
			     _("Error launching the command “%s” in order to "
			     "decompress the comic book: %s"),
			     basename,
			     err->message);
		g_error_free (err);
	} else if (WIFEXITED (retval)) {
		if (WEXITSTATUS (retval) == EXIT_SUCCESS) {
			g_free (std_out);
			g_free (basename);
			return TRUE;
		} else {
			g_set_error (error,
				     _("The command “%s” failed at "
				     "decompressing the comic book."),
				     basename);
			g_free (std_out);
	} else {
		g_set_error (error,
			     EV_DOCUMENT_ERROR,
			     EV_DOCUMENT_ERROR_INVALID,
			     _("The command “%s” did not end normally."),
			     basename);
		g_free (std_out);
	g_free (basename);
	return FALSE;
/* This function shows how to use the choosen command for decompressing a
 * comic book file. It modifies fields of the ComicsDocument struct with
 * this information */
static gboolean
comics_generate_command_lines (ComicsDocument *comics_document,
			       GError         **error)
	gchar *quoted_file, *quoted_file_aux;
	gchar *quoted_command;
	ComicBookDecompressType type;

	type = comics_document->command_usage;
	comics_document->regex_arg = command_usage_def[type].regex_arg;
	quoted_command = g_shell_quote (comics_document->selected_command);
	if (comics_document->regex_arg) {
		quoted_file = comics_regex_quote (comics_document->archive);
		quoted_file_aux = g_shell_quote (comics_document->archive);
		comics_document->list_command =
			   g_strdup_printf (command_usage_def[type].list,
			                    comics_document->alternative_command,
			                    quoted_file_aux);
		g_free (quoted_file_aux);
	} else {
		quoted_file = g_shell_quote (comics_document->archive);
		comics_document->list_command =
				g_strdup_printf (command_usage_def[type].list,
				                 quoted_command, quoted_file);
	}
	comics_document->extract_command =
			    g_strdup_printf (command_usage_def[type].extract,
				             quoted_command);
	comics_document->offset = command_usage_def[type].offset;
	if (command_usage_def[type].decompress_tmp) {
		comics_document->dir = ev_mkdtemp ("atril-comics-XXXXXX", error);
                if (comics_document->dir == NULL)
                        return FALSE;

		/* unrar-free can't create directories, but ev_mkdtemp already created the dir */

		comics_document->decompress_tmp =
			g_strdup_printf (command_usage_def[type].decompress_tmp,
					 quoted_command, quoted_file,
					 comics_document->dir);
		g_free (quoted_file);
		g_free (quoted_command);

		if (!comics_decompress_temp_dir (comics_document->decompress_tmp,
		    comics_document->selected_command, error))
			return FALSE;
		else
			return TRUE;
	} else {
		g_free (quoted_file);
		g_free (quoted_command);
		return TRUE;
	}
/* This function chooses an external command for decompressing a comic
 * book based on its mime tipe. */
comics_check_decompress_command	(gchar          *mime_type,
	gboolean success;
	gchar *std_out, *std_err;
	gint retval;
	GError *err = NULL;

	/* FIXME, use proper cbr/cbz mime types once they're
	 * included in shared-mime-info */

	        /* The RARLAB provides a no-charge proprietary (freeware)
	        * decompress-only client for Linux called unrar. Another
		* option is a GPLv2-licensed command-line tool developed by
		* the Gna! project. Confusingly enough, the free software RAR
		* decoder is also named unrar. For this reason we need to add
		* some lines for disambiguation. Sorry for the added the
		* complexity but it's life :)
		* Finally, some distributions, like Debian, rename this free
		* option as unrar-free.
		* */
		comics_document->selected_command =
					g_find_program_in_path ("unrar");
		if (comics_document->selected_command) {
			/* We only use std_err to avoid printing useless error
			 * messages on the terminal */
			success =
				g_spawn_command_line_sync (
				              comics_document->selected_command,
							   &std_out, &std_err,
							   &retval, &err);
			if (!success) {
				g_propagate_error (error, err);
				g_error_free (err);
				return FALSE;
			/* I don't check retval status because RARLAB unrar
			 * doesn't have a way to return 0 without involving an
			 * operation with a file*/
			} else if (WIFEXITED (retval)) {
				if (g_strrstr (std_out,"freeware") != NULL)
					/* The RARLAB freeware client */
					comics_document->command_usage = RARLABS;
				else
					/* The Gna! free software client */
					comics_document->command_usage = GNAUNRAR;

				g_free (std_out);
				g_free (std_err);
				return TRUE;
			}
		}
		/* The Gna! free software client with Debian naming convention */
		comics_document->selected_command =
				g_find_program_in_path ("unrar-free");
		if (comics_document->selected_command) {
			comics_document->command_usage = GNAUNRAR;
			return TRUE;
		}
		comics_document->selected_command =
				g_find_program_in_path ("lsar");
		if (comics_document->selected_command) {
			comics_document->command_usage = UNARCHIVER;
			return TRUE;
		}
		comics_document->selected_command =
				g_find_program_in_path ("bsdtar");
		if (comics_document->selected_command) {
			comics_document->command_usage = TAR;
		}

		/* InfoZIP's unzip program */
		comics_document->selected_command =
				g_find_program_in_path ("unzip");
		comics_document->alternative_command =
				g_find_program_in_path ("zipnote");
		if (comics_document->selected_command &&
		    comics_document->alternative_command) {
			comics_document->command_usage = UNZIP;
		}
		comics_document->selected_command =
				g_find_program_in_path ("bsdtar");
		if (comics_document->selected_command) {
			comics_document->command_usage = TAR;
			return TRUE;
		}
		comics_document->selected_command =
				g_find_program_in_path ("lsar");
		if (comics_document->selected_command) {
			comics_document->command_usage = UNARCHIVER;
			return TRUE;
		}

		/* 7zr, 7za and 7z are the commands from the p7zip project able
		 * to decompress .7z files */
		comics_document->selected_command =
			g_find_program_in_path ("7zr");
		if (comics_document->selected_command) {
			comics_document->command_usage = P7ZIP;
		}
		comics_document->selected_command =
			g_find_program_in_path ("7za");
		if (comics_document->selected_command) {
			comics_document->command_usage = P7ZIP;
			return TRUE;
		}
		comics_document->selected_command =
			g_find_program_in_path ("7z");
		if (comics_document->selected_command) {
			comics_document->command_usage = P7ZIP;
			return TRUE;
		}
		comics_document->selected_command =
				g_find_program_in_path ("bsdtar");
		if (comics_document->selected_command) {
			comics_document->command_usage = TAR;
			return TRUE;
		}
		comics_document->selected_command =
				g_find_program_in_path ("lsar");
		if (comics_document->selected_command) {
			comics_document->command_usage = UNARCHIVER;
			return TRUE;
		}
		/* tar utility (Tape ARchive) */
		comics_document->selected_command =
				g_find_program_in_path ("tar");
		if (comics_document->selected_command) {
			comics_document->command_usage = TAR;
		}
		comics_document->selected_command =
				g_find_program_in_path ("bsdtar");
		if (comics_document->selected_command) {
			comics_document->command_usage = TAR;
			return TRUE;
		}
		comics_document->selected_command =
				g_find_program_in_path ("lsar");
		if (comics_document->selected_command) {
			comics_document->command_usage = UNARCHIVER;
			return TRUE;
		}
			     _("Can't find an appropriate command to "
			     "decompress this type of comic book"));
	const char *name_1, *name_2;
	gchar *key_1, *key_2;
	gboolean sort_last_1, sort_last_2;
	int compare;

	name_1 = * (const char **) a;
	name_2 = * (const char **) b;

	#define SORT_LAST_CHAR1 '.'
	#define SORT_LAST_CHAR2 '#'

	sort_last_1 = name_1[0] == SORT_LAST_CHAR1 || name_1[0] == SORT_LAST_CHAR2;
	sort_last_2 = name_2[0] == SORT_LAST_CHAR1 || name_2[0] == SORT_LAST_CHAR2;

	#undef SORT_LAST_CHAR1
	#undef SORT_LAST_CHAR2

	if (sort_last_1 && !sort_last_2)
	{
		compare = +1;
	}
	else if (!sort_last_1 && sort_last_2)
	{
		compare = -1;
	}
	else
	{
		key_1 = g_utf8_collate_key_for_filename (name_1, -1);
		key_2 = g_utf8_collate_key_for_filename (name_2, -1);

		compare = strcmp (key_1, key_2);

		g_free (key_1);
		g_free (key_2);
	}

	return compare;
	GSList *supported_extensions;
	gchar *std_out;
	gchar **cb_files, *cb_file;
	gboolean success;
	int i, retval;
	GError *err = NULL;

	comics_document->archive = g_filename_from_uri (uri, NULL, error);
	if (!comics_document->archive)
		return FALSE;

	mime_type = ev_file_get_mime_type (uri, FALSE, &err);
	if (!mime_type) {
		if (err) {
			g_propagate_error (error, err);
		} else {
			g_set_error_literal (error,
					     EV_DOCUMENT_ERROR,
					     EV_DOCUMENT_ERROR_INVALID,
					     _("Unknown MIME Type"));
		}

		return FALSE;
	}

	if (!comics_check_decompress_command (mime_type, comics_document,
	error)) {
		g_free (mime_type);
		return FALSE;
	} else if (!comics_generate_command_lines (comics_document, error)) {
		   g_free (mime_type);
		return FALSE;
	}
	g_free (mime_type);

	/* Get list of files in archive */
	success = g_spawn_command_line_sync (comics_document->list_command,
					     &std_out, NULL, &retval, error);

	if (!success) {
		return FALSE;
	} else if (!WIFEXITED(retval) || WEXITSTATUS(retval) != EXIT_SUCCESS) {
	/* FIXME: is this safe against filenames containing \n in the archive ? */
	cb_files = g_strsplit (std_out, EV_EOL, 0);
	g_free (std_out);
	if (!cb_files) {
		g_set_error_literal (error,
				     EV_DOCUMENT_ERROR,
				     EV_DOCUMENT_ERROR_INVALID,
				     _("No files in archive"));
        comics_document->page_names = g_ptr_array_sized_new (64);

	supported_extensions = get_supported_image_extensions ();
	for (i = 0; cb_files[i] != NULL; i++) {
		if (comics_document->offset != NO_OFFSET) {
			if (g_utf8_strlen (cb_files[i],-1) >
			    comics_document->offset) {
				cb_file =
					g_utf8_offset_to_pointer (cb_files[i],
						       comics_document->offset);
			} else {
				continue;
			}
		} else {
			cb_file = cb_files[i];
		}
		gchar *suffix = g_strrstr (cb_file, ".");
		if (!suffix)
			continue;
		suffix = g_ascii_strdown (suffix + 1, -1);
		if (g_slist_find_custom (supported_extensions, suffix,
					 (GCompareFunc) strcmp) != NULL) {
                        g_ptr_array_add (comics_document->page_names,
                                         g_strstrip (g_strdup (cb_file)));
		}
		g_free (suffix);
	}
	g_strfreev (cb_files);
	g_slist_foreach (supported_extensions, (GFunc) g_free, NULL);
	g_slist_free (supported_extensions);
	if (comics_document->page_names->len == 0) {
		g_set_error (error,
			     EV_DOCUMENT_ERROR,
			     EV_DOCUMENT_ERROR_INVALID,
			     _("No images found in archive %s"),
			     uri);
	}
	return ev_xfer_uri_simple (comics_document->archive, uri, error);
	char **argv;
	guchar buf[1024];
	gboolean success, got_size = FALSE;
	gint outpipe = -1;
	GPid child_pid;
	gssize bytes;
	GdkPixbuf *pixbuf;
	gchar *filename;
	if (!comics_document->decompress_tmp) {
		argv = extract_argv (document, page->index);
		success = g_spawn_async_with_pipes (NULL, argv, NULL,
						    G_SPAWN_SEARCH_PATH |
						    G_SPAWN_STDERR_TO_DEV_NULL,
						    NULL, NULL,
						    &child_pid,
						    NULL, &outpipe, NULL, NULL);
		g_strfreev (argv);
		g_return_if_fail (success == TRUE);

		loader = gdk_pixbuf_loader_new ();
		g_signal_connect (loader, "area-prepared",
				  G_CALLBACK (get_page_size_area_prepared_cb),
				  &got_size);

		while (outpipe >= 0) {
			bytes = read (outpipe, buf, 1024);

			if (bytes > 0)
			gdk_pixbuf_loader_write (loader, buf, bytes, NULL);
			if (bytes <= 0 || got_size) {
				close (outpipe);
				outpipe = -1;
				gdk_pixbuf_loader_close (loader, NULL);
		pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
		if (pixbuf) {
			if (width)
				*width = gdk_pixbuf_get_width (pixbuf);
			if (height)
				*height = gdk_pixbuf_get_height (pixbuf);
		}
		g_spawn_close_pid (child_pid);
		g_object_unref (loader);
	} else {
		filename = g_build_filename (comics_document->dir,
                                             (char *) comics_document->page_names->pdata[page->index],
					     NULL);
		pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
		if (pixbuf) {
			if (width)
				*width = gdk_pixbuf_get_width (pixbuf);
			if (height)
				*height = gdk_pixbuf_get_height (pixbuf);
			g_object_unref (pixbuf);
		g_free (filename);
get_page_size_area_prepared_cb (GdkPixbufLoader *loader,
				gpointer         data)
	gboolean *got_size = data;
	*got_size = TRUE;
	GdkPixbuf *rotated_pixbuf, *tmp_pixbuf;
	char **argv;
	guchar buf[4096];
	gboolean success;
	gint outpipe = -1;
	GPid child_pid;
	gssize bytes;
	gint width, height;
	gchar *filename;
	if (!comics_document->decompress_tmp) {
		argv = extract_argv (document, rc->page->index);
		success = g_spawn_async_with_pipes (NULL, argv, NULL,
						    G_SPAWN_SEARCH_PATH |
						    G_SPAWN_STDERR_TO_DEV_NULL,
						    NULL, NULL,
						    &child_pid,
						    NULL, &outpipe, NULL, NULL);
		g_strfreev (argv);
		g_return_val_if_fail (success == TRUE, NULL);

		loader = gdk_pixbuf_loader_new ();
		g_signal_connect (loader, "size-prepared",
				  G_CALLBACK (render_pixbuf_size_prepared_cb),
				  &rc->scale);

		while (outpipe >= 0) {
			bytes = read (outpipe, buf, 4096);

			if (bytes > 0) {
				gdk_pixbuf_loader_write (loader, buf, bytes,
				NULL);
				close (outpipe);
				gdk_pixbuf_loader_close (loader, NULL);
				outpipe = -1;
		tmp_pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
		rotated_pixbuf =
			gdk_pixbuf_rotate_simple (tmp_pixbuf,
						  360 - rc->rotation);
		g_spawn_close_pid (child_pid);
		g_object_unref (loader);
	} else {
		filename =
			g_build_filename (comics_document->dir,
                                          (char *) comics_document->page_names->pdata[rc->page->index],
					  NULL);

		gdk_pixbuf_get_file_info (filename, &width, &height);

		tmp_pixbuf =
			gdk_pixbuf_new_from_file_at_size (
				    filename, width * (rc->scale) + 0.5,
				    height * (rc->scale) + 0.5, NULL);
		rotated_pixbuf =
			gdk_pixbuf_rotate_simple (tmp_pixbuf,
						  360 - rc->rotation);
		g_free (filename);
		g_object_unref (tmp_pixbuf);
	g_object_unref (pixbuf);

static void
render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
				gint             width,
				gint             height,
				gpointer         data)
{
	double *scale = data;
	int w = (width  * (*scale) + 0.5);
	int h = (height * (*scale) + 0.5);

	gdk_pixbuf_loader_set_size (loader, w, h);
}

/**
 * comics_remove_dir: Removes a directory recursively.
 * Returns:
 *   	0 if it was successfully deleted,
 * 	-1 if an error occurred
 */
static int
comics_remove_dir (gchar *path_name)
{
	GDir  *content_dir;
	const gchar *filename;
	gchar *filename_with_path;

	if (g_file_test (path_name, G_FILE_TEST_IS_DIR)) {
		content_dir = g_dir_open  (path_name, 0, NULL);
		filename  = g_dir_read_name (content_dir);
		while (filename) {
			filename_with_path =
				g_build_filename (path_name,
						  filename, NULL);
			comics_remove_dir (filename_with_path);
			g_free (filename_with_path);
			filename = g_dir_read_name (content_dir);
		}
		g_dir_close (content_dir);
	}
	/* Note from g_remove() documentation: on Windows, it is in general not
	 * possible to remove a file that is open to some process, or mapped
	 * into memory.*/
	return (g_remove (path_name));
}

	if (comics_document->decompress_tmp) {
		if (comics_remove_dir (comics_document->dir) == -1)
			g_warning (_("There was an error deleting “%s”."),
				   comics_document->dir);
		g_free (comics_document->dir);
	}


	g_free (comics_document->archive);
	g_free (comics_document->selected_command);
	g_free (comics_document->alternative_command);
	g_free (comics_document->extract_command);
	g_free (comics_document->list_command);
	comics_document->archive = NULL;
	comics_document->page_names = NULL;
	comics_document->extract_command = NULL;
}

/* Returns a list of file extensions supported by gdk-pixbuf */
static GSList*
get_supported_image_extensions(void)
{
	GSList *extensions = NULL;
	GSList *formats = gdk_pixbuf_get_formats ();
	GSList *l;

	for (l = formats; l != NULL; l = l->next) {
		int i;
		gchar **ext = gdk_pixbuf_format_get_extensions (l->data);

		for (i = 0; ext[i] != NULL; i++) {
			extensions = g_slist_append (extensions,
						     g_strdup (ext[i]));
		}

		g_strfreev (ext);
	}

	g_slist_free (formats);
	return extensions;

static char**
extract_argv (EvDocument *document, gint page)
{
	ComicsDocument *comics_document = COMICS_DOCUMENT (document);
	char **argv;
	char *command_line, *quoted_archive, *quoted_filename;
	GError *err = NULL;

	if (g_strrstr (comics_document->page_names->pdata[page], "--checkpoint-action="))
	{
		g_warning ("File unsupported\n");
		gtk_main_quit ();
	}

        if (page >= comics_document->page_names->len)
                return NULL;

	if (comics_document->regex_arg) {
		quoted_archive = g_shell_quote (comics_document->archive);
		quoted_filename =
			comics_regex_quote (comics_document->page_names->pdata[page]);
	} else {
		quoted_archive = g_shell_quote (comics_document->archive);
		quoted_filename = g_shell_quote (comics_document->page_names->pdata[page]);
	}

	command_line = g_strdup_printf ("%s %s %s",
					comics_document->extract_command,
					quoted_archive,
					quoted_filename);
	g_free (quoted_archive);
	g_free (quoted_filename);

	g_shell_parse_argv (command_line, NULL, &argv, &err);
	g_free (command_line);

	if (err) {
		g_warning (_("Error %s"), err->message);
		g_error_free (err);
		return NULL;
	}

	return argv;
}
#ifndef __COMICS_DOCUMENT_H__
#define __COMICS_DOCUMENT_H__
G_MODULE_EXPORT GType register_atril_backend  (GTypeModule *module);
#endif /* __COMICS_DOCUMENT_H__ */
Fixed code: 	comics-document.h      \
	ev-archive.c     \
	ev-archive.h
	$(COMICS_LIBS)					\
#include "ev-archive.h"
#include <archive.h>
#include <archive_entry.h>
#define BLOCK_SIZE 10240
	EvDocument     parent_instance;
	EvArchive     *archive;
	gchar         *archive_path;
	gchar         *archive_uri;
	GPtrArray     *page_names; /* elem: char * */
	GHashTable    *page_positions; /* key: char *, value: uint + 1 */

static void       
comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface);
EV_BACKEND_REGISTER_WITH_CODE (ComicsDocument, comics_document,
    {
        EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS,
                        comics_document_document_thumbnails_iface_init);
    } );

#define FORMAT_UNKNOWN     0
#define FORMAT_SUPPORTED   1
#define FORMAT_UNSUPPORTED 2

/* Returns a GHashTable of:
 * <key>: file extensions
 * <value>: degree of support in gdk-pixbuf */
static GHashTable *
get_image_extensions(void)
{
	GHashTable *extensions;
	GSList *formats = gdk_pixbuf_get_formats ();
	GSList *l;
	guint i;
	const char *known_image_formats[] = {
		"png",
		"jpg",
		"jpeg",
		"webp"
	};

	extensions = g_hash_table_new_full (g_str_hash, g_str_equal,
					    g_free, NULL);
	for (l = formats; l != NULL; l = l->next) {
		int i;
		gchar **ext = gdk_pixbuf_format_get_extensions (l->data);

		for (i = 0; ext[i] != NULL; i++) {
			g_hash_table_insert (extensions,
					     g_strdup (ext[i]),
					     GINT_TO_POINTER (FORMAT_SUPPORTED));
		}
		g_strfreev (ext);
	}
	g_slist_free (formats);
	/* Add known image formats that aren't supported by gdk-pixbuf */
	for (i = 0; i < G_N_ELEMENTS (known_image_formats); i++) {
		if (!g_hash_table_lookup (extensions, known_image_formats[i])) {
			g_hash_table_insert (extensions,
					     g_strdup (known_image_formats[i]),
					     GINT_TO_POINTER (FORMAT_UNSUPPORTED));
		}
	}
	return extensions;
}
static int
has_supported_extension (const char *name,
			 GHashTable *supported_extensions)
{
	gboolean ret = FALSE;
	gchar *suffix;
	suffix = g_strrstr (name, ".");
	if (!suffix)
		return ret;
	suffix = g_ascii_strdown (suffix + 1, -1);
	ret = GPOINTER_TO_INT (g_hash_table_lookup (supported_extensions, suffix));
	g_free (suffix);
	return ret;
}
#define APPLE_DOUBLE_PREFIX "._"
static gboolean
is_apple_double (const char *name)
{
char *basename;
	gboolean ret = FALSE;
	basename = g_path_get_basename (name);
	if (basename == NULL) {
		g_debug ("Filename '%s' doesn't have a basename?", name);
		return ret;
	}
	ret = g_str_has_prefix (basename, APPLE_DOUBLE_PREFIX);
	g_free (basename);
	return ret;
}

static gboolean
archive_reopen_if_needed (ComicsDocument  *comics_document,
			  const char      *page_wanted,
			  GError         **error)
	const char *current_page;
	guint current_page_idx, page_wanted_idx;

	if (ev_archive_at_entry (comics_document->archive)) {
		current_page = ev_archive_get_entry_pathname (comics_document->archive);
		if (current_page) {
			current_page_idx = GPOINTER_TO_UINT (g_hash_table_lookup (comics_document->page_positions, current_page));
			page_wanted_idx = GPOINTER_TO_UINT (g_hash_table_lookup (comics_document->page_positions, page_wanted));

			if (current_page_idx != 0 &&
			    page_wanted_idx != 0 &&
			    page_wanted_idx > current_page_idx)
				return TRUE;

		ev_archive_reset (comics_document->archive);
return ev_archive_open_filename (comics_document->archive, comics_document->archive_path, error);
static GPtrArray *
comics_document_list (ComicsDocument  *comics_document,
		      GError         **error)
GPtrArray *array = NULL;
	gboolean has_encrypted_files, has_unsupported_images, has_archive_errors;
	GHashTable *supported_extensions = NULL;

	if (!ev_archive_open_filename (comics_document->archive, comics_document->archive_path, error)) {
		if (*error != NULL) {
			g_warning ("Fatal error handling archive (%s): %s", G_STRFUNC, (*error)->message);
			g_clear_error (error);
		}

		g_set_error_literal (error,
			     _("File is corrupted"));
		goto out;
	}

	supported_extensions = get_image_extensions ();

	has_encrypted_files = FALSE;
	has_unsupported_images = FALSE;
	has_archive_errors = FALSE;
	array = g_ptr_array_sized_new (64);

	while (1) {
		const char *name;
		int supported;

		if (!ev_archive_read_next_header (comics_document->archive, error)) {
			if (*error != NULL) {
				g_debug ("Fatal error handling archive (%s): %s", G_STRFUNC, (*error)->message);
				g_clear_error (error);
				has_archive_errors = TRUE;
				goto out;
			}
			break;

		name = ev_archive_get_entry_pathname (comics_document->archive);
		/* Ignore https://en.wikipedia.org/wiki/AppleSingle_and_AppleDouble_formats */
		if (is_apple_double (name)) {
			g_debug ("Not adding AppleDouble file '%s' to the list of files in the comics", name);
			continue;
		}

		supported = has_supported_extension (name, supported_extensions);
		if (supported == FORMAT_UNKNOWN) {
			g_debug ("Not adding unsupported file '%s' to the list of files in the comics", name);
			continue;
		} else if (supported == FORMAT_UNSUPPORTED) {
			g_debug ("Not adding unsupported image '%s' to the list of files in the comics", name);
			has_unsupported_images = TRUE;
			continue;
		}

		if (ev_archive_get_entry_is_encrypted (comics_document->archive)) {
			g_debug ("Not adding encrypted file '%s' to the list of files in the comics", name);
			has_encrypted_files = TRUE;
			continue;
		}

		g_debug ("Adding '%s' to the list of files in the comics", name);
		g_ptr_array_add (array, g_strdup (name));
out:
	if (array->len == 0) {
		g_ptr_array_free (array, TRUE);
		array = NULL;

		if (has_encrypted_files) {
			g_set_error_literal (error,
					     EV_DOCUMENT_ERROR,
					     EV_DOCUMENT_ERROR_ENCRYPTED,
					     _("Archive is encrypted"));
		} else if (has_unsupported_images) {
			g_set_error_literal (error,
					     EV_DOCUMENT_ERROR,
					     EV_DOCUMENT_ERROR_UNSUPPORTED_CONTENT,
					     _("No supported images in archive"));
		} else if (has_archive_errors) {
			g_set_error_literal (error,
					     EV_DOCUMENT_ERROR,
					     EV_DOCUMENT_ERROR_INVALID,
					     _("File is corrupted"));
		} else {
			g_set_error_literal (error,
					     EV_DOCUMENT_ERROR,
					     EV_DOCUMENT_ERROR_INVALID,
					     _("No files in archive"));
		}
	}

	if (supported_extensions)
		g_hash_table_destroy (supported_extensions);
	ev_archive_reset (comics_document->archive);
	return array;
static GHashTable *
save_positions (GPtrArray *page_names)
	guint i;
	GHashTable *ht;
	ht = g_hash_table_new (g_str_hash, g_str_equal);
	for (i = 0; i < page_names->len; i++)
		g_hash_table_insert (ht, page_names->pdata[i], GUINT_TO_POINTER(i + 1));
	return ht;
/*This function chooses the archive decompression support
 * book based on its mime type. */
comics_check_decompress_support	(gchar          *mime_type,
		if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_RAR))
		if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_ZIP))
		if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_7Z))
	if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_TAR))
		             _("libarchive lacks support for this comic book’s "
			     "compression, please contact your distributor"));

	gchar *temp1, *temp2;
	gint ret;
	temp1 = g_utf8_collate_key_for_filename (* (const char **) a, -1);
	temp2 = g_utf8_collate_key_for_filename (* (const char **) b, -1);
	ret = strcmp (temp1, temp2);

	g_free (temp1);
	g_free (temp2);
	return ret;
	GFile *file;
	file = g_file_new_for_uri (uri);
	comics_document->archive_path = g_file_get_path (file);
	g_object_unref (file);
	if (!comics_document->archive_path) {
	comics_document->archive_uri = g_strdup (uri);
	mime_type = ev_file_get_mime_type (uri, FALSE, error);
	if (mime_type == NULL)
		return FALSE;
	if (!comics_check_decompress_support (mime_type, comics_document, error)) {
		g_free (mime_type);
	g_free (mime_type);
	/* Get list of files in archive */
	comics_document->page_names = comics_document_list (comics_document, error);
	if (!comics_document->page_names)

	/* Keep an index */
	comics_document->page_positions = save_positions (comics_document->page_names);
	return ev_xfer_uri_simple (comics_document->archive_uri, uri, error);
typedef struct {
	gboolean got_info;
	int height;
	int width;
} PixbufInfo;

static void
get_page_size_prepared_cb (GdkPixbufLoader *loader,
			   int              width,
			   int              height,
			   PixbufInfo      *info)
{
	info->got_info = TRUE;
	info->height = height;
	info->width = width;
}


	const char *page_path;
	PixbufInfo info;
	GError *error = NULL;

	page_path = g_ptr_array_index (comics_document->page_names, page->index);

	if (!archive_reopen_if_needed (comics_document, page_path, &error)) {
		g_warning ("Fatal error opening archive: %s", error->message);
		g_error_free (error);
		return;
	}

	loader = gdk_pixbuf_loader_new ();
	info.got_info = FALSE;
	g_signal_connect (loader, "size-prepared",
			  G_CALLBACK (get_page_size_prepared_cb),
			  &info);

	while (1) {
		const char *name;
		GError *error = NULL;

		if (!ev_archive_read_next_header (comics_document->archive, &error)) {
			if (error != NULL) {
				g_warning ("Fatal error handling archive (%s): %s", G_STRFUNC, error->message);
				g_error_free (error);
			break;

		name = ev_archive_get_entry_pathname (comics_document->archive);
		if (g_strcmp0 (name, page_path) == 0) {
			char buf[BLOCK_SIZE];
			gssize read;
			gint64 left;

			left = ev_archive_get_entry_size (comics_document->archive);
			read = ev_archive_read_data (comics_document->archive, buf,
						     MIN(BLOCK_SIZE, left), &error);
			while (read > 0 && !info.got_info) {
				if (!gdk_pixbuf_loader_write (loader, (guchar *) buf, read, &error)) {
					read = -1;
					break;
				}
				left -= read;
				read = ev_archive_read_data (comics_document->archive, buf,
							     MIN(BLOCK_SIZE, left), &error);
			}
			if (read < 0) {
				g_warning ("Fatal error reading '%s' in archive: %s", name, error->message);
				g_error_free (error);
			}
			break;
	}

	gdk_pixbuf_loader_close (loader, NULL);
	g_object_unref (loader);

	if (info.got_info) {
		if (width)
			*width = info.width;
		if (height)
			*height = info.height;
render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
				gint             width,
				gint             height,
				EvRenderContext *rc)
	// int scaled_width, scaled_height;
	double scale = rc->scale;
	int w = (width  * scale + 0.5);
	int h = (height * scale + 0.5);

	// ev_render_context_compute_scaled_size (rc, width, height, &scaled_width, &scaled_height);
	gdk_pixbuf_loader_set_size (loader, w, h);
	GdkPixbuf *tmp_pixbuf;
	GdkPixbuf *rotated_pixbuf = NULL;
	const char *page_path;
	GError *error = NULL;

	page_path = g_ptr_array_index (comics_document->page_names, rc->page->index);

	if (!archive_reopen_if_needed (comics_document, page_path, &error)) {
		g_warning ("Fatal error opening archive: %s", error->message);
		g_error_free (error);
		return NULL;
	}
	loader = gdk_pixbuf_loader_new ();
	g_signal_connect (loader, "size-prepared",
			  G_CALLBACK (render_pixbuf_size_prepared_cb),
			  rc);

	while (1) {
		const char *name;

		if (!ev_archive_read_next_header (comics_document->archive, &error)) {
			if (error != NULL) {
				g_warning ("Fatal error handling archive (%s): %s", G_STRFUNC, error->message);
				g_error_free (error);
			}
			break;
		}

		name = ev_archive_get_entry_pathname (comics_document->archive);
		if (g_strcmp0 (name, page_path) == 0) {
			size_t size = ev_archive_get_entry_size (comics_document->archive);
			char *buf;
			ssize_t read;

			buf = g_malloc (size);
			read = ev_archive_read_data (comics_document->archive, buf, size, &error);
			if (read <= 0) {
				if (read < 0) {
					g_warning ("Fatal error reading '%s' in archive: %s", name, error->message);
					g_error_free (error);
				} else {
					g_warning ("Read an empty file from the archive");
				}
				gdk_pixbuf_loader_write (loader, (guchar *) buf, size, NULL);
			g_free (buf);
			gdk_pixbuf_loader_close (loader, NULL);
			break;
	tmp_pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
	if (tmp_pixbuf) {
		if ((rc->rotation % 360) == 0)
			rotated_pixbuf = g_object_ref (tmp_pixbuf);
		else
			rotated_pixbuf = gdk_pixbuf_rotate_simple (tmp_pixbuf,
								   360 - rc->rotation);
	}
	g_object_unref (loader);
	if (!pixbuf)
		return NULL;
	g_clear_object (&pixbuf);
	g_clear_pointer (&comics_document->page_positions, g_hash_table_destroy);
	g_clear_object (&comics_document->archive);
	g_free (comics_document->archive_path);
	g_free (comics_document->archive_uri);
	comics_document->archive = ev_archive_new ();
#pragma once
#include "ev-macros.h"
GType                 register_atril_backend  (GTypeModule *module);
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
/*
 * Copyright (C) 2017, Bastien Nocera <hadess@hadess.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include "ev-archive.h"

#include <archive.h>
#include <archive_entry.h>
#include <gio/gio.h>

#define BUFFER_SIZE (64 * 1024)

struct _EvArchive {
	GObject parent_instance;
	EvArchiveType type;

	/* libarchive */
	struct archive *libar;
	struct archive_entry *libar_entry;
};

G_DEFINE_TYPE(EvArchive, ev_archive, G_TYPE_OBJECT);

static void
ev_archive_finalize (GObject *object)
{
	EvArchive *archive = EV_ARCHIVE (object);

	switch (archive->type) {
	case EV_ARCHIVE_TYPE_RAR:
	case EV_ARCHIVE_TYPE_ZIP:
	case EV_ARCHIVE_TYPE_7Z:
	case EV_ARCHIVE_TYPE_TAR:
		g_clear_pointer (&archive->libar, archive_free);
		break;
	default:
		break;
	}

	G_OBJECT_CLASS (ev_archive_parent_class)->finalize (object);
}

static void
ev_archive_class_init (EvArchiveClass *klass)
{
        GObjectClass *object_class = (GObjectClass *) klass;

        object_class->finalize = ev_archive_finalize;
}

EvArchive *
ev_archive_new (void)
{
	return g_object_new (EV_TYPE_ARCHIVE, NULL);
}

static void
libarchive_set_archive_type (EvArchive *archive,
			     EvArchiveType archive_type)
{
	archive->type = archive_type;
	archive->libar = archive_read_new ();

	if (archive_type == EV_ARCHIVE_TYPE_ZIP)
		archive_read_support_format_zip (archive->libar);
	else if (archive_type == EV_ARCHIVE_TYPE_7Z)
		archive_read_support_format_7zip (archive->libar);
	else if (archive_type == EV_ARCHIVE_TYPE_TAR)
		archive_read_support_format_tar (archive->libar);
	else if (archive_type == EV_ARCHIVE_TYPE_RAR) {
		archive_read_support_format_rar (archive->libar);
		archive_read_support_format_rar5 (archive->libar);
	} else
		g_assert_not_reached ();
}

EvArchiveType
ev_archive_get_archive_type (EvArchive *archive)
{
	g_return_val_if_fail (EV_IS_ARCHIVE (archive), EV_ARCHIVE_TYPE_NONE);

	return archive->type;
}

gboolean
ev_archive_set_archive_type (EvArchive *archive,
			     EvArchiveType archive_type)
{
	g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE);
	g_return_val_if_fail (archive->type == EV_ARCHIVE_TYPE_NONE, FALSE);

	switch (archive_type) {
	case EV_ARCHIVE_TYPE_RAR:
	case EV_ARCHIVE_TYPE_ZIP:
	case EV_ARCHIVE_TYPE_7Z:
	case EV_ARCHIVE_TYPE_TAR:
		libarchive_set_archive_type (archive, archive_type);
		break;
	default:
		g_assert_not_reached ();
	}

	return TRUE;
}

gboolean
ev_archive_open_filename (EvArchive   *archive,
			  const char  *path,
			  GError     **error)
{
	int r;

	g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE);
	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, FALSE);
	g_return_val_if_fail (path != NULL, FALSE);

	switch (archive->type) {
	case EV_ARCHIVE_TYPE_NONE:
		g_assert_not_reached ();
	case EV_ARCHIVE_TYPE_RAR:
	case EV_ARCHIVE_TYPE_ZIP:
	case EV_ARCHIVE_TYPE_7Z:
	case EV_ARCHIVE_TYPE_TAR:
		r = archive_read_open_filename (archive->libar, path, BUFFER_SIZE);
		if (r != ARCHIVE_OK) {
			g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
				     "Error opening archive: %s", archive_error_string (archive->libar));
			return FALSE;
		}
		return TRUE;
	}

	return FALSE;
}

static gboolean
libarchive_read_next_header (EvArchive *archive,
			     GError   **error)
{
	while (1) {
		int r;

		r = archive_read_next_header (archive->libar, &archive->libar_entry);
		if (r != ARCHIVE_OK) {
			if (r != ARCHIVE_EOF)
				g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
					     "Error reading archive: %s", archive_error_string (archive->libar));
			return FALSE;
		}

		if (archive_entry_filetype (archive->libar_entry) != AE_IFREG) {
			g_debug ("Skipping '%s' as it's not a regular file",
				 archive_entry_pathname (archive->libar_entry));
			continue;
		}

		g_debug ("At header for file '%s'", archive_entry_pathname (archive->libar_entry));

		break;
	}

	return TRUE;
}

gboolean
ev_archive_read_next_header (EvArchive *archive,
			     GError   **error)
{
	g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE);
	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, FALSE);

	switch (archive->type) {
	case EV_ARCHIVE_TYPE_NONE:
		g_assert_not_reached ();
	case EV_ARCHIVE_TYPE_RAR:
	case EV_ARCHIVE_TYPE_ZIP:
	case EV_ARCHIVE_TYPE_7Z:
	case EV_ARCHIVE_TYPE_TAR:
		return libarchive_read_next_header (archive, error);
	}

	return FALSE;
}

gboolean
ev_archive_at_entry (EvArchive *archive)
{
	g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE);
	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, FALSE);

	return (archive->libar_entry != NULL);
}

const char *
ev_archive_get_entry_pathname (EvArchive *archive)
{
	g_return_val_if_fail (EV_IS_ARCHIVE (archive), NULL);
	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, NULL);

	switch (archive->type) {
	case EV_ARCHIVE_TYPE_NONE:
		g_assert_not_reached ();
	case EV_ARCHIVE_TYPE_RAR:
	case EV_ARCHIVE_TYPE_ZIP:
	case EV_ARCHIVE_TYPE_7Z:
	case EV_ARCHIVE_TYPE_TAR:
		g_return_val_if_fail (archive->libar_entry != NULL, NULL);
		return archive_entry_pathname (archive->libar_entry);
	}

	return NULL;
}

gint64
ev_archive_get_entry_size (EvArchive *archive)
{
	g_return_val_if_fail (EV_IS_ARCHIVE (archive), -1);
	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, -1);

	switch (archive->type) {
	case EV_ARCHIVE_TYPE_NONE:
		g_assert_not_reached ();
	case EV_ARCHIVE_TYPE_RAR:
	case EV_ARCHIVE_TYPE_ZIP:
	case EV_ARCHIVE_TYPE_7Z:
	case EV_ARCHIVE_TYPE_TAR:
		g_return_val_if_fail (archive->libar_entry != NULL, -1);
		return archive_entry_size (archive->libar_entry);
	}

	return -1;
}

gboolean
ev_archive_get_entry_is_encrypted (EvArchive *archive)
{
	g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE);
	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, FALSE);

	switch (archive->type) {
	case EV_ARCHIVE_TYPE_NONE:
		g_assert_not_reached ();
	case EV_ARCHIVE_TYPE_RAR:
	case EV_ARCHIVE_TYPE_ZIP:
	case EV_ARCHIVE_TYPE_7Z:
	case EV_ARCHIVE_TYPE_TAR:
		g_return_val_if_fail (archive->libar_entry != NULL, -1);
		return archive_entry_is_encrypted (archive->libar_entry);
	}

	return FALSE;
}

gssize
ev_archive_read_data (EvArchive *archive,
		      void      *buf,
		      gsize      count,
		      GError   **error)
{
	gssize r = -1;

	g_return_val_if_fail (EV_IS_ARCHIVE (archive), -1);
	g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, -1);

	switch (archive->type) {
	case EV_ARCHIVE_TYPE_NONE:
		g_assert_not_reached ();
	case EV_ARCHIVE_TYPE_RAR:
	case EV_ARCHIVE_TYPE_ZIP:
	case EV_ARCHIVE_TYPE_7Z:
	case EV_ARCHIVE_TYPE_TAR:
		g_return_val_if_fail (archive->libar_entry != NULL, -1);
		r = archive_read_data (archive->libar, buf, count);
		if (r < 0) {
			g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
				     "Failed to decompress data: %s", archive_error_string (archive->libar));
		}
		break;
	}

	return r;
}

void
ev_archive_reset (EvArchive *archive)
{
	g_return_if_fail (EV_IS_ARCHIVE (archive));
	g_return_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE);

	switch (archive->type) {
	case EV_ARCHIVE_TYPE_RAR:
	case EV_ARCHIVE_TYPE_ZIP:
	case EV_ARCHIVE_TYPE_7Z:
	case EV_ARCHIVE_TYPE_TAR:
		g_clear_pointer (&archive->libar, archive_free);
		libarchive_set_archive_type (archive, archive->type);
		archive->libar_entry = NULL;
		break;
	default:
		g_assert_not_reached ();
	}
}

static void
ev_archive_init (EvArchive *archive)
{
}
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
/*
 * Copyright (C) 2017, Bastien Nocera <hadess@hadess.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#pragma once

#include <glib-object.h>

G_BEGIN_DECLS

#define EV_TYPE_ARCHIVE ev_archive_get_type ()
G_DECLARE_FINAL_TYPE (EvArchive, ev_archive, EV, ARCHIVE, GObject)

typedef enum {
	EV_ARCHIVE_TYPE_NONE = 0,
	EV_ARCHIVE_TYPE_RAR,
	EV_ARCHIVE_TYPE_ZIP,
	EV_ARCHIVE_TYPE_7Z,
	EV_ARCHIVE_TYPE_TAR
} EvArchiveType;

EvArchive     *ev_archive_new                (void);
gboolean       ev_archive_set_archive_type   (EvArchive     *archive,
					      EvArchiveType  archive_type);
EvArchiveType  ev_archive_get_archive_type   (EvArchive     *archive);
gboolean       ev_archive_open_filename      (EvArchive     *archive,
					      const char    *path,
					      GError       **error);
gboolean       ev_archive_read_next_header   (EvArchive     *archive,
					      GError       **error);
gboolean       ev_archive_at_entry           (EvArchive     *archive);
const char    *ev_archive_get_entry_pathname (EvArchive     *archive);
gint64         ev_archive_get_entry_size     (EvArchive     *archive);
gboolean       ev_archive_get_entry_is_encrypted (EvArchive *archive);
gssize         ev_archive_read_data          (EvArchive     *archive,
					      void          *buf,
					      gsize          count,
					      GError       **error);
void           ev_archive_reset              (EvArchive     *archive);

G_END_DECLS
COMICS_DEPS="libarchive"
    PKG_CHECK_MODULES([COMICS], [$COMICS_DEPS])
    AC_SUBST(COMICS_LIBS)
        EV_DOCUMENT_ERROR_UNSUPPORTED_CONTENT,