Add support for bzip2 compression and decompression.

More correctly set "version needed".
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ded98c4..5fe928f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -98,6 +98,9 @@
   MESSAGE(FATAL_ERROR "-- ZLIB version too old, please install at least v1.1.2")
 ENDIF(ZLIB_VERSION_STRING VERSION_LESS "1.1.2")
 
+FIND_PACKAGE(BZIP2)
+INCLUDE_DIRECTORIES(${BZIP2_INCLUDE_DIR})
+
 IF(MSVC)
 ADD_DEFINITIONS("-D_CRT_SECURE_NO_WARNINGS")
 ENDIF(MSVC)
diff --git a/TODO.md b/TODO.md
index 35c980f..f7d56b8 100644
--- a/TODO.md
+++ b/TODO.md
@@ -8,11 +8,11 @@
 const zip_uint8_t *zip_get_archive_prefix(struct zip *za, zip_uint64_t *lengthp);
 ````
 
-# Compression API
+# Compression
 
-* `zip_source_compress` that gets function pointers to the actual
-  compression/decompression implementation and hides the underlying
-  differences between zlib, bzip2, and xz/lzma.
+* Test CMAKE for bzip2
+* disable bzip2 tests if bzip2 not enabled
+* add lzma support
 
 # API Issues
 
@@ -34,10 +34,10 @@
 * xz support
 * consistently use `_zip_crypto_clear()` for passwords
 * implement compression flags for `zip_set_file_compression()`
-* support setting extra fields from zip_source
+* support setting extra fields from `zip_source`
   * introduce layers of extra fields:
     * original
-    * from zip_source
+    * from `zip_source`
     * manually set
   * when querying extra fields, search all of them in reverse order
   * add whiteout (deleted) flag
@@ -49,7 +49,9 @@
 * set `O_CLOEXEC` flag after fopen and mkstemp
 * add append-only mode writing file to disk incrementally to keep memory usage low
 * `zip_file_set_mtime()`: support InfoZIP time stamps
-
+* `zipcmp`: support comparing more features:
+  * version needed/made by
+  * general purpose bit flags
 * support streaming output (creating new archive to e.g. stdout)
 * add functions to:
   * read/set ASCII file flag? (more general options?)
diff --git a/configure.ac b/configure.ac
index 6c8d4d4..71be4bd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -49,6 +49,27 @@
     AC_MSG_ERROR([ZLIB version too old, please install at least v1.1.2])
 fi
 
+AC_ARG_WITH(bzip2,
+    [  --with-bzip2=PREFIX  specify prefix for bzip2 library],,
+    with_bzip2=yes)
+
+if test "$with_bzip2" != "yes"
+then
+    if test -f "$with_bzip2"/bzlib.h
+    then
+	# PREFIX is to uninstalled version in distribution directory
+	CFLAGS="$CFLAGS -I$with_bzip2"
+	LDFLAGS="$LDFLAGS -L$with_bzip2"
+    else if test -f "$with_bzip2"/include/bzlib.h
+    then
+	# PREFIX is installation prefix
+	CFLAGS="$CFLAGS -I$with_bzip2/include"
+	LDFLAGS="$LDFLAGS -L$with_bzip2/lib"
+    fi
+    fi
+fi
+AC_CHECK_LIB(bz2, main)
+
 AC_EXEEXT
 
 LT_INIT
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 92e21eb..18c0884 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -57,6 +57,7 @@
   zip_add.c
   zip_add_dir.c
   zip_add_entry.c
+  zip_algorithm_bzip2.c
   zip_algorithm_deflate.c
   zip_buffer.c
   zip_close.c
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 0389e08..524f418 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -33,6 +33,7 @@
 	zip_add.c \
 	zip_add_dir.c \
 	zip_add_entry.c \
+	zip_algorithm_bzip2.c \
 	zip_algorithm_deflate.c \
 	zip_buffer.c \
 	zip_close.c \
diff --git a/lib/zip.h b/lib/zip.h
index dd8afe8..e1965cb 100644
--- a/lib/zip.h
+++ b/lib/zip.h
@@ -102,37 +102,38 @@
 
 /* libzip error codes */
 
