archive-zip.c 15.4 KB
Newer Older
Rene Scharfe's avatar
Rene Scharfe committed
1 2 3 4
/*
 * Copyright (c) 2006 Rene Scharfe
 */
#include "cache.h"
5
#include "config.h"
6
#include "archive.h"
7
#include "streaming.h"
8
#include "utf8.h"
9 10
#include "userdiff.h"
#include "xdiff-interface.h"
Rene Scharfe's avatar
Rene Scharfe committed
11 12 13 14 15 16 17 18 19

static int zip_date;
static int zip_time;

static unsigned char *zip_dir;
static unsigned int zip_dir_size;

static unsigned int zip_offset;
static unsigned int zip_dir_offset;
20 21 22
static uint64_t zip_dir_entries;

static unsigned int max_creator_version;
Rene Scharfe's avatar
Rene Scharfe committed
23 24

#define ZIP_DIRECTORY_MIN_SIZE	(1024 * 1024)
25 26
#define ZIP_STREAM	(1 <<  3)
#define ZIP_UTF8	(1 << 11)
Rene Scharfe's avatar
Rene Scharfe committed
27 28 29 30 31 32 33 34 35 36 37 38 39

struct zip_local_header {
	unsigned char magic[4];
	unsigned char version[2];
	unsigned char flags[2];
	unsigned char compression_method[2];
	unsigned char mtime[2];
	unsigned char mdate[2];
	unsigned char crc32[4];
	unsigned char compressed_size[4];
	unsigned char size[4];
	unsigned char filename_length[2];
	unsigned char extra_length[2];
40
	unsigned char _end[1];
Rene Scharfe's avatar
Rene Scharfe committed
41 42
};

43 44 45 46 47 48 49 50
struct zip_data_desc {
	unsigned char magic[4];
	unsigned char crc32[4];
	unsigned char compressed_size[4];
	unsigned char size[4];
	unsigned char _end[1];
};

Rene Scharfe's avatar
Rene Scharfe committed
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
struct zip_dir_header {
	unsigned char magic[4];
	unsigned char creator_version[2];
	unsigned char version[2];
	unsigned char flags[2];
	unsigned char compression_method[2];
	unsigned char mtime[2];
	unsigned char mdate[2];
	unsigned char crc32[4];
	unsigned char compressed_size[4];
	unsigned char size[4];
	unsigned char filename_length[2];
	unsigned char extra_length[2];
	unsigned char comment_length[2];
	unsigned char disk[2];
	unsigned char attr1[2];
	unsigned char attr2[4];
	unsigned char offset[4];
69
	unsigned char _end[1];
Rene Scharfe's avatar
Rene Scharfe committed
70 71 72 73 74 75 76 77 78 79 80
};

struct zip_dir_trailer {
	unsigned char magic[4];
	unsigned char disk[2];
	unsigned char directory_start_disk[2];
	unsigned char entries_on_this_disk[2];
	unsigned char entries[2];
	unsigned char size[4];
	unsigned char offset[4];
	unsigned char comment_length[2];
81
	unsigned char _end[1];
Rene Scharfe's avatar
Rene Scharfe committed
82 83
};

84 85 86 87 88 89 90 91
struct zip_extra_mtime {
	unsigned char magic[2];
	unsigned char extra_size[2];
	unsigned char flags[1];
	unsigned char mtime[4];
	unsigned char _end[1];
};

92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
struct zip64_dir_trailer {
	unsigned char magic[4];
	unsigned char record_size[8];
	unsigned char creator_version[2];
	unsigned char version[2];
	unsigned char disk[4];
	unsigned char directory_start_disk[4];
	unsigned char entries_on_this_disk[8];
	unsigned char entries[8];
	unsigned char size[8];
	unsigned char offset[8];
	unsigned char _end[1];
};

struct zip64_dir_trailer_locator {
	unsigned char magic[4];
	unsigned char disk[4];
	unsigned char offset[8];
	unsigned char number_of_disks[4];
	unsigned char _end[1];
};

114 115 116 117 118 119
/*
 * On ARM, padding is added at the end of the struct, so a simple
 * sizeof(struct ...) reports two bytes more than the payload size
 * we're interested in.
 */
