TorrentZip support.

- two new functions (zip_get_archive_flag, zip_set_archive_flag)
  to manipulate archive-global flags, currently only wether an archive
  is/should be torrentzipped.
- new flag to zip_source_zip (ZIP_FL_RECOMPRESS) to force recompression
- new program to torrentzip existing archive.

--HG--
branch : HEAD
diff --git a/.hgignore b/.hgignore
index 93a8fb3..d23e0d4 100644
--- a/.hgignore
+++ b/.hgignore
@@ -30,3 +30,4 @@
 ^regress/set_comment_revert$
 ^src/zipcmp$
 ^src/zipmerge$
+^src/ziptorrent$
diff --git a/NEWS b/NEWS
index ea25d58..f63f6ed 100644
--- a/NEWS
+++ b/NEWS
@@ -2,12 +2,15 @@
 
 * on Windows, explictly set dllimport/dllexport
 * remove erroneous references to GPL
+* add support for torrentzip
+* new functions: zip_get_archive_flag, zip_set_archive_flag
+* zip_source_zip: add flag to force recompression
 
 0.8 [2007/06/06]
 
 * fix for zip archives larger than 2GiB
 * fix zip_error_strerror to include libzip error string
-* add support for streamed zip files
+* add support for reading streamed zip files
 * new functions: zip_add_dir, zip_error_clear, zip_file_error_clear
 * add basic support for building with CMake (incomplete)
 
diff --git a/TODO b/TODO
index e49d184..3270b90 100644
--- a/TODO
+++ b/TODO
@@ -5,12 +5,10 @@
 - [doc] document that no empty archives will be created
 - [bug] zip_close: on empty archive, don't try to delete non-existent file
 - [bug] zip_merge: zip_close too early for source archives
-- [feature] torrentzip support
-  . two flag members in struct zip (has, should)
-  . zip_get_global_flag(zip, flag, unchanged?)
-  . zip_set_global_flag(zip, flag, value)
-  . if was TZ but shouldn't be, delete comment
-  . if should be TZ, convert existing (if wasn't), normalize added
++ [doc] zip_source_zip ZIP_FL_RECOMPRESS
++ [doc] zip_get_archive_flag, zip_set_archive_flag
++ [doc] ziptorrent(1)
++ [test] ziptorrent support
 + [feature] Windows support:
   . better mkstemp replacement function (no getpid; other problems?)
   . snprintf replacement
diff --git a/lib/Makefile.am b/lib/Makefile.am
index a55920c..5a6d095 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -25,6 +25,7 @@
 	zip_file_error_get.c \
 	zip_file_get_offset.c \
 	zip_file_strerror.c \
+	zip_filerange_crc.c \
 	zip_fopen.c \
 	zip_fopen_index.c \
 	zip_fread.c \
diff --git a/lib/zip.h b/lib/zip.h
index 014df7a..48431a3 100644
--- a/lib/zip.h
+++ b/lib/zip.h
@@ -65,6 +65,7 @@
 #define ZIP_FL_NODIR		2 /* ignore directory component */
 #define ZIP_FL_COMPRESSED	4 /* read compressed data */
 #define ZIP_FL_UNCHANGED	8 /* use original data, ignoring changes */
+#define ZIP_FL_RECOMPRESS      16 /* force recompression of data */
 
 /* archive global flags flags */
 
diff --git a/lib/zip_close.c b/lib/zip_close.c
index 326c26f..49462d8 100644
--- a/lib/zip_close.c
+++ b/lib/zip_close.c
@@ -42,16 +42,26 @@
 
 #include "zipint.h"
 
-static int add_data(struct zip *, int, struct zip_dirent *, FILE *);
+static int add_data(struct zip *, struct zip_source *, struct zip_dirent *,
+		    FILE *);
 static int add_data_comp(zip_source_callback, void *, struct zip_stat *,
 			 FILE *, struct zip_error *);
-static int add_data_uncomp(zip_source_callback, void *, struct zip_stat *,
-			   FILE *, struct zip_error *);
+static int add_data_uncomp(struct zip *, zip_source_callback, void *,
+			   struct zip_stat *, FILE *);
 static void ch_set_error(struct zip_error *, zip_source_callback, void *);
 static int copy_data(FILE *, off_t, FILE *, struct zip_error *);
+static int write_cdir(struct zip *, struct zip_cdir *, FILE *);
 static int _zip_cdir_set_comment(struct zip_cdir *, struct zip *);
 static int _zip_changed(struct zip *, int *);
 static char *_zip_create_temp_output(struct zip *, FILE **);
+static int _zip_torrentzip_cmp(const void *, const void *);
+
+
+
+struct filelist {
+    int idx;
+    const char *name;
+};
 
 
 
@@ -65,7 +75,9 @@
     mode_t mask;
     struct zip_cdir *cd;
     struct zip_dirent de;
+    struct filelist *filelist;
     int reopen_on_error;
+    int new_torrentzip;
 
     reopen_on_error = 0;
 
@@ -88,16 +100,37 @@
 	_zip_free(za);
 	return 0;
     }	       