-#define ZIP_ER_OK             0  /* N No error */
-#define ZIP_ER_MULTIDISK      1  /* N Multi-disk zip archives not supported */
-#define ZIP_ER_RENAME         2  /* S Renaming temporary file failed */
-#define ZIP_ER_CLOSE          3  /* S Closing zip archive failed */
-#define ZIP_ER_SEEK           4  /* S Seek error */
-#define ZIP_ER_READ           5  /* S Read error */
-#define ZIP_ER_WRITE          6  /* S Write error */
-#define ZIP_ER_CRC            7  /* N CRC error */
-#define ZIP_ER_ZIPCLOSED      8  /* N Containing zip archive was closed */
-#define ZIP_ER_NOENT          9  /* N No such file */
-#define ZIP_ER_EXISTS        10  /* N File already exists */
-#define ZIP_ER_OPEN          11  /* S Can't open file */
-#define ZIP_ER_TMPOPEN       12  /* S Failure to create temporary file */
-#define ZIP_ER_ZLIB          13  /* Z Zlib error */
-#define ZIP_ER_MEMORY        14  /* N Malloc failure */
-#define ZIP_ER_CHANGED       15  /* N Entry has been changed */
-#define ZIP_ER_COMPNOTSUPP   16  /* N Compression method not supported */
-#define ZIP_ER_EOF           17  /* N Premature end of file */
-#define ZIP_ER_INVAL         18  /* N Invalid argument */
-#define ZIP_ER_NOZIP         19  /* N Not a zip archive */
-#define ZIP_ER_INTERNAL      20  /* N Internal error */
-#define ZIP_ER_INCONS        21  /* N Zip archive inconsistent */
-#define ZIP_ER_REMOVE        22  /* S Can't remove file */
-#define ZIP_ER_DELETED       23  /* N Entry has been deleted */
-#define ZIP_ER_ENCRNOTSUPP   24  /* N Encryption method not supported */
-#define ZIP_ER_RDONLY        25  /* N Read-only archive */
-#define ZIP_ER_NOPASSWD      26  /* N No password provided */
-#define ZIP_ER_WRONGPASSWD   27  /* N Wrong password provided */
-#define ZIP_ER_OPNOTSUPP     28  /* N Operation not supported */
-#define ZIP_ER_INUSE         29  /* N Resource still in use */
-#define ZIP_ER_TELL          30  /* S Tell error */
+#define ZIP_ER_OK                0  /* N No error */
+#define ZIP_ER_MULTIDISK         1  /* N Multi-disk zip archives not supported */
+#define ZIP_ER_RENAME            2  /* S Renaming temporary file failed */
+#define ZIP_ER_CLOSE             3  /* S Closing zip archive failed */
+#define ZIP_ER_SEEK              4  /* S Seek error */
+#define ZIP_ER_READ              5  /* S Read error */
+#define ZIP_ER_WRITE             6  /* S Write error */
+#define ZIP_ER_CRC               7  /* N CRC error */
+#define ZIP_ER_ZIPCLOSED         8  /* N Containing zip archive was closed */
+#define ZIP_ER_NOENT             9  /* N No such file */
+#define ZIP_ER_EXISTS           10  /* N File already exists */
+#define ZIP_ER_OPEN             11  /* S Can't open file */
+#define ZIP_ER_TMPOPEN          12  /* S Failure to create temporary file */
+#define ZIP_ER_ZLIB             13  /* Z Zlib error */
+#define ZIP_ER_MEMORY           14  /* N Malloc failure */
+#define ZIP_ER_CHANGED          15  /* N Entry has been changed */
+#define ZIP_ER_COMPNOTSUPP      16  /* N Compression method not supported */
+#define ZIP_ER_EOF              17  /* N Premature end of file */
+#define ZIP_ER_INVAL            18  /* N Invalid argument */
+#define ZIP_ER_NOZIP            19  /* N Not a zip archive */
+#define ZIP_ER_INTERNAL         20  /* N Internal error */
+#define ZIP_ER_INCONS           21  /* N Zip archive inconsistent */
+#define ZIP_ER_REMOVE           22  /* S Can't remove file */
+#define ZIP_ER_DELETED          23  /* N Entry has been deleted */
+#define ZIP_ER_ENCRNOTSUPP      24  /* N Encryption method not supported */
+#define ZIP_ER_RDONLY           25  /* N Read-only archive */
+#define ZIP_ER_NOPASSWD         26  /* N No password provided */
+#define ZIP_ER_WRONGPASSWD      27  /* N Wrong password provided */
+#define ZIP_ER_OPNOTSUPP        28  /* N Operation not supported */
+#define ZIP_ER_INUSE            29  /* N Resource still in use */
+#define ZIP_ER_TELL             30  /* S Tell error */
+#define ZIP_ER_COMPRESSED_DATA	31  /* N Compressed data invalid */    
 
 /* type of system error value */
 
