1 /*
2  *  Copyright (C) 2008 Giuseppe Torelli - <colossus73@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17  *  MA 02110-1301 USA.
18  */
19 
20 #include <string.h>
21 #include <glib/gstdio.h>
22 #include "7zip.h"
23 #include "gzip_et_al.h"
24 #include "interface.h"
25 #include "main.h"
26 #include "string_utils.h"
27 #include "support.h"
28 #include "window.h"
29 
30 #define INDEX (archive->type == XARCHIVETYPE_RAR ? (archive->tag == 5 ? 1 : 0) : 0)
31 
32 static gboolean data_line, encrypted, last_line;
33 
xa_7zip_seek_position(const gchar * filename,GIOChannel ** file,gint64 offset,GSeekType type)34 static void xa_7zip_seek_position (const gchar *filename, GIOChannel **file, gint64 offset, GSeekType type)
35 {
36 	gchar byte;
37 
38 	g_io_channel_seek_position(*file, offset, type, NULL);
39 
40 	/* check whether it's a volume. i.e. whether offset is beyond the end of the file */
41 	if (g_io_channel_read_chars(*file, &byte, sizeof(byte), NULL, NULL) == G_IO_STATUS_NORMAL)
42 		/* doesn't seem so - back to requested position */
43 		g_io_channel_seek_position(*file, -(gint64) sizeof(byte), G_SEEK_CUR, NULL);
44 	else /* find the volume the offset is pointing to */
45 	{
46 		guint64 position, volsizes = 0;
47 		gchar *fvname;
48 		size_t ext;
49 		guint i;
50 		GStatBuf st;
51 		GIOChannel *fnew;
52 
53 		if (!g_str_has_suffix(filename, ".001"))
54 			return;
55 
56 		position = 12 + 8 + (guint64) offset;   // absolute position
57 
58 		fvname = g_strdup(filename);
59 		ext = strlen(fvname) - 4;
60 
61 		/* check volumes ... */
62 		for (i = 1; i < 1000; i++)
63 		{
64 			fvname[ext] = 0;
65 			sprintf(fvname, "%s.%03u", fvname, i);
66 
67 			if (!g_file_test(fvname, G_FILE_TEST_EXISTS) || (g_stat(fvname, &st) != 0))
68 				break;
69 
70 			volsizes += (guint64) st.st_size;
71 
72 			/* ... up to the one we're looking for */
73 			if (volsizes > position)
74 			{
75 				fnew = g_io_channel_new_file(fvname, "r", NULL);
76 
77 				if (!fnew)
78 					break;
79 
80 				/* switch to volume */
81 
82 				g_io_channel_shutdown(*file, FALSE, NULL);
83 
84 				*file = fnew;
85 
86 				g_io_channel_set_encoding(*file, NULL, NULL);
87 				g_io_channel_seek_position(*file, position - (volsizes - (guint64) st.st_size), G_SEEK_SET, NULL);
88 
89 				break;
90 			}
91 		}
92 
93 		g_free(fvname);
94 	}
95 }
96 
xa_7zip_uint64_skip(GIOChannel * file)97 static void xa_7zip_uint64_skip (GIOChannel *file)
98 {
99 	gchar first, byte;
100 	guchar mask = 0x80;
101 
102 	g_io_channel_read_chars(file, &first, sizeof(first), NULL, NULL);
103 
104 	/* 7z uint64 is specially encoded */
105 	while ((mask > 1) && (first & mask))
106 	{
107 		g_io_channel_read_chars(file, &byte, sizeof(byte), NULL, NULL);
108 		mask >>= 1;
109 	}
110 }
111 
is_encrypted(XArchive * archive)112 static gboolean is_encrypted (XArchive *archive)
113 {
114 	gchar *command, *output;
115 	gboolean result;
116 
117 	command = g_strconcat(archiver[archive->type].program[INDEX], " l -slt ", archive->path[0], NULL);
118 	g_spawn_command_line_sync(command, &output, NULL, NULL, NULL);
119 
120 	result = (strstr(output, "\nEncrypted = +\n") != NULL);
121 
122 	g_free(output);
123 	g_free(command);
124 
125 	return result;
126 }
127 
is7zip_mhe(const gchar * filename)128 gboolean is7zip_mhe (const gchar *filename)
129 {
130 	GIOChannel *file;
131 	guint i;
132 	gchar byte;
133 	guint64 offset = 0;
134 	gboolean result = FALSE;
135 
136 	file = g_io_channel_new_file(filename, "r", NULL);
137 
138 	if (file)
139 	{
140 		g_io_channel_set_encoding(file, NULL, NULL);
141 		g_io_channel_set_buffered(file, FALSE);
142 
143 		/* skip signature, version and header CRC32 */
144 		g_io_channel_seek_position(file, 12, G_SEEK_SET, NULL);
145 
146 		/* next header offset (uint64_t) */
147 		for (i = 0; i < 8; i++)
148 		{
149 			g_io_channel_read_chars(file, &byte, sizeof(byte), NULL, NULL);
150 			offset |= (guint64) (guchar) byte << (8 * i);
151 		}
152 
153 		/* skip next header size and CRC32 */
154 		xa_7zip_seek_position(filename, &file, 12 + offset, G_SEEK_CUR);
155 
156 		/* header info */
157 		g_io_channel_read_chars(file, &byte, sizeof(byte), NULL, NULL);
158 
159 		/* encoded header */
160 		if (byte == 0x17)
161 		{
162 			/* streams info */
163 			g_io_channel_read_chars(file, &byte, sizeof(byte), NULL, NULL);
164 
165 			/* pack info */
166 			if (byte == 0x06)
167 			{
168 				/* skip pack position */
169 				xa_7zip_uint64_skip(file);
170 				/* skip number of pack streams */
171 				xa_7zip_uint64_skip(file);
172 
173 				g_io_channel_read_chars(file, &byte, sizeof(byte), NULL, NULL);
174 
175 				/* size info */
176 				if (byte == 0x09)
177 				{
178 					/* skip unpack sizes */
179 					xa_7zip_uint64_skip(file);
180 
181 					g_io_channel_read_chars(file, &byte, sizeof(byte), NULL, NULL);
182 
183 					/* pack info end */
184 					if (byte == 0x00)
185 					{
186 						/* coders info */
187 						g_io_channel_read_chars(file, &byte, sizeof(byte), NULL, NULL);
188 
189 						/* unpack info */
190 						if (byte == 0x07)
191 						{
192 							g_io_channel_read_chars(file, &byte, sizeof(byte), NULL, NULL);
193 
194 							/* folder */
195 							if (byte == 0x0b)
196 							{
197 								/* skip number of folders */
198 								xa_7zip_uint64_skip(file);
199 
200 								g_io_channel_read_chars(file, &byte, sizeof(byte), NULL, NULL);
201 
202 								/* coders info end */
203 								if (byte == 0x00)
204 								{
205 									g_io_channel_read_chars(file, &byte, sizeof(byte), NULL, NULL);
206 
207 									/* header or archive properties */
208 									if (byte == 0x01 || byte == 0x02)
209 									{
210 										/* codec id size */
211 										g_io_channel_read_chars(file, &byte, sizeof(byte), NULL, NULL);
212 
213 										if ((byte & 0x0f) == 4)
214 										{
215 											gchar id[4];
216 
217 											/* codec id */
218 											g_io_channel_read_chars(file, id, sizeof(id), NULL, NULL);
219 
220 											/* check for id of 7zAES */
221 											result = (memcmp(id, "\x06\xf1\x07\x01", 4) == 0);
222 										}
223 									}
224 								}
225 							}
226 						}
227 					}
228 				}
229 			}
230 		}
231 
232 		g_io_channel_shutdown(file, FALSE, NULL);
233 	}
234 
235 	return result;
236 }
237 
238 /* it can handle other archive types as well */
xa_7zip_ask(XArchive * archive)239 void xa_7zip_ask (XArchive *archive)
240 {
241 	archive->can_test = TRUE;
242 	archive->can_extract = TRUE;
243 	archive->can_add = archiver[archive->type].is_compressor;
244 	archive->can_delete = (archiver[archive->type].is_compressor && !SINGLE_FILE_COMPRESSOR(archive));
245 	archive->can_sfx = (archive->type == XARCHIVETYPE_7ZIP);
246 	archive->can_password = (archive->type == XARCHIVETYPE_7ZIP);
247 	archive->can_full_path[0] = TRUE;
248 	archive->can_overwrite = TRUE;
249 	archive->can_update[1] = archiver[archive->type].is_compressor;
250 	archive->can_freshen[1] = (archiver[archive->type].is_compressor && !SINGLE_FILE_COMPRESSOR(archive));
251 	archive->can_move = archiver[archive->type].is_compressor;
252 	archive->can_solid = (archive->type == XARCHIVETYPE_7ZIP);
253 }
254 
xa_7zip_password_str(XArchive * archive)255 static gchar *xa_7zip_password_str (XArchive *archive)
256 {
257 	if (archive->password)
258 		return g_strconcat(" -p", archive->password, NULL);
259 	else
260 		return g_strdup("");
261 }
262 
xa_7zip_parse_output(gchar * line,XArchive * archive)263 static void xa_7zip_parse_output (gchar *line, XArchive *archive)
264 {
265 	XEntry *entry;
266 	gchar *filename;
267 	gpointer item[5];
268 	gint linesize = 0,a = 0;
269 	gboolean dir;
270 
271 	if (last_line)
272 		return;
273 
274 	if (!data_line)
275 	{
276 		if (strncmp(line, "Method = ", 9) == 0 && strstr(line, "7zAES"))
277 		{
278 			encrypted = TRUE;
279 			archive->has_password = TRUE;
280 		}
281 
282 		if (strncmp(line, "-----", 5) == 0)
283 		{
284 			data_line = TRUE;
285 			return;
286 		}
287 		return;
288 	}
289 	if (line[0] == '-')
290 	{
291 		last_line = TRUE;
292 		return;
293 	}
294 
295 	linesize = strlen(line);
296 
297 	/* Date */
298 	line[10] = '\0';
299 	item[2] = line;
300 
301 	/* Time */
302 	line[19] = '\0';
303 	item[3] = line + 11;
304 
305 	/* Permissions */
306 	line[25] = '\0';
307 	item[4] = line + 20;
308 
309 	dir = (*(char *) item[4] == 'D');
310 
311 	/* Size */
312 	for(a=26; a < linesize; ++a)
313 		if(line[a] >= '0' && line[a] <= '9')
314 			break;
315 
316 	line[38] = '\0';
317 	item[0] = line + a;
318 
319 	/* Compressed */
320 	/* Is this item solid? */
321 	if (line[50] == ' ')
322 	{
323 		line[linesize-1] = '\0';
324 		item[1] = "0";
325 	}
326 	else
327 	{
328 		for(a=39; a < linesize; ++a)
329 			if(line[a] >= '0' && line[a] <= '9')
330 				break;
331 		line[51] = '\0';
332 		item[1] = line + a;
333 		line[linesize-1] = '\0';
334 	}
335 
336 	filename = g_strdup(line + 53);
337 	entry = xa_set_archive_entries_for_each_row(archive, filename, item);
338 
339 	if (entry)
340 	{
341 		if (dir)
342 			entry->is_dir = TRUE;
343 
344 		entry->is_encrypted = encrypted;
345 
346 		if (!entry->is_dir)
347 			archive->files++;
348 
349 		archive->files_size += g_ascii_strtoull(item[0], NULL, 0);
350 	}
351 
352 	g_free(filename);
353 }
354 
xa_7zip_list(XArchive * archive)355 void xa_7zip_list (XArchive *archive)
356 {
357 	const GType types[] = {GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_UINT64, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER};
358 	const gchar *titles[] = {_("Original Size"), _("Compressed"), _("Date"), _("Time"), _("Attributes")};
359 	gchar *password_str, *command;
360 	guint i;
361 
362 	if (!archive->has_password)
363 	{
364 		if (archive->type == XARCHIVETYPE_7ZIP)
365 			archive->has_password = is7zip_mhe(archive->path[0]);
366 		else
367 			archive->has_password = is_encrypted(archive);
368 	}
369 
370 	if ((archive->type == XARCHIVETYPE_7ZIP) && archive->has_password)
371 		if (!xa_check_password(archive))
372 			return;
373 
374 	/* a single file compressor archive is no longer new and empty now */
375 	archive->can_add = (archiver[archive->type].is_compressor && !SINGLE_FILE_COMPRESSOR(archive));
376 
377 	data_line = FALSE;
378 	last_line = FALSE;
379 	encrypted = archive->has_password;
380 
381 	password_str = xa_7zip_password_str(archive);
382 
383 	if (archive->type == XARCHIVETYPE_CPIO && archive->tag == 'E')
384 		command = g_strconcat("sh -c \"echo ", _("Unsupported binary format!"), " >&2; exit 1\"", NULL);
385 	else
386 		command = g_strconcat(archiver[archive->type].program[INDEX], " l", password_str, " ", archive->path[1], NULL);
387 
388 	g_free(password_str);
389 
390 	archive->files_size = 0;
391 	archive->files = 0;
392 	archive->parse_output = xa_7zip_parse_output;
393 	xa_spawn_async_process (archive,command);
394 	g_free ( command );
395 
396 	archive->columns = 8;
397 	archive->size_column = 2;
398 	archive->column_types = g_malloc0(sizeof(types));
399 
400 	for (i = 0; i < archive->columns; i++)
401 		archive->column_types[i] = types[i];
402 
403 	xa_create_liststore(archive, titles);
404 }
405 
xa_7zip_test(XArchive * archive)406 void xa_7zip_test (XArchive *archive)
407 {
408 	gchar *password_str, *command;
409 
410 	password_str = xa_7zip_password_str(archive);
411 	command = g_strconcat(archiver[archive->type].program[INDEX], " t", password_str, " -bd -y ", archive->path[1], NULL);
412 	g_free(password_str);
413 
414 	xa_run_command(archive, command);
415 	g_free(command);
416 }
417 
418 /*
419  * Note: 7zip's wildcard handling (even with switch -spd) seems buggy.
420  * Everything is okay as long as no file name in the working directory
421  * matches. If there is a wildcard match, it asks "would you like to replace
422  * the existing file" and fails, i.e. extraction of files named '?' or '*'
423  * always fails (even in an empty directory) and extraction of a file named
424  * 't*' fails if there is already a file name 'test', for example, in the
425  * extraction path (while extraction would succeed otherwise).
426  */
427 
xa_7zip_extract(XArchive * archive,GSList * file_list)428 gboolean xa_7zip_extract (XArchive *archive, GSList *file_list)
429 {
430 	GString *files;
431 	gchar *password_str, *command;
432 	gboolean result;
433 
434 	files = xa_quote_filenames(file_list, NULL, TRUE);
435 	password_str = xa_7zip_password_str(archive);
436 	command = g_strconcat(archiver[archive->type].program[INDEX],
437 	                      archive->do_full_path ? " x" : " e",
438 	                      archive->do_overwrite ? " -aoa" : " -aos",
439 	                      password_str, " -bd -spd -y ",
440 	                      archive->path[1], files->str,
441 	                      " -o", archive->extraction_dir, NULL);
442 	g_free(password_str);
443 	g_string_free(files,TRUE);
444 
445 	result = xa_run_command(archive, command);
446 	g_free(command);
447 
448 	return result;
449 }
450 
xa_7zip_add(XArchive * archive,GSList * file_list,gchar * compression)451 void xa_7zip_add (XArchive *archive, GSList *file_list, gchar *compression)
452 {
453 	GString *files;
454 	gchar *password_str, *solid, *command;
455 
456 	if (archive->location_path != NULL)
457 		archive->child_dir = g_strdup(archive->working_dir);
458 
459 	if (!compression)
460 		compression = "5";
461 
462 	files = xa_quote_filenames(file_list, NULL, TRUE);
463 	password_str = xa_7zip_password_str(archive);
464 	solid = g_strconcat(" -ms=", archive->do_solid ? "on" : "off", NULL);
465 	command = g_strconcat(archiver[archive->type].program[0],
466 	                      archive->do_update ? " u" : " a",
467 	                      archive->do_freshen ? " -ur0w0x1z1" : "",
468 	                      archive->do_move ? " -sdel" : "",
469 	                      archive->type == XARCHIVETYPE_7ZIP ? solid : "",
470 	                      " -mx=", compression,
471 	                      password_str, " -bd -spd -y ",
472 	                      archive->path[1], files->str, NULL);
473 	g_free(solid);
474 	g_free(password_str);
475 	g_string_free(files,TRUE);
476 
477 	xa_run_command(archive, command);
478 	g_free(command);
479 }
480 
xa_7zip_delete(XArchive * archive,GSList * file_list)481 void xa_7zip_delete (XArchive *archive, GSList *file_list)
482 {
483 	GString *files;
484 	gchar *password_str, *command;
485 
486 	files = xa_quote_filenames(file_list, NULL, TRUE);
487 	password_str = xa_7zip_password_str(archive);
488 	command = g_strconcat(archiver[archive->type].program[0], " d", password_str, " -bd -spd -y ", archive->path[1], files->str, NULL);
489 	g_free(password_str);
490 	g_string_free(files,TRUE);
491 
492 	xa_run_command(archive, command);
493 	g_free(command);
494 }
495