#define ZIP_LOCAL_HEADER_SIZE	offsetof(struct zip_local_header, _end)
120
#define ZIP_DATA_DESC_SIZE	offsetof(struct zip_data_desc, _end)
121 122
#define ZIP_DIR_HEADER_SIZE	offsetof(struct zip_dir_header, _end)
#define ZIP_DIR_TRAILER_SIZE	offsetof(struct zip_dir_trailer, _end)
123 124 125
#define ZIP_EXTRA_MTIME_SIZE	offsetof(struct zip_extra_mtime, _end)
#define ZIP_EXTRA_MTIME_PAYLOAD_SIZE \
	(ZIP_EXTRA_MTIME_SIZE - offsetof(struct zip_extra_mtime, flags))
126 127 128 129 130 131
#define ZIP64_DIR_TRAILER_SIZE	offsetof(struct zip64_dir_trailer, _end)
#define ZIP64_DIR_TRAILER_RECORD_SIZE \
	(ZIP64_DIR_TRAILER_SIZE - \
	 offsetof(struct zip64_dir_trailer, creator_version))
#define ZIP64_DIR_TRAILER_LOCATOR_SIZE \
	offsetof(struct zip64_dir_trailer_locator, _end)
132

Rene Scharfe's avatar
Rene Scharfe committed
133 134 135 136 137 138 139 140 141 142 143 144 145 146
static void copy_le16(unsigned char *dest, unsigned int n)
{
	dest[0] = 0xff & n;
	dest[1] = 0xff & (n >> 010);
}

static void copy_le32(unsigned char *dest, unsigned int n)
{
	dest[0] = 0xff & n;
	dest[1] = 0xff & (n >> 010);
	dest[2] = 0xff & (n >> 020);
	dest[3] = 0xff & (n >> 030);
}

147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
static void copy_le64(unsigned char *dest, uint64_t n)
{
	dest[0] = 0xff & n;
	dest[1] = 0xff & (n >> 010);
	dest[2] = 0xff & (n >> 020);
	dest[3] = 0xff & (n >> 030);
	dest[4] = 0xff & (n >> 040);
	dest[5] = 0xff & (n >> 050);
	dest[6] = 0xff & (n >> 060);
	dest[7] = 0xff & (n >> 070);
}

static uint64_t clamp_max(uint64_t n, uint64_t max, int *clamped)
{
	if (n <= max)
		return n;
	*clamped = 1;
	return max;
}

static void copy_le16_clamp(unsigned char *dest, uint64_t n, int *clamped)
{
	copy_le16(dest, clamp_max(n, 0xffff, clamped));
}

172 173 174
static void *zlib_deflate_raw(void *data, unsigned long size,
			      int compression_level,
			      unsigned long *compressed_size)
Rene Scharfe's avatar
Rene Scharfe committed
175
{
176
	git_zstream stream;
Rene Scharfe's avatar
Rene Scharfe committed
177 178 179 180
	unsigned long maxsize;
	void *buffer;
	int result;

181
	git_deflate_init_raw(&stream, compression_level);
182
	maxsize = git_deflate_bound(&stream, size);
Rene Scharfe's avatar
Rene Scharfe committed
183 184 185 186 187 188 189 190
	buffer = xmalloc(maxsize);

	stream.next_in = data;
	stream.avail_in = size;
	stream.next_out = buffer;
	stream.avail_out = maxsize;

	do {
191
		result = git_deflate(&stream, Z_FINISH);
Rene Scharfe's avatar
Rene Scharfe committed
192 193 194 195 196 197 198
	} while (result == Z_OK);

	if (result != Z_STREAM_END) {
		free(buffer);
		return NULL;
	}

199
	git_deflate_end(&stream);
Rene Scharfe's avatar
Rene Scharfe committed
200 201 202 203 204
	*compressed_size = stream.total_out;

	return buffer;
}