diff --git a/lib/zip_algorithm_bzip2.c b/lib/zip_algorithm_bzip2.c
new file mode 100644
index 0000000..96bab75
--- /dev/null
+++ b/lib/zip_algorithm_bzip2.c
@@ -0,0 +1,268 @@
+/*
+  zip_algorithm_bzip2.c -- bzip2 (de)compression routines
+  Copyright (C) 2017 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"
+
+#if defined(HAVE_LIBBZ2)
+
+#include <bzlib.h>
+#include <stdlib.h>
+
+struct ctx {
+    zip_error_t *error;
+    bool compress;
+    int compression_flags;
+    bool end_of_input;
+    bz_stream zstr;
+};
+
+
+static void *
+allocate(bool compress, int compression_flags, zip_error_t *error) {
+    struct ctx *ctx;
+
+    if ((ctx = (struct ctx *)malloc(sizeof(*ctx))) == NULL) {
+	return NULL;
+    }
+
+    ctx->error = error;
+    ctx->compress = compress;
+    ctx->compression_flags = compression_flags;
+    ctx->end_of_input = false;
+
+    ctx->zstr.bzalloc = NULL;
+    ctx->zstr.bzfree = NULL;
+    ctx->zstr.opaque = NULL;
+
+    return ctx;
+}
+
+
+static void *
+compress_allocate(zip_uint16_t method, int compression_flags, zip_error_t *error) {
+    return allocate(true, compression_flags, error);
+}
+
+
+static void *
+decompress_allocate(zip_uint16_t method, int compression_flags, zip_error_t *error) {
+    return allocate(false, compression_flags, error);
+}
+
+
+static void
+deallocate(void *ud) {
+    struct ctx *ctx = (struct ctx *)ud;
+
+    free(ctx);
+}
+
+
+static int
+compression_flags(void *ud) {
+    return 0;
+}
+
+
+static int
+map_error(int ret) {
+    switch (ret) {
+    case BZ_FINISH_OK:
+    case BZ_FLUSH_OK:
+    case BZ_OK:
+    case BZ_RUN_OK:
+    case BZ_STREAM_END:
+	return ZIP_ER_OK;
+	
+    case BZ_DATA_ERROR:
+    case BZ_DATA_ERROR_MAGIC:
+    case BZ_UNEXPECTED_EOF:
+	return ZIP_ER_COMPRESSED_DATA;
+
+    case BZ_MEM_ERROR:
+	return ZIP_ER_MEMORY;
+
+    case BZ_PARAM_ERROR:
+	return ZIP_ER_INVAL;
+
+    case BZ_CONFIG_ERROR: /* actually, bzip2 miscompiled */
+    case BZ_IO_ERROR:
+    case BZ_OUTBUFF_FULL:
+    case BZ_SEQUENCE_ERROR:
+	return ZIP_ER_INTERNAL;
+
+    default:
+	return ZIP_ER_INTERNAL;
+    }
+
+}
+
+static bool
+start(void *ud) {
+    struct ctx *ctx = (struct ctx *)ud;
+    int ret;
+
+    ctx->zstr.avail_in = 0;
+    ctx->zstr.next_in = NULL;
+    ctx->zstr.avail_out = 0;
+    ctx->zstr.next_out = NULL;
+
+    if (ctx->compress) {
+	/* TODO: use ctx->compression_flags */
+	ret = BZ2_bzCompressInit(&ctx->zstr, 9, 0, 30);
+
+    }
+    else {
+	ret = BZ2_bzDecompressInit(&ctx->zstr, 0, 0);
+    }
+    
+    if (ret != BZ_OK) {
+	zip_error_set(ctx->error, map_error(ret), 0);
+	return false;
+    }
+    
+    
+    return true;
+}
+
+
+static bool
+end(void *ud) {
+    struct ctx *ctx = (struct ctx *)ud;
+
+    /* TODO: can this fail? */
+    if (ctx->compress) {
+	BZ2_bzCompressEnd(&ctx->zstr);
+    }
+    else {
+	BZ2_bzDecompressEnd(&ctx->zstr);
+    }
+
+    return true;
+}
+
+
+static bool input(void *ud, zip_uint8_t *data, zip_uint64_t length) {
+    struct ctx *ctx = (struct ctx *)ud;
+
+    if (length > UINT_MAX || ctx->zstr.avail_in > 0) {
+	zip_error_set(ctx->error, ZIP_ER_INVAL, 0);
+	return false;
+    }
+
+    ctx->zstr.avail_in = (unsigned int)length;
+    ctx->zstr.next_in = (char *)data;
+
+    return true;
+}
+
+
+static void end_of_input(void *ud) {
+    struct ctx *ctx = (struct ctx *)ud;
+
+    ctx->end_of_input = true;
+}
+
+
+static zip_compression_status_t
+process(void *ud, zip_uint8_t *data, zip_uint64_t *length) {
+    struct ctx *ctx = (struct ctx *)ud;
+
+    int ret;
+
+    if (ctx->zstr.avail_in == 0 && !ctx->end_of_input) {
+	*length = 0;
+	return ZIP_COMPRESSION_NEED_DATA;
+    }
+
+    ctx->zstr.avail_out = (unsigned int)ZIP_MIN(UINT_MAX, *length);
+    ctx->zstr.next_out = (char *)data;
+
+    if (ctx->compress) {
+	ret = BZ2_bzCompress(&ctx->zstr, ctx->end_of_input ? BZ_FINISH : BZ_RUN);
+    }
+    else {
+	ret = BZ2_bzDecompress(&ctx->zstr);
+    }
+
+    *length = *length - ctx->zstr.avail_out;
+    
+    switch (ret) {
+    case BZ_FINISH_OK: /* compression */
+	return ZIP_COMPRESSION_OK;
+
+    case BZ_OK:	/* decompression */
+    case BZ_RUN_OK: /* compression */
+	if (ctx->zstr.avail_in == 0) {
+	    return ZIP_COMPRESSION_NEED_DATA;
+	}
+	return ZIP_COMPRESSION_OK;
+
+    case BZ_STREAM_END:
+	return ZIP_COMPRESSION_END;
+
+    default:
+	zip_error_set(ctx->error, map_error(ret), 0);
+	return ZIP_COMPRESSION_ERROR;
+    }
+}
+
+
+zip_compression_algorithm_t zip_algorithm_bzip2_compress = {
+    compress_allocate,
+    deallocate,
+    compression_flags,
+    start,
+    end,
+    input,
+    end_of_input,
+    process
+};
+
+
+zip_compression_algorithm_t zip_algorithm_bzip2_decompress = {
+    decompress_allocate,
+    deallocate,
+    compression_flags,
+    start,
+    end,
+    input,
+    end_of_input,
+    process
+};
+
+#else
+
+static int dummy;
+
+#endif /* HAVE_LIBBZ2 */
diff --git a/lib/zip_algorithm_deflate.c b/lib/zip_algorithm_deflate.c
index 8abced4..c40a32a 100644
--- a/lib/zip_algorithm_deflate.c
+++ b/lib/zip_algorithm_deflate.c
@@ -34,6 +34,7 @@
 #include "zipint.h"
 
 #include <stdlib.h>