-	
-    if ((cd=_zip_cdir_new(survivors, &za->error)) == NULL)
+
+    if ((filelist=(struct filelist *)malloc(sizeof(filelist[0])*survivors))
+	== NULL)
 	return -1;
 
+    if ((cd=_zip_cdir_new(survivors, &za->error)) == NULL) {
+	free(filelist);
+	return -1;
+    }
+
     for (i=0; i<survivors; i++)
 	_zip_dirent_init(&cd->entry[i]);
 
-    if (_zip_cdir_set_comment(cd, za) == -1) {
-	_zip_cdir_free(cd);
-	return -1;
+    /* archive comment is special for torrentzip */
+    if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0)) {
+	cd->comment = _zip_memdup(TORRENT_SIG "XXXXXXXX",
+				  TORRENT_SIG_LEN + TORRENT_CRC_LEN,
+				  &za->error);
+	if (cd->comment == NULL) {
+	    _zip_cdir_free(cd);
+	    free(filelist);
+	    return -1;
+	}
+	cd->comment_len = TORRENT_SIG_LEN + TORRENT_CRC_LEN;
+    }
+    else if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, ZIP_FL_UNCHANGED) == 0) {
+	if (_zip_cdir_set_comment(cd, za) == -1) {
+	    _zip_cdir_free(cd);
+	    free(filelist);
+	    return -1;
+	}
     }
 
     if ((temp=_zip_create_temp_output(za, &out)) == NULL) {
@@ -105,29 +138,48 @@
 	return -1;
     }
 
