CVE-2023-44452: Insufficient Information (NVD-CWE-noinfo)
value: Linux Mint Xreader CBT File Parsing Argument Injection Remote Code Execution Vulnerability. This vulnerability allows remote attackers to execute arbitrary code on affected installations of Linux Mint Xreader. User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file.

The specific flaw exists within the parsing of CBT files. The issue results from the lack of proper validation of a user-supplied string before using it to execute a system call. An attacker can leverage this vulnerability to execute code in the context of the current user. Was ZDI-CAN-22132.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-05-03T03:16Z
Repository: https://github.com/linuxmint/xreader
Language: C
Files changed: backend/comics/comics-document.c, backend/comics/comics-document.h, backend/comics/comicsdocument.xreader-backend.in, backend/comics/ev-archive.c, backend/comics/ev-archive.h, backend/comics/meson.build, backend/comics/test-ev-archive.c, debian/control, libdocument/ev-document.h, meson.build
Commit date: 2023-12-12 11:26:38 +0000
Commit message: comics: Use libarchive to unpack documents [CVE-2023-44452].

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

Fixes:
CVE-2023-44452 - Linux Mint Xreader CBT File Parsing Argument
                 Injection Remote Code Execution Vulnerability.

Based on:
https://gitlab.gnome.org/GNOME/evince/-/commit/7b5ad18399b04cbfce02730d28baf30e9fc35b58

This vulnerability was discovered by:
Febin Mon Saji working with Trend Micro Zero Day Initiative

Diff: diff --git a/backend/comics/comics-document.c b/backend/comics/comics-document.c
index 99967f09..17c76b07 100644
--- a/backend/comics/comics-document.c
+++ b/backend/comics/comics-document.c
@@ -1,9 +1,10 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
 /*
  * Copyright (C) 2009-2010 Juanjo Marín <juanj.marin@juntadeandalucia.es>
  * Copyright (C) 2005, Teemu Tervo <teemu.tervo@gmx.net>
+ * Copyright (C) 2016-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.
@@ -28,432 +29,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"
 
-#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);
 
-static const ComicBookDecompressCommand command_usage_def[] = {
-        /* RARLABS unrar */
-	{"%s p -c- -ierr --", "%s vb -c- -- %s", NULL             , FALSE, NO_OFFSET},
+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);
 
-        /* GNA! unrar */
-	{NULL               , "%s t %s"        , "%s -xf %s %s"   , FALSE, NO_OFFSET},
+		for (i = 0; ext[i] != NULL; i++) {
+			g_hash_table_insert (extensions,
+					     g_strdup (ext[i]),
+					     GINT_TO_POINTER (FORMAT_SUPPORTED));
+		}
+
+		g_strfreev (ext);
+	}
 
-        /* unzip */
-	{"%s -p -C --"      , "%s %s"          , NULL             , TRUE , OFFSET_ZIP},
+	g_slist_free (formats);
 
-        /* 7zip */
-	{NULL               , "%s l -- %s"     , "%s x -y %s -o%s", FALSE, OFFSET_7Z},
+	/* 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));
+		}
+	}
 
-        /* tar */
-	{"%s -xOf"          , "%s -tf %s"      , NULL             , FALSE, NO_OFFSET},
+	return extensions;
+}
 
-	/* UNARCHIVER */
-	{"unar -o -"	    , "%s %s"	       , NULL		  , FALSE, NO_OFFSET}
-};
+static int
+has_supported_extension (const char *name,
+			 GHashTable *supported_extensions)
+{
+	gboolean ret = FALSE;
+	gchar *suffix;
 
-static void       comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface);
+	suffix = g_strrstr (name, ".");
+	if (!suffix)
+		return ret;
 
-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);
+	suffix = g_ascii_strdown (suffix + 1, -1);
+	ret = GPOINTER_TO_INT (g_hash_table_lookup (supported_extensions, suffix));
+	g_free (suffix);
 
+	return ret;
+}
 
-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)
+#define APPLE_DOUBLE_PREFIX "._"
+static gboolean
+is_apple_double (const char *name)
 {
-	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;
+	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;
 	}
-	g_string_append_c (dest, '\'');
-	return g_string_free (dest, FALSE);
+
+	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);
+	}
 
-/* 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)
+	return ev_archive_open_filename (comics_document->archive, comics_document->archive_path, 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 ("xreader-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. */
-static gboolean 
-comics_check_decompress_command	(gchar          *mime_type, 
+/* This function chooses the archive decompression support
+ * book based on its mime type. */
+static gboolean
+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;
+		if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_RAR))
 			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;