+#include <zlib.h>
 
 struct ctx {
     zip_error_t *error;
@@ -206,40 +207,6 @@
 }
 
 
-static zip_compression_status_t
-decompress_process(void *ud, zip_uint8_t *data, zip_uint64_t *length) {
-    struct ctx *ctx = (struct ctx *)ud;
-
-    int ret;
-
-    ctx->zstr.avail_out = (uInt)ZIP_MIN(UINT_MAX, *length);
-    ctx->zstr.next_out = (Bytef *)data;
-
-    ret = deflate(&ctx->zstr, ctx->end_of_input ? Z_FINISH : 0);
-
-    *length = *length - ctx->zstr.avail_out;
-    
-    switch (ret) {
-    case Z_OK:
-	return ZIP_COMPRESSION_OK;
-
-    case Z_STREAM_END:
-	return ZIP_COMPRESSION_END;
-
-    case Z_BUF_ERROR:
-	if (ctx->zstr.avail_in == 0) {
-	    return ZIP_COMPRESSION_NEED_DATA;
-	}
-
-	/* fallthrough */
-
-    default:
-	zip_error_set(ctx->error, ZIP_ER_ZLIB, ret);
-	return ZIP_COMPRESSION_ERROR;
-    }
-}
-
-
 zip_compression_algorithm_t zip_algorithm_deflate_compress = {
     compress_allocate,
     deallocate,
diff --git a/lib/zip_close.c b/lib/zip_close.c
index c3a4b39..f20b019 100644
--- a/lib/zip_close.c
+++ b/lib/zip_close.c
@@ -51,9 +51,6 @@
 #endif
 
 
-/* max deflate size increase: size + ceil(size/16k)*5+6 */
-#define MAX_DEFLATE_SIZE_32	4293656963u
-
 typedef struct {
     double last_update;  /* last value callback function was called with */
 
@@ -300,9 +297,31 @@
 	data_length = (zip_int64_t)st.size;
 	
 	if ((st.valid & ZIP_STAT_COMP_SIZE) == 0) {
-	    if (( ((de->comp_method == ZIP_CM_DEFLATE || ZIP_CM_IS_DEFAULT(de->comp_method)) && st.size > MAX_DEFLATE_SIZE_32)
-		  || (de->comp_method != ZIP_CM_STORE && de->comp_method != ZIP_CM_DEFLATE && !ZIP_CM_IS_DEFAULT(de->comp_method))))
+	    zip_uint64_t max_size;
+
+	    switch (ZIP_CM_ACTUAL(de->comp_method)) {
+	    case ZIP_CM_BZIP2:
+		/* computed by looking at increase of 10 random files of size 1MB when
+		 * compressed with bzip2, rounded up: 1.006 */
+		max_size = 4269351188u;
+		break;
+
+	    case ZIP_CM_DEFLATE:
+		/* max deflate size increase: size + ceil(size/16k)*5+6 */
+		max_size = 4293656963u;
+		break;
+
+	    case ZIP_CM_STORE:
+		max_size = 0xffffffffu;
+		break;
+
+	    default:
+		max_size = 0;
+	    }
+
+	    if (st.size > max_size) {
 		flags |= ZIP_FL_FORCE_ZIP64;
+	    }
 	}
 	else
 	    de->comp_size = st.comp_size;
@@ -319,7 +338,7 @@
 	return -1;
     }
 
-    needs_recompress = !((st.comp_method == de->comp_method) || (ZIP_CM_IS_DEFAULT(de->comp_method) && st.comp_method == ZIP_CM_DEFLATE));
+    needs_recompress = st.comp_method != ZIP_CM_ACTUAL(de->comp_method);
     needs_decompress = needs_recompress && (st.comp_method != ZIP_CM_STORE);
     needs_crc = (st.comp_method == ZIP_CM_STORE) || needs_decompress;
     needs_compress = needs_recompress && (de->comp_method != ZIP_CM_STORE);
@@ -455,9 +474,9 @@
     de->crc = st.crc;
     de->uncomp_size = st.size;
     de->comp_size = (zip_uint64_t)(offend - offdata);
-    /* TODO: version needed */
     de->bitflags = (zip_uint16_t)((de->bitflags & (zip_uint16_t)~6) | ((zip_uint8_t)compression_flags << 1));
-
+    _zip_dirent_set_version_needed(de, (flags & ZIP_FL_FORCE_ZIP64) != 0);
+    
     if ((ret=_zip_dirent_write(za, de, flags)) < 0)
 	return -1;
  
@@ -466,7 +485,6 @@
 	zip_error_set(&za->error, ZIP_ER_INTERNAL, 0);
 	return -1;
     }
