xref: /PHP-7.3/ext/phar/zip.c (revision 7355ab81)
1 /*
2   +----------------------------------------------------------------------+
3   | ZIP archive support for Phar                                         |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2007-2018 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt.                                 |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Gregory Beaver <cellog@php.net>                             |
16   +----------------------------------------------------------------------+
17 */
18 
19 #include "phar_internal.h"
20 
21 #define PHAR_GET_16(var) ((uint16_t)((((uint16_t)var[0]) & 0xff) | \
22 	(((uint16_t)var[1]) & 0xff) << 8))
23 #define PHAR_GET_32(var) ((uint32_t)((((uint32_t)var[0]) & 0xff) | \
24 	(((uint32_t)var[1]) & 0xff) << 8 | \
25 	(((uint32_t)var[2]) & 0xff) << 16 | \
26 	(((uint32_t)var[3]) & 0xff) << 24))
phar_write_32(char buffer[4],uint32_t value)27 static inline void phar_write_32(char buffer[4], uint32_t value)
28 {
29 	buffer[3] = (unsigned char) ((value & 0xff000000) >> 24);
30 	buffer[2] = (unsigned char) ((value & 0xff0000) >> 16);
31 	buffer[1] = (unsigned char) ((value & 0xff00) >> 8);
32 	buffer[0] = (unsigned char) (value & 0xff);
33 }
phar_write_16(char buffer[2],uint32_t value)34 static inline void phar_write_16(char buffer[2], uint32_t value)
35 {
36 	buffer[1] = (unsigned char) ((value & 0xff00) >> 8);
37 	buffer[0] = (unsigned char) (value & 0xff);
38 }
39 # define PHAR_SET_32(var, value) phar_write_32(var, (uint32_t) (value));
40 # define PHAR_SET_16(var, value) phar_write_16(var, (uint16_t) (value));
41 
phar_zip_process_extra(php_stream * fp,phar_entry_info * entry,uint16_t len)42 static int phar_zip_process_extra(php_stream *fp, phar_entry_info *entry, uint16_t len) /* {{{ */
43 {
44 	union {
45 		phar_zip_extra_field_header header;
46 		phar_zip_unix3 unix3;
47 	} h;
48 	size_t read;
49 
50 	do {
51 		if (sizeof(h.header) != php_stream_read(fp, (char *) &h.header, sizeof(h.header))) {
52 			return FAILURE;
53 		}
54 
55 		if (h.header.tag[0] != 'n' || h.header.tag[1] != 'u') {
56 			/* skip to next header */
57 			php_stream_seek(fp, PHAR_GET_16(h.header.size), SEEK_CUR);
58 			len -= PHAR_GET_16(h.header.size) + 4;
59 			continue;
60 		}
61 
62 		/* unix3 header found */
63 		read = php_stream_read(fp, (char *) &(h.unix3.crc32), sizeof(h.unix3) - sizeof(h.header));
64 		len -= read + 4;
65 
66 		if (sizeof(h.unix3) - sizeof(h.header) != read) {
67 			return FAILURE;
68 		}
69 
70 		if (PHAR_GET_16(h.unix3.size) > sizeof(h.unix3) - 4) {
71 			/* skip symlink filename - we may add this support in later */
72 			php_stream_seek(fp, PHAR_GET_16(h.unix3.size) - sizeof(h.unix3.size), SEEK_CUR);
73 		}
74 
75 		/* set permissions */
76 		entry->flags &= PHAR_ENT_COMPRESSION_MASK;
77 
78 		if (entry->is_dir) {
79 			entry->flags |= PHAR_GET_16(h.unix3.perms) & PHAR_ENT_PERM_MASK;
80 		} else {
81 			entry->flags |= PHAR_GET_16(h.unix3.perms) & PHAR_ENT_PERM_MASK;
82 		}
83 
84 	} while (len);
85 
86 	return SUCCESS;
87 }
88 /* }}} */
89 
90 /*
91   extracted from libzip
92   zip_dirent.c -- read directory entry (local or central), clean dirent
93   Copyright (C) 1999, 2003, 2004, 2005 Dieter Baron and Thomas Klausner
94 
95   This function is part of libzip, a library to manipulate ZIP archives.
96   The authors can be contacted at <nih@giga.or.at>
97 
98   Redistribution and use in source and binary forms, with or without
99   modification, are permitted provided that the following conditions
100   are met:
101   1. Redistributions of source code must retain the above copyright
102      notice, this list of conditions and the following disclaimer.
103   2. Redistributions in binary form must reproduce the above copyright
104      notice, this list of conditions and the following disclaimer in
105      the documentation and/or other materials provided with the
106      distribution.
107   3. The names of the authors may not be used to endorse or promote
108      products derived from this software without specific prior
109      written permission.
110 
111   THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
112   OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
113   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
114   ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
115   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
116   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
117   GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
118   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
119   IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
120   OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
121   IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
122  */
phar_zip_d2u_time(char * cdtime,char * cddate)123 static time_t phar_zip_d2u_time(char *cdtime, char *cddate) /* {{{ */
124 {
125 	int dtime = PHAR_GET_16(cdtime), ddate = PHAR_GET_16(cddate);
126 	struct tm *tm, tmbuf;
127 	time_t now;
128 
129 	now = time(NULL);
130 	tm = php_localtime_r(&now, &tmbuf);
131 
132 	tm->tm_year = ((ddate>>9)&127) + 1980 - 1900;
133 	tm->tm_mon = ((ddate>>5)&15) - 1;
134 	tm->tm_mday = ddate&31;
135 
136 	tm->tm_hour = (dtime>>11)&31;
137 	tm->tm_min = (dtime>>5)&63;
138 	tm->tm_sec = (dtime<<1)&62;
139 
140 	return mktime(tm);
141 }
142 /* }}} */
143 
phar_zip_u2d_time(time_t time,char * dtime,char * ddate)144 static void phar_zip_u2d_time(time_t time, char *dtime, char *ddate) /* {{{ */
145 {
146 	uint16_t ctime, cdate;
147 	struct tm *tm, tmbuf;
148 
149 	tm = php_localtime_r(&time, &tmbuf);
150 	cdate = ((tm->tm_year+1900-1980)<<9) + ((tm->tm_mon+1)<<5) + tm->tm_mday;
151 	ctime = ((tm->tm_hour)<<11) + ((tm->tm_min)<<5) + ((tm->tm_sec)>>1);
152 	PHAR_SET_16(dtime, ctime);
153 	PHAR_SET_16(ddate, cdate);
154 }
155 /* }}} */
156 
157 /**
158  * Does not check for a previously opened phar in the cache.
159  *
160  * Parse a new one and add it to the cache, returning either SUCCESS or
161  * FAILURE, and setting pphar to the pointer to the manifest entry
162  *
163  * This is used by phar_open_from_fp to process a zip-based phar, but can be called
164  * directly.
165  */
phar_parse_zipfile(php_stream * fp,char * fname,size_t fname_len,char * alias,size_t alias_len,phar_archive_data ** pphar,char ** error)166 int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alias, size_t alias_len, phar_archive_data** pphar, char **error) /* {{{ */
167 {
168 	phar_zip_dir_end locator;
169 	char buf[sizeof(locator) + 65536];
170 	zend_off_t size;
171 	uint16_t i;
172 	phar_archive_data *mydata = NULL;
173 	phar_entry_info entry = {0};
174 	char *p = buf, *ext, *actual_alias = NULL;
175 	char *metadata = NULL;
176 
177 	size = php_stream_tell(fp);
178 
179 	if (size > sizeof(locator) + 65536) {
180 		/* seek to max comment length + end of central directory record */
181 		size = sizeof(locator) + 65536;
182 		if (FAILURE == php_stream_seek(fp, -size, SEEK_END)) {
183 			php_stream_close(fp);
184 			if (error) {
185 				spprintf(error, 4096, "phar error: unable to search for end of central directory in zip-based phar \"%s\"", fname);
186 			}
187 			return FAILURE;
188 		}
189 	} else {
190 		php_stream_seek(fp, 0, SEEK_SET);
191 	}
192 
193 	if (!php_stream_read(fp, buf, size)) {
194 		php_stream_close(fp);
195 		if (error) {
196 			spprintf(error, 4096, "phar error: unable to read in data to search for end of central directory in zip-based phar \"%s\"", fname);
197 		}
198 		return FAILURE;
199 	}
200 
201 	while ((p=(char *) memchr(p + 1, 'P', (size_t) (size - (p + 1 - buf)))) != NULL) {
202 		if ((p - buf) + sizeof(locator) <= (size_t)size && !memcmp(p + 1, "K\5\6", 3)) {
203 			memcpy((void *)&locator, (void *) p, sizeof(locator));
204 			if (PHAR_GET_16(locator.centraldisk) != 0 || PHAR_GET_16(locator.disknumber) != 0) {
205 				/* split archives not handled */
206 				php_stream_close(fp);
207 				if (error) {
208 					spprintf(error, 4096, "phar error: split archives spanning multiple zips cannot be processed in zip-based phar \"%s\"", fname);
209 				}
210 				return FAILURE;
211 			}
212 
213 			if (PHAR_GET_16(locator.counthere) != PHAR_GET_16(locator.count)) {
214 				if (error) {
215 					spprintf(error, 4096, "phar error: corrupt zip archive, conflicting file count in end of central directory record in zip-based phar \"%s\"", fname);
216 				}
217 				php_stream_close(fp);
218 				return FAILURE;
219 			}
220 
221 			mydata = pecalloc(1, sizeof(phar_archive_data), PHAR_G(persist));
222 			mydata->is_persistent = PHAR_G(persist);
223 
224 			/* read in archive comment, if any */
225 			if (PHAR_GET_16(locator.comment_len)) {
226 
227 				metadata = p + sizeof(locator);
228 
229 				if (PHAR_GET_16(locator.comment_len) != size - (metadata - buf)) {
230 					if (error) {
231 						spprintf(error, 4096, "phar error: corrupt zip archive, zip file comment truncated in zip-based phar \"%s\"", fname);
232 					}
233 					php_stream_close(fp);
234 					pefree(mydata, mydata->is_persistent);
235 					return FAILURE;
236 				}
237 
238 				mydata->metadata_len = PHAR_GET_16(locator.comment_len);
239 
240 				if (phar_parse_metadata(&metadata, &mydata->metadata, PHAR_GET_16(locator.comment_len)) == FAILURE) {
241 					mydata->metadata_len = 0;
242 					/* if not valid serialized data, it is a regular string */
243 
244 					ZVAL_NEW_STR(&mydata->metadata, zend_string_init(metadata, PHAR_GET_16(locator.comment_len), mydata->is_persistent));
245 				}
246 			} else {
247 				ZVAL_UNDEF(&mydata->metadata);
248 			}
249 
250 			goto foundit;
251 		}
252 	}
253 
254 	php_stream_close(fp);
255 
256 	if (error) {
257 		spprintf(error, 4096, "phar error: end of central directory not found in zip-based phar \"%s\"", fname);
258 	}
259 
260 	return FAILURE;
261 foundit:
262 	mydata->fname = pestrndup(fname, fname_len, mydata->is_persistent);
263 #ifdef PHP_WIN32
264 	phar_unixify_path_separators(mydata->fname, fname_len);
265 #endif
266 	mydata->is_zip = 1;
267 	mydata->fname_len = fname_len;
268 	ext = strrchr(mydata->fname, '/');
269 
270 	if (ext) {
271 		mydata->ext = memchr(ext, '.', (mydata->fname + fname_len) - ext);
272 		if (mydata->ext == ext) {
273 			mydata->ext = memchr(ext + 1, '.', (mydata->fname + fname_len) - ext - 1);
274 		}
275 		if (mydata->ext) {
276 			mydata->ext_len = (mydata->fname + fname_len) - mydata->ext;
277 		}
278 	}
279 
280 	/* clean up on big-endian systems */
281 	/* seek to central directory */
282 	php_stream_seek(fp, PHAR_GET_32(locator.cdir_offset), SEEK_SET);
283 	/* read in central directory */
284 	zend_hash_init(&mydata->manifest, PHAR_GET_16(locator.count),
285 		zend_get_hash_value, destroy_phar_manifest_entry, (zend_bool)mydata->is_persistent);
286 	zend_hash_init(&mydata->mounted_dirs, 5,
287 		zend_get_hash_value, NULL, (zend_bool)mydata->is_persistent);
288 	zend_hash_init(&mydata->virtual_dirs, PHAR_GET_16(locator.count) * 2,
289 		zend_get_hash_value, NULL, (zend_bool)mydata->is_persistent);
290 	entry.phar = mydata;
291 	entry.is_zip = 1;
292 	entry.fp_type = PHAR_FP;
293 	entry.is_persistent = mydata->is_persistent;
294 #define PHAR_ZIP_FAIL_FREE(errmsg, save) \
295 			zend_hash_destroy(&mydata->manifest); \
296 			HT_FLAGS(&mydata->manifest) = 0; \
297 			zend_hash_destroy(&mydata->mounted_dirs); \
298 			HT_FLAGS(&mydata->mounted_dirs) = 0; \
299 			zend_hash_destroy(&mydata->virtual_dirs); \
300 			HT_FLAGS(&mydata->virtual_dirs) = 0; \
301 			php_stream_close(fp); \
302 			zval_ptr_dtor(&mydata->metadata); \
303 			if (mydata->signature) { \
304 				efree(mydata->signature); \
305 			} \
306 			if (error) { \
307 				spprintf(error, 4096, "phar error: %s in zip-based phar \"%s\"", errmsg, mydata->fname); \
308 			} \
309 			pefree(mydata->fname, mydata->is_persistent); \
310 			if (mydata->alias) { \
311 				pefree(mydata->alias, mydata->is_persistent); \
312 			} \
313 			pefree(mydata, mydata->is_persistent); \
314 			efree(save); \
315 			return FAILURE;
316 #define PHAR_ZIP_FAIL(errmsg) \
317 			zend_hash_destroy(&mydata->manifest); \
318 			HT_FLAGS(&mydata->manifest) = 0; \
319 			zend_hash_destroy(&mydata->mounted_dirs); \
320 			HT_FLAGS(&mydata->mounted_dirs) = 0; \
321 			zend_hash_destroy(&mydata->virtual_dirs); \
322 			HT_FLAGS(&mydata->virtual_dirs) = 0; \
323 			php_stream_close(fp); \
324 			zval_ptr_dtor(&mydata->metadata); \
325 			if (mydata->signature) { \
326 				efree(mydata->signature); \
327 			} \
328 			if (error) { \
329 				spprintf(error, 4096, "phar error: %s in zip-based phar \"%s\"", errmsg, mydata->fname); \
330 			} \
331 			pefree(mydata->fname, mydata->is_persistent); \
332 			if (mydata->alias) { \
333 				pefree(mydata->alias, mydata->is_persistent); \
334 			} \
335 			pefree(mydata, mydata->is_persistent); \
336 			return FAILURE;
337 
338 	/* add each central directory item to the manifest */
339 	for (i = 0; i < PHAR_GET_16(locator.count); ++i) {
340 		phar_zip_central_dir_file zipentry;
341 		zend_off_t beforeus = php_stream_tell(fp);
342 
343 		if (sizeof(zipentry) != php_stream_read(fp, (char *) &zipentry, sizeof(zipentry))) {
344 			PHAR_ZIP_FAIL("unable to read central directory entry, truncated");
345 		}
346 
347 		/* clean up for bigendian systems */
348 		if (memcmp("PK\1\2", zipentry.signature, 4)) {
349 			/* corrupted entry */
350 			PHAR_ZIP_FAIL("corrupted central directory entry, no magic signature");
351 		}
352 
353 		if (entry.is_persistent) {
354 			entry.manifest_pos = i;
355 		}
356 
357 		entry.compressed_filesize = PHAR_GET_32(zipentry.compsize);
358 		entry.uncompressed_filesize = PHAR_GET_32(zipentry.uncompsize);
359 		entry.crc32 = PHAR_GET_32(zipentry.crc32);
360 		/* do not PHAR_GET_16 either on the next line */
361 		entry.timestamp = phar_zip_d2u_time(zipentry.timestamp, zipentry.datestamp);
362 		entry.flags = PHAR_ENT_PERM_DEF_FILE;
363 		entry.header_offset = PHAR_GET_32(zipentry.offset);
364 		entry.offset = entry.offset_abs = PHAR_GET_32(zipentry.offset) + sizeof(phar_zip_file_header) + PHAR_GET_16(zipentry.filename_len) +
365 			PHAR_GET_16(zipentry.extra_len);
366 
367 		if (PHAR_GET_16(zipentry.flags) & PHAR_ZIP_FLAG_ENCRYPTED) {
368 			PHAR_ZIP_FAIL("Cannot process encrypted zip files");
369 		}
370 
371 		if (!PHAR_GET_16(zipentry.filename_len)) {
372 			PHAR_ZIP_FAIL("Cannot process zips created from stdin (zero-length filename)");
373 		}
374 
375 		entry.filename_len = PHAR_GET_16(zipentry.filename_len);
376 		entry.filename = (char *) pemalloc(entry.filename_len + 1, entry.is_persistent);
377 
378 		if (entry.filename_len != php_stream_read(fp, entry.filename, entry.filename_len)) {
379 			pefree(entry.filename, entry.is_persistent);
380 			PHAR_ZIP_FAIL("unable to read in filename from central directory, truncated");
381 		}
382 
383 		entry.filename[entry.filename_len] = '\0';
384 
385 		if (entry.filename[entry.filename_len - 1] == '/') {
386 			entry.is_dir = 1;
387 			if(entry.filename_len > 1) {
388 				entry.filename_len--;
389 			}
390 			entry.flags |= PHAR_ENT_PERM_DEF_DIR;
391 		} else {
392 			entry.is_dir = 0;
393 		}
394 
395 		if (entry.filename_len == sizeof(".phar/signature.bin")-1 && !strncmp(entry.filename, ".phar/signature.bin", sizeof(".phar/signature.bin")-1)) {
396 			size_t read;
397 			php_stream *sigfile;
398 			char *sig;
399 			size_t sig_len;
400 
401 			php_stream_tell(fp);
402 			pefree(entry.filename, entry.is_persistent);
403 			sigfile = php_stream_fopen_tmpfile();
404 			if (!sigfile) {
405 				PHAR_ZIP_FAIL("couldn't open temporary file");
406 			}
407 
408 			php_stream_seek(fp, 0, SEEK_SET);
409 			/* copy file contents + local headers and zip comment, if any, to be hashed for signature */
410 			php_stream_copy_to_stream_ex(fp, sigfile, entry.header_offset, NULL);
411 			/* seek to central directory */
412 			php_stream_seek(fp, PHAR_GET_32(locator.cdir_offset), SEEK_SET);
413 			/* copy central directory header */
414 			php_stream_copy_to_stream_ex(fp, sigfile, beforeus - PHAR_GET_32(locator.cdir_offset), NULL);
415 			if (metadata) {
416 				php_stream_write(sigfile, metadata, PHAR_GET_16(locator.comment_len));
417 			}
418 			php_stream_seek(fp, sizeof(phar_zip_file_header) + entry.header_offset + entry.filename_len + PHAR_GET_16(zipentry.extra_len), SEEK_SET);
419 			sig = (char *) emalloc(entry.uncompressed_filesize);
420 			read = php_stream_read(fp, sig, entry.uncompressed_filesize);
421 			if (read != entry.uncompressed_filesize || read <= 8) {
422 				php_stream_close(sigfile);
423 				efree(sig);
424 				PHAR_ZIP_FAIL("signature cannot be read");
425 			}
426 			mydata->sig_flags = PHAR_GET_32(sig);
427 			if (FAILURE == phar_verify_signature(sigfile, php_stream_tell(sigfile), mydata->sig_flags, sig + 8, entry.uncompressed_filesize - 8, fname, &mydata->signature, &sig_len, error)) {
428 				efree(sig);
429 				if (error) {
430 					char *save;
431 					php_stream_close(sigfile);
432 					spprintf(&save, 4096, "signature cannot be verified: %s", *error);
433 					efree(*error);
434 					PHAR_ZIP_FAIL_FREE(save, save);
435 				} else {
436 					php_stream_close(sigfile);
437 					PHAR_ZIP_FAIL("signature cannot be verified");
438 				}
439 			}
440 			mydata->sig_len = sig_len;
441 			php_stream_close(sigfile);
442 			efree(sig);
443 			/* signature checked out, let's ensure this is the last file in the phar */
444 			if (i != PHAR_GET_16(locator.count) - 1) {
445 				PHAR_ZIP_FAIL("entries exist after signature, invalid phar");
446 			}
447 
448 			continue;
449 		}
450 
451 		phar_add_virtual_dirs(mydata, entry.filename, entry.filename_len);
452 
453 		if (PHAR_GET_16(zipentry.extra_len)) {
454 			zend_off_t loc = php_stream_tell(fp);
455 			if (FAILURE == phar_zip_process_extra(fp, &entry, PHAR_GET_16(zipentry.extra_len))) {
456 				pefree(entry.filename, entry.is_persistent);
457 				PHAR_ZIP_FAIL("Unable to process extra field header for file in central directory");
458 			}
459 			php_stream_seek(fp, loc + PHAR_GET_16(zipentry.extra_len), SEEK_SET);
460 		}
461 
462 		switch (PHAR_GET_16(zipentry.compressed)) {
463 			case PHAR_ZIP_COMP_NONE :
464 				/* compression flag already set */
465 				break;
466 			case PHAR_ZIP_COMP_DEFLATE :
467 				entry.flags |= PHAR_ENT_COMPRESSED_GZ;
468 				if (!PHAR_G(has_zlib)) {
469 					pefree(entry.filename, entry.is_persistent);
470 					PHAR_ZIP_FAIL("zlib extension is required");
471 				}
472 				break;
473 			case PHAR_ZIP_COMP_BZIP2 :
474 				entry.flags |= PHAR_ENT_COMPRESSED_BZ2;
475 				if (!PHAR_G(has_bz2)) {
476 					pefree(entry.filename, entry.is_persistent);
477 					PHAR_ZIP_FAIL("bzip2 extension is required");
478 				}
479 				break;
480 			case 1 :
481 				pefree(entry.filename, entry.is_persistent);
482 				PHAR_ZIP_FAIL("unsupported compression method (Shrunk) used in this zip");
483 			case 2 :
484 			case 3 :
485 			case 4 :
486 			case 5 :
487 				pefree(entry.filename, entry.is_persistent);
488 				PHAR_ZIP_FAIL("unsupported compression method (Reduce) used in this zip");
489 			case 6 :
490 				pefree(entry.filename, entry.is_persistent);
491 				PHAR_ZIP_FAIL("unsupported compression method (Implode) used in this zip");
492 			case 7 :
493 				pefree(entry.filename, entry.is_persistent);
494 				PHAR_ZIP_FAIL("unsupported compression method (Tokenize) used in this zip");
495 			case 9 :
496 				pefree(entry.filename, entry.is_persistent);
497 				PHAR_ZIP_FAIL("unsupported compression method (Deflate64) used in this zip");
498 			case 10 :
499 				pefree(entry.filename, entry.is_persistent);
500 				PHAR_ZIP_FAIL("unsupported compression method (PKWare Implode/old IBM TERSE) used in this zip");
501 			case 14 :
502 				pefree(entry.filename, entry.is_persistent);
503 				PHAR_ZIP_FAIL("unsupported compression method (LZMA) used in this zip");
504 			case 18 :
505 				pefree(entry.filename, entry.is_persistent);
506 				PHAR_ZIP_FAIL("unsupported compression method (IBM TERSE) used in this zip");
507 			case 19 :
508 				pefree(entry.filename, entry.is_persistent);
509 				PHAR_ZIP_FAIL("unsupported compression method (IBM LZ77) used in this zip");
510 			case 97 :
511 				pefree(entry.filename, entry.is_persistent);
512 				PHAR_ZIP_FAIL("unsupported compression method (WavPack) used in this zip");
513 			case 98 :
514 				pefree(entry.filename, entry.is_persistent);
515 				PHAR_ZIP_FAIL("unsupported compression method (PPMd) used in this zip");
516 			default :
517 				pefree(entry.filename, entry.is_persistent);
518 				PHAR_ZIP_FAIL("unsupported compression method (unknown) used in this zip");
519 		}
520 
521 		/* get file metadata */
522 		if (PHAR_GET_16(zipentry.comment_len)) {
523 			if (PHAR_GET_16(zipentry.comment_len) != php_stream_read(fp, buf, PHAR_GET_16(zipentry.comment_len))) {
524 				pefree(entry.filename, entry.is_persistent);
525 				PHAR_ZIP_FAIL("unable to read in file comment, truncated");
526 			}
527 
528 			p = buf;
529 			entry.metadata_len = PHAR_GET_16(zipentry.comment_len);
530 
531 			if (phar_parse_metadata(&p, &(entry.metadata), PHAR_GET_16(zipentry.comment_len)) == FAILURE) {
532 				entry.metadata_len = 0;
533 				/* if not valid serialized data, it is a regular string */
534 
535 				ZVAL_NEW_STR(&entry.metadata, zend_string_init(buf, PHAR_GET_16(zipentry.comment_len), entry.is_persistent));
536 			}
537 		} else {
538 			ZVAL_UNDEF(&entry.metadata);
539 		}
540 
541 		if (!actual_alias && entry.filename_len == sizeof(".phar/alias.txt")-1 && !strncmp(entry.filename, ".phar/alias.txt", sizeof(".phar/alias.txt")-1)) {
542 			php_stream_filter *filter;
543 			zend_off_t saveloc;
544 			/* verify local file header */
545 			phar_zip_file_header local;
546 
547 			/* archive alias found */
548 			saveloc = php_stream_tell(fp);
549 			php_stream_seek(fp, PHAR_GET_32(zipentry.offset), SEEK_SET);
550 
551 			if (sizeof(local) != php_stream_read(fp, (char *) &local, sizeof(local))) {
552 				pefree(entry.filename, entry.is_persistent);
553 				PHAR_ZIP_FAIL("phar error: internal corruption of zip-based phar (cannot read local file header for alias)");
554 			}
555 
556 			/* verify local header */
557 			if (entry.filename_len != PHAR_GET_16(local.filename_len) || entry.crc32 != PHAR_GET_32(local.crc32) || entry.uncompressed_filesize != PHAR_GET_32(local.uncompsize) || entry.compressed_filesize != PHAR_GET_32(local.compsize)) {
558 				pefree(entry.filename, entry.is_persistent);
559 				PHAR_ZIP_FAIL("phar error: internal corruption of zip-based phar (local header of alias does not match central directory)");
560 			}
561 
562 			/* construct actual offset to file start - local extra_len can be different from central extra_len */
563 			entry.offset = entry.offset_abs =
564 				sizeof(local) + entry.header_offset + PHAR_GET_16(local.filename_len) + PHAR_GET_16(local.extra_len);
565 			php_stream_seek(fp, entry.offset, SEEK_SET);
566 			/* these next lines should be for php < 5.2.6 after 5.3 filters are fixed */
567 			fp->writepos = 0;
568 			fp->readpos = 0;
569 			php_stream_seek(fp, entry.offset, SEEK_SET);
570 			fp->writepos = 0;
571 			fp->readpos = 0;
572 			/* the above lines should be for php < 5.2.6 after 5.3 filters are fixed */
573 
574 			mydata->alias_len = entry.uncompressed_filesize;
575 			if (entry.flags & PHAR_ENT_COMPRESSED_GZ) {
576 				filter = php_stream_filter_create("zlib.inflate", NULL, php_stream_is_persistent(fp));
577 
578 				if (!filter) {
579 					pefree(entry.filename, entry.is_persistent);
580 					PHAR_ZIP_FAIL("unable to decompress alias, zlib filter creation failed");
581 				}
582 
583 				php_stream_filter_append(&fp->readfilters, filter);
584 
585 				// TODO: refactor to avoid reallocation ???
586 //???			entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &actual_alias, entry.uncompressed_filesize, 0)
587 				{
588 					zend_string *str = php_stream_copy_to_mem(fp, entry.uncompressed_filesize, 0);
589 					if (str) {
590 						entry.uncompressed_filesize = ZSTR_LEN(str);
591 						actual_alias = estrndup(ZSTR_VAL(str), ZSTR_LEN(str));
592 						zend_string_release_ex(str, 0);
593 					} else {
594 						actual_alias = NULL;
595 						entry.uncompressed_filesize = 0;
596 					}
597 				}
598 
599 				if (!entry.uncompressed_filesize || !actual_alias) {
600 					pefree(entry.filename, entry.is_persistent);
601 					PHAR_ZIP_FAIL("unable to read in alias, truncated");
602 				}
603 
604 				php_stream_filter_flush(filter, 1);
605 				php_stream_filter_remove(filter, 1);
606 
607 			} else if (entry.flags & PHAR_ENT_COMPRESSED_BZ2) {
608 				filter = php_stream_filter_create("bzip2.decompress", NULL, php_stream_is_persistent(fp));
609 
610 				if (!filter) {
611 					pefree(entry.filename, entry.is_persistent);
612 					PHAR_ZIP_FAIL("unable to read in alias, bzip2 filter creation failed");
613 				}
614 
615 				php_stream_filter_append(&fp->readfilters, filter);
616 
617 				// TODO: refactor to avoid reallocation ???
618 //???			entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &actual_alias, entry.uncompressed_filesize, 0)
619 				{
620 					zend_string *str = php_stream_copy_to_mem(fp, entry.uncompressed_filesize, 0);
621 					if (str) {
622 						entry.uncompressed_filesize = ZSTR_LEN(str);
623 						actual_alias = estrndup(ZSTR_VAL(str), ZSTR_LEN(str));
624 						zend_string_release_ex(str, 0);
625 					} else {
626 						actual_alias = NULL;
627 						entry.uncompressed_filesize = 0;
628 					}
629 				}
630 
631 				if (!entry.uncompressed_filesize || !actual_alias) {
632 					pefree(entry.filename, entry.is_persistent);
633 					PHAR_ZIP_FAIL("unable to read in alias, truncated");
634 				}
635 
636 				php_stream_filter_flush(filter, 1);
637 				php_stream_filter_remove(filter, 1);
638 			} else {
639 				// TODO: refactor to avoid reallocation ???
640 //???			entry.uncompressed_filesize = php_stream_copy_to_mem(fp, &actual_alias, entry.uncompressed_filesize, 0)
641 				{
642 					zend_string *str = php_stream_copy_to_mem(fp, entry.uncompressed_filesize, 0);
643 					if (str) {
644 						entry.uncompressed_filesize = ZSTR_LEN(str);
645 						actual_alias = estrndup(ZSTR_VAL(str), ZSTR_LEN(str));
646 						zend_string_release_ex(str, 0);
647 					} else {
648 						actual_alias = NULL;
649 						entry.uncompressed_filesize = 0;
650 					}
651 				}
652 
653 				if (!entry.uncompressed_filesize || !actual_alias) {
654 					pefree(entry.filename, entry.is_persistent);
655 					PHAR_ZIP_FAIL("unable to read in alias, truncated");
656 				}
657 			}
658 
659 			/* return to central directory parsing */
660 			php_stream_seek(fp, saveloc, SEEK_SET);
661 		}
662 
663 		phar_set_inode(&entry);
664 		zend_hash_str_add_mem(&mydata->manifest, entry.filename, entry.filename_len, (void *)&entry, sizeof(phar_entry_info));
665 	}
666 
667 	mydata->fp = fp;
668 
669 	if (zend_hash_str_exists(&(mydata->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1)) {
670 		mydata->is_data = 0;
671 	} else {
672 		mydata->is_data = 1;
673 	}
674 
675 	zend_hash_str_add_ptr(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len, mydata);
676 
677 	if (actual_alias) {
678 		phar_archive_data *fd_ptr;
679 
680 		if (!phar_validate_alias(actual_alias, mydata->alias_len)) {
681 			if (error) {
682 				spprintf(error, 4096, "phar error: invalid alias \"%s\" in zip-based phar \"%s\"", actual_alias, fname);
683 			}
684 			efree(actual_alias);
685 			zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len);
686 			return FAILURE;
687 		}
688 
689 		mydata->is_temporary_alias = 0;
690 
691 		if (NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), actual_alias, mydata->alias_len))) {
692 			if (SUCCESS != phar_free_alias(fd_ptr, actual_alias, mydata->alias_len)) {
693 				if (error) {
694 					spprintf(error, 4096, "phar error: Unable to add zip-based phar \"%s\" with implicit alias, alias is already in use", fname);
695 				}
696 				efree(actual_alias);
697 				zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len);
698 				return FAILURE;
699 			}
700 		}
701 
702 		mydata->alias = entry.is_persistent ? pestrndup(actual_alias, mydata->alias_len, 1) : actual_alias;
703 
704 		if (entry.is_persistent) {
705 			efree(actual_alias);
706 		}
707 
708 		zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), mydata->alias, mydata->alias_len, mydata);
709 	} else {
710 		phar_archive_data *fd_ptr;
711 
712 		if (alias_len) {
713 			if (NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len))) {
714 				if (SUCCESS != phar_free_alias(fd_ptr, alias, alias_len)) {
715 					if (error) {
716 						spprintf(error, 4096, "phar error: Unable to add zip-based phar \"%s\" with explicit alias, alias is already in use", fname);
717 					}
718 					zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len);
719 					return FAILURE;
720 				}
721 			}
722 
723 			zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), actual_alias, mydata->alias_len, mydata);
724 			mydata->alias = pestrndup(alias, alias_len, mydata->is_persistent);
725 			mydata->alias_len = alias_len;
726 		} else {
727 			mydata->alias = pestrndup(mydata->fname, fname_len, mydata->is_persistent);
728 			mydata->alias_len = fname_len;
729 		}
730 
731 		mydata->is_temporary_alias = 1;
732 	}
733 
734 	if (pphar) {
735 		*pphar = mydata;
736 	}
737 
738 	return SUCCESS;
739 }
740 /* }}} */
741 
742 /**
743  * Create or open a zip-based phar for writing
744  */
phar_open_or_create_zip(char * fname,size_t fname_len,char * alias,size_t alias_len,int is_data,uint32_t options,phar_archive_data ** pphar,char ** error)745 int phar_open_or_create_zip(char *fname, size_t fname_len, char *alias, size_t alias_len, int is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */
746 {
747 	phar_archive_data *phar;
748 	int ret = phar_create_or_parse_filename(fname, fname_len, alias, alias_len, is_data, options, &phar, error);
749 
750 	if (FAILURE == ret) {
751 		return FAILURE;
752 	}
753 
754 	if (pphar) {
755 		*pphar = phar;
756 	}
757 
758 	phar->is_data = is_data;
759 
760 	if (phar->is_zip) {
761 		return ret;
762 	}
763 
764 	if (phar->is_brandnew) {
765 		phar->internal_file_start = 0;
766 		phar->is_zip = 1;
767 		phar->is_tar = 0;
768 		return SUCCESS;
769 	}
770 
771 	/* we've reached here - the phar exists and is a regular phar */
772 	if (error) {
773 		spprintf(error, 4096, "phar zip error: phar \"%s\" already exists as a regular phar and must be deleted from disk prior to creating as a zip-based phar", fname);
774 	}
775 
776 	return FAILURE;
777 }
778 /* }}} */
779 
780 struct _phar_zip_pass {
781 	php_stream *filefp;
782 	php_stream *centralfp;
783 	php_stream *old;
784 	int free_fp;
785 	int free_ufp;
786 	char **error;
787 };
788 /* perform final modification of zip contents for each file in the manifest before saving */
phar_zip_changed_apply_int(phar_entry_info * entry,void * arg)789 static int phar_zip_changed_apply_int(phar_entry_info *entry, void *arg) /* {{{ */
790 {
791 	phar_zip_file_header local;
792 	phar_zip_unix3 perms;
793 	phar_zip_central_dir_file central;
794 	struct _phar_zip_pass *p;
795 	uint32_t newcrc32;
796 	zend_off_t offset;
797 	int not_really_modified = 0;
798 	p = (struct _phar_zip_pass*) arg;
799 
800 	if (entry->is_mounted) {
801 		return ZEND_HASH_APPLY_KEEP;
802 	}
803 
804 	if (entry->is_deleted) {
805 		if (entry->fp_refcount <= 0) {
806 			return ZEND_HASH_APPLY_REMOVE;
807 		} else {
808 			/* we can't delete this in-memory until it is closed */
809 			return ZEND_HASH_APPLY_KEEP;
810 		}
811 	}
812 
813 	phar_add_virtual_dirs(entry->phar, entry->filename, entry->filename_len);
814 	memset(&local, 0, sizeof(local));
815 	memset(&central, 0, sizeof(central));
816 	memset(&perms, 0, sizeof(perms));
817 	strncpy(local.signature, "PK\3\4", 4);
818 	strncpy(central.signature, "PK\1\2", 4);
819 	PHAR_SET_16(central.extra_len, sizeof(perms));
820 	PHAR_SET_16(local.extra_len, sizeof(perms));
821 	perms.tag[0] = 'n';
822 	perms.tag[1] = 'u';
823 	PHAR_SET_16(perms.size, sizeof(perms) - 4);
824 	PHAR_SET_16(perms.perms, entry->flags & PHAR_ENT_PERM_MASK);
825 	{
826 		uint32_t crc = (uint32_t) ~0;
827 		CRC32(crc, perms.perms[0]);
828 		CRC32(crc, perms.perms[1]);
829 		PHAR_SET_32(perms.crc32, ~crc);
830 	}
831 
832 	if (entry->flags & PHAR_ENT_COMPRESSED_GZ) {
833 		PHAR_SET_16(central.compressed, PHAR_ZIP_COMP_DEFLATE);
834 		PHAR_SET_16(local.compressed, PHAR_ZIP_COMP_DEFLATE);
835 	}
836 
837 	if (entry->flags & PHAR_ENT_COMPRESSED_BZ2) {
838 		PHAR_SET_16(central.compressed, PHAR_ZIP_COMP_BZIP2);
839 		PHAR_SET_16(local.compressed, PHAR_ZIP_COMP_BZIP2);
840 	}
841 
842 	/* do not use PHAR_GET_16 on either field of the next line */
843 	phar_zip_u2d_time(entry->timestamp, local.timestamp, local.datestamp);
844 	memcpy(central.timestamp, local.timestamp, sizeof(local.timestamp));
845 	memcpy(central.datestamp, local.datestamp, sizeof(local.datestamp));
846 	PHAR_SET_16(central.filename_len, entry->filename_len + (entry->is_dir ? 1 : 0));
847 	PHAR_SET_16(local.filename_len, entry->filename_len + (entry->is_dir ? 1 : 0));
848 	PHAR_SET_32(central.offset, php_stream_tell(p->filefp));
849 
850 	/* do extra field for perms later */
851 	if (entry->is_modified) {
852 		uint32_t loc;
853 		php_stream_filter *filter;
854 		php_stream *efp;
855 
856 		if (entry->is_dir) {
857 			entry->is_modified = 0;
858 			if (entry->fp_type == PHAR_MOD && entry->fp != entry->phar->fp && entry->fp != entry->phar->ufp) {
859 				php_stream_close(entry->fp);
860 				entry->fp = NULL;
861 				entry->fp_type = PHAR_FP;
862 			}
863 			goto continue_dir;
864 		}
865 
866 		if (FAILURE == phar_open_entry_fp(entry, p->error, 0)) {
867 			spprintf(p->error, 0, "unable to open file contents of file \"%s\" in zip-based phar \"%s\"", entry->filename, entry->phar->fname);
868 			return ZEND_HASH_APPLY_STOP;
869 		}
870 
871 		/* we can be modified and already be compressed, such as when chmod() is executed */
872 		if (entry->flags & PHAR_ENT_COMPRESSION_MASK && (entry->old_flags == entry->flags || !entry->old_flags)) {
873 			not_really_modified = 1;
874 			goto is_compressed;
875 		}
876 
877 		if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) {
878 			spprintf(p->error, 0, "unable to seek to start of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
879 			return ZEND_HASH_APPLY_STOP;
880 		}
881 
882 		efp = phar_get_efp(entry, 0);
883 		newcrc32 = ~0;
884 
885 		for (loc = 0;loc < entry->uncompressed_filesize; ++loc) {
886 			CRC32(newcrc32, php_stream_getc(efp));
887 		}
888 
889 		entry->crc32 = ~newcrc32;
890 		PHAR_SET_32(central.uncompsize, entry->uncompressed_filesize);
891 		PHAR_SET_32(local.uncompsize, entry->uncompressed_filesize);
892 
893 		if (!(entry->flags & PHAR_ENT_COMPRESSION_MASK)) {
894 			/* not compressed */
895 			entry->compressed_filesize = entry->uncompressed_filesize;
896 			PHAR_SET_32(central.compsize, entry->uncompressed_filesize);
897 			PHAR_SET_32(local.compsize, entry->uncompressed_filesize);
898 			goto not_compressed;
899 		}
900 
901 		filter = php_stream_filter_create(phar_compress_filter(entry, 0), NULL, 0);
902 
903 		if (!filter) {
904 			if (entry->flags & PHAR_ENT_COMPRESSED_GZ) {
905 				spprintf(p->error, 0, "unable to gzip compress file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
906 			} else {
907 				spprintf(p->error, 0, "unable to bzip2 compress file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
908 			}
909 			return ZEND_HASH_APPLY_STOP;
910 		}
911 
912 		/* create new file that holds the compressed version */
913 		/* work around inability to specify freedom in write and strictness
914 		in read count */
915 		entry->cfp = php_stream_fopen_tmpfile();
916 
917 		if (!entry->cfp) {
918 			spprintf(p->error, 0, "unable to create temporary file for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
919 			return ZEND_HASH_APPLY_STOP;
920 		}
921 
922 		php_stream_flush(efp);
923 
924 		if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) {
925 			spprintf(p->error, 0, "unable to seek to start of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
926 			return ZEND_HASH_APPLY_STOP;
927 		}
928 
929 		php_stream_filter_append((&entry->cfp->writefilters), filter);
930 
931 		if (SUCCESS != php_stream_copy_to_stream_ex(efp, entry->cfp, entry->uncompressed_filesize, NULL)) {
932 			spprintf(p->error, 0, "unable to copy compressed file contents of file \"%s\" while creating new phar \"%s\"", entry->filename, entry->phar->fname);
933 			return ZEND_HASH_APPLY_STOP;
934 		}
935 
936 		php_stream_filter_flush(filter, 1);
937 		php_stream_flush(entry->cfp);
938 		php_stream_filter_remove(filter, 1);
939 		php_stream_seek(entry->cfp, 0, SEEK_END);
940 		entry->compressed_filesize = (uint32_t) php_stream_tell(entry->cfp);
941 		PHAR_SET_32(central.compsize, entry->compressed_filesize);
942 		PHAR_SET_32(local.compsize, entry->compressed_filesize);
943 		/* generate crc on compressed file */
944 		php_stream_rewind(entry->cfp);
945 		entry->old_flags = entry->flags;
946 		entry->is_modified = 1;
947 	} else {
948 is_compressed:
949 		PHAR_SET_32(central.uncompsize, entry->uncompressed_filesize);
950 		PHAR_SET_32(local.uncompsize, entry->uncompressed_filesize);
951 		PHAR_SET_32(central.compsize, entry->compressed_filesize);
952 		PHAR_SET_32(local.compsize, entry->compressed_filesize);
953 		if (p->old) {
954 			if (-1 == php_stream_seek(p->old, entry->offset_abs, SEEK_SET)) {
955 				spprintf(p->error, 0, "unable to seek to start of file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
956 				return ZEND_HASH_APPLY_STOP;
957 			}
958 		}
959 	}
960 not_compressed:
961 	PHAR_SET_32(central.crc32, entry->crc32);
962 	PHAR_SET_32(local.crc32, entry->crc32);
963 continue_dir:
964 	/* set file metadata */
965 	if (Z_TYPE(entry->metadata) != IS_UNDEF) {
966 		php_serialize_data_t metadata_hash;
967 
968 		if (entry->metadata_str.s) {
969 			smart_str_free(&entry->metadata_str);
970 		}
971 		entry->metadata_str.s = NULL;
972 		PHP_VAR_SERIALIZE_INIT(metadata_hash);
973 		php_var_serialize(&entry->metadata_str, &entry->metadata, &metadata_hash);
974 		PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
975 		PHAR_SET_16(central.comment_len, ZSTR_LEN(entry->metadata_str.s));
976 	}
977 
978 	entry->header_offset = php_stream_tell(p->filefp);
979 	offset = entry->header_offset + sizeof(local) + entry->filename_len + (entry->is_dir ? 1 : 0) + sizeof(perms);
980 
981 	if (sizeof(local) != php_stream_write(p->filefp, (char *)&local, sizeof(local))) {
982 		spprintf(p->error, 0, "unable to write local file header of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
983 		return ZEND_HASH_APPLY_STOP;
984 	}
985 
986 	if (sizeof(central) != php_stream_write(p->centralfp, (char *)&central, sizeof(central))) {
987 		spprintf(p->error, 0, "unable to write central directory entry for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
988 		return ZEND_HASH_APPLY_STOP;
989 	}
990 
991 	if (entry->is_dir) {
992 		if (entry->filename_len != php_stream_write(p->filefp, entry->filename, entry->filename_len)) {
993 			spprintf(p->error, 0, "unable to write filename to local directory entry for directory \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
994 			return ZEND_HASH_APPLY_STOP;
995 		}
996 
997 		if (1 != php_stream_write(p->filefp, "/", 1)) {
998 			spprintf(p->error, 0, "unable to write filename to local directory entry for directory \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
999 			return ZEND_HASH_APPLY_STOP;
1000 		}
1001 
1002 		if (entry->filename_len != php_stream_write(p->centralfp, entry->filename, entry->filename_len)) {
1003 			spprintf(p->error, 0, "unable to write filename to central directory entry for directory \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1004 			return ZEND_HASH_APPLY_STOP;
1005 		}
1006 
1007 		if (1 != php_stream_write(p->centralfp, "/", 1)) {
1008 			spprintf(p->error, 0, "unable to write filename to central directory entry for directory \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1009 			return ZEND_HASH_APPLY_STOP;
1010 		}
1011 	} else {
1012 		if (entry->filename_len != php_stream_write(p->filefp, entry->filename, entry->filename_len)) {
1013 			spprintf(p->error, 0, "unable to write filename to local directory entry for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1014 			return ZEND_HASH_APPLY_STOP;
1015 		}
1016 
1017 		if (entry->filename_len != php_stream_write(p->centralfp, entry->filename, entry->filename_len)) {
1018 			spprintf(p->error, 0, "unable to write filename to central directory entry for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1019 			return ZEND_HASH_APPLY_STOP;
1020 		}
1021 	}
1022 
1023 	if (sizeof(perms) != php_stream_write(p->filefp, (char *)&perms, sizeof(perms))) {
1024 		spprintf(p->error, 0, "unable to write local extra permissions file header of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1025 		return ZEND_HASH_APPLY_STOP;
1026 	}
1027 
1028 	if (sizeof(perms) != php_stream_write(p->centralfp, (char *)&perms, sizeof(perms))) {
1029 		spprintf(p->error, 0, "unable to write central extra permissions file header of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1030 		return ZEND_HASH_APPLY_STOP;
1031 	}
1032 
1033 	if (!not_really_modified && entry->is_modified) {
1034 		if (entry->cfp) {
1035 			if (SUCCESS != php_stream_copy_to_stream_ex(entry->cfp, p->filefp, entry->compressed_filesize, NULL)) {
1036 				spprintf(p->error, 0, "unable to write compressed contents of file \"%s\" in zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1037 				return ZEND_HASH_APPLY_STOP;
1038 			}
1039 
1040 			php_stream_close(entry->cfp);
1041 			entry->cfp = NULL;
1042 		} else {
1043 			if (FAILURE == phar_open_entry_fp(entry, p->error, 0)) {
1044 				return ZEND_HASH_APPLY_STOP;
1045 			}
1046 
1047 			phar_seek_efp(entry, 0, SEEK_SET, 0, 0);
1048 
1049 			if (SUCCESS != php_stream_copy_to_stream_ex(phar_get_efp(entry, 0), p->filefp, entry->uncompressed_filesize, NULL)) {
1050 				spprintf(p->error, 0, "unable to write contents of file \"%s\" in zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1051 				return ZEND_HASH_APPLY_STOP;
1052 			}
1053 		}
1054 
1055 		if (entry->fp_type == PHAR_MOD && entry->fp != entry->phar->fp && entry->fp != entry->phar->ufp && entry->fp_refcount == 0) {
1056 			php_stream_close(entry->fp);
1057 		}
1058 
1059 		entry->is_modified = 0;
1060 	} else {
1061 		entry->is_modified = 0;
1062 		if (entry->fp_refcount) {
1063 			/* open file pointers refer to this fp, do not free the stream */
1064 			switch (entry->fp_type) {
1065 				case PHAR_FP:
1066 					p->free_fp = 0;
1067 					break;
1068 				case PHAR_UFP:
1069 					p->free_ufp = 0;
1070 				default:
1071 					break;
1072 			}
1073 		}
1074 
1075 		if (!entry->is_dir && entry->compressed_filesize && SUCCESS != php_stream_copy_to_stream_ex(p->old, p->filefp, entry->compressed_filesize, NULL)) {
1076 			spprintf(p->error, 0, "unable to copy contents of file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1077 			return ZEND_HASH_APPLY_STOP;
1078 		}
1079 	}
1080 
1081 	entry->fp = NULL;
1082 	entry->offset = entry->offset_abs = offset;
1083 	entry->fp_type = PHAR_FP;
1084 
1085 	if (entry->metadata_str.s) {
1086 		if (ZSTR_LEN(entry->metadata_str.s) != php_stream_write(p->centralfp, ZSTR_VAL(entry->metadata_str.s), ZSTR_LEN(entry->metadata_str.s))) {
1087 			spprintf(p->error, 0, "unable to write metadata as file comment for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname);
1088 			smart_str_free(&entry->metadata_str);
1089 			return ZEND_HASH_APPLY_STOP;
1090 		}
1091 
1092 		smart_str_free(&entry->metadata_str);
1093 	}
1094 
1095 	return ZEND_HASH_APPLY_KEEP;
1096 }
1097 /* }}} */
1098 
phar_zip_changed_apply(zval * zv,void * arg)1099 static int phar_zip_changed_apply(zval *zv, void *arg) /* {{{ */
1100 {
1101 	return phar_zip_changed_apply_int(Z_PTR_P(zv), arg);
1102 }
1103 /* }}} */
1104 
phar_zip_applysignature(phar_archive_data * phar,struct _phar_zip_pass * pass,smart_str * metadata)1105 static int phar_zip_applysignature(phar_archive_data *phar, struct _phar_zip_pass *pass,
1106 				   smart_str *metadata) /* {{{ */
1107 {
1108 	/* add signature for executable tars or tars explicitly set with setSignatureAlgorithm */
1109 	if (!phar->is_data || phar->sig_flags) {
1110 		size_t signature_length;
1111 		char *signature, sigbuf[8];
1112 		phar_entry_info entry = {0};
1113 		php_stream *newfile;
1114 		zend_off_t tell;
1115 
1116 		newfile = php_stream_fopen_tmpfile();
1117 		if (newfile == NULL) {
1118 			spprintf(pass->error, 0, "phar error: unable to create temporary file for the signature file");
1119 			return FAILURE;
1120 		}
1121 		tell = php_stream_tell(pass->filefp);
1122 		/* copy the local files, central directory, and the zip comment to generate the hash */
1123 		php_stream_seek(pass->filefp, 0, SEEK_SET);
1124 		php_stream_copy_to_stream_ex(pass->filefp, newfile, tell, NULL);
1125 		tell = php_stream_tell(pass->centralfp);
1126 		php_stream_seek(pass->centralfp, 0, SEEK_SET);
1127 		php_stream_copy_to_stream_ex(pass->centralfp, newfile, tell, NULL);
1128 		if (metadata->s) {
1129 			php_stream_write(newfile, ZSTR_VAL(metadata->s), ZSTR_LEN(metadata->s));
1130 		}
1131 
1132 		if (FAILURE == phar_create_signature(phar, newfile, &signature, &signature_length, pass->error)) {
1133 			if (pass->error) {
1134 				char *save = *(pass->error);
1135 				spprintf(pass->error, 0, "phar error: unable to write signature to zip-based phar: %s", save);
1136 				efree(save);
1137 			}
1138 
1139 			php_stream_close(newfile);
1140 			return FAILURE;
1141 		}
1142 
1143 		entry.filename = ".phar/signature.bin";
1144 		entry.filename_len = sizeof(".phar/signature.bin")-1;
1145 		entry.fp = php_stream_fopen_tmpfile();
1146 		entry.fp_type = PHAR_MOD;
1147 		entry.is_modified = 1;
1148 		if (entry.fp == NULL) {
1149 			spprintf(pass->error, 0, "phar error: unable to create temporary file for signature");
1150 			return FAILURE;
1151 		}
1152 
1153 		PHAR_SET_32(sigbuf, phar->sig_flags);
1154 		PHAR_SET_32(sigbuf + 4, signature_length);
1155 
1156 		if (Z_UL(8) != php_stream_write(entry.fp, sigbuf, 8) || signature_length != php_stream_write(entry.fp, signature, signature_length)) {
1157 			efree(signature);
1158 			if (pass->error) {
1159 				spprintf(pass->error, 0, "phar error: unable to write signature to zip-based phar %s", phar->fname);
1160 			}
1161 
1162 			php_stream_close(newfile);
1163 			return FAILURE;
1164 		}
1165 
1166 		efree(signature);
1167 		entry.uncompressed_filesize = entry.compressed_filesize = signature_length + 8;
1168 		entry.phar = phar;
1169 		/* throw out return value and write the signature */
1170 		phar_zip_changed_apply_int(&entry, (void *)pass);
1171 		php_stream_close(newfile);
1172 
1173 		if (pass->error && *(pass->error)) {
1174 			/* error is set by writeheaders */
1175 			return FAILURE;
1176 		}
1177 	} /* signature */
1178 	return SUCCESS;
1179 }
1180 /* }}} */
1181 
phar_zip_flush(phar_archive_data * phar,char * user_stub,zend_long len,int defaultstub,char ** error)1182 int phar_zip_flush(phar_archive_data *phar, char *user_stub, zend_long len, int defaultstub, char **error) /* {{{ */
1183 {
1184 	char *pos;
1185 	smart_str main_metadata_str = {0};
1186 	static const char newstub[] = "<?php // zip-based phar archive stub file\n__HALT_COMPILER();";
1187 	char halt_stub[] = "__HALT_COMPILER();";
1188 	char *tmp;
1189 
1190 	php_stream *stubfile, *oldfile;
1191 	php_serialize_data_t metadata_hash;
1192 	int free_user_stub, closeoldfile = 0;
1193 	phar_entry_info entry = {0};
1194 	char *temperr = NULL;
1195 	struct _phar_zip_pass pass;
1196 	phar_zip_dir_end eocd;
1197 	uint32_t cdir_size, cdir_offset;
1198 
1199 	pass.error = &temperr;
1200 	entry.flags = PHAR_ENT_PERM_DEF_FILE;
1201 	entry.timestamp = time(NULL);
1202 	entry.is_modified = 1;
1203 	entry.is_zip = 1;
1204 	entry.phar = phar;
1205 	entry.fp_type = PHAR_MOD;
1206 
1207 	if (phar->is_persistent) {
1208 		if (error) {
1209 			spprintf(error, 0, "internal error: attempt to flush cached zip-based phar \"%s\"", phar->fname);
1210 		}
1211 		return EOF;
1212 	}
1213 
1214 	if (phar->is_data) {
1215 		goto nostub;
1216 	}
1217 
1218 	/* set alias */
1219 	if (!phar->is_temporary_alias && phar->alias_len) {
1220 		entry.fp = php_stream_fopen_tmpfile();
1221 		if (entry.fp == NULL) {
1222 			spprintf(error, 0, "phar error: unable to create temporary file");
1223 			return EOF;
1224 		}
1225 		if (phar->alias_len != php_stream_write(entry.fp, phar->alias, phar->alias_len)) {
1226 			if (error) {
1227 				spprintf(error, 0, "unable to set alias in zip-based phar \"%s\"", phar->fname);
1228 			}
1229 			return EOF;
1230 		}
1231 
1232 		entry.uncompressed_filesize = entry.compressed_filesize = phar->alias_len;
1233 		entry.filename = estrndup(".phar/alias.txt", sizeof(".phar/alias.txt")-1);
1234 		entry.filename_len = sizeof(".phar/alias.txt")-1;
1235 
1236 		zend_hash_str_update_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info));
1237 	} else {
1238 		zend_hash_str_del(&phar->manifest, ".phar/alias.txt", sizeof(".phar/alias.txt")-1);
1239 	}
1240 
1241 	/* register alias */
1242 	if (phar->alias_len) {
1243 		if (FAILURE == phar_get_archive(&phar, phar->fname, phar->fname_len, phar->alias, phar->alias_len, error)) {
1244 			return EOF;
1245 		}
1246 	}
1247 
1248 	/* set stub */
1249 	if (user_stub && !defaultstub) {
1250 		if (len < 0) {
1251 			/* resource passed in */
1252 			if (!(php_stream_from_zval_no_verify(stubfile, (zval *)user_stub))) {
1253 				if (error) {
1254 					spprintf(error, 0, "unable to access resource to copy stub to new zip-based phar \"%s\"", phar->fname);
1255 				}
1256 				return EOF;
1257 			}
1258 
1259 			if (len == -1) {
1260 				len = PHP_STREAM_COPY_ALL;
1261 			} else {
1262 				len = -len;
1263 			}
1264 
1265 			user_stub = 0;
1266 
1267 			// TODO: refactor to avoid reallocation ???
1268 //???		len = php_stream_copy_to_mem(stubfile, &user_stub, len, 0)
1269 			{
1270 				zend_string *str = php_stream_copy_to_mem(stubfile, len, 0);
1271 				if (str) {
1272 					len = ZSTR_LEN(str);
1273 					user_stub = estrndup(ZSTR_VAL(str), ZSTR_LEN(str));
1274 					zend_string_release_ex(str, 0);
1275 				} else {
1276 					user_stub = NULL;
1277 					len = 0;
1278 				}
1279 			}
1280 
1281 			if (!len || !user_stub) {
1282 				if (error) {
1283 					spprintf(error, 0, "unable to read resource to copy stub to new zip-based phar \"%s\"", phar->fname);
1284 				}
1285 				return EOF;
1286 			}
1287 			free_user_stub = 1;
1288 		} else {
1289 			free_user_stub = 0;
1290 		}
1291 
1292 		tmp = estrndup(user_stub, len);
1293 		if ((pos = php_stristr(tmp, halt_stub, len, sizeof(halt_stub) - 1)) == NULL) {
1294 			efree(tmp);
1295 			if (error) {
1296 				spprintf(error, 0, "illegal stub for zip-based phar \"%s\"", phar->fname);
1297 			}
1298 			if (free_user_stub) {
1299 				efree(user_stub);
1300 			}
1301 			return EOF;
1302 		}
1303 		pos = user_stub + (pos - tmp);
1304 		efree(tmp);
1305 
1306 		len = pos - user_stub + 18;
1307 		entry.fp = php_stream_fopen_tmpfile();
1308 		if (entry.fp == NULL) {
1309 			spprintf(error, 0, "phar error: unable to create temporary file");
1310 			return EOF;
1311 		}
1312 		entry.uncompressed_filesize = len + 5;
1313 
1314 		if ((size_t)len != php_stream_write(entry.fp, user_stub, len)
1315 		||            5 != php_stream_write(entry.fp, " ?>\r\n", 5)) {
1316 			if (error) {
1317 				spprintf(error, 0, "unable to create stub from string in new zip-based phar \"%s\"", phar->fname);
1318 			}
1319 			if (free_user_stub) {
1320 				efree(user_stub);
1321 			}
1322 			php_stream_close(entry.fp);
1323 			return EOF;
1324 		}
1325 
1326 		entry.filename = estrndup(".phar/stub.php", sizeof(".phar/stub.php")-1);
1327 		entry.filename_len = sizeof(".phar/stub.php")-1;
1328 
1329 		zend_hash_str_update_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info));
1330 
1331 		if (free_user_stub) {
1332 			efree(user_stub);
1333 		}
1334 	} else {
1335 		/* Either this is a brand new phar (add the stub), or the default stub is required (overwrite the stub) */
1336 		entry.fp = php_stream_fopen_tmpfile();
1337 		if (entry.fp == NULL) {
1338 			spprintf(error, 0, "phar error: unable to create temporary file");
1339 			return EOF;
1340 		}
1341 		if (sizeof(newstub)-1 != php_stream_write(entry.fp, newstub, sizeof(newstub)-1)) {
1342 			php_stream_close(entry.fp);
1343 			if (error) {
1344 				spprintf(error, 0, "unable to %s stub in%szip-based phar \"%s\", failed", user_stub ? "overwrite" : "create", user_stub ? " " : " new ", phar->fname);
1345 			}
1346 			return EOF;
1347 		}
1348 
1349 		entry.uncompressed_filesize = entry.compressed_filesize = sizeof(newstub) - 1;
1350 		entry.filename = estrndup(".phar/stub.php", sizeof(".phar/stub.php")-1);
1351 		entry.filename_len = sizeof(".phar/stub.php")-1;
1352 
1353 		if (!defaultstub) {
1354 			if (!zend_hash_str_exists(&phar->manifest, ".phar/stub.php", sizeof(".phar/stub.php")-1)) {
1355 				if (NULL == zend_hash_str_add_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info))) {
1356 					php_stream_close(entry.fp);
1357 					efree(entry.filename);
1358 					if (error) {
1359 						spprintf(error, 0, "unable to create stub in zip-based phar \"%s\"", phar->fname);
1360 					}
1361 					return EOF;
1362 				}
1363 			} else {
1364 				php_stream_close(entry.fp);
1365 				efree(entry.filename);
1366 			}
1367 		} else {
1368 			zend_hash_str_update_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info));
1369 		}
1370 	}
1371 nostub:
1372 	if (phar->fp && !phar->is_brandnew) {
1373 		oldfile = phar->fp;
1374 		closeoldfile = 0;
1375 		php_stream_rewind(oldfile);
1376 	} else {
1377 		oldfile = php_stream_open_wrapper(phar->fname, "rb", 0, NULL);
1378 		closeoldfile = oldfile != NULL;
1379 	}
1380 
1381 	/* save modified files to the zip */
1382 	pass.old = oldfile;
1383 	pass.filefp = php_stream_fopen_tmpfile();
1384 
1385 	if (!pass.filefp) {
1386 fperror:
1387 		if (closeoldfile) {
1388 			php_stream_close(oldfile);
1389 		}
1390 		if (error) {
1391 			spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to open temporary file", phar->fname);
1392 		}
1393 		return EOF;
1394 	}
1395 
1396 	pass.centralfp = php_stream_fopen_tmpfile();
1397 
1398 	if (!pass.centralfp) {
1399 		goto fperror;
1400 	}
1401 
1402 	pass.free_fp = pass.free_ufp = 1;
1403 	memset(&eocd, 0, sizeof(eocd));
1404 
1405 	strncpy(eocd.signature, "PK\5\6", 4);
1406 	if (!phar->is_data && !phar->sig_flags) {
1407 		phar->sig_flags = PHAR_SIG_SHA1;
1408 	}
1409 	if (phar->sig_flags) {
1410 		PHAR_SET_16(eocd.counthere, zend_hash_num_elements(&phar->manifest) + 1);
1411 		PHAR_SET_16(eocd.count, zend_hash_num_elements(&phar->manifest) + 1);
1412 	} else {
1413 		PHAR_SET_16(eocd.counthere, zend_hash_num_elements(&phar->manifest));
1414 		PHAR_SET_16(eocd.count, zend_hash_num_elements(&phar->manifest));
1415 	}
1416 	zend_hash_apply_with_argument(&phar->manifest, phar_zip_changed_apply, (void *) &pass);
1417 
1418 	if (Z_TYPE(phar->metadata) != IS_UNDEF) {
1419 		/* set phar metadata */
1420 		PHP_VAR_SERIALIZE_INIT(metadata_hash);
1421 		php_var_serialize(&main_metadata_str, &phar->metadata, &metadata_hash);
1422 		PHP_VAR_SERIALIZE_DESTROY(metadata_hash);
1423 	}
1424 	if (temperr) {
1425 		if (error) {
1426 			spprintf(error, 4096, "phar zip flush of \"%s\" failed: %s", phar->fname, temperr);
1427 		}
1428 		efree(temperr);
1429 temperror:
1430 		php_stream_close(pass.centralfp);
1431 nocentralerror:
1432 		if (Z_TYPE(phar->metadata) != IS_UNDEF) {
1433 			smart_str_free(&main_metadata_str);
1434 		}
1435 		php_stream_close(pass.filefp);
1436 		if (closeoldfile) {
1437 			php_stream_close(oldfile);
1438 		}
1439 		return EOF;
1440 	}
1441 
1442 	if (FAILURE == phar_zip_applysignature(phar, &pass, &main_metadata_str)) {
1443 		goto temperror;
1444 	}
1445 
1446 	/* save zip */
1447 	cdir_size = php_stream_tell(pass.centralfp);
1448 	cdir_offset = php_stream_tell(pass.filefp);
1449 	PHAR_SET_32(eocd.cdir_size, cdir_size);
1450 	PHAR_SET_32(eocd.cdir_offset, cdir_offset);
1451 	php_stream_seek(pass.centralfp, 0, SEEK_SET);
1452 
1453 	{
1454 		size_t clen;
1455 		int ret = php_stream_copy_to_stream_ex(pass.centralfp, pass.filefp, PHP_STREAM_COPY_ALL, &clen);
1456 		if (SUCCESS != ret || clen != cdir_size) {
1457 			if (error) {
1458 				spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write central-directory", phar->fname);
1459 			}
1460 			goto temperror;
1461 		}
1462 	}
1463 
1464 	php_stream_close(pass.centralfp);
1465 
1466 	if (Z_TYPE(phar->metadata) != IS_UNDEF) {
1467 		/* set phar metadata */
1468 		PHAR_SET_16(eocd.comment_len, ZSTR_LEN(main_metadata_str.s));
1469 
1470 		if (sizeof(eocd) != php_stream_write(pass.filefp, (char *)&eocd, sizeof(eocd))) {
1471 			if (error) {
1472 				spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write end of central-directory", phar->fname);
1473 			}
1474 			goto nocentralerror;
1475 		}
1476 
1477 		if (ZSTR_LEN(main_metadata_str.s) != php_stream_write(pass.filefp, ZSTR_VAL(main_metadata_str.s), ZSTR_LEN(main_metadata_str.s))) {
1478 			if (error) {
1479 				spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write metadata to zip comment", phar->fname);
1480 			}
1481 			goto nocentralerror;
1482 		}
1483 
1484 		smart_str_free(&main_metadata_str);
1485 
1486 	} else {
1487 		if (sizeof(eocd) != php_stream_write(pass.filefp, (char *)&eocd, sizeof(eocd))) {
1488 			if (error) {
1489 				spprintf(error, 4096, "phar zip flush of \"%s\" failed: unable to write end of central-directory", phar->fname);
1490 			}
1491 			goto nocentralerror;
1492 		}
1493 	}
1494 
1495 	if (phar->fp && pass.free_fp) {
1496 		php_stream_close(phar->fp);
1497 	}
1498 
1499 	if (phar->ufp) {
1500 		if (pass.free_ufp) {
1501 			php_stream_close(phar->ufp);
1502 		}
1503 		phar->ufp = NULL;
1504 	}
1505 
1506 	/* re-open */
1507 	phar->is_brandnew = 0;
1508 
1509 	if (phar->donotflush) {
1510 		/* deferred flush */
1511 		phar->fp = pass.filefp;
1512 	} else {
1513 		phar->fp = php_stream_open_wrapper(phar->fname, "w+b", IGNORE_URL|STREAM_MUST_SEEK|REPORT_ERRORS, NULL);
1514 		if (!phar->fp) {
1515 			if (closeoldfile) {
1516 				php_stream_close(oldfile);
1517 			}
1518 			phar->fp = pass.filefp;
1519 			if (error) {
1520 				spprintf(error, 4096, "unable to open new phar \"%s\" for writing", phar->fname);
1521 			}
1522 			return EOF;
1523 		}
1524 		php_stream_rewind(pass.filefp);
1525 		php_stream_copy_to_stream_ex(pass.filefp, phar->fp, PHP_STREAM_COPY_ALL, NULL);
1526 		/* we could also reopen the file in "rb" mode but there is no need for that */
1527 		php_stream_close(pass.filefp);
1528 	}
1529 
1530 	if (closeoldfile) {
1531 		php_stream_close(oldfile);
1532 	}
1533 	return EOF;
1534 }
1535 /* }}} */
1536 
1537 /*
1538  * Local variables:
1539  * tab-width: 4
1540  * c-basic-offset: 4
1541  * End:
1542  * vim600: noet sw=4 ts=4 fdm=marker
1543  * vim<600: noet sw=4 ts=4
1544  */
1545