-			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;
-			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_ZIP))
 			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;
-			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;
+		if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_7Z))
 			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;
-			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;
+		if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_TAR))
 			return TRUE;
-		}
 	} else {
 		g_set_error (error,
 			     EV_DOCUMENT_ERROR,
 			     EV_DOCUMENT_ERROR_INVALID,
 			     _("Not a comic book MIME type: %s"),
@@ -461,183 +324,89 @@ 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;
+  gchar *temp1, *temp2;
+  gint ret;
 
-	#define SORT_LAST_CHAR1 '.'
-	#define SORT_LAST_CHAR2 '#'
+  temp1 = g_utf8_collate_key_for_filename (* (const char **) a, -1);
+  temp2 = g_utf8_collate_key_for_filename (* (const char **) b, -1);
 
-	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;
+  ret = strcmp (temp1, temp2);
 
-	#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);
-	}
+  g_free (temp1);
+  g_free (temp2);
 
-	return compare;
+  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;
+	GFile *file;
 
-	comics_document->archive = g_filename_from_uri (uri, NULL, error);
-	if (!comics_document->archive)
-		return FALSE;
+	file = g_file_new_for_uri (uri);
+	comics_document->archive_path = g_file_get_path (file);
+	g_object_unref (file);
 
-	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) {
+	if (!comics_document->archive_path) {
 		g_set_error_literal (error,
                                      EV_DOCUMENT_ERROR,
                                      EV_DOCUMENT_ERROR_INVALID,
-                                     _("File corrupted"));
+                                     _("Can not get local path for archive"));
 		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);
 
-	g_free (std_out);
+	mime_type = ev_file_get_mime_type (uri, FALSE, error);
 
-	if (!cb_files) {
-		g_set_error_literal (error,
-				     EV_DOCUMENT_ERROR,
-				     EV_DOCUMENT_ERROR_INVALID,
-				     _("No files in archive"));
+	if (mime_type == NULL)
 		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);
+	if (!comics_check_decompress_support (mime_type, comics_document, error)) {
+		g_free (mime_type);
+		return FALSE;
 	}
-	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;
 }
 
-
 static gboolean
 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)
 {
@@ -647,155 +416,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);
-	
-	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);
-			} else if (bytes <= 0) {
-				close (outpipe);
-				gdk_pixbuf_loader_close (loader, NULL);
-				outpipe = -1;
+	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");
+				}
+			} else {
+				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,
@@ -803,83 +603,32 @@ 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);
-	
-	return surface;
-}
+	g_clear_object (&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));
+	return surface;
 }
 
 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
@@ -898,124 +647,53 @@ 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,
-					  gboolean              border)
+                      EvRenderContext      *rc,
+                      gboolean              border)
 {
-	GdkPixbuf *thumbnail;
+    GdkPixbuf *thumbnail;
 
-	thumbnail = comics_document_render_pixbuf (EV_DOCUMENT (document), rc);
+    thumbnail = comics_document_render_pixbuf (EV_DOCUMENT (document), rc);
 
-	if (border) {
-	      GdkPixbuf *tmp_pixbuf = thumbnail;
-	      
-	      thumbnail = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf);
-	      g_object_unref (tmp_pixbuf);
-	}
+    if (border) {
+          GdkPixbuf *tmp_pixbuf = thumbnail;
+          
+          thumbnail = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf);
+          g_object_unref (tmp_pixbuf);
+    }
 
-	return thumbnail;
+    return thumbnail;
 }
 
 static void
 comics_document_thumbnails_get_dimensions (EvDocumentThumbnails *document,
-					   EvRenderContext      *rc,
-					   gint                 *width,
-					   gint                 *height)
+                       EvRenderContext      *rc,
+                       gint                 *width,
+                       gint                 *height)
 {
-	gdouble page_width, page_height;
-	
-	comics_document_get_page_size (EV_DOCUMENT (document), rc->page,
-				       &page_width, &page_height);
-
-	if (rc->rotation == 90 || rc->rotation == 270) {
-		*width = (gint) (page_height * rc->scale);
-		*height = (gint) (page_width * rc->scale);
-	} else {
-		*width = (gint) (page_width * rc->scale);
-		*height = (gint) (page_height * rc->scale);
-	}
+    gdouble page_width, page_height;
+    
+    comics_document_get_page_size (EV_DOCUMENT (document), rc->page,
+                       &page_width, &page_height);
+
+    if (rc->rotation == 90 || rc->rotation == 270) {
+        *width = (gint) (page_height * rc->scale);
+        *height = (gint) (page_width * rc->scale);
+    } else {
+        *width = (gint) (page_width * rc->scale);
+        *height = (gint) (page_height * rc->scale);
+    }
 }
 
 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;