-    error = 0;
+
+    /* create list of files with index into original archive  */
     for (i=j=0; i<za->nentry; i++) {
 	if (za->entry[i].state == ZIP_ST_DELETED)
 	    continue;
 
+	filelist[j].idx = i;
+	filelist[j].name = zip_get_name(za, i, 0);
+	j++;
+    }
+    if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0))
+	qsort(filelist, survivors, sizeof(filelist[0]),
+	      _zip_torrentzip_cmp);
+
+    new_torrentzip = (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0) == 1
+		      && zip_get_archive_flag(za, ZIP_AFL_TORRENT,
+					      ZIP_FL_UNCHANGED) == 0);
+    error = 0;
+    for (j=0; j<survivors; j++) {
+	i = filelist[j].idx;
+
 	/* create new local directory entry */
-	if (ZIP_ENTRY_DATA_CHANGED(za->entry+i)) {
+	if (ZIP_ENTRY_DATA_CHANGED(za->entry+i) || new_torrentzip) {
 	    _zip_dirent_init(&de);
+
+	    if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0))
+		_zip_dirent_torrent_normalize(&de);
+		
 	    /* use it as central directory entry */
 	    memcpy(cd->entry+j, &de, sizeof(cd->entry[j]));
 
 	    /* set/update file name */
 	    if (za->entry[i].ch_filename == NULL) {
-		if (za->entry[i].state == ZIP_ST_REPLACED) {
-		    de.filename = strdup(za->cdir->entry[i].filename);
-		    de.filename_len = strlen(de.filename);
-		    cd->entry[j].filename = za->cdir->entry[i].filename;
-		    cd->entry[j].filename_len = de.filename_len;
-		}
-		else {
+		if (za->entry[i].state == ZIP_ST_ADDED) {
 		    de.filename = strdup("-");
 		    de.filename_len = 1;
 		    cd->entry[j].filename = "-";
+		}
+		else {
+		    de.filename = strdup(za->cdir->entry[i].filename);
+		    de.filename_len = strlen(de.filename);
+		    cd->entry[j].filename = za->cdir->entry[i].filename;
 		    cd->entry[j].filename_len = de.filename_len;
 		}
 	    }
@@ -163,7 +215,8 @@
 	    cd->entry[j].filename_len = de.filename_len;
 	}
 
-	if (za->entry[i].ch_comment_len != -1) {
+	if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0) == 0
+	    && za->entry[i].ch_comment_len != -1) {
 	    /* as the rest of cd entries, its malloc/free is done by za */
 	    cd->entry[j].comment = za->entry[i].ch_comment;
 	    cd->entry[j].comment_len = za->entry[i].ch_comment_len;
@@ -171,8 +224,19 @@
 
 	cd->entry[j].offset = ftello(out);
 
-	if (ZIP_ENTRY_DATA_CHANGED(za->entry+i)) {
-	    if (add_data(za, i, &de, out) < 0) {
+	if (ZIP_ENTRY_DATA_CHANGED(za->entry+i) || new_torrentzip) {
+	    struct zip_source *zs;
+
+	    zs = NULL;
+	    if (!ZIP_ENTRY_DATA_CHANGED(za->entry+i)) {
+		if ((zs=zip_source_zip(za, za, i, ZIP_FL_RECOMPRESS, 0, -1))
+		    == NULL) {
+		    error = 1;
+		    break;
+		}
+	    }
+
+	    if (add_data(za, zs ? zs : za->entry[i].source, &de, out) < 0) {
 		error = 1;
 		break;
 	    }
@@ -195,16 +259,14 @@
 	    }
 	}
 
-	j++;
-
 	_zip_dirent_finalize(&de);
     }
 
     if (!error) {
-	if (_zip_cdir_write(cd, out, &za->error) < 0)
+	if (write_cdir(za, cd, out) < 0)
 	    error = 1;
     }
-    
+   
     /* pointers in cd entries are owned by za */
     cd->nentry = 0;
     _zip_cdir_free(cd);
@@ -252,15 +314,15 @@
 
 
 static int
-add_data(struct zip *za, int idx, struct zip_dirent *de, FILE *ft)
+add_data(struct zip *za, struct zip_source *zs, struct zip_dirent *de, FILE *ft)
 {
     off_t offstart, offend;
     zip_source_callback cb;
     void *ud;
     struct zip_stat st;
     
-    cb = za->entry[idx].source->f;
-    ud = za->entry[idx].source->ud;
+    cb = zs->f;
+    ud = zs->ud;
 
     if (cb(ud, &st, sizeof(st), ZIP_SOURCE_STAT) < (ssize_t)sizeof(st)) {
 	ch_set_error(&za->error, cb, ud);
@@ -282,7 +344,7 @@
 	    return -1;
     }
     else {
-	if (add_data_uncomp(cb, ud, &st, ft, &za->error) < 0)
+	if (add_data_uncomp(za, cb, ud, &st, ft) < 0)
 	    return -1;
     }
 
@@ -297,13 +359,17 @@
 	_zip_error_set(&za->error, ZIP_ER_SEEK, errno);
 	return -1;
     }
+
     
-    de->comp_method = st.comp_method;
     de->last_mod = st.mtime;
+    de->comp_method = st.comp_method;
     de->crc = st.crc;
     de->uncomp_size = st.size;
     de->comp_size = st.comp_size;
 
+    if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0))
+	_zip_dirent_torrent_normalize(de);
+
     if (_zip_dirent_write(de, ft, 1, &za->error) < 0)
 	return -1;
     
@@ -344,14 +410,15 @@
 
 
 static int
-add_data_uncomp(zip_source_callback cb, void *ud, struct zip_stat *st,
-		FILE *ft, struct zip_error *error)
+add_data_uncomp(struct zip *za, zip_source_callback cb, void *ud,
+		struct zip_stat *st, FILE *ft)
 {
     char b1[BUFSIZE], b2[BUFSIZE];
     int end, flush, ret;
     ssize_t n;
     size_t n2;
     z_stream zstr;
+    int mem_level;
 
     st->comp_method = ZIP_CM_DEFLATE;
     st->comp_size = st->size = 0;
@@ -363,8 +430,13 @@
     zstr.avail_in = 0;
     zstr.avail_out = 0;
 
-    /* -15: undocumented feature of zlib to _not_ write a zlib header */
-    deflateInit2(&zstr, Z_BEST_COMPRESSION, Z_DEFLATED, -15, 9,
+    if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0))
+	mem_level = TORRENT_MEM_LEVEL;
+    else
+	mem_level = MAX_MEM_LEVEL;
+
+    /* -MAX_WBITS: undocumented feature of zlib to _not_ write a zlib header */
+    deflateInit2(&zstr, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, mem_level,
 		 Z_DEFAULT_STRATEGY);
 
     zstr.next_out = (Bytef *)b2;