205 206 207 208 209 210 211 212 213 214 215 216 217
static void write_zip_data_desc(unsigned long size,
				unsigned long compressed_size,
				unsigned long crc)
{
	struct zip_data_desc trailer;

	copy_le32(trailer.magic, 0x08074b50);
	copy_le32(trailer.crc32, crc);
	copy_le32(trailer.compressed_size, compressed_size);
	copy_le32(trailer.size, size);
	write_or_die(1, &trailer, ZIP_DATA_DESC_SIZE);
}

218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
static void set_zip_dir_data_desc(struct zip_dir_header *header,
				  unsigned long size,
				  unsigned long compressed_size,
				  unsigned long crc)
{
	copy_le32(header->crc32, crc);
	copy_le32(header->compressed_size, compressed_size);
	copy_le32(header->size, size);
}

static void set_zip_header_data_desc(struct zip_local_header *header,
				     unsigned long size,
				     unsigned long compressed_size,
				     unsigned long crc)
{
	copy_le32(header->crc32, crc);
	copy_le32(header->compressed_size, compressed_size);
	copy_le32(header->size, size);
}

238 239 240 241 242 243 244 245 246 247 248
static int has_only_ascii(const char *s)
{
	for (;;) {
		int c = *s++;
		if (c == '\0')
			return 1;
		if (!isascii(c))
			return 0;
	}
}

249 250 251 252 253 254 255 256 257 258
static int entry_is_binary(const char *path, const void *buffer, size_t size)
{
	struct userdiff_driver *driver = userdiff_find_by_path(path);
	if (!driver)
		driver = userdiff_find_by_name("default");
	if (driver->binary != -1)
		return driver->binary;
	return buffer_is_binary(buffer, size);
}

259 260
#define STREAM_BUFFER_SIZE (1024 * 16)

261
static int write_zip_entry(struct archiver_args *args,
262 263 264
			   const unsigned char *sha1,
			   const char *path, size_t pathlen,
			   unsigned int mode)
Rene Scharfe's avatar
Rene Scharfe committed
265 266 267
{
	struct zip_local_header header;
	struct zip_dir_header dirent;
268
	struct zip_extra_mtime extra;
269
	unsigned long attr2;
Rene Scharfe's avatar
Rene Scharfe committed
270 271 272 273 274 275
	unsigned long compressed_size;
	unsigned long crc;
	unsigned long direntsize;
	int method;
	unsigned char *out;
	void *deflated = NULL;
276
	void *buffer;
277 278
	struct git_istream *stream = NULL;
	unsigned long flags = 0;
279
	unsigned long size;
280 281
	int is_binary = -1;
	const char *path_without_prefix = path + args->baselen;
282
	unsigned int creator_version = 0;
Rene Scharfe's avatar
Rene Scharfe committed
283

284
	crc = crc32(0, NULL, 0);
Rene Scharfe's avatar
Rene Scharfe committed
285

286 287 288 289 290 291 292
	if (!has_only_ascii(path)) {
		if (is_utf8(path))
			flags |= ZIP_UTF8;
		else
			warning("Path is not valid UTF-8: %s", path);
	}

Rene Scharfe's avatar
Rene Scharfe committed
293
	if (pathlen > 0xffff) {
294 295
		return error("path too long (%d chars, SHA1: %s): %s",
				(int)pathlen, sha1_to_hex(sha1), path);
Rene Scharfe's avatar
Rene Scharfe committed
296 297
	}

298
	if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
Rene Scharfe's avatar
Rene Scharfe committed
299
		method = 0;
300
		attr2 = 16;
Rene Scharfe's avatar
Rene Scharfe committed
301
		out = NULL;
302
		size = 0;
Rene Scharfe's avatar
Rene Scharfe committed
303
		compressed_size = 0;
304
		buffer = NULL;
305
	} else if (S_ISREG(mode) || S_ISLNK(mode)) {
306
		enum object_type type = sha1_object_info(sha1, &size);
307

308
		method = 0;
309 310
		attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) :
			(mode & 0111) ? ((mode) << 16) : 0;
311 312
		if (S_ISLNK(mode) || (mode & 0111))
			creator_version = 0x0317;
313
		if (S_ISREG(mode) && args->compression_level != 0 && size > 0)
314
			method = 8;
