1 /*
2  *  Copyright (C) 2002 Jorn Baayen
3  *  Copyright © 2009 Christian Persch
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 #include <config.h>
21 
22 #include <stdlib.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <errno.h>
27 
28 #include <glib.h>
29 #include <glib/gstdio.h>
30 #include <glib/gi18n-lib.h>
31 
32 #include "ev-file-helpers.h"
33 
34 static gchar *tmp_dir = NULL;
35 
36 /*
37  * ev_dir_ensure_exists:
38  * @dir: the directory name
39  * @mode: permissions to use when creating the directory
40  * @error: a location to store a #GError
41  *
42  * Create @dir recursively with permissions @mode.
43  *
44  * Returns: %TRUE on success, or %FALSE on error with @error filled in
45  */
46 static gboolean
_ev_dir_ensure_exists(const gchar * dir,int mode,GError ** error)47 _ev_dir_ensure_exists (const gchar *dir,
48                        int          mode,
49                        GError     **error)
50 {
51         int errsv;
52         char *display_name;
53 
54         g_return_val_if_fail (dir != NULL, FALSE);
55 
56         errno = 0;
57 	if (g_mkdir_with_parents (dir, mode) == 0)
58 		return TRUE;
59 
60         errsv = errno;
61 	if (errsv == EEXIST && g_file_test (dir, G_FILE_TEST_IS_DIR))
62                 return TRUE;
63 
64         display_name = g_filename_display_name (dir);
65         g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
66                      "Failed to create directory '%s': %s",
67                      display_name, g_strerror (errsv));
68         g_free (display_name);
69 
70 	return FALSE;
71 }
72 
73 /*
74  * _ev_tmp_dir:
75  * @error: a location to store a #GError
76  *
77  * Returns the tmp directory.
78  *
79  * Returns: the tmp directory, or %NULL with @error filled in if the
80  *   directory could not be created
81  */
82 static const char *
_ev_tmp_dir(GError ** error)83 _ev_tmp_dir (GError **error)
84 {
85 
86         if (tmp_dir == NULL) {
87                 const gchar *prgname;
88                 gchar *dirname;
89 
90                 prgname = g_get_prgname ();
91                 dirname = g_strdup_printf ("%s-%u", prgname ? prgname : "unknown", getpid ());
92                 tmp_dir = g_build_filename (g_get_tmp_dir (), dirname, NULL);
93                 g_free (dirname);
94         }
95 
96         if (!_ev_dir_ensure_exists (tmp_dir, 0700, error))
97                 return NULL;
98 
99 	return tmp_dir;
100 }
101 
102 void
_ev_file_helpers_init(void)103 _ev_file_helpers_init (void)
104 {
105 }
106 
107 void
_ev_file_helpers_shutdown(void)108 _ev_file_helpers_shutdown (void)
109 {
110 	if (tmp_dir != NULL)
111 		g_rmdir (tmp_dir);
112 
113 	g_free (tmp_dir);
114 	tmp_dir = NULL;
115 }
116 
117 /**
118  * ev_mkstemp:
119  * @template: a template string; must contain 'XXXXXX', but not necessarily as a suffix
120  * @file_name: a location to store the filename of the temp file
121  * @error: a location to store a #GError
122  *
123  * Creates a temp file in the xreader temp directory.
124  *
125  * Returns: a file descriptor to the newly created temp file name, or %-1
126  *   on error with @error filled in
127  */
128 int
ev_mkstemp(const char * template,char ** file_name,GError ** error)129 ev_mkstemp (const char  *template,
130             char       **file_name,
131             GError     **error)
132 {
133         const char *tmp;
134         char *name;
135         int fd;
136 
137         if ((tmp = _ev_tmp_dir (error)) == NULL)
138               return -1;
139 
140         name = g_build_filename (tmp, template, NULL);
141         fd = g_mkstemp (name);
142 
143         if (fd == -1) {
144 		int errsv = errno;
145 
146                 g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
147                              _("Failed to create a temporary file: %s"),
148                              g_strerror (errsv));
149 
150                 g_free (name);
151                 return -1;
152         }
153 
154         if (file_name)
155                 *file_name = name;
156 
157         return fd;
158 }
159 
160 static void
close_fd_cb(gpointer fdptr)161 close_fd_cb (gpointer fdptr)
162 {
163         int fd = GPOINTER_TO_INT (fdptr);
164 
165         close (fd);
166 }
167 
168 /**
169  * ev_mkstemp_file:
170  * @template: a template string; must contain 'XXXXXX', but not necessarily as a suffix
171  * @error: a location to store a #GError
172  *
173  * Creates a temp #GFile in the xreader temp directory. See ev_mkstemp() for more information.
174  *
175  * Returns: a newly allocated #GFile for the newly created temp file name, or %NULL
176  *   on error with @error filled in
177  */
178 GFile *
ev_mkstemp_file(const char * template,GError ** error)179 ev_mkstemp_file (const char        *template,
180                  GError           **error)
181 {
182         char *file_name;
183         int fd;
184         GFile *file;
185 
186         fd = ev_mkstemp (template, &file_name, error);
187         if (fd == -1)
188                 return NULL;
189 
190         file = g_file_new_for_path (file_name);
191         g_free (file_name);
192 
193         g_object_set_data_full (G_OBJECT (file), "ev-mkstemp-fd",
194                                 GINT_TO_POINTER (fd), (GDestroyNotify) close_fd_cb);
195 
196         return file;
197 }
198 
199 /**
200  * This function is copied from
201  * http://bugzilla.gnome.org/show_bug.cgi?id=524831
202  * and renamed from g_mkdtemp to _ev_g_mkdtemp.
203  *
204  * If/when this function gets added to glib, it can be removed from
205  * xreader' sources.
206  *
207  *
208  * g_mkdtemp:
209  * @tmpl: template directory name
210  *
211  * Creates a temporary directory. See the mkdtemp() documentation
212  * on most UNIX-like systems.
213  *
214  * The parameter is a string that should follow the rules for
215  * mkdtemp() templates, i.e. contain the string "XXXXXX".  g_mkdtemp()
216  * is slightly more flexible than mkdtemp() in that the sequence does
217  * not have to occur at the very end of the template. The X string
218  * will be modified to form the name of a directory that didn't
219  * already exist.  The string should be in the GLib file name
220  * encoding. Most importantly, on Windows it should be in UTF-8.
221  *
222  * Return value: If a temporary directory was successfully created,
223  * @tmpl will be returned with the XXXXXX string modified in such a
224  * way as to make the path unique.  In case of errors, %NULL is
225  * returned.
226  */
227 static gchar *
_ev_g_mkdtemp(gchar * tmpl)228 _ev_g_mkdtemp (gchar *tmpl)
229 {
230   static const char letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
231   static const int NLETTERS = sizeof (letters) - 1;
232   static int counter = 0;
233   char *XXXXXX;
234   GTimeVal tv;
235   glong value;
236   int count;
237 
238   /* find the last occurrence of "XXXXXX" */
239   XXXXXX = g_strrstr (tmpl, "XXXXXX");
240 
241   if (!XXXXXX || strncmp (XXXXXX, "XXXXXX", 6))
242     {
243       errno = EINVAL;
244       return NULL;
245     }
246 
247   /* Get some more or less random data.  */
248   g_get_current_time (&tv);
249   value = (tv.tv_usec ^ tv.tv_sec) + counter++;
250 
251   for (count = 0; count < 100; value += 7777, ++count)
252     {
253       glong v = value;
254 
255       /* Fill in the random bits.  */
256       XXXXXX[0] = letters[v % NLETTERS];
257       v /= NLETTERS;
258       XXXXXX[1] = letters[v % NLETTERS];
259       v /= NLETTERS;
260       XXXXXX[2] = letters[v % NLETTERS];
261       v /= NLETTERS;
262       XXXXXX[3] = letters[v % NLETTERS];
263       v /= NLETTERS;
264       XXXXXX[4] = letters[v % NLETTERS];
265       v /= NLETTERS;
266       XXXXXX[5] = letters[v % NLETTERS];
267 
268       /* tmpl is in UTF-8 on Windows, thus use g_mkdir() */
269       if (g_mkdir (tmpl, 0700) == 0)
270         return tmpl;
271 
272       if (errno != EEXIST)
273          /* Any other error will apply also to other names we might
274          *  try, and there are 2^32 or so of them, so give up now.
275          */
276          return NULL;
277     }
278 
279   /* We got out of the loop because we ran out of combinations to try.  */
280   errno = EEXIST;
281   return NULL;
282 }
283 
284 /**
285  * ev_mkdtemp:
286  * @template: a template string; must end in 'XXXXXX'
287  * @error: a location to store a #GError
288  *
289  * Creates a temp directory in the xreader temp directory.
290  *
291  * Returns: a newly allocated string with the temp directory name, or %NULL
292  *   on error with @error filled in
293  */
294 gchar *
ev_mkdtemp(const char * template,GError ** error)295 ev_mkdtemp (const char        *template,
296             GError           **error)
297 {
298         const char *tmp;
299         char *name;
300 
301         if ((tmp = _ev_tmp_dir (error)) == NULL)
302               return NULL;
303 
304         name = g_build_filename (tmp, template, NULL);
305         if (_ev_g_mkdtemp (name) == NULL) {
306 		int errsv = errno;
307 
308                 g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
309                              _("Failed to create a temporary directory: %s"),
310                              g_strerror (errsv));
311 
312                 g_free (name);
313                 return NULL;
314         }
315 
316         return name;
317 }
318 
319 /* Remove a local temp file created by xreader */
320 void
ev_tmp_filename_unlink(const gchar * filename)321 ev_tmp_filename_unlink (const gchar *filename)
322 {
323 	if (!filename)
324 		return;
325 
326 	if (!tmp_dir)
327 		return;
328 
329 	if (g_str_has_prefix (filename, tmp_dir)) {
330 		g_unlink (filename);
331 	}
332 }
333 
334 void
ev_tmp_file_unlink(GFile * file)335 ev_tmp_file_unlink (GFile *file)
336 {
337 	gboolean res;
338 	GError  *error = NULL;
339 
340 	if (!file)
341 		return;
342 
343 	res = g_file_delete (file, NULL, &error);
344 	if (!res) {
345 		char *uri;
346 
347 		uri = g_file_get_uri (file);
348 		g_warning ("Unable to delete temp file %s: %s\n", uri, error->message);
349 		g_free (uri);
350 		g_error_free (error);
351 	}
352 }
353 
354 void
ev_tmp_uri_unlink(const gchar * uri)355 ev_tmp_uri_unlink (const gchar *uri)
356 {
357 	GFile *file;
358 
359 	if (!uri)
360 		return;
361 
362 	file = g_file_new_for_uri (uri);
363 	if (!g_file_is_native (file)) {
364 		g_warning ("Attempting to delete non native uri: %s\n", uri);
365 		g_object_unref (file);
366 		return;
367 	}
368 
369 	ev_tmp_file_unlink (file);
370 	g_object_unref (file);
371 }
372 
373 gboolean
ev_file_is_temp(GFile * file)374 ev_file_is_temp (GFile *file)
375 {
376 	gchar   *path;
377 	gboolean retval;
378 
379 	if (!g_file_is_native (file))
380 		return FALSE;
381 
382 	path = g_file_get_path (file);
383 	if (!path)
384 		return FALSE;
385 
386 	retval = g_str_has_prefix (path, g_get_tmp_dir ());
387 	g_free (path);
388 
389 	return retval;
390 }
391 
392 /**
393  * ev_xfer_uri_simple:
394  * @from: the source URI
395  * @to: the target URI
396  * @error: a #GError location to store an error, or %NULL
397  *
398  * Performs a g_file_copy() from @from to @to.
399  *
400  * Returns: %TRUE on success, or %FALSE on error with @error filled in
401  */
402 gboolean
ev_xfer_uri_simple(const char * from,const char * to,GError ** error)403 ev_xfer_uri_simple (const char *from,
404 		    const char *to,
405 		    GError     **error)
406 {
407 	GFile *source_file;
408 	GFile *target_file;
409 	gboolean result;
410 
411 	if (!from)
412 		return TRUE;
413 
414         g_return_val_if_fail (to != NULL, TRUE);
415 
416 	source_file = g_file_new_for_uri (from);
417 	target_file = g_file_new_for_uri (to);
418 
419 	result = g_file_copy (source_file, target_file,
420 			      G_FILE_COPY_TARGET_DEFAULT_PERMS |
421 			      G_FILE_COPY_OVERWRITE,
422 			      NULL, NULL, NULL, error);
423 
424 	g_object_unref (target_file);
425 	g_object_unref (source_file);
426 
427 	return result;
428 }
429 
430 static gchar *
get_mime_type_from_uri(const gchar * uri,GError ** error)431 get_mime_type_from_uri (const gchar *uri, GError **error)
432 {
433 	GFile       *file;
434 	GFileInfo   *file_info;
435 	const gchar *content_type;
436         gchar       *mime_type = NULL;
437 
438 	file = g_file_new_for_uri (uri);
439 	file_info = g_file_query_info (file,
440 				       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
441 				       0, NULL, error);
442 	g_object_unref (file);
443 
444 	if (file_info == NULL)
445 		return NULL;
446 
447 	content_type = g_file_info_get_content_type (file_info);
448 	if (content_type) {
449                 mime_type = g_content_type_get_mime_type (content_type);
450         }
451 
452 	g_object_unref (file_info);
453 	return mime_type;
454 }
455 
456 static gchar *
get_mime_type_from_data(const gchar * uri,GError ** error)457 get_mime_type_from_data (const gchar *uri, GError **error)
458 {
459 	GFile            *file;
460 	GFileInputStream *input_stream;
461 	gssize            size_read;
462 	guchar            buffer[1024];
463 	gboolean          retval;
464 	gchar            *content_type, *mime_type;
465 
466 	file = g_file_new_for_uri (uri);
467 
468 	input_stream = g_file_read (file, NULL, error);
469 	if (!input_stream) {
470 		g_object_unref (file);
471 		return NULL;
472 	}
473 
474 	size_read = g_input_stream_read (G_INPUT_STREAM (input_stream),
475 					 buffer, sizeof (buffer), NULL, error);
476 	if (size_read == -1) {
477 		g_object_unref (input_stream);
478 		g_object_unref (file);
479 		return NULL;
480 	}
481 
482 	retval = g_input_stream_close (G_INPUT_STREAM (input_stream), NULL, error);
483 
484 	g_object_unref (input_stream);
485 	g_object_unref (file);
486 	if (!retval)
487 		return NULL;
488 
489 	content_type = g_content_type_guess (NULL, /* no filename */
490 					     buffer, size_read,
491 					     NULL);
492 	if (!content_type)
493 		return NULL;
494 
495 	mime_type = g_content_type_get_mime_type (content_type);
496 	g_free (content_type);
497 	return mime_type;
498 }
499 
500 /**
501  * ev_file_get_mime_type:
502  * @uri: the URI
503  * @fast: whether to use fast MIME type detection
504  * @error: a #GError location to store an error, or %NULL
505  *
506  * Note: on unknown MIME types, this may return NULL without @error
507  * being filled in.
508  *
509  * Returns: a newly allocated string with the MIME type of the file at
510  *   @uri, or %NULL on error or if the MIME type could not be determined
511  */
512 gchar *
ev_file_get_mime_type(const gchar * uri,gboolean fast,GError ** error)513 ev_file_get_mime_type (const gchar *uri,
514 		       gboolean     fast,
515 		       GError     **error)
516 {
517 	return fast ? get_mime_type_from_uri (uri, error) : get_mime_type_from_data (uri, error);
518 }
519 
520 /* Compressed files support */
521 
522 static const char *compressor_cmds[] = {
523   NULL,
524   "bzip2",
525   "gzip",
526   "xz"
527 };
528 
529 #define N_ARGS      4
530 #define BUFFER_SIZE 1024
531 
532 static gchar *
compression_run(const gchar * uri,EvCompressionType type,gboolean compress,GError ** error)533 compression_run (const gchar       *uri,
534 		 EvCompressionType  type,
535 		 gboolean           compress,
536 		 GError           **error)
537 {
538 	gchar *argv[N_ARGS];
539 	gchar *uri_dst = NULL;
540 	gchar *filename, *filename_dst = NULL;
541 	gchar *cmd;
542 	gint   fd, pout;
543 	GError *err = NULL;
544 
545 	if (type == EV_COMPRESSION_NONE)
546 		return NULL;
547 
548 	cmd = g_find_program_in_path (compressor_cmds[type]);
549 	if (!cmd) {
550 		/* FIXME: better error codes! */
551 		/* FIXME: i18n later */
552 		g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
553 			     "Failed to find the \"%s\" command in the search path.",
554                              compressor_cmds[type]);
555 		return NULL;
556 	}
557 
558 	filename = g_filename_from_uri (uri, NULL, error);
559 	if (!filename) {
560 		g_free (cmd);
561 		return NULL;
562 	}
563 
564         fd = ev_mkstemp ("comp.XXXXXX", &filename_dst, error);
565 	if (fd == -1) {
566 		g_free (cmd);
567 		g_free (filename);
568 
569 		return NULL;
570 	}
571 
572 	argv[0] = cmd;
573 	argv[1] = compress ? "-c" : "-cd";
574 	argv[2] = filename;
575 	argv[3] = NULL;
576 
577 	if (g_spawn_async_with_pipes (NULL, argv, NULL,
578 				      G_SPAWN_STDERR_TO_DEV_NULL,
579 				      NULL, NULL, NULL,
580 				      NULL, &pout, NULL, &err)) {
581 		GIOChannel *in, *out;
582 		gchar buf[BUFFER_SIZE];
583 		GIOStatus read_st, write_st;
584 		gsize bytes_read, bytes_written;
585 
586 		in = g_io_channel_unix_new (pout);
587 		g_io_channel_set_encoding (in, NULL, NULL);
588 		out = g_io_channel_unix_new (fd);
589 		g_io_channel_set_encoding (out, NULL, NULL);
590 
591 		do {
592 			read_st = g_io_channel_read_chars (in, buf,
593 							   BUFFER_SIZE,
594 							   &bytes_read,
595 							   error);
596 			if (read_st == G_IO_STATUS_NORMAL) {
597 				write_st = g_io_channel_write_chars (out, buf,
598 								     bytes_read,
599 								     &bytes_written,
600 								     error);
601 				if (write_st == G_IO_STATUS_ERROR)
602 					break;
603 			} else if (read_st == G_IO_STATUS_ERROR) {
604 				break;
605 			}
606 		} while (bytes_read > 0);
607 
608 		g_io_channel_unref (in);
609 		g_io_channel_unref (out);
610 	}
611 
612 	close (fd);
613 
614 	if (err) {
615 		g_propagate_error (error, err);
616 	} else {
617 		uri_dst = g_filename_to_uri (filename_dst, NULL, error);
618 	}
619 
620 	g_free (cmd);
621 	g_free (filename);
622 	g_free (filename_dst);
623 
624 	return uri_dst;
625 }
626 
627 /**
628  * ev_file_uncompress:
629  * @uri: a file URI
630  * @type: the compression type
631  * @error: a #GError location to store an error, or %NULL
632  *
633  * Uncompresses the file at @uri.
634  *
635  * If @type is %EV_COMPRESSION_NONE, it does nothing and returns %NULL.
636  *
637  * Otherwise, it returns the filename of a
638  * temporary file containing the decompressed data from the file at @uri.
639  * On error it returns %NULL and fills in @error.
640  *
641  * It is the caller's responsibility to unlink the temp file after use.
642  *
643  * Returns: a newly allocated string URI, or %NULL on error
644  */
645 gchar *
ev_file_uncompress(const gchar * uri,EvCompressionType type,GError ** error)646 ev_file_uncompress (const gchar       *uri,
647 		    EvCompressionType  type,
648 		    GError           **error)
649 {
650 	g_return_val_if_fail (uri != NULL, NULL);
651 
652 	return compression_run (uri, type, FALSE, error);
653 }
654 
655 /**
656  * ev_file_compress:
657  * @uri: a file URI
658  * @type: the compression type
659  * @error: a #GError location to store an error, or %NULL
660  *
661  * Compresses the file at @uri.
662 
663  * If @type is %EV_COMPRESSION_NONE, it does nothing and returns %NULL.
664  *
665  * Otherwise, it returns the filename of a
666  * temporary file containing the compressed data from the file at @uri.
667  *
668  * On error it returns %NULL and fills in @error.
669  *
670  * It is the caller's responsibility to unlink the temp file after use.
671  *
672  * Returns: a newly allocated string URI, or %NULL on error
673  */
674 gchar *
ev_file_compress(const gchar * uri,EvCompressionType type,GError ** error)675 ev_file_compress (const gchar       *uri,
676 		  EvCompressionType  type,
677 		  GError           **error)
678 {
679 	g_return_val_if_fail (uri != NULL, NULL);
680 
681 	return compression_run (uri, type, TRUE, error);
682 }
683