1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* gzip-method.c - GZIP access method for the GNOME Virtual File
5 Copyright (C) 1999 Free Software Foundation
7 The Gnome Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public License as
9 published by the Free Software Foundation; either version 2 of the
10 License, or (at your option) any later version.
12 The Gnome Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
17 You should have received a copy of the GNU Library General Public
18 License along with the Gnome Library; see the file COPYING.LIB. If not,
19 write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA.
22 Author: Ettore Perazzoli <ettore@comm2000.it> */
26 #include <glib/galloca.h>
27 #include <glib/gmessages.h>
28 #include <glib/gstrfuncs.h>
29 #include <libgnomevfs/gnome-vfs-mime.h>
30 #include <libgnomevfs/gnome-vfs-module.h>
31 #include <libgnomevfs/gnome-vfs-ops.h>
37 struct _GZipMethodHandle {
39 GnomeVFSHandle *parent_handle;
40 GnomeVFSOpenMode open_mode;
41 time_t modification_time;
43 GnomeVFSResult last_vfs_result;
49 typedef struct _GZipMethodHandle GZipMethodHandle;
52 #define GZIP_MAGIC_1 0x1f
53 #define GZIP_MAGIC_2 0x8b
55 #define GZIP_FLAG_ASCII 0x01 /* bit 0 set: file probably ascii text */
56 #define GZIP_FLAG_HEAD_CRC 0x02 /* bit 1 set: header CRC present */
57 #define GZIP_FLAG_EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
58 #define GZIP_FLAG_ORIG_NAME 0x08 /* bit 3 set: original file name present */
59 #define GZIP_FLAG_COMMENT 0x10 /* bit 4 set: file comment present */
60 #define GZIP_FLAG_RESERVED 0xE0 /* bits 5..7: reserved */
62 #define GZIP_HEADER_SIZE 10
63 #define GZIP_FOOTER_SIZE 8
65 #define Z_BUFSIZE 16384
68 static GnomeVFSResult do_open (GnomeVFSMethod *method,
69 GnomeVFSMethodHandle **method_handle,
71 GnomeVFSOpenMode mode,
72 GnomeVFSContext *context);
74 static GnomeVFSResult do_create (GnomeVFSMethod *method,
75 GnomeVFSMethodHandle **method_handle,
77 GnomeVFSOpenMode mode,
80 GnomeVFSContext *context);
82 static GnomeVFSResult do_close (GnomeVFSMethod *method,
83 GnomeVFSMethodHandle *method_handle,
84 GnomeVFSContext *context);
86 static GnomeVFSResult do_read (GnomeVFSMethod *method,
87 GnomeVFSMethodHandle *method_handle,
89 GnomeVFSFileSize num_bytes,
90 GnomeVFSFileSize *bytes_read,
91 GnomeVFSContext *context);
93 static GnomeVFSResult do_write (GnomeVFSMethod *method,
94 GnomeVFSMethodHandle *method_handle,
96 GnomeVFSFileSize num_bytes,
97 GnomeVFSFileSize *bytes_written,
98 GnomeVFSContext *context);
100 static GnomeVFSResult do_get_file_info(GnomeVFSMethod *method,
102 GnomeVFSFileInfo *file_info,
103 GnomeVFSFileInfoOptions options,
104 GnomeVFSContext *context);
106 static gboolean do_is_local (GnomeVFSMethod *method,
107 const GnomeVFSURI *uri);
109 static GnomeVFSMethod method = {
110 sizeof (GnomeVFSMethod),
118 NULL, /* truncate_handle FIXME bugzilla.eazel.com 1175 */
119 NULL, /* open_directory */
120 NULL, /* close_directory */
121 NULL, /* read_directory */
123 NULL, /* get_file_info_from_handle */
125 NULL, /* make_directory */
126 NULL, /* remove_directory */
129 NULL, /* check_same_fs */
130 NULL, /* set_file_info */
132 NULL, /* find_directory */
133 NULL /* create_symbolic_link */
136 #define RETURN_IF_FAIL(action) \
138 GnomeVFSResult __tmp_result; \
140 __tmp_result = (action); \
141 if (__tmp_result != GNOME_VFS_OK) \
142 return __tmp_result; \
145 #define VALID_URI(u) ((u)->parent!=NULL&&(((u)->text==NULL)||((u)->text[0]=='\0')||(((u)->text[0]=='/')&&((u)->text[1]=='\0'))))
148 /* GZip handle creation/destruction. */
150 static GZipMethodHandle *
151 gzip_method_handle_new (GnomeVFSHandle *parent_handle,
152 time_t modification_time,
154 GnomeVFSOpenMode open_mode)
156 GZipMethodHandle *new;
158 new = g_new (GZipMethodHandle, 1);
160 new->parent_handle = parent_handle;
161 new->modification_time = modification_time;
162 new->uri = gnome_vfs_uri_ref (uri);
163 new->open_mode = open_mode;
166 new->crc = crc32 (0, Z_NULL, 0);
172 gzip_method_handle_destroy (GZipMethodHandle *handle)
174 gnome_vfs_uri_unref (handle->uri);
175 g_free (handle->buffer);
180 /* GZip method initialization for compression/decompression. */
183 gzip_method_handle_init_for_inflate (GZipMethodHandle *handle)
185 handle->zstream.zalloc = NULL;
186 handle->zstream.zfree = NULL;
187 handle->zstream.opaque = NULL;
189 g_free (handle->buffer);
191 handle->buffer = g_malloc (Z_BUFSIZE);
192 handle->zstream.next_in = handle->buffer;
193 handle->zstream.avail_in = 0;
195 if (inflateInit2 (&handle->zstream, -MAX_WBITS) != Z_OK) {
196 g_free (handle->buffer);
200 handle->last_z_result = Z_OK;
201 handle->last_vfs_result = GNOME_VFS_OK;
207 gzip_method_handle_init_for_deflate (GZipMethodHandle *handle)
209 handle->zstream.zalloc = NULL;
210 handle->zstream.zfree = NULL;
211 handle->zstream.opaque = NULL;
213 g_free (handle->buffer);
215 handle->buffer = g_malloc (Z_BUFSIZE);
216 handle->zstream.next_out = handle->buffer;
217 handle->zstream.avail_out = Z_BUFSIZE;
219 /* FIXME bugzilla.eazel.com 1174: We want this to be user-configurable. */
220 if (deflateInit2 (&handle->zstream, Z_DEFAULT_COMPRESSION,
221 Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL,
222 Z_DEFAULT_STRATEGY) != Z_OK) {
223 g_free (handle->buffer);
227 handle->last_z_result = Z_OK;
228 handle->last_vfs_result = GNOME_VFS_OK;
234 static GnomeVFSResult
235 result_from_z_result (gint z_result)
239 case Z_STREAM_END: /* FIXME bugzilla.eazel.com 1173: Is this right? */
242 return GNOME_VFS_ERROR_CORRUPTED_DATA;
244 return GNOME_VFS_ERROR_INTERNAL;
249 /* Functions to skip data in the file. */
251 static GnomeVFSResult
252 skip_string (GnomeVFSHandle *handle)
254 GnomeVFSResult result;
256 GnomeVFSFileSize bytes_read;
259 result = gnome_vfs_read (handle, &c, 1, &bytes_read);
260 RETURN_IF_FAIL (result);
263 return GNOME_VFS_ERROR_WRONG_FORMAT;
270 skip (GnomeVFSHandle *handle,
271 GnomeVFSFileSize num_bytes)
273 GnomeVFSResult result;
275 GnomeVFSFileSize bytes_read;
277 tmp = g_alloca (num_bytes);
279 result = gnome_vfs_read (handle, tmp, num_bytes, &bytes_read);
280 RETURN_IF_FAIL (result);
282 if (bytes_read != num_bytes)
283 return GNOME_VFS_ERROR_WRONG_FORMAT;
289 /* Utility function to write a gulong value. */
291 static GnomeVFSResult
292 write_guint32 (GnomeVFSHandle *handle,
297 GnomeVFSFileSize bytes_written;
299 for (i = 0; i < 4; i++) {
300 buffer[i] = value & 0xff;
304 return gnome_vfs_write (handle, buffer, 4, &bytes_written);
308 /* GZIP Header functions. */
310 static GnomeVFSResult
311 read_gzip_header (GnomeVFSHandle *handle,
312 time_t *modification_time)
314 GnomeVFSResult result;
315 guchar buffer[GZIP_HEADER_SIZE];
316 GnomeVFSFileSize bytes_read;
320 result = gnome_vfs_read (handle, buffer, GZIP_HEADER_SIZE,
322 RETURN_IF_FAIL (result);
324 if (bytes_read != GZIP_HEADER_SIZE)
325 return GNOME_VFS_ERROR_WRONG_FORMAT;
327 if (buffer[0] != GZIP_MAGIC_1 || buffer[1] != GZIP_MAGIC_2)
328 return GNOME_VFS_ERROR_WRONG_FORMAT;
331 if (mode != 8) /* Mode: deflate */
332 return GNOME_VFS_ERROR_WRONG_FORMAT;
336 if (flags & GZIP_FLAG_RESERVED)
337 return GNOME_VFS_ERROR_WRONG_FORMAT;
339 if (flags & GZIP_FLAG_EXTRA_FIELD) {
341 GnomeVFSFileSize bytes_read;
343 if (gnome_vfs_read (handle, tmp, 2, &bytes_read)
345 return GNOME_VFS_ERROR_WRONG_FORMAT;
346 if (! skip (handle, tmp[0] | (tmp[0] << 8)))
347 return GNOME_VFS_ERROR_WRONG_FORMAT;
350 if (flags & GZIP_FLAG_ORIG_NAME)
351 RETURN_IF_FAIL (skip_string (handle));
353 if (flags & GZIP_FLAG_COMMENT)
354 RETURN_IF_FAIL (skip_string (handle));
356 if (flags & GZIP_FLAG_HEAD_CRC)
357 RETURN_IF_FAIL (skip (handle, 2));
359 *modification_time = (buffer[4] | (buffer[5] << 8)
360 | (buffer[6] << 16) | (buffer[7] << 24));
364 static GnomeVFSResult
365 write_gzip_header (GnomeVFSHandle *handle, time_t modification_time)
367 GnomeVFSResult result;
368 guchar buffer[GZIP_HEADER_SIZE];
369 GnomeVFSFileSize bytes_written;
371 buffer[0] = GZIP_MAGIC_1; /* magic 1 */
372 buffer[1] = GZIP_MAGIC_2; /* magic 2 */
373 buffer[2] = Z_DEFLATED; /* method */
374 buffer[3] = 0; /* flags */
375 buffer[4] = (guchar) ((modification_time >> 0) & 0xFF); /* time 1 */
376 buffer[5] = (guchar) ((modification_time >> 8) & 0xFF); /* time 2 */
377 buffer[6] = (guchar) ((modification_time >> 16) & 0xFF); /* time 3 */
378 buffer[7] = (guchar) ((modification_time >> 24) & 0xFF); /* time 4 */
379 buffer[8] = 0; /* xflags */
380 buffer[9] = 3; /* OS (Unix) */
382 result = gnome_vfs_write (handle, buffer, GZIP_HEADER_SIZE,
384 RETURN_IF_FAIL (result);
386 if (bytes_written != GZIP_HEADER_SIZE)
387 return GNOME_VFS_ERROR_IO;
392 static GnomeVFSResult
393 flush_write (GZipMethodHandle *gzip_handle)
395 GnomeVFSHandle *parent_handle;
396 GnomeVFSResult result;
401 zstream = &gzip_handle->zstream;
402 zstream->avail_in = 0; /* (Should be zero already anyway.) */
404 parent_handle = gzip_handle->parent_handle;
408 while (z_result == Z_OK || z_result == Z_STREAM_END) {
409 GnomeVFSFileSize bytes_written;
410 GnomeVFSFileSize len;
412 len = Z_BUFSIZE - zstream->avail_out;
414 result = gnome_vfs_write (parent_handle, gzip_handle->buffer,
415 len, &bytes_written);
416 RETURN_IF_FAIL (result);
418 zstream->next_out = gzip_handle->buffer;
419 zstream->avail_out = Z_BUFSIZE;
424 z_result = deflate(zstream, Z_FINISH);
426 /* Ignore the second of two consecutive flushes. */
427 if (z_result == Z_BUF_ERROR)
430 /* Deflate has finished flushing only when it hasn't used up
431 all the available space in the output buffer. */
432 done = (zstream->avail_out != 0 || z_result == Z_STREAM_END);
435 result = write_guint32 (parent_handle, gzip_handle->crc);
436 RETURN_IF_FAIL (result);
438 result = write_guint32 (parent_handle, zstream->total_in);
439 RETURN_IF_FAIL (result);
441 if (z_result == Z_OK || z_result == Z_STREAM_END)
444 return result_from_z_result (z_result);
451 - Check that there is no subpath. */
452 static GnomeVFSResult
453 do_open (GnomeVFSMethod *method,
454 GnomeVFSMethodHandle **method_handle,
456 GnomeVFSOpenMode open_mode,
457 GnomeVFSContext *context)
459 GnomeVFSHandle *parent_handle;
460 GnomeVFSURI *parent_uri;
461 GnomeVFSResult result;
462 GZipMethodHandle *gzip_handle;
463 time_t modification_time;
465 _GNOME_VFS_METHOD_PARAM_CHECK (method_handle != NULL);
466 _GNOME_VFS_METHOD_PARAM_CHECK (uri != NULL);
468 /* Check that the URI is valid. */
469 if (!VALID_URI(uri)) return GNOME_VFS_ERROR_INVALID_URI;
471 parent_uri = uri->parent;
473 if (open_mode & GNOME_VFS_OPEN_RANDOM)
474 return GNOME_VFS_ERROR_NOT_SUPPORTED;
476 result = gnome_vfs_open_uri (&parent_handle, parent_uri, open_mode);
477 RETURN_IF_FAIL (result);
479 if (open_mode & GNOME_VFS_OPEN_READ) {
480 result = read_gzip_header (parent_handle, &modification_time);
481 if (result != GNOME_VFS_OK) {
482 gnome_vfs_close (parent_handle);
486 gzip_handle = gzip_method_handle_new (parent_handle,
491 if (! gzip_method_handle_init_for_inflate (gzip_handle)) {
492 gnome_vfs_close (parent_handle);
493 gzip_method_handle_destroy (gzip_handle);
494 return GNOME_VFS_ERROR_INTERNAL;
496 } else { /* GNOME_VFS_OPEN_WRITE */
497 modification_time = time (NULL);
498 result = write_gzip_header (parent_handle, modification_time);
499 RETURN_IF_FAIL (result);
501 /* FIXME bugzilla.eazel.com 1172: need to set modification_time */
502 gzip_handle = gzip_method_handle_new (parent_handle,
507 if (! gzip_method_handle_init_for_deflate (gzip_handle)) {
508 gnome_vfs_close (parent_handle);
509 gzip_method_handle_destroy (gzip_handle);
510 return GNOME_VFS_ERROR_INTERNAL;
514 *method_handle = (GnomeVFSMethodHandle *) gzip_handle;
522 static GnomeVFSResult
523 do_create (GnomeVFSMethod *method,
524 GnomeVFSMethodHandle **method_handle,
526 GnomeVFSOpenMode mode,
529 GnomeVFSContext *context)
531 _GNOME_VFS_METHOD_PARAM_CHECK (method_handle != NULL);
532 _GNOME_VFS_METHOD_PARAM_CHECK (uri != NULL);
534 return GNOME_VFS_ERROR_NOT_SUPPORTED; /* FIXME bugzilla.eazel.com 1170 */
540 static GnomeVFSResult
541 do_close (GnomeVFSMethod *method,
542 GnomeVFSMethodHandle *method_handle,
543 GnomeVFSContext *context)
545 GZipMethodHandle *gzip_handle;
546 GnomeVFSResult result;
548 _GNOME_VFS_METHOD_PARAM_CHECK (method_handle != NULL);
550 gzip_handle = (GZipMethodHandle *) method_handle;
552 if (gzip_handle->open_mode & GNOME_VFS_OPEN_WRITE)
553 result = flush_write (gzip_handle);
555 result = GNOME_VFS_OK;
557 if (result == GNOME_VFS_OK)
558 result = gnome_vfs_close (gzip_handle->parent_handle);
560 gzip_method_handle_destroy (gzip_handle);
567 static GnomeVFSResult
568 fill_buffer (GZipMethodHandle *gzip_handle,
569 GnomeVFSFileSize num_bytes)
571 GnomeVFSResult result;
572 GnomeVFSFileSize count;
575 zstream = &gzip_handle->zstream;
577 if (zstream->avail_in > 0)
580 result = gnome_vfs_read (gzip_handle->parent_handle,
585 if (result != GNOME_VFS_OK) {
586 if (zstream->avail_out == num_bytes)
588 gzip_handle->last_vfs_result = result;
590 zstream->next_in = gzip_handle->buffer;
591 zstream->avail_in = count;
597 /* FIXME bugzilla.eazel.com 1165: TODO:
598 - Concatenated GZIP file handling. */
599 static GnomeVFSResult
600 do_read (GnomeVFSMethod *method,
601 GnomeVFSMethodHandle *method_handle,
603 GnomeVFSFileSize num_bytes,
604 GnomeVFSFileSize *bytes_read,
605 GnomeVFSContext *context)
607 GZipMethodHandle *gzip_handle;
608 GnomeVFSResult result;
617 gzip_handle = (GZipMethodHandle *) method_handle;
619 zstream = &gzip_handle->zstream;
621 if (gzip_handle->last_z_result != Z_OK) {
622 if (gzip_handle->last_z_result == Z_STREAM_END) {
626 return result_from_z_result (gzip_handle->last_z_result);
627 } else if (gzip_handle->last_vfs_result != GNOME_VFS_OK) {
628 return gzip_handle->last_vfs_result;
631 zstream->next_out = buffer;
632 zstream->avail_out = num_bytes;
634 while (zstream->avail_out != 0) {
635 result = fill_buffer (gzip_handle, num_bytes);
636 RETURN_IF_FAIL (result);
638 z_result = inflate (&gzip_handle->zstream, Z_NO_FLUSH);
639 if (z_result == Z_STREAM_END) {
640 gzip_handle->last_z_result = z_result;
642 } else if (z_result != Z_OK) {
643 /* FIXME bugzilla.eazel.com 1165: Concatenated GZIP files? */
644 gzip_handle->last_z_result = z_result;
647 if (gzip_handle->last_z_result != Z_OK
648 && zstream->avail_out == num_bytes)
649 return result_from_z_result (gzip_handle->last_z_result);
652 gzip_handle->crc = crc32 (gzip_handle->crc,
654 (guint) (zstream->next_out - crc_start));
656 *bytes_read = num_bytes - zstream->avail_out;
664 static GnomeVFSResult
665 do_write (GnomeVFSMethod *method,
666 GnomeVFSMethodHandle *method_handle,
667 gconstpointer buffer,
668 GnomeVFSFileSize num_bytes,
669 GnomeVFSFileSize *bytes_written,
670 GnomeVFSContext *context)
672 GZipMethodHandle *gzip_handle;
673 GnomeVFSResult result;
677 gzip_handle = (GZipMethodHandle *) method_handle;
678 zstream = &gzip_handle->zstream;
680 /* This cast sucks. It is not my fault, though. :-) */
681 zstream->next_in = (gpointer) buffer;
682 zstream->avail_in = num_bytes;
684 result = GNOME_VFS_OK;
686 while (zstream->avail_in != 0 && result == GNOME_VFS_OK) {
687 if (zstream->avail_out == 0) {
688 GnomeVFSFileSize written;
690 zstream->next_out = gzip_handle->buffer;
691 result = gnome_vfs_write (gzip_handle->parent_handle,
693 Z_BUFSIZE, &written);
695 if (result != GNOME_VFS_OK)
698 zstream->avail_out += written;
701 z_result = deflate (zstream, Z_NO_FLUSH);
702 result = result_from_z_result (z_result);
705 gzip_handle->crc = crc32 (gzip_handle->crc, buffer, num_bytes);
707 *bytes_written = num_bytes - zstream->avail_in;
713 static GnomeVFSResult
714 do_get_file_info (GnomeVFSMethod *method,
716 GnomeVFSFileInfo *file_info,
717 GnomeVFSFileInfoOptions options,
718 GnomeVFSContext *context) {
719 GnomeVFSResult result;
721 if (!VALID_URI(uri)) return GNOME_VFS_ERROR_INVALID_URI;
723 result = gnome_vfs_get_file_info_uri(uri->parent, file_info, options);
724 if(result == GNOME_VFS_OK) {
725 gint namelen = strlen(file_info->name);
727 /* work out the name */
728 /* FIXME bugzilla.eazel.com 2790: handle uppercase */
730 file_info->name[namelen-1] == 'z' &&
731 file_info->name[namelen-2] == 'g' &&
732 file_info->name[namelen-3] == '.')
733 file_info->name[namelen-3] = '\0';
735 /* we can't tell the size without uncompressing it */
736 //file_info->valid_fields &= ~GNOME_VFS_FILE_INFO_FIELDS_SIZE;
738 /* guess the mime type of the file inside */
739 /* FIXME bugzilla.eazel.com 2791: guess mime based on contents */
740 g_free(file_info->mime_type);
741 file_info->mime_type = g_strdup(gnome_vfs_mime_type_from_name(file_info->name));
750 do_is_local (GnomeVFSMethod *method,
751 const GnomeVFSURI *uri)
753 g_return_val_if_fail (uri != NULL, FALSE);
755 return gnome_vfs_uri_is_local (uri->parent);
762 vfs_module_init (const char *method_name, const char *args)
768 vfs_module_shutdown (GnomeVFSMethod *method)