+    iface->get_thumbnail = comics_document_thumbnails_get_thumbnail;
+    iface->get_dimensions = comics_document_thumbnails_get_dimensions;
 }
diff --git a/backend/comics/comics-document.h b/backend/comics/comics-document.h
index 8a02f3d4..0a308cff 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,8 @@ 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_evince_backend  (GTypeModule *module);
 
-G_MODULE_EXPORT GType register_xreader_backend  (GTypeModule *module);
-     
 G_END_DECLS
-
-#endif /* __COMICS_DOCUMENT_H__ */
diff --git a/backend/comics/comicsdocument.xreader-backend.in b/backend/comics/comicsdocument.xreader-backend.in
index dbadbe55..552fb648 100644
--- a/backend/comics/comicsdocument.xreader-backend.in
+++ b/backend/comics/comicsdocument.xreader-backend.in
@@ -1,4 +1,4 @@
 [Xreader Backend]
 Module=comicsdocument
-_TypeDescription=Comic Books
-MimeType=application/x-cbr;application/x-cbz;application/x-cb7;application/x-cbt;application/vnd.comicbook+zip;
+TypeDescription=Comic Books
+MimeType=@COMICS_MIME_TYPES@;
diff --git a/backend/comics/ev-archive.c b/backend/comics/ev-archive.c
new file mode 100644
index 00000000..568e1621
--- /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 00000000..b4e1399c
--- /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/backend/comics/meson.build b/backend/comics/meson.build
index 396596a4..bcb935eb 100644
--- a/backend/comics/meson.build
+++ b/backend/comics/meson.build
@@ -1,30 +1,49 @@
-comics_sources = [
-    'comics-document.c',
-    'comics-document.h'
-]
+backend_desktop_conf = configuration_data()
+backend_desktop_conf.set('COMICS_MIME_TYPES', comic_mimetypes)
+
+backend_desktop = configure_file(
+    input: 'comicsdocument.xreader-backend.in',
+    output: 'comicsdocument.xreader-backend',
+    configuration: backend_desktop_conf,
+    install: true,
+    install_dir: backendsdir,
+)
+
+comics_sources = files(
+  'comics-document.c',
+  'ev-archive.c',
+)
 
 comics_deps = [
-    glib,
-    cairo,
-    gtk
+  cairo,
+  glib,
+  gtk,
+  libarchive_dep,
 ]
 
 shared_module(
     'comicsdocument',
     comics_sources,
     link_with: [libdocument],
     link_args: ['-Wl,-Bsymbolic', '-Wl,-z,relro', '-Wl,-z,now'],
     include_directories: include_dirs,
     dependencies: comics_deps,
     install: true,
-    install_dir: backendsdir,
+    install_dir: backendsdir
 )
 