@@ -376,7 +448,7 @@
     while (!end) {
 	if (zstr.avail_in == 0 && !flush) {
 	    if ((n=cb(ud, b1, sizeof(b1), ZIP_SOURCE_READ)) < 0) {
-		ch_set_error(error, cb, ud);
+		ch_set_error(&za->error, cb, ud);
 		deflateEnd(&zstr);
 		return -1;
 	    }
@@ -392,7 +464,7 @@
 
 	ret = deflate(&zstr, flush);
 	if (ret != Z_OK && ret != Z_STREAM_END) {
-	    _zip_error_set(error, ZIP_ER_ZLIB, ret);
+	    _zip_error_set(&za->error, ZIP_ER_ZLIB, ret);
 	    return -1;
 	}
 	
@@ -400,7 +472,7 @@
 	    n2 = sizeof(b2) - zstr.avail_out;
 	    
 	    if (fwrite(b2, 1, n2, ft) != n2) {
-		_zip_error_set(error, ZIP_ER_WRITE, errno);
+		_zip_error_set(&za->error, ZIP_ER_WRITE, errno);
 		return -1;
 	    }
 	
@@ -471,6 +543,44 @@
 
 
 static int
+write_cdir(struct zip *za, struct zip_cdir *cd, FILE *out)
+{
+    off_t offset;
+    uLong crc;
+    char buf[TORRENT_CRC_LEN+1];
+    
+    if (_zip_cdir_write(cd, out, &za->error) < 0)
+	return -1;
+    
+    if (zip_get_archive_flag(za, ZIP_AFL_TORRENT, 0) == 0)
+	return 0;
+
+
+    /* fix up torrentzip comment */
+
+    offset = ftello(out);
+
+    if (_zip_filerange_crc(out, cd->offset, cd->size, &crc, &za->error) < 0)
+	return -1;
+
+    snprintf(buf, sizeof(buf), "%08lX", (long)crc);
+
+    if (fseeko(out, offset-TORRENT_CRC_LEN, SEEK_SET) < 0) {
+	_zip_error_set(&za->error, ZIP_ER_SEEK, errno);
+	return -1;
+    }
+
+    if (fwrite(buf, TORRENT_CRC_LEN, 1, out) != 1) {
+	_zip_error_set(&za->error, ZIP_ER_WRITE, errno);
+	return -1;
+    }
+
+    return 0;
+}
+
+
+
+static int
 _zip_cdir_set_comment(struct zip_cdir *dest, struct zip *src)
 {
     if (src->ch_comment_len != -1) {
@@ -501,7 +611,8 @@
 
     changed = survivors = 0;
 
-    if (za->ch_comment_len != -1)
+    if (za->ch_comment_len != -1
+	|| za->ch_flags != za->flags)
 	changed = 1;
 
     for (i=0; i<za->nentry; i++) {
@@ -550,3 +661,12 @@
     *outp = tfp;
     return temp;
 }
+
+
+
+static int
+_zip_torrentzip_cmp(const void *a, const void *b)
+{
+    return strcasecmp(((const struct filelist *)a)->name,
+		      ((const struct filelist *)b)->name);
+}
diff --git a/lib/zip_dirent.c b/lib/zip_dirent.c
index 7b95359..5f39a8b 100644
--- a/lib/zip_dirent.c
+++ b/lib/zip_dirent.c
@@ -1,6 +1,6 @@
 /*
   zip_dirent.c -- read directory entry (local or central), clean dirent
-  Copyright (C) 1999-2007 Dieter Baron and Thomas Klausner
+  Copyright (C) 1999-2008 Dieter Baron and Thomas Klausner
 
   This file is part of libzip, a library to manipulate ZIP archives.
   The authors can be contacted at <libzip@nih.at>
@@ -318,6 +318,50 @@
 
 
 
+/* _zip_dirent_torrent_normalize(de);
+   Set values suitable for torrentzip.
+*/
+
+void
+_zip_dirent_torrent_normalize(struct zip_dirent *de)
+{
+    static struct tm torrenttime = {
+	0, 32, 23,  24, 11, 96,  0, 0,  -1, 0, "UTC"
+    };
+    static last_mod = 0;
+
+    if (last_mod == 0) {
+	time_t now;
+	struct tm *l;
+
+	time(&now);
+	l = localtime(&now);
+	torrenttime.tm_gmtoff = l->tm_gmtoff;
+	torrenttime.tm_zone = l->tm_zone;
+	last_mod = mktime(&torrenttime);
+    }
+    
+    de->version_madeby = 0;
+    de->version_needed = 20; /* 2.0 */
+    de->bitflags = 2; /* maximum compression */
+    de->comp_method = ZIP_CM_DEFLATE;
+    de->last_mod = last_mod;
+
+    de->disk_number = 0;
+    de->int_attrib = 0;
+    de->ext_attrib = 0;
+    de->offset = 0;
+
+    free(de->extrafield);
+    de->extrafield = NULL;
+    de->extrafield_len = 0;
+    free(de->comment);
+    de->comment = NULL;
+    de->comment_len = 0;
+}
+
+
+
 /* _zip_dirent_write(zde, fp, localp, error):
    Writes zip directory entry zde to file fp.
 
diff --git a/lib/zip_filerange_crc.c b/lib/zip_filerange_crc.c
new file mode 100644
index 0000000..4d1ad56
--- /dev/null
+++ b/lib/zip_filerange_crc.c
@@ -0,0 +1,71 @@
+/*
+  zip_filerange_crc.c -- compute CRC32 for a range of a file
+  Copyright (C) 2008 Dieter Baron and Thomas Klausner
+
+  This file is part of libzip, a library to manipulate ZIP archives.
+  The authors can be contacted at <libzip@nih.at>
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions
+  are met:
+  1. Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+  2. Redistributions in binary form must reproduce the above copyright
+     notice, this list of conditions and the following disclaimer in
+     the documentation and/or other materials provided with the
+     distribution.
+  3. The names of the authors may not be used to endorse or promote
+     products derived from this software without specific prior
+     written permission.
+ 
+  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
+  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "zipint.h"
+
+
+
+
+int
+_zip_filerange_crc(FILE *fp, off_t start, off_t len, uLong *crcp,
+		   struct zip_error *errp)
+{
+    Bytef buf[BUFSIZE];
+    size_t n;
+
+    *crcp = crc32(0L, Z_NULL, 0);
+
+    if (fseeko(fp, start, SEEK_SET) != 0) {
+	_zip_error_set(errp, ZIP_ER_SEEK, errno);
+	return -1;
+    }
+    
+    while (len > 0) {
+	n = len > BUFSIZE ? BUFSIZE : len;
+	if ((n=fread(buf, 1, n, fp)) <= 0) {
+	    _zip_error_set(errp, ZIP_ER_READ, errno);
+	    return -1;
+	}
+
+	*crcp = crc32(*crcp, buf, n);
+
+	len-= n;
+    }
+
+    return 0;
+}
diff --git a/lib/zip_get_archive_flag.c b/lib/zip_get_archive_flag.c
new file mode 100644
index 0000000..4733d92
--- /dev/null
+++ b/lib/zip_get_archive_flag.c
@@ -0,0 +1,48 @@
+/*
+  zip_get_archive_flag.c -- get archive global flag
+  Copyright (C) 2008 Dieter Baron and Thomas Klausner
+
+  This file is part of libzip, a library to manipulate ZIP archives.
+  The authors can be contacted at <libzip@nih.at>
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions
+  are met:
+  1. Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+  2. Redistributions in binary form must reproduce the above copyright
+     notice, this list of conditions and the following disclaimer in
+     the documentation and/or other materials provided with the
+     distribution.
+  3. The names of the authors may not be used to endorse or promote
+     products derived from this software without specific prior
+     written permission.
+ 
+  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
+  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+
+#include "zipint.h"
+
+
+
+ZIP_EXTERN int
+zip_get_archive_flag(struct zip *za, int flag, int flags)
+{
+    int fl;
+
+    fl = (flags & ZIP_FL_UNCHANGED) ? za->flags : za->ch_flags;
+
+    return (fl & flag) ? 1 : 0;
+}
diff --git a/lib/zip_open.c b/lib/zip_open.c
index d3306c8..2d8fa90 100644
--- a/lib/zip_open.c
+++ b/lib/zip_open.c
@@ -311,8 +311,6 @@
 {
     uLong crc_got, crc_should;
     char *end;
-    Bytef buf[BUFSIZE];
-    unsigned int n, remain;
 
     if (za->zp == NULL || za->cdir == NULL)
 	return;
@@ -325,22 +323,10 @@
     crc_should = strtoul(za->cdir->comment+TORRENT_SIG_LEN, &end, 16);
     if ((crc_should == UINT_MAX && errno != 0) || (end && *end))
 	return;
-    
-    crc_got = crc32(0L, Z_NULL, 0);
 
-    if (fseek(za->zp, za->cdir->offset, SEEK_SET) != 0)
+    if (_zip_filerange_crc(za->zp, za->cdir->offset, za->cdir->size,
+			   &crc_got, NULL) < 0)
 	return;
-    remain = za->cdir->size;
-
-    while (remain > 0) {
-	n = remain > BUFSIZE ? BUFSIZE : remain;
-	if ((n=fread(buf, 1, n, za->zp)) <= 0)
-	    return;
-
-	crc_got = crc32(crc_got, buf, n);
-
-	remain -= n;
-    }
 
     if (crc_got == crc_should)
 	za->flags |= ZIP_AFL_TORRENT;
diff --git a/lib/zip_set_archive_flag.c b/lib/zip_set_archive_flag.c
new file mode 100644
index 0000000..720e1f3
--- /dev/null
+++ b/lib/zip_set_archive_flag.c
@@ -0,0 +1,49 @@
+/*
+  zip_get_archive_flag.c -- set archive global flag
+  Copyright (C) 2008 Dieter Baron and Thomas Klausner
+
+  This file is part of libzip, a library to manipulate ZIP archives.
+  The authors can be contacted at <libzip@nih.at>
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions
+  are met:
+  1. Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+  2. Redistributions in binary form must reproduce the above copyright
+     notice, this list of conditions and the following disclaimer in
+     the documentation and/or other materials provided with the
+     distribution.
+  3. The names of the authors may not be used to endorse or promote
+     products derived from this software without specific prior
+     written permission.
+ 
+  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
+  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+
+#include "zipint.h"
+
+
+
+ZIP_EXTERN int
+zip_set_archive_flag(struct zip *za, int flag, int value)
+{
+    if (value)
+	za->ch_flags |= flag;
+    else
+	za->ch_flags &= ~flag;
+
+    return 0;
+}
diff --git a/lib/zip_source_zip.c b/lib/zip_source_zip.c
index 0768027..3eef552 100644
--- a/lib/zip_source_zip.c
+++ b/lib/zip_source_zip.c
@@ -1,6 +1,6 @@
 /*
   zip_source_zip.c -- create data source from zip file
-  Copyright (C) 1999-2007 Dieter Baron and Thomas Klausner
+  Copyright (C) 1999-2008 Dieter Baron and Thomas Klausner
 
   This file is part of libzip, a library to manipulate ZIP archives.
   The authors can be contacted at <libzip@nih.at>
@@ -57,6 +57,8 @@
     struct zip_source *zs;
     struct read_zip *p;
 
+    /* XXX: ZIP_FL_RECOMPRESS */
+
     if (za == NULL)
 	return NULL;
 
@@ -74,7 +76,7 @@
     if (len == 0)
 	len = -1;
 
-    if (start == 0 && len == -1)
+    if (start == 0 && len == -1 && (flags & ZIP_FL_RECOMPRESS) == 0)
 	flags |= ZIP_FL_COMPRESSED;
     else
 	flags &= ~ZIP_FL_COMPRESSED;
diff --git a/lib/zip_unchange_archive.c b/lib/zip_unchange_archive.c
index 6378e39..8f9c024 100644
--- a/lib/zip_unchange_archive.c
+++ b/lib/zip_unchange_archive.c
@@ -1,6 +1,6 @@
 /*
   zip_unchange_archive.c -- undo global changes to ZIP archive
-  Copyright (C) 2006-2007 Dieter Baron and Thomas Klausner
+  Copyright (C) 2006-2008 Dieter Baron and Thomas Klausner
 
   This file is part of libzip, a library to manipulate ZIP archives.
   The authors can be contacted at <libzip@nih.at>
@@ -46,5 +46,7 @@
     za->ch_comment = NULL;
     za->ch_comment_len = -1;
 
+    za->ch_flags = za->flags;
+
     return 0;
 }
diff --git a/lib/zipint.h b/lib/zipint.h
index 7107585..051be5f 100644
--- a/lib/zipint.h
+++ b/lib/zipint.h
@@ -72,6 +72,8 @@
 #define DATADES_MAGIC "PK\7\8"
 #define TORRENT_SIG	"TORRENTZIPPED-"
 #define TORRENT_SIG_LEN	14
+#define TORRENT_CRC_LEN 8
+#define TORRENT_MEM_LEVEL	8
 #define CDENTRYSIZE         46u
 #define LENTRYSIZE          30
 #define MAXCOMLEN        65536
@@ -213,6 +215,7 @@
 
 
 
+int _zip_cdir_compute_crc(struct zip *, uLong *);
 void _zip_cdir_free(struct zip_cdir *);
 struct zip_cdir *_zip_cdir_new(int, struct zip_error *);
 int _zip_cdir_write(struct zip_cdir *, FILE *, struct zip_error *);
@@ -221,6 +224,7 @@
 void _zip_dirent_init(struct zip_dirent *);
 int _zip_dirent_read(struct zip_dirent *, FILE *,
 		     unsigned char **, unsigned int, int, struct zip_error *);
+void _zip_dirent_torrent_normalize(struct zip_dirent *);
 int _zip_dirent_write(struct zip_dirent *, FILE *, int, struct zip_error *);
 
 void _zip_entry_free(struct zip_entry *);
@@ -238,6 +242,8 @@
 int _zip_file_fillbuf(void *, size_t, struct zip_file *);
 unsigned int _zip_file_get_offset(struct zip *, int);
 
+int _zip_filerange_crc(FILE *, off_t, off_t, uLong *, struct zip_error *);
+
 void _zip_free(struct zip *);
 const char *_zip_get_name(struct zip *, int, int, struct zip_error *);
 int _zip_local_header_read(struct zip *, int);
diff --git a/man/zip_source_zip.mdoc b/man/zip_source_zip.mdoc
index d3437a7..860c2c3 100644
--- a/man/zip_source_zip.mdoc
+++ b/man/zip_source_zip.mdoc
@@ -76,6 +76,7 @@
 have been made to
 .Ar srcarchive
 after opening it.
+.\" XXX: document ZIP_FL_RECOMPRESS
 .Sh RETURN VALUES
 Upon successful completion, the created source is returned.
 Otherwise,
diff --git a/man/zip_unchange_archive.mdoc b/man/zip_unchange_archive.mdoc
index 2b3137c..dc331b3 100644
--- a/man/zip_unchange_archive.mdoc
+++ b/man/zip_unchange_archive.mdoc
@@ -1,7 +1,7 @@
 .\" $NiH: zip_unchange_all.mdoc,v 1.10 2005/06/09 21:14:54 wiz Exp $
 .\"
 .\" zip_unchange_archive.mdoc -- undo changes to all files in zip archive
-.\" Copyright (C) 2006 Dieter Baron and Thomas Klausner
+.\" Copyright (C) 2006-2008 Dieter Baron and Thomas Klausner
 .\"
 .\" This file is part of libzip, a library to manipulate ZIP archives.
 .\" The authors can be contacted at <libzip@nih.at>
@@ -31,7 +31,7 @@
 .\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 .\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd April 23, 2006
+.Dd May 14, 2008
 .Dt ZIP_UNCHANGE_ARCHIVE 3
 .Os
 .Sh NAME
@@ -46,7 +46,7 @@
 .Sh DESCRIPTION
 Revert all global changes to the archive
 .Ar archive .
-For now, this only reverts archive comment changes.
+This reverts changes to the archive comment and global flags.
 .Sh RETURN VALUES
 Upon successful completion 0 is returned.
 Otherwise, \-1 is returned and the error code in
diff --git a/src/Makefile.am b/src/Makefile.am
index 3235bde..2bd9a1b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,8 +1,10 @@
-bin_PROGRAMS=zipcmp zipmerge
+bin_PROGRAMS=zipcmp zipmerge ziptorrent
 
 zipcmp_CPPFLAGS=-I${top_srcdir}/lib
 zipcmp_LDADD=${top_builddir}/lib/libzip.la
 zipmerge_CPPFLAGS=-I${top_srcdir}/lib
 zipmerge_LDADD=${top_builddir}/lib/libzip.la
+ziptorrent_CPPFLAGS=-I${top_srcdir}/lib
+ziptorrent_LDADD=${top_builddir}/lib/libzip.la
 
 EXTRA_DIST=	CMakeLists.txt
diff --git a/src/ziptorrent.c b/src/ziptorrent.c
new file mode 100644
index 0000000..0ca7197
--- /dev/null
+++ b/src/ziptorrent.c
@@ -0,0 +1,143 @@
+/*
+  zipcmp.c -- compare zip files
+  Copyright (C) 2008 Dieter Baron and Thomas Klausner
+
+  This file is part of libzip, a library to manipulate ZIP archives.
+  The authors can be contacted at <libzip@nih.at>
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions
+  are met:
+  1. Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+  2. Redistributions in binary form must reproduce the above copyright
+     notice, this list of conditions and the following disclaimer in
+     the documentation and/or other materials provided with the
+     distribution.
+  3. The names of the authors may not be used to endorse or promote
+     products derived from this software without specific prior
+     written permission.
+ 
+  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
+  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#include "config.h"
+#include "zip.h"
+
+
+
+const char *prg;
+
+#define PROGRAM	"ziptorrent"
+
+char *usage = "usage: %s [-hV] zip [...]\n";
+
+char help_head[] =
+    PROGRAM " (" PACKAGE ") by Dieter Baron and Thomas Klausner\n\n";
+
+char help[] = "\n\
+  -h       display this help message\n\
+  -V       display version number\n\
+\n\
+Report bugs to <libzip@nih.at>.\n";
+
+char version_string[] = PROGRAM " (" PACKAGE " " VERSION ")\n\
+Copyright (C) 2008 Dieter Baron and Thomas Klausner\n\
+" PACKAGE " comes with ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n";
+
+#define OPTIONS "hV"
+
+static int torrentzip(const char *);
+
+
+
+int
+main(int argc, char * const argv[])
+{
+    int err;
+    int c;
+
+    prg = argv[0];
+
+    while ((c=getopt(argc, argv, OPTIONS)) != -1) {
+	switch (c) {
+	case 'h':
+	    fputs(help_head, stdout);
+	    printf(usage, prg);
+	    fputs(help, stdout);
+	    exit(0);
+	case 'V':
+	    fputs(version_string, stdout);
+	    exit(0);
+
+	default:
+	    fprintf(stderr, usage, prg);
+	    exit(2);
+	}
+    }
+
+    if (argc == optind) {
+	fprintf(stderr, usage, prg);
+	exit(2);
+    }
+
+    err = 0;
+    while (optind < argc) {
+	err |= torrentzip(argv[optind++]);
+    }
+
+    return (err ? 1 : 0);
+}
+
+
+
+static int
+torrentzip(const char *fname)
+{
+    struct zip *za;
+    int err;
+    char errstr[1024];
+
+    if ((za=zip_open(fname, 0, &err)) == NULL) {
+	zip_error_to_str(errstr, sizeof(errstr), err, errno);
+	fprintf(stderr, "%s: cannot open zip archive `%s': %s\n",
+		prg, fname, errstr);
+	return -1;
+    }
+
+    if (zip_set_archive_flag(za, ZIP_AFL_TORRENT, 1) < 0) {
+	fprintf(stderr,	"%s: cannot set torrentzip flag in `%s': %s\n",
+		prg, fname, zip_strerror(za));
+	zip_close(za);
+	return -1;
+    }
+
+    if (zip_close(za) < 0) {
+	fprintf(stderr,	"%s: cannot torrentzip `%s': %s\n",
+		prg, fname, zip_strerror(za));
+	zip_unchange_all(za);
+	zip_close(za);
+	return -1;
+    }
+
+    return 0;
+}