315 316

		if (S_ISREG(mode) && type == OBJ_BLOB && !args->convert &&
317
		    size > big_file_threshold) {
318 319 320 321 322 323 324 325 326 327 328 329 330
			stream = open_istream(sha1, &type, &size, NULL);
			if (!stream)
				return error("cannot stream blob %s",
					     sha1_to_hex(sha1));
			flags |= ZIP_STREAM;
			out = buffer = NULL;
		} else {
			buffer = sha1_file_to_archive(args, path, sha1, mode,
						      &type, &size);
			if (!buffer)
				return error("cannot read %s",
					     sha1_to_hex(sha1));
			crc = crc32(crc, buffer, size);
331 332
			is_binary = entry_is_binary(path_without_prefix,
						    buffer, size);
333 334
			out = buffer;
		}
335
		compressed_size = (method == 0) ? size : 0;
Rene Scharfe's avatar
Rene Scharfe committed
336
	} else {
337 338
		return error("unsupported file mode: 0%o (SHA1: %s)", mode,
				sha1_to_hex(sha1));
Rene Scharfe's avatar
Rene Scharfe committed
339 340
	}

341 342 343
	if (creator_version > max_creator_version)
		max_creator_version = creator_version;

344
	if (buffer && method == 8) {
345 346 347 348 349
		out = deflated = zlib_deflate_raw(buffer, size,
						  args->compression_level,
						  &compressed_size);
		if (!out || compressed_size >= size) {
			out = buffer;
Rene Scharfe's avatar
Rene Scharfe committed
350 351 352 353 354
			method = 0;
			compressed_size = size;
		}
	}

355 356 357 358 359
	copy_le16(extra.magic, 0x5455);
	copy_le16(extra.extra_size, ZIP_EXTRA_MTIME_PAYLOAD_SIZE);
	extra.flags[0] = 1;	/* just mtime */
	copy_le32(extra.mtime, args->time);

Rene Scharfe's avatar
Rene Scharfe committed
360
	/* make sure we have enough free space in the dictionary */
361
	direntsize = ZIP_DIR_HEADER_SIZE + pathlen + ZIP_EXTRA_MTIME_SIZE;
Rene Scharfe's avatar
Rene Scharfe committed
362 363 364 365 366 367
	while (zip_dir_size < zip_dir_offset + direntsize) {
		zip_dir_size += ZIP_DIRECTORY_MIN_SIZE;
		zip_dir = xrealloc(zip_dir, zip_dir_size);
	}

	copy_le32(dirent.magic, 0x02014b50);
368
	copy_le16(dirent.creator_version, creator_version);
369
	copy_le16(dirent.version, 10);
370
	copy_le16(dirent.flags, flags);
Rene Scharfe's avatar
Rene Scharfe committed
371 372 373
	copy_le16(dirent.compression_method, method);
	copy_le16(dirent.mtime, zip_time);
	copy_le16(dirent.mdate, zip_date);
374
	set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
Rene Scharfe's avatar
Rene Scharfe committed
375
	copy_le16(dirent.filename_length, pathlen);
376
	copy_le16(dirent.extra_length, ZIP_EXTRA_MTIME_SIZE);
Rene Scharfe's avatar
Rene Scharfe committed
377 378
	copy_le16(dirent.comment_length, 0);
	copy_le16(dirent.disk, 0);
379
	copy_le32(dirent.attr2, attr2);
Rene Scharfe's avatar
Rene Scharfe committed
380 381 382
	copy_le32(dirent.offset, zip_offset);

	copy_le32(header.magic, 0x04034b50);
383
	copy_le16(header.version, 10);
384
	copy_le16(header.flags, flags);
Rene Scharfe's avatar
Rene Scharfe committed
385 386 387
	copy_le16(header.compression_method, method);
	copy_le16(header.mtime, zip_time);
	copy_le16(header.mdate, zip_date);
388
	set_zip_header_data_desc(&header, size, compressed_size, crc);
Rene Scharfe's avatar
Rene Scharfe committed
389
	copy_le16(header.filename_length, pathlen);
390
	copy_le16(header.extra_length, ZIP_EXTRA_MTIME_SIZE);
391 392
	write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE);
	zip_offset += ZIP_LOCAL_HEADER_SIZE;