-
    
     if (zip_source_seek_write(za->src, offend, SEEK_SET) < 0) {
 	_zip_error_set_from_source(&za->error, za->src);
@@ -540,7 +558,6 @@
     return ret;
 }
 
-
 static int
 write_cdir(zip_t *za, const zip_filelist_t *filelist, zip_uint64_t survivors)
 {
diff --git a/lib/zip_dirent.c b/lib/zip_dirent.c
index bfef9fe..197bb3b 100644
--- a/lib/zip_dirent.c
+++ b/lib/zip_dirent.c
@@ -281,8 +281,8 @@
     de->cloned = 0;
 
     de->crc_valid = true;
-    de->version_madeby = 20 | (ZIP_OPSYS_DEFAULT << 8);
-    de->version_needed = 20; /* 2.0 */
+    de->version_madeby = 63 | (ZIP_OPSYS_DEFAULT << 8);
+    de->version_needed = 10; /* 1.0 */
     de->bitflags = 0;
     de->comp_method = ZIP_CM_DEFAULT;
     de->last_mod = 0;
@@ -1103,3 +1103,39 @@
 
     return;
 }
+
+
+void
+_zip_dirent_set_version_needed(zip_dirent_t *de, bool force_zip64) {
+    zip_uint16_t length;
+
+    if (de->comp_method == ZIP_CM_LZMA) {
+	de->version_needed = 63;
+	return;
+    }
+
+    if (de->comp_method == ZIP_CM_BZIP2) {
+	de->version_needed = 46;
+	return;
+    }
+
+    if (force_zip64 || _zip_dirent_needs_zip64(de, 0)) {
+	de->version_needed = 45;
+	return;
+    }
+    
+    if (de->comp_method == ZIP_CM_DEFLATE || de->encryption_method == ZIP_EM_TRAD_PKWARE) {
+	de->version_needed = 20;
+	return;
+    }
+
+    /* directory */
+    if ((length = _zip_string_length(de->filename)) > 0) {
+	if (de->filename->raw[length-1] == '/') {
+	    de->version_needed = 20;
+	    return;
+	}
+    }
+    
+    de->version_needed = 10;
+}
diff --git a/lib/zip_err_str.c b/lib/zip_err_str.c
index 9c9adb5..6560bba 100644
--- a/lib/zip_err_str.c
+++ b/lib/zip_err_str.c
@@ -37,6 +37,7 @@
     "Operation not supported",
     "Resource still in use",
     "Tell error",