-custom_target(
-    'comics_backend',
-    input: 'comicsdocument.xreader-backend.in',
-    output: 'comicsdocument.xreader-backend',
-    command: [intltool_merge, '-d', '-u', po_dir, '@INPUT@', '@OUTPUT@'],
-    install: true,
-    install_dir: backendsdir,
+
+
+test_name = 'test-ev-archive'
+
+test_sources = files(
+  'ev-archive.c',
+  'test-ev-archive.c',
+)
+
+executable(
+  test_name,
+  test_sources,
+  include_directories: include_dirs,
+  dependencies: comics_deps,
 )
diff --git a/backend/comics/test-ev-archive.c b/backend/comics/test-ev-archive.c
new file mode 100644
index 00000000..772495a9
--- /dev/null
+++ b/backend/comics/test-ev-archive.c
@@ -0,0 +1,116 @@
+/* -*- 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"
+
+static void
+usage (const char *prog)
+{
+	g_print ("- Lists file in a supported archive format\n");
+	g_print ("Usage: %s archive-type filename\n", prog);
+	g_print ("Where archive-type is one of rar, zip, 7z or tar\n");
+}
+
+static EvArchiveType
+str_to_archive_type (const char *str)
+{
+	g_return_val_if_fail (str != NULL, EV_ARCHIVE_TYPE_NONE);
+
+	if (g_strcmp0 (str, "rar") == 0)
+		return EV_ARCHIVE_TYPE_RAR;
+	if (g_strcmp0 (str, "zip") == 0)
+		return EV_ARCHIVE_TYPE_ZIP;
+	if (g_strcmp0 (str, "7z") == 0)
+		return EV_ARCHIVE_TYPE_7Z;
+	if (g_strcmp0 (str, "tar") == 0)
+		return EV_ARCHIVE_TYPE_TAR;
+
+	g_warning ("Archive type '%s' not supported", str);
+	return EV_ARCHIVE_TYPE_NONE;
+}
+
+int
+main (int argc, char **argv)
+{
+	EvArchive *ar;
+	EvArchiveType ar_type;
+	GError *error = NULL;
+	gboolean printed_header = FALSE;
+
+	if (argc != 3) {
+		usage (argv[0]);
+		return 1;
+	}
+
+	ar_type = str_to_archive_type (argv[1]);
+	if (ar_type == EV_ARCHIVE_TYPE_NONE)
+		return 1;
+
+	ar = ev_archive_new ();
+	if (!ev_archive_set_archive_type (ar, ar_type)) {
+		g_warning ("Failed to set archive type");
+		goto out;
+	}
+
+	if (!ev_archive_open_filename (ar, argv[2], &error)) {
+		g_warning ("Failed to open '%s': %s",
+			   argv[2], error->message);
+		g_error_free (error);
+		goto out;
+	}
+
+	while (1) {
+		const char *name;
+		gboolean is_encrypted;
+		gint64 size;
+
+		if (!ev_archive_read_next_header (ar, &error)) {
+			if (error != NULL) {
+				g_warning ("Fatal error handling archive: %s", error->message);
+				g_clear_error (&error);
+				goto out;
+			}
+			break;
+		}
+
+		name = ev_archive_get_entry_pathname (ar);
+		is_encrypted = ev_archive_get_entry_is_encrypted (ar);
+		size = ev_archive_get_entry_size (ar);
+
+		if (!printed_header) {
+			g_print ("P\tSIZE\tNAME\n");
+			printed_header = TRUE;
+		}
+
+		g_print ("%c\t%"G_GINT64_FORMAT"\t%s\n",
+			 is_encrypted ? 'P' : ' ',
+			 size, name);
+	}
+
+	ev_archive_reset (ar);
+	g_clear_object (&ar);
+
+	return 0;
+
+out:
+	g_clear_object (&ar);
+	return 1;
+}
diff --git a/debian/control b/debian/control
index 7dd64d9b..8b809c30 100644
--- a/debian/control
+++ b/debian/control
@@ -3,10 +3,11 @@ Section: x11
 Priority: optional
 Maintainer: Linux Mint <root@linuxmint.com>
 Build-Depends: debhelper-compat (= 12),
                gobject-introspection,
                intltool,
+               libarchive-dev (>= 3.6.0),
                libdjvulibre-dev,
                libgail-3-dev,
                libgirepository1.0-dev,
                libglib2.0-dev,
                libgtk-3-dev,
diff --git a/libdocument/ev-document.h b/libdocument/ev-document.h
index 114c9530..1918adf8 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;
diff --git a/meson.build b/meson.build
index 48702b0b..a2e12efe 100644
--- a/meson.build
+++ b/meson.build
@@ -96,12 +96,26 @@ if get_option('pixbuf')
     backend_subdirs += 'pixbuf'
     xreader_mime_types += 'image/*;'
     xreader_conf.set10('ENABLE_PIXBUF', true)
 endif
 if get_option('comics')
+    libarchive_req_version = '>= 3.6.0'
+    libarchive_dep = dependency('libarchive', version: '>= 3.6.0', required: true)
     backend_subdirs += 'comics'
-    xreader_mime_types += 'application/x-cbr;application/x-cbz;application/x-cb7;application/x-cbt;application/vnd.comicbook+zip;application/vnd.comicbook-rar;'
+    comic_mimetypes = ';'.join([
+        'application/vnd.comicbook-rar',
+        'application/vnd.comicbook+zip',
+        'application/x-cb7',
+        'application/x-cbr',
+        'application/x-cbt',
+        'application/x-cbz',
+        'application/x-ext-cb7',
+        'application/x-ext-cbr',
+        'application/x-ext-cbt',
+        'application/x-ext-cbz',
+    ])
+    xreader_mime_types += comic_mimetypes
 endif
 if get_option('xps')
     xps = dependency('libgxps', version: '>= 0.2.1')
     backend_subdirs += 'xps'
     xreader_mime_types += 'application/oxps;application/vnd.ms-xpsdocument;'

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

#define EV_EOL "\n"

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 ("xreader-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. */
static gboolean 
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;
		}
		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;
			return TRUE;
		}

		/* 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;
			return TRUE;
		}
		comics_document->selected_command =
				g_find_program_in_path ("bsdtar");
		if (comics_document->selected_command) {
			comics_document->command_usage = TAR;
		}
		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;
			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;
		}
		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;
			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;
		}
			     _("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) {
                                     _("File corrupted"));
	/* 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);
			} else if (bytes <= 0) {
				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);
	
	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));
	
	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;
					  EvRenderContext      *rc,
					  gboolean              border)
	GdkPixbuf *thumbnail;
	thumbnail = comics_document_render_pixbuf (EV_DOCUMENT (document), rc);
	if (border) {
	      GdkPixbuf *tmp_pixbuf = thumbnail;
	      
	      thumbnail = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf);
	      g_object_unref (tmp_pixbuf);
	}
	return thumbnail;
					   EvRenderContext      *rc,
					   gint                 *width,
					   gint                 *height)
	gdouble page_width, page_height;
	
	comics_document_get_page_size (EV_DOCUMENT (document), rc->page,
				       &page_width, &page_height);

	if (rc->rotation == 90 || rc->rotation == 270) {
		*width = (gint) (page_height * rc->scale);
		*height = (gint) (page_width * rc->scale);
	} else {
		*width = (gint) (page_width * rc->scale);
		*height = (gint) (page_height * rc->scale);
	}
	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;
#ifndef __COMICS_DOCUMENT_H__
#define __COMICS_DOCUMENT_H__
G_MODULE_EXPORT GType register_xreader_backend  (GTypeModule *module);
     

#endif /* __COMICS_DOCUMENT_H__ */
_TypeDescription=Comic Books
MimeType=application/x-cbr;application/x-cbz;application/x-cb7;application/x-cbt;application/vnd.comicbook+zip;
comics_sources = [
    'comics-document.c',
    'comics-document.h'
]
    glib,
    cairo,
    gtk
    install_dir: backendsdir,