Rene Scharfe's avatar
Rene Scharfe committed
393 394
	write_or_die(1, path, pathlen);
	zip_offset += pathlen;
395 396
	write_or_die(1, &extra, ZIP_EXTRA_MTIME_SIZE);
	zip_offset += ZIP_EXTRA_MTIME_SIZE;
397 398 399 400 401 402 403 404 405
	if (stream && method == 0) {
		unsigned char buf[STREAM_BUFFER_SIZE];
		ssize_t readlen;

		for (;;) {
			readlen = read_istream(stream, buf, sizeof(buf));
			if (readlen <= 0)
				break;
			crc = crc32(crc, buf, readlen);
406 407 408
			if (is_binary == -1)
				is_binary = entry_is_binary(path_without_prefix,
							    buf, readlen);
409 410 411 412 413 414 415 416 417 418 419 420
			write_or_die(1, buf, readlen);
		}
		close_istream(stream);
		if (readlen)
			return readlen;

		compressed_size = size;
		zip_offset += compressed_size;

		write_zip_data_desc(size, compressed_size, crc);
		zip_offset += ZIP_DATA_DESC_SIZE;

421 422 423 424 425 426 427 428 429
		set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
	} else if (stream && method == 8) {
		unsigned char buf[STREAM_BUFFER_SIZE];
		ssize_t readlen;
		git_zstream zstream;
		int result;
		size_t out_len;
		unsigned char compressed[STREAM_BUFFER_SIZE * 2];

430
		git_deflate_init_raw(&zstream, args->compression_level);
431 432 433 434 435 436 437 438 439 440

		compressed_size = 0;
		zstream.next_out = compressed;
		zstream.avail_out = sizeof(compressed);

		for (;;) {
			readlen = read_istream(stream, buf, sizeof(buf));
			if (readlen <= 0)
				break;
			crc = crc32(crc, buf, readlen);
441 442 443
			if (is_binary == -1)
				is_binary = entry_is_binary(path_without_prefix,
							    buf, readlen);
444 445 446 447 448 449

			zstream.next_in = buf;
			zstream.avail_in = readlen;
			result = git_deflate(&zstream, 0);
			if (result != Z_OK)
				die("deflate error (%d)", result);
450
			out_len = zstream.next_out - compressed;
451 452

			if (out_len > 0) {
453
				write_or_die(1, compressed, out_len);
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
				compressed_size += out_len;
				zstream.next_out = compressed;
				zstream.avail_out = sizeof(compressed);
			}

		}
		close_istream(stream);
		if (readlen)
			return readlen;

		zstream.next_in = buf;
		zstream.avail_in = 0;
		result = git_deflate(&zstream, Z_FINISH);
		if (result != Z_STREAM_END)
			die("deflate error (%d)", result);

		git_deflate_end(&zstream);
471 472
		out_len = zstream.next_out - compressed;
		write_or_die(1, compressed, out_len);
473 474 475 476 477 478
		compressed_size += out_len;
		zip_offset += compressed_size;

		write_zip_data_desc(size, compressed_size, crc);
		zip_offset += ZIP_DATA_DESC_SIZE;

479 480
		set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
	} else if (compressed_size > 0) {
Rene Scharfe's avatar
Rene Scharfe committed
481 482 483 484 485
		write_or_die(1, out, compressed_size);
		zip_offset += compressed_size;
	}

	free(deflated);
486
	free(buffer);
Rene Scharfe's avatar
Rene Scharfe committed
487

488 489
	copy_le16(dirent.attr1, !is_binary);

490 491 492 493
	memcpy(zip_dir + zip_dir_offset, &dirent, ZIP_DIR_HEADER_SIZE);
	zip_dir_offset += ZIP_DIR_HEADER_SIZE;
	memcpy(zip_dir + zip_dir_offset, path, pathlen);
	zip_dir_offset += pathlen;
494 495
	memcpy(zip_dir + zip_dir_offset, &extra, ZIP_EXTRA_MTIME_SIZE);
	zip_dir_offset += ZIP_EXTRA_MTIME_SIZE;
496 497
	zip_dir_entries++;

498
	return 0;
