From 8d48f633a48232c0ae8785a7388ae7e99faf3c5b Mon Sep 17 00:00:00 2001
From: Paolo Bacchilega <paobac@src.gnome.org>
Date: Sun, 12 Apr 2020 11:38:35 +0200
Subject: [PATCH] libarchive: do not follow external links when extracting
 files

Do not extract a file if its parent is a symbolic link to a
directory external to the destination.
---
 src/fr-archive-libarchive.c | 157 ++++++++++++++++++++++++++++++++++++
 1 file changed, 157 insertions(+)

diff --git a/src/fr-archive-libarchive.c b/src/fr-archive-libarchive.c
index 1d45164..e86ed3d 100644
--- a/src/fr-archive-libarchive.c
+++ b/src/fr-archive-libarchive.c
@@ -603,6 +603,149 @@ _g_output_stream_add_padding (ExtractData    *extract_data,
 }
 
 
+static gboolean
+_symlink_is_external_to_destination (GFile      *file,
+				     const char *symlink,
+				     GFile      *destination,
+				     GHashTable *external_links);
+
+
+static gboolean
+_g_file_is_external_link (GFile      *file,
+			  GFile      *destination,
+			  GHashTable *external_links)
+{
+	GFileInfo *info;
+	gboolean   external;
+
+	if (g_hash_table_lookup (external_links, file) != NULL)
+		return TRUE;
+
+	info = g_file_query_info (file,
+				  G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK "," G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
+				  G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+				  NULL,
+				  NULL);
+
+	if (info == NULL)
+		return FALSE;
+
+	external = FALSE;
+
+	if (g_file_info_get_is_symlink (info)) {
+		if (_symlink_is_external_to_destination (file,
+							 g_file_info_get_symlink_target (info),
+							 destination,
+							 external_links))
+		{
+			g_hash_table_insert (external_links, g_object_ref (file), GINT_TO_POINTER (1));
+			external = TRUE;
+		}
+	}
+
+	g_object_unref (info);
+
+	return external;
+}
+
+
+static gboolean
+_symlink_is_external_to_destination (GFile      *file,
+				     const char *symlink,
+				     GFile      *destination,
+				     GHashTable *external_links)
+{
+	gboolean  external = FALSE;
+	GFile    *parent;
+	char    **components;
+	int       i;
+
+	if ((file == NULL) || (symlink == NULL))
+		return FALSE;
+
+	if (symlink[0] == '/')
+		return TRUE;
+
+	parent = g_file_get_parent (file);
+	components = g_strsplit (symlink, "/", -1);
+	for (i = 0; components[i] != NULL; i++) {
+		char  *name = components[i];
+		GFile *tmp;
+
+		if ((name[0] == 0) || ((name[0] == '.') && (name[1] == 0)))
+			continue;
+
+		if ((name[0] == '.') && (name[1] == '.') && (name[2] == 0)) {
+			if (g_file_equal (parent, destination)) {
+				external = TRUE;
+				break;
+			}
+			else {
+				tmp = g_file_get_parent (parent);
+				g_object_unref (parent);
+				parent = tmp;
+			}
+		}
+		else {
+			tmp = g_file_get_child (parent, components[i]);
+			g_object_unref (parent);
+			parent = tmp;
+		}
+
+		if (_g_file_is_external_link (parent, destination, external_links)) {
+			external = TRUE;
+			break;
+		}
+	}
+
+	g_strfreev (components);
+	g_object_unref (parent);
+
+	return external;
+}
+
+
+static gboolean
+_g_path_is_external_to_destination (const char *relative_path,
+				    GFile      *destination,
+				    GHashTable *external_links)
+{
+	gboolean  external = FALSE;
+	GFile    *parent;
+	char    **components;
+	int       i;
+
+	if (relative_path == NULL)
+		return FALSE;
+
+	if (destination == NULL)
+		return TRUE;
+
+	parent = g_object_ref (destination);
+	components = g_strsplit (relative_path, "/", -1);
+	for (i = 0; (components[i] != NULL) && (components[i + 1] != NULL); i++) {
+		GFile *tmp;
+
+		if (components[i][0] == 0)
+			continue;
+
+		tmp = g_file_get_child (parent, components[i]);
+		g_object_unref (parent);
+		parent = tmp;
+
+		if (_g_file_is_external_link (parent, destination, external_links)) {
+			external = TRUE;
+			break;
+		}
+	}
+
+	g_strfreev (components);
+	g_object_unref (parent);
+
+	return external;
+}
+
+
 static void
 extract_archive_thread (GSimpleAsyncResult *result,
 			GObject            *object,
@@ -613,6 +756,7 @@ extract_archive_thread (GSimpleAsyncResult *result,
 	GHashTable           *checked_folders;
 	GHashTable           *created_files;
 	GHashTable           *folders_created_during_extraction;
+	GHashTable           *external_links;
 	struct archive       *a;
 	struct archive_entry *entry;
 	int                   r;
@@ -623,6 +767,7 @@ extract_archive_thread (GSimpleAsyncResult *result,
 	checked_folders = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
 	created_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, g_object_unref);
 	folders_created_during_extraction = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
+	external_links = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
 	fr_archive_progress_set_total_files (load_data->archive, extract_data->n_files_to_extract);
 
 	a = archive_read_new ();
@@ -654,6 +799,15 @@ extract_archive_thread (GSimpleAsyncResult *result,
 		fullpath = (*pathname == '/') ? g_strdup (pathname) : g_strconcat ("/", pathname, NULL);
 		relative_path = _g_path_get_relative_basename_safe (fullpath, extract_data->base_dir, extract_data->junk_paths);
 		if (relative_path == NULL) {
+			fr_archive_progress_inc_completed_files (load_data->archive, 1);
+			fr_archive_progress_inc_completed_bytes (load_data->archive, archive_entry_size_is_set (entry) ? archive_entry_size (entry) : 0);
+			archive_read_data_skip (a);
+			continue;
+		}
+
+		if (_g_path_is_external_to_destination (relative_path, extract_data->destination, external_links)) {
+			fr_archive_progress_inc_completed_files (load_data->archive, 1);
+			fr_archive_progress_inc_completed_bytes (load_data->archive, archive_entry_size_is_set (entry) ? archive_entry_size (entry) : 0);
 			archive_read_data_skip (a);
 			continue;
 		}
@@ -862,6 +1016,8 @@ extract_archive_thread (GSimpleAsyncResult *result,
 						load_data->error = g_error_copy (local_error);
 					g_clear_error (&local_error);
 				}
+				else if (_symlink_is_external_to_destination (file, archive_entry_symlink (entry), extract_data->destination, external_links))
+					g_hash_table_insert (external_links, g_object_ref (file), GINT_TO_POINTER (1));
 				archive_read_data_skip (a);
 				break;
 
@@ -896,6 +1052,7 @@ extract_archive_thread (GSimpleAsyncResult *result,
 	g_hash_table_unref (folders_created_during_extraction);
 	g_hash_table_unref (created_files);
 	g_hash_table_unref (checked_folders);
+	g_hash_table_unref (external_links);
 	archive_read_free (a);
 	extract_data_free (extract_data);
 }
-- 
2.23.0