custom_target(
    'comics_backend',
    input: 'comicsdocument.xreader-backend.in',
    output: 'comicsdocument.xreader-backend',
    command: [intltool_merge, '-d', '-u', po_dir, '@INPUT@', '@OUTPUT@'],
    install: true,
    install_dir: backendsdir,
    xreader_mime_types += 'application/x-cbr;application/x-cbz;application/x-cb7;application/x-cbt;application/vnd.comicbook+zip;application/vnd.comicbook-rar;'
Fixed code:  * Copyright (C) 2016-2017, Bastien Nocera <hadess@hadess.net>
#include "ev-archive.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. */
static gboolean
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) {
                                     _("Can not get local path for archive"));
	comics_document->archive_uri = g_strdup (uri);
	mime_type = ev_file_get_mime_type (uri, FALSE, error);
	if (mime_type == NULL)
	if (!comics_check_decompress_support (mime_type, comics_document, error)) {
		g_free (mime_type);
		return FALSE;
	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");
				}
			} else {
				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);
	return surface;

	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 ();
                      EvRenderContext      *rc,
                      gboolean              border)
    GdkPixbuf *thumbnail;
    thumbnail = comics_document_render_pixbuf (EV_DOCUMENT (document), rc);
    if (border) {
          GdkPixbuf *tmp_pixbuf = thumbnail;
          
          thumbnail = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf);
          g_object_unref (tmp_pixbuf);
    }
    return thumbnail;
                       EvRenderContext      *rc,
                       gint                 *width,
                       gint                 *height)
    gdouble page_width, page_height;
    
    comics_document_get_page_size (EV_DOCUMENT (document), rc->page,
                       &page_width, &page_height);

    if (rc->rotation == 90 || rc->rotation == 270) {
        *width = (gint) (page_height * rc->scale);
        *height = (gint) (page_width * rc->scale);
    } else {
        *width = (gint) (page_width * rc->scale);
        *height = (gint) (page_height * rc->scale);
    }
    iface->get_thumbnail = comics_document_thumbnails_get_thumbnail;
    iface->get_dimensions = comics_document_thumbnails_get_dimensions;