+    "Compressed data invalid",    
 };
 
 const int _zip_nerr_str = sizeof(_zip_err_str)/sizeof(_zip_err_str[0]);
@@ -77,4 +78,5 @@
     N,
     N,
     S,
+    N,    
 };
diff --git a/lib/zip_random_unix.c b/lib/zip_random_unix.c
index 7a932ef..26706f0 100644
--- a/lib/zip_random_unix.c
+++ b/lib/zip_random_unix.c
@@ -34,6 +34,7 @@
 #include "zipint.h"
 
 #include <fcntl.h>
+#include <unistd.h>
 
 bool
 zip_random(zip_uint8_t *buffer, zip_uint16_t length)
diff --git a/lib/zip_set_file_compression.c b/lib/zip_set_file_compression.c
index 7bb0bf9..5321687 100644
--- a/lib/zip_set_file_compression.c
+++ b/lib/zip_set_file_compression.c
@@ -51,7 +51,7 @@
 	return -1;
     }
 
-    if (method != ZIP_CM_DEFAULT && method != ZIP_CM_STORE && method != ZIP_CM_DEFLATE) {
+    if (!zip_compression_method_supported(method, true)) {
 	zip_error_set(&za->error, ZIP_ER_COMPNOTSUPP, 0);
 	return -1;
     }
diff --git a/lib/zip_source_compress.c b/lib/zip_source_compress.c
index 03f6280..37e0318 100644
--- a/lib/zip_source_compress.c
+++ b/lib/zip_source_compress.c
@@ -64,6 +64,9 @@
 
 static struct implementation implementations[] = {
     { ZIP_CM_DEFLATE, &zip_algorithm_deflate_compress, &zip_algorithm_deflate_decompress },
+#if defined(HAVE_LIBBZ2)
+    { ZIP_CM_BZIP2, &zip_algorithm_bzip2_compress, &zip_algorithm_bzip2_decompress },
+#endif
 };
 
 static size_t implementations_size = sizeof(implementations) / sizeof(implementations[0]);
@@ -74,6 +77,33 @@
 static struct context *context_new(zip_int32_t method, bool compress, int compression_flags, zip_compression_algorithm_t *algorithm);
 static zip_int64_t compress_read(zip_source_t *, struct context *, void *, zip_uint64_t);
 
+static zip_compression_algorithm_t *
+get_algorithm(zip_int32_t method, bool compress) {
+    size_t i;
+    zip_uint16_t real_method = ZIP_CM_ACTUAL(method);
+
+    for (i = 0; i < implementations_size; i++) {
+	if (implementations[i].method == real_method) {
+	    if (compress) {
+		return implementations[i].compress;
+	    }
+	    else {
+		return implementations[i].decompress;
+	    }
+	}
+    }
+
+    return NULL;
+}
+
+bool
+zip_compression_method_supported(zip_int32_t method, bool compress) {
+    if (method == ZIP_CM_STORE) {
+	return true;
+    }
+    return get_algorithm(method, compress) != NULL;
+}
+
 zip_source_t *
 zip_source_compress(zip_t *za, zip_source_t *src, zip_int32_t method, int compression_flags) {
     return compression_source_new(za, src, method, true, compression_flags);
@@ -90,8 +120,6 @@
 {
     struct context *ctx;
     zip_source_t *s2;
-    zip_uint16_t real_method;
-    size_t i;
     zip_compression_algorithm_t *algorithm = NULL;
     
     if (src == NULL) {
@@ -99,21 +127,7 @@
 	return NULL;
     }
 
-    real_method = ZIP_CM_ACTUAL(method);
-
-    for (i = 0; i < implementations_size; i++) {
-	if (implementations[i].method == real_method) {
-	    if (compress) {
-		algorithm = implementations[i].compress;
-	    }
-	    else {
-		algorithm = implementations[i].decompress;
-	    }
-	    break;
-	}
-    }
-
-    if (algorithm == NULL) {
+    if ((algorithm = get_algorithm(method, compress)) == NULL) {
 	zip_error_set(&za->error, ZIP_ER_COMPNOTSUPP, 0);
 	return NULL;
     }
diff --git a/lib/zipint.h b/lib/zipint.h
index 7198889..0e725b2 100644
--- a/lib/zipint.h
+++ b/lib/zipint.h
@@ -133,10 +133,13 @@
 };
 typedef struct zip_compression_algorithm zip_compression_algorithm_t;
 
-
+extern zip_compression_algorithm_t zip_algorithm_bzip2_compress;
+extern zip_compression_algorithm_t zip_algorithm_bzip2_decompress;
 extern zip_compression_algorithm_t zip_algorithm_deflate_compress;
 extern zip_compression_algorithm_t zip_algorithm_deflate_decompress;
 
+bool zip_compression_method_supported(zip_int32_t method, bool compress);
+
 /* This API is not final yet, but we need it internally, so it's private for now. */
 
 const zip_uint8_t *zip_get_extra_field_by_id(zip_t *, int, int, zip_uint16_t, int, zip_uint16_t *);
@@ -441,6 +444,7 @@
 bool _zip_dirent_needs_zip64(const zip_dirent_t *, zip_flags_t);
 zip_dirent_t *_zip_dirent_new(void);
 zip_int64_t _zip_dirent_read(zip_dirent_t *zde, zip_source_t *src, zip_buffer_t *buffer, bool local, zip_error_t *error);
+void _zip_dirent_set_version_needed(zip_dirent_t *de, bool force_zip64);
 zip_int32_t _zip_dirent_size(zip_source_t *src, zip_uint16_t, zip_error_t *);
 int _zip_dirent_write(zip_t *za, zip_dirent_t *dirent, zip_flags_t flags);
 
diff --git a/regress/CMakeLists.txt b/regress/CMakeLists.txt
index 622f134..8ea31d1 100644
--- a/regress/CMakeLists.txt
+++ b/regress/CMakeLists.txt
@@ -104,8 +104,11 @@
 	set_comment_localonly.test
 	set_comment_removeglobal.test
 	set_comment_revert.test
+	set_compression_bzip2_to_deflate.test
+	set_compression_deflate_to_bzip2.test
 	set_compression_deflate_to_deflate.test
 	set_compression_deflate_to_store.test
+	set_compression_store_to_bzip2.test
 	set_compression_store_to_deflate.test
 	set_compression_store_to_store.test
 	set_compression_unknown.test
diff --git a/regress/Makefile.am b/regress/Makefile.am
index 693c48c..efffd56 100644
--- a/regress/Makefile.am
+++ b/regress/Makefile.am
@@ -118,6 +118,7 @@
 	test-utf8.zip \
 	test-utf8-unmarked.zip \
 	testbuffer.zip \
+	testbzip2.zip \
 	testdir.zip \
 	testchanged.zip \
 	testchangedlocal.zip \
@@ -245,8 +246,11 @@
 	set_comment_localonly.test \
 	set_comment_removeglobal.test \
 	set_comment_revert.test \
+	set_compression_bzip2_to_deflate.test \
+	set_compression_deflate_to_bzip2.test \
 	set_compression_deflate_to_deflate.test \
 	set_compression_deflate_to_store.test \
+	set_compression_store_to_bzip2.test \
 	set_compression_store_to_deflate.test \
 	set_compression_store_to_store.test \
 	set_compression_unknown.test \
diff --git a/regress/bigstored.zh b/regress/bigstored.zh
index 6951028..359a943 100644
--- a/regress/bigstored.zh
+++ b/regress/bigstored.zh
Binary files differ
diff --git a/regress/encrypt-aes128-noentropy.zip b/regress/encrypt-aes128-noentropy.zip
index e545722..c588fc9 100644
--- a/regress/encrypt-aes128-noentropy.zip
+++ b/regress/encrypt-aes128-noentropy.zip
Binary files differ
diff --git a/regress/encrypt-aes192-noentropy.zip b/regress/encrypt-aes192-noentropy.zip
index 2600691..59605a0 100644
--- a/regress/encrypt-aes192-noentropy.zip
+++ b/regress/encrypt-aes192-noentropy.zip
Binary files differ
diff --git a/regress/encrypt-aes256-noentropy.zip b/regress/encrypt-aes256-noentropy.zip
index 0a3627a..742ada6 100644
--- a/regress/encrypt-aes256-noentropy.zip
+++ b/regress/encrypt-aes256-noentropy.zip
Binary files differ
diff --git a/regress/encrypt-none.zip b/regress/encrypt-none.zip
index 3238aa6..0a2f770 100644
--- a/regress/encrypt-none.zip
+++ b/regress/encrypt-none.zip
Binary files differ
diff --git a/regress/set_compression_bzip2_to_deflate.test b/regress/set_compression_bzip2_to_deflate.test
new file mode 100644
index 0000000..bdfacb5
--- /dev/null
+++ b/regress/set_compression_bzip2_to_deflate.test
@@ -0,0 +1,5 @@
+# change method from bzip2 to deflated
+features LIBBZ2
+return 0
+args test.zip  set_file_compression 0 deflate 0
+file test.zip testbzip2.zip testdeflated.zip
diff --git a/regress/set_compression_deflate_to_bzip2.test b/regress/set_compression_deflate_to_bzip2.test
new file mode 100644
index 0000000..197b435
--- /dev/null
+++ b/regress/set_compression_deflate_to_bzip2.test
@@ -0,0 +1,5 @@
+# change method from deflated to bzip2
+features LIBBZ2
+return 0
+args test.zip  set_file_compression 0 bzip2 0
+file test.zip testdeflated.zip testbzip2.zip
diff --git a/regress/set_compression_store_to_bzip2.test b/regress/set_compression_store_to_bzip2.test
new file mode 100644
index 0000000..28bddd7
--- /dev/null
+++ b/regress/set_compression_store_to_bzip2.test
@@ -0,0 +1,5 @@
+# change method from stored to bzip2
+features LIBBZ2
+return 0
+args test.zip  set_file_compression 0 bzip2 0
+file test.zip teststored.zip testbzip2.zip
diff --git a/regress/testbzip2.zip b/regress/testbzip2.zip
new file mode 100644
index 0000000..7c9a9e7
--- /dev/null
+++ b/regress/testbzip2.zip
Binary files differ
diff --git a/regress/utf-8-standardization-output.zip b/regress/utf-8-standardization-output.zip
index 375489e..2507a76 100644
--- a/regress/utf-8-standardization-output.zip
+++ b/regress/utf-8-standardization-output.zip
Binary files differ
diff --git a/src/ziptool.c b/src/ziptool.c
index 2994cb2..6c3f25e 100644
--- a/src/ziptool.c
+++ b/src/ziptool.c
@@ -649,6 +649,10 @@
         return ZIP_CM_STORE;
     else if (strcmp(arg, "deflate") == 0)
         return ZIP_CM_DEFLATE;
+#if defined(HAVE_LIBBZ2)
+    else if (strcmp(arg, "bzip2") == 0)
+        return ZIP_CM_BZIP2;
+#endif
     else if (strcmp(arg, "unknown") == 0)
         return 100;
     return 0; /* TODO: error handling */
@@ -1023,6 +1027,9 @@
 	    "\tu\tZIP_FL_UNCHANGED\n");
     fprintf(out, "\nSupported compression methods are:\n"
 	    "\tdefault\n"
+#if defined(HAVE_LIBBZ2)
+	    "\tbzip2\n"
+#endif
 	    "\tdeflate\n"
 	    "\tstore\n");
     fprintf(out, "\nSupported compression methods are:\n"