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