#pragma once
#include "ev-macros.h"
GType                 register_evince_backend  (GTypeModule *module);
TypeDescription=Comic Books
MimeType=@COMICS_MIME_TYPES@;
/* -*- 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
backend_desktop_conf = configuration_data()
backend_desktop_conf.set('COMICS_MIME_TYPES', comic_mimetypes)

backend_desktop = configure_file(
    input: 'comicsdocument.xreader-backend.in',
    output: 'comicsdocument.xreader-backend',
    configuration: backend_desktop_conf,
    install: true,
    install_dir: backendsdir,
)

comics_sources = files(
  'comics-document.c',
  'ev-archive.c',
)
  cairo,
  glib,
  gtk,
  libarchive_dep,
    install_dir: backendsdir


test_name = 'test-ev-archive'

test_sources = files(
  'ev-archive.c',
  'test-ev-archive.c',
)

executable(
  test_name,
  test_sources,
  include_directories: include_dirs,
  dependencies: comics_deps,
/* -*- 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"

static void
usage (const char *prog)
{
	g_print ("- Lists file in a supported archive format\n");
	g_print ("Usage: %s archive-type filename\n", prog);
	g_print ("Where archive-type is one of rar, zip, 7z or tar\n");
}

static EvArchiveType
str_to_archive_type (const char *str)
{
	g_return_val_if_fail (str != NULL, EV_ARCHIVE_TYPE_NONE);

	if (g_strcmp0 (str, "rar") == 0)
		return EV_ARCHIVE_TYPE_RAR;
	if (g_strcmp0 (str, "zip") == 0)
		return EV_ARCHIVE_TYPE_ZIP;
	if (g_strcmp0 (str, "7z") == 0)
		return EV_ARCHIVE_TYPE_7Z;
	if (g_strcmp0 (str, "tar") == 0)
		return EV_ARCHIVE_TYPE_TAR;

	g_warning ("Archive type '%s' not supported", str);
	return EV_ARCHIVE_TYPE_NONE;
}

int
main (int argc, char **argv)
{
	EvArchive *ar;
	EvArchiveType ar_type;
	GError *error = NULL;
	gboolean printed_header = FALSE;

	if (argc != 3) {
		usage (argv[0]);
		return 1;
	}

	ar_type = str_to_archive_type (argv[1]);
	if (ar_type == EV_ARCHIVE_TYPE_NONE)
		return 1;

	ar = ev_archive_new ();
	if (!ev_archive_set_archive_type (ar, ar_type)) {
		g_warning ("Failed to set archive type");
		goto out;
	}

	if (!ev_archive_open_filename (ar, argv[2], &error)) {
		g_warning ("Failed to open '%s': %s",
			   argv[2], error->message);
		g_error_free (error);
		goto out;
	}

	while (1) {
		const char *name;
		gboolean is_encrypted;
		gint64 size;

		if (!ev_archive_read_next_header (ar, &error)) {
			if (error != NULL) {
				g_warning ("Fatal error handling archive: %s", error->message);
				g_clear_error (&error);
				goto out;
			}
			break;
		}

		name = ev_archive_get_entry_pathname (ar);
		is_encrypted = ev_archive_get_entry_is_encrypted (ar);
		size = ev_archive_get_entry_size (ar);

		if (!printed_header) {
			g_print ("P\tSIZE\tNAME\n");
			printed_header = TRUE;
		}

		g_print ("%c\t%"G_GINT64_FORMAT"\t%s\n",
			 is_encrypted ? 'P' : ' ',
			 size, name);
	}

	ev_archive_reset (ar);
	g_clear_object (&ar);

	return 0;

out:
	g_clear_object (&ar);
	return 1;
}
               libarchive-dev (>= 3.6.0),
        EV_DOCUMENT_ERROR_UNSUPPORTED_CONTENT,
    libarchive_req_version = '>= 3.6.0'
    libarchive_dep = dependency('libarchive', version: '>= 3.6.0', required: true)
    comic_mimetypes = ';'.join([
        'application/vnd.comicbook-rar',
        'application/vnd.comicbook+zip',
        'application/x-cb7',
        'application/x-cbr',
        'application/x-cbt',
        'application/x-cbz',
        'application/x-ext-cb7',
        'application/x-ext-cbr',
        'application/x-ext-cbt',
        'application/x-ext-cbz',
    ])
    xreader_mime_types += comic_mimetypes