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