Rene Scharfe's avatar
Rene Scharfe committed
499 500
}

501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
static void write_zip64_trailer(void)
{
	struct zip64_dir_trailer trailer64;
	struct zip64_dir_trailer_locator locator64;

	copy_le32(trailer64.magic, 0x06064b50);
	copy_le64(trailer64.record_size, ZIP64_DIR_TRAILER_RECORD_SIZE);
	copy_le16(trailer64.creator_version, max_creator_version);
	copy_le16(trailer64.version, 45);
	copy_le32(trailer64.disk, 0);
	copy_le32(trailer64.directory_start_disk, 0);
	copy_le64(trailer64.entries_on_this_disk, zip_dir_entries);
	copy_le64(trailer64.entries, zip_dir_entries);
	copy_le64(trailer64.size, zip_dir_offset);
	copy_le64(trailer64.offset, zip_offset);

	copy_le32(locator64.magic, 0x07064b50);
	copy_le32(locator64.disk, 0);
	copy_le64(locator64.offset, zip_offset + zip_dir_offset);
	copy_le32(locator64.number_of_disks, 1);

	write_or_die(1, &trailer64, ZIP64_DIR_TRAILER_SIZE);
	write_or_die(1, &locator64, ZIP64_DIR_TRAILER_LOCATOR_SIZE);
}

Rene Scharfe's avatar
Rene Scharfe committed
526 527 528
static void write_zip_trailer(const unsigned char *sha1)
{
	struct zip_dir_trailer trailer;
529
	int clamped = 0;
Rene Scharfe's avatar
Rene Scharfe committed
530 531 532 533

	copy_le32(trailer.magic, 0x06054b50);
	copy_le16(trailer.disk, 0);
	copy_le16(trailer.directory_start_disk, 0);
534 535 536
	copy_le16_clamp(trailer.entries_on_this_disk, zip_dir_entries,
			&clamped);
	copy_le16_clamp(trailer.entries, zip_dir_entries, &clamped);
Rene Scharfe's avatar
Rene Scharfe committed
537 538
	copy_le32(trailer.size, zip_dir_offset);
	copy_le32(trailer.offset, zip_offset);
539
	copy_le16(trailer.comment_length, sha1 ? GIT_SHA1_HEXSZ : 0);
Rene Scharfe's avatar
Rene Scharfe committed
540 541

	write_or_die(1, zip_dir, zip_dir_offset);
542 543
	if (clamped)
		write_zip64_trailer();
544
	write_or_die(1, &trailer, ZIP_DIR_TRAILER_SIZE);
Rene Scharfe's avatar
Rene Scharfe committed
545
	if (sha1)
546
		write_or_die(1, sha1_to_hex(sha1), GIT_SHA1_HEXSZ);
Rene Scharfe's avatar
Rene Scharfe committed
547 548 549 550 551 552 553 554 555 556 557
}

static void dos_time(time_t *time, int *dos_date, int *dos_time)
{
	struct tm *t = localtime(time);

	*dos_date = t->tm_mday + (t->tm_mon + 1) * 32 +
	            (t->tm_year + 1900 - 1980) * 512;
	*dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048;
}

558 559 560 561 562
static int archive_zip_config(const char *var, const char *value, void *data)
{
	return userdiff_config(var, value);
}

563 564
static int write_zip_archive(const struct archiver *ar,
			     struct archiver_args *args)
565
{
566 567
	int err;

568 569
	git_config(archive_zip_config, NULL);

570 571 572 573
	dos_time(&args->time, &zip_date, &zip_time);

	zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE);
	zip_dir_size = ZIP_DIRECTORY_MIN_SIZE;
574 575 576 577

	err = write_archive_entries(args, write_zip_entry);
	if (!err)
		write_zip_trailer(args->commit_sha1);
578 579 580

	free(zip_dir);

581
	return err;
582
}
583 584 585 586

static struct archiver zip_archiver = {
	"zip",
	write_zip_archive,
587
	ARCHIVER_WANT_COMPRESSION_LEVELS|ARCHIVER_REMOTE
588 589 590 591 592 593
};

void init_zip_archiver(void)
{
	register_archiver(&zip_archiver);
}