xref: /PHP-8.2/ext/zip/zip_stream.c (revision 2223853c)
1 /*
2   +----------------------------------------------------------------------+
3   | Copyright (c) The PHP Group                                          |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | https://www.php.net/license/3_01.txt                                 |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Author: Piere-Alain Joye <pierre@php.net>                            |
14   +----------------------------------------------------------------------+
15 */
16 
17 #ifdef HAVE_CONFIG_H
18 #   include "config.h"
19 #endif
20 #include "php.h"
21 #ifdef HAVE_ZIP
22 
23 #include "php_streams.h"
24 #include "ext/standard/file.h"
25 #include "ext/standard/php_string.h"
26 #include "fopen_wrappers.h"
27 #include "php_zip.h"
28 
29 #include "ext/standard/url.h"
30 
31 /* needed for ssize_t definition */
32 #include <sys/types.h>
33 
34 struct php_zip_stream_data_t {
35 	struct zip *za;
36 	struct zip_file *zf;
37 	size_t cursor;
38 	php_stream *stream;
39 };
40 
41 #define STREAM_DATA_FROM_STREAM() \
42 	struct php_zip_stream_data_t *self = (struct php_zip_stream_data_t *) stream->abstract;
43 
44 
45 /* {{{ php_zip_ops_read */
php_zip_ops_read(php_stream * stream,char * buf,size_t count)46 static ssize_t php_zip_ops_read(php_stream *stream, char *buf, size_t count)
47 {
48 	ssize_t n = 0;
49 	STREAM_DATA_FROM_STREAM();
50 
51 	if (self->zf) {
52 		n = zip_fread(self->zf, buf, count);
53 		if (n < 0) {
54 #if LIBZIP_VERSION_MAJOR < 1
55 			int ze, se;
56 			zip_file_error_get(self->zf, &ze, &se);
57 			stream->eof = 1;
58 			php_error_docref(NULL, E_WARNING, "Zip stream error: %s", zip_file_strerror(self->zf));
59 #else
60 			zip_error_t *err;
61 			err = zip_file_get_error(self->zf);
62 			stream->eof = 1;
63 			php_error_docref(NULL, E_WARNING, "Zip stream error: %s", zip_error_strerror(err));
64 			zip_error_fini(err);
65 #endif
66 			return -1;
67 		}
68 		/* cast count to signed value to avoid possibly negative n
69 		 * being cast to unsigned value */
70 		if (n == 0 || n < (ssize_t)count) {
71 			stream->eof = 1;
72 		} else {
73 			self->cursor += n;
74 		}
75 	}
76 	return n;
77 }
78 /* }}} */
79 
80 /* {{{ php_zip_ops_write */
php_zip_ops_write(php_stream * stream,const char * buf,size_t count)81 static ssize_t php_zip_ops_write(php_stream *stream, const char *buf, size_t count)
82 {
83 	if (!stream) {
84 		return -1;
85 	}
86 
87 	return count;
88 }
89 /* }}} */
90 
91 /* {{{ php_zip_ops_close */
php_zip_ops_close(php_stream * stream,int close_handle)92 static int php_zip_ops_close(php_stream *stream, int close_handle)
93 {
94 	STREAM_DATA_FROM_STREAM();
95 	if (close_handle) {
96 		if (self->zf) {
97 			zip_fclose(self->zf);
98 			self->zf = NULL;
99 		}
100 
101 		if (self->za) {
102 			zip_close(self->za);
103 			self->za = NULL;
104 		}
105 	}
106 	efree(self);
107 	stream->abstract = NULL;
108 	return EOF;
109 }
110 /* }}} */
111 
112 /* {{{ php_zip_ops_flush */
php_zip_ops_flush(php_stream * stream)113 static int php_zip_ops_flush(php_stream *stream)
114 {
115 	if (!stream) {
116 		return 0;
117 	}
118 
119 	return 0;
120 }
121 /* }}} */
122 
php_zip_ops_stat(php_stream * stream,php_stream_statbuf * ssb)123 static int php_zip_ops_stat(php_stream *stream, php_stream_statbuf *ssb) /* {{{ */
124 {
125 	struct zip_stat sb;
126 	const char *path = stream->orig_path;
127 	size_t path_len = strlen(stream->orig_path);
128 	char file_dirname[MAXPATHLEN];
129 	struct zip *za;
130 	char *fragment;
131 	size_t fragment_len;
132 	int err;
133 	zend_string *file_basename;
134 
135 	fragment = strchr(path, '#');
136 	if (!fragment) {
137 		return -1;
138 	}
139 
140 
141 	if (strncasecmp("zip://", path, 6) == 0) {
142 		path += 6;
143 	}
144 
145 	fragment_len = strlen(fragment);
146 
147 	if (fragment_len < 1) {
148 		return -1;
149 	}
150 	path_len = strlen(path);
151 	if (path_len >= MAXPATHLEN) {
152 		return -1;
153 	}
154 
155 	memcpy(file_dirname, path, path_len - fragment_len);
156 	file_dirname[path_len - fragment_len] = '\0';
157 
158 	file_basename = php_basename((char *)path, path_len - fragment_len, NULL, 0);
159 	fragment++;
160 
161 	if (ZIP_OPENBASEDIR_CHECKPATH(file_dirname)) {
162 		zend_string_release_ex(file_basename, 0);
163 		return -1;
164 	}
165 
166 	za = zip_open(file_dirname, ZIP_CREATE, &err);
167 	if (za) {
168 		memset(ssb, 0, sizeof(php_stream_statbuf));
169 		if (zip_stat(za, fragment, ZIP_FL_NOCASE, &sb) != 0) {
170 			zip_close(za);
171 			zend_string_release_ex(file_basename, 0);
172 			return -1;
173 		}
174 		zip_close(za);
175 
176 		if (path[path_len-1] != '/') {
177 			ssb->sb.st_size = sb.size;
178 			ssb->sb.st_mode |= S_IFREG; /* regular file */
179 		} else {
180 			ssb->sb.st_size = 0;
181 			ssb->sb.st_mode |= S_IFDIR; /* regular directory */
182 		}
183 
184 		ssb->sb.st_mtime = sb.mtime;
185 		ssb->sb.st_atime = sb.mtime;
186 		ssb->sb.st_ctime = sb.mtime;
187 		ssb->sb.st_nlink = 1;
188 		ssb->sb.st_rdev = -1;
189 #ifndef PHP_WIN32
190 		ssb->sb.st_blksize = -1;
191 		ssb->sb.st_blocks = -1;
192 #endif
193 		ssb->sb.st_ino = -1;
194 	}
195 	zend_string_release_ex(file_basename, 0);
196 	return 0;
197 }
198 /* }}} */
199 
200 #if LIBZIP_ATLEAST(1,9,1)
201 /* {{{ php_zip_ops_seek */
php_zip_ops_seek(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffset)202 static int php_zip_ops_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset)
203 {
204 	int ret = -1;
205 	STREAM_DATA_FROM_STREAM();
206 
207 	if (self->zf) {
208 		ret = zip_fseek(self->zf, offset, whence);
209 		*newoffset = zip_ftell(self->zf);
210 	}
211 
212 	return ret;
213 }
214 /* }}} */
215 
216 /* with seek command */
217 const php_stream_ops php_stream_zipio_seek_ops = {
218 	php_zip_ops_write, php_zip_ops_read,
219 	php_zip_ops_close, php_zip_ops_flush,
220 	"zip",
221 	php_zip_ops_seek, /* seek */
222 	NULL, /* cast */
223 	php_zip_ops_stat, /* stat */
224 	NULL  /* set_option */
225 };
226 #endif
227 
228 /* without seek command */
229 const php_stream_ops php_stream_zipio_ops = {
230 	php_zip_ops_write, php_zip_ops_read,
231 	php_zip_ops_close, php_zip_ops_flush,
232 	"zip",
233 	NULL, /* seek */
234 	NULL, /* cast */
235 	php_zip_ops_stat, /* stat */
236 	NULL  /* set_option */
237 };
238 
239 /* {{{ php_stream_zip_open */
php_stream_zip_open(struct zip * arch,struct zip_stat * sb,const char * mode,zip_flags_t flags STREAMS_DC)240 php_stream *php_stream_zip_open(struct zip *arch, struct zip_stat *sb, const char *mode, zip_flags_t flags STREAMS_DC)
241 {
242 	struct zip_file *zf = NULL;
243 
244 	php_stream *stream = NULL;
245 	struct php_zip_stream_data_t *self;
246 
247 	if (strncmp(mode,"r", strlen("r")) != 0) {
248 		return NULL;
249 	}
250 
251 	if (arch) {
252 		zf = zip_fopen_index(arch, sb->index, flags);
253 		if (zf) {
254 			self = emalloc(sizeof(*self));
255 
256 			self->za = NULL; /* to keep it open on stream close */
257 			self->zf = zf;
258 			self->stream = NULL;
259 			self->cursor = 0;
260 #if LIBZIP_ATLEAST(1,9,1)
261 			if (zip_file_is_seekable(zf) > 0) {
262 				stream = php_stream_alloc(&php_stream_zipio_seek_ops, self, NULL, mode);
263 			} else
264 #endif
265 			{
266 				stream = php_stream_alloc(&php_stream_zipio_ops, self, NULL, mode);
267 			}
268 			stream->orig_path = estrdup(sb->name);
269 		}
270 	}
271 
272 	return stream;
273 }
274 /* }}} */
275 
276 /* {{{ php_stream_zip_opener */
php_stream_zip_opener(php_stream_wrapper * wrapper,const char * path,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)277 php_stream *php_stream_zip_opener(php_stream_wrapper *wrapper,
278 											const char *path,
279 											const char *mode,
280 											int options,
281 											zend_string **opened_path,
282 											php_stream_context *context STREAMS_DC)
283 {
284 	size_t path_len;
285 
286 	zend_string *file_basename;
287 	char file_dirname[MAXPATHLEN];
288 
289 	struct zip *za;
290 	struct zip_file *zf = NULL;
291 	char *fragment;
292 	size_t fragment_len;
293 	int err;
294 
295 	php_stream *stream = NULL;
296 	struct php_zip_stream_data_t *self;
297 
298 	fragment = strchr(path, '#');
299 	if (!fragment) {
300 		return NULL;
301 	}
302 
303 	if (strncasecmp("zip://", path, 6) == 0) {
304 		path += 6;
305 	}
306 
307 	fragment_len = strlen(fragment);
308 
309 	if (fragment_len < 1) {
310 		return NULL;
311 	}
312 	path_len = strlen(path);
313 	if (path_len >= MAXPATHLEN || mode[0] != 'r') {
314 		return NULL;
315 	}
316 
317 	memcpy(file_dirname, path, path_len - fragment_len);
318 	file_dirname[path_len - fragment_len] = '\0';
319 
320 	file_basename = php_basename(path, path_len - fragment_len, NULL, 0);
321 	fragment++;
322 
323 	if (ZIP_OPENBASEDIR_CHECKPATH(file_dirname)) {
324 		zend_string_release_ex(file_basename, 0);
325 		return NULL;
326 	}
327 
328 	za = zip_open(file_dirname, ZIP_CREATE, &err);
329 	if (za) {
330 		zval *tmpzval;
331 
332 		if (context && NULL != (tmpzval = php_stream_context_get_option(context, "zip", "password"))) {
333 			if (Z_TYPE_P(tmpzval) != IS_STRING || zip_set_default_password(za, Z_STRVAL_P(tmpzval))) {
334 				php_error_docref(NULL, E_WARNING, "Can't set zip password");
335 			}
336 		}
337 
338 		zf = zip_fopen(za, fragment, 0);
339 		if (zf) {
340 			self = emalloc(sizeof(*self));
341 
342 			self->za = za;
343 			self->zf = zf;
344 			self->stream = NULL;
345 			self->cursor = 0;
346 #if LIBZIP_ATLEAST(1,9,1)
347 			if (zip_file_is_seekable(zf) > 0) {
348 				stream = php_stream_alloc(&php_stream_zipio_seek_ops, self, NULL, mode);
349 			} else
350 #endif
351 			{
352 				stream = php_stream_alloc(&php_stream_zipio_ops, self, NULL, mode);
353 			}
354 
355 			if (opened_path) {
356 				*opened_path = zend_string_init(path, strlen(path), 0);
357 			}
358 		} else {
359 			zip_close(za);
360 		}
361 	}
362 
363 	zend_string_release_ex(file_basename, 0);
364 
365 	if (!stream) {
366 		return NULL;
367 	} else {
368 		return stream;
369 	}
370 }
371 /* }}} */
372 
373 static const php_stream_wrapper_ops zip_stream_wops = {
374 	php_stream_zip_opener,
375 	NULL,	/* close */
376 	NULL,	/* fstat */
377 	NULL,	/* stat */
378 	NULL,	/* opendir */
379 	"zip wrapper",
380 	NULL,	/* unlink */
381 	NULL,	/* rename */
382 	NULL,	/* mkdir */
383 	NULL,	/* rmdir */
384 	NULL	/* metadata */
385 };
386 
387 const php_stream_wrapper php_stream_zip_wrapper = {
388 	&zip_stream_wops,
389 	NULL,
390 	0 /* is_url */
391 };
392 #endif /* HAVE_ZIP */
393