xref: /PHP-8.2/main/streams/memory.c (revision 93021c63)
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: Marcus Boerger <helly@php.net>                               |
14    +----------------------------------------------------------------------+
15  */
16 
17 #ifndef _GNU_SOURCE
18 # define _GNU_SOURCE
19 #endif
20 #include "php.h"
21 #include "ext/standard/base64.h"
22 
23 PHPAPI size_t php_url_decode(char *str, size_t len);
24 
25 /* Memory streams use a dynamic memory buffer to emulate a stream.
26  * You can use php_stream_memory_open to create a readonly stream
27  * from an existing memory buffer.
28  */
29 
30 /* Temp streams are streams that uses memory streams as long their
31  * size is less than a given memory amount. When a write operation
32  * exceeds that limit the content is written to a temporary file.
33  */
34 
35 /* {{{ ------- MEMORY stream implementation -------*/
36 
37 typedef struct {
38 	zend_string *data;
39 	size_t      fpos;
40 	int			mode;
41 } php_stream_memory_data;
42 
43 
44 /* {{{ */
php_stream_memory_write(php_stream * stream,const char * buf,size_t count)45 static ssize_t php_stream_memory_write(php_stream *stream, const char *buf, size_t count)
46 {
47 	php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
48 	assert(ms != NULL);
49 
50 	if (ms->mode & TEMP_STREAM_READONLY) {
51 		return (ssize_t) -1;
52 	} else if (ms->mode & TEMP_STREAM_APPEND) {
53 		ms->fpos = ZSTR_LEN(ms->data);
54 	}
55 	if (ms->fpos + count > ZSTR_LEN(ms->data)) {
56 		ms->data = zend_string_realloc(ms->data, ms->fpos + count, 0);
57 	} else {
58 		ms->data = zend_string_separate(ms->data, 0);
59 	}
60 	if (count) {
61 		ZEND_ASSERT(buf != NULL);
62 		memcpy(ZSTR_VAL(ms->data) + ms->fpos, (char*) buf, count);
63 		ZSTR_VAL(ms->data)[ZSTR_LEN(ms->data)] = '\0';
64 		ms->fpos += count;
65 	}
66 	return count;
67 }
68 /* }}} */
69 
70 
71 /* {{{ */
php_stream_memory_read(php_stream * stream,char * buf,size_t count)72 static ssize_t php_stream_memory_read(php_stream *stream, char *buf, size_t count)
73 {
74 	php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
75 	assert(ms != NULL);
76 
77 	if (ms->fpos == ZSTR_LEN(ms->data)) {
78 		stream->eof = 1;
79 		count = 0;
80 	} else {
81 		if (ms->fpos + count > ZSTR_LEN(ms->data)) {
82 			count = ZSTR_LEN(ms->data) - ms->fpos;
83 		}
84 		if (count) {
85 			ZEND_ASSERT(buf != NULL);
86 			memcpy(buf, ZSTR_VAL(ms->data) + ms->fpos, count);
87 			ms->fpos += count;
88 		}
89 	}
90 	return count;
91 }
92 /* }}} */
93 
94 
95 /* {{{ */
php_stream_memory_close(php_stream * stream,int close_handle)96 static int php_stream_memory_close(php_stream *stream, int close_handle)
97 {
98 	php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
99 	ZEND_ASSERT(ms != NULL);
100 	zend_string_release(ms->data);
101 	efree(ms);
102 	return 0;
103 }
104 /* }}} */
105 
106 
107 /* {{{ */
php_stream_memory_flush(php_stream * stream)108 static int php_stream_memory_flush(php_stream *stream)
109 {
110 	/* nothing to do here */
111 	return 0;
112 }
113 /* }}} */
114 
115 
116 /* {{{ */
php_stream_memory_seek(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffs)117 static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
118 {
119 	php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
120 	assert(ms != NULL);
121 
122 	switch(whence) {
123 		case SEEK_CUR:
124 			if (offset < 0) {
125 				if (ms->fpos < (size_t)(-offset)) {
126 					ms->fpos = 0;
127 					*newoffs = -1;
128 					return -1;
129 				} else {
130 					ms->fpos = ms->fpos + offset;
131 					*newoffs = ms->fpos;
132 					stream->eof = 0;
133 					return 0;
134 				}
135 			} else {
136 				if (ms->fpos + (size_t)(offset) > ZSTR_LEN(ms->data)) {
137 					ms->fpos = ZSTR_LEN(ms->data);
138 					*newoffs = -1;
139 					return -1;
140 				} else {
141 					ms->fpos = ms->fpos + offset;
142 					*newoffs = ms->fpos;
143 					stream->eof = 0;
144 					return 0;
145 				}
146 			}
147 		case SEEK_SET:
148 			if (ZSTR_LEN(ms->data) < (size_t)(offset)) {
149 				ms->fpos = ZSTR_LEN(ms->data);
150 				*newoffs = -1;
151 				return -1;
152 			} else {
153 				ms->fpos = offset;
154 				*newoffs = ms->fpos;
155 				stream->eof = 0;
156 				return 0;
157 			}
158 		case SEEK_END:
159 			if (offset > 0) {
160 				ms->fpos = ZSTR_LEN(ms->data);
161 				*newoffs = -1;
162 				return -1;
163 			} else if (ZSTR_LEN(ms->data) < (size_t)(-offset)) {
164 				ms->fpos = 0;
165 				*newoffs = -1;
166 				return -1;
167 			} else {
168 				ms->fpos = ZSTR_LEN(ms->data) + offset;
169 				*newoffs = ms->fpos;
170 				stream->eof = 0;
171 				return 0;
172 			}
173 		default:
174 			*newoffs = ms->fpos;
175 			return -1;
176 	}
177 }
178 /* }}} */
179 
180 /* {{{ */
php_stream_memory_cast(php_stream * stream,int castas,void ** ret)181 static int php_stream_memory_cast(php_stream *stream, int castas, void **ret)
182 {
183 	return FAILURE;
184 }
185 /* }}} */
186 
php_stream_memory_stat(php_stream * stream,php_stream_statbuf * ssb)187 static int php_stream_memory_stat(php_stream *stream, php_stream_statbuf *ssb) /* {{{ */
188 {
189 	time_t timestamp = 0;
190 	php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
191 	assert(ms != NULL);
192 
193 	memset(ssb, 0, sizeof(php_stream_statbuf));
194 	/* read-only across the board */
195 
196 	ssb->sb.st_mode = ms->mode & TEMP_STREAM_READONLY ? 0444 : 0666;
197 
198 	ssb->sb.st_size = ZSTR_LEN(ms->data);
199 	ssb->sb.st_mode |= S_IFREG; /* regular file */
200 	ssb->sb.st_mtime = timestamp;
201 	ssb->sb.st_atime = timestamp;
202 	ssb->sb.st_ctime = timestamp;
203 	ssb->sb.st_nlink = 1;
204 	ssb->sb.st_rdev = -1;
205 	/* this is only for APC, so use /dev/null device - no chance of conflict there! */
206 	ssb->sb.st_dev = 0xC;
207 	/* generate unique inode number for alias/filename, so no phars will conflict */
208 	ssb->sb.st_ino = 0;
209 
210 #ifndef PHP_WIN32
211 	ssb->sb.st_blksize = -1;
212 	ssb->sb.st_blocks = -1;
213 #endif
214 
215 	return 0;
216 }
217 /* }}} */
218 
php_stream_memory_set_option(php_stream * stream,int option,int value,void * ptrparam)219 static int php_stream_memory_set_option(php_stream *stream, int option, int value, void *ptrparam) /* {{{ */
220 {
221 	php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
222 	size_t newsize;
223 
224 	switch(option) {
225 		case PHP_STREAM_OPTION_TRUNCATE_API:
226 			switch (value) {
227 				case PHP_STREAM_TRUNCATE_SUPPORTED:
228 					return PHP_STREAM_OPTION_RETURN_OK;
229 
230 				case PHP_STREAM_TRUNCATE_SET_SIZE:
231 					if (ms->mode & TEMP_STREAM_READONLY) {
232 						return PHP_STREAM_OPTION_RETURN_ERR;
233 					}
234 					newsize = *(size_t*)ptrparam;
235 					if (newsize <= ZSTR_LEN(ms->data)) {
236 						ms->data = zend_string_truncate(ms->data, newsize, 0);
237 						if (newsize < ms->fpos) {
238 							ms->fpos = newsize;
239 						}
240 					} else {
241 						size_t old_size = ZSTR_LEN(ms->data);
242 						ms->data = zend_string_realloc(ms->data, newsize, 0);
243 						memset(ZSTR_VAL(ms->data) + old_size, 0, newsize - old_size);
244 						ZSTR_VAL(ms->data)[ZSTR_LEN(ms->data)] = '\0';
245 					}
246 					return PHP_STREAM_OPTION_RETURN_OK;
247 			}
248 	}
249 
250 	return PHP_STREAM_OPTION_RETURN_NOTIMPL;
251 }
252 /* }}} */
253 
254 PHPAPI const php_stream_ops	php_stream_memory_ops = {
255 	php_stream_memory_write, php_stream_memory_read,
256 	php_stream_memory_close, php_stream_memory_flush,
257 	"MEMORY",
258 	php_stream_memory_seek,
259 	php_stream_memory_cast,
260 	php_stream_memory_stat,
261 	php_stream_memory_set_option
262 };
263 
264 /* {{{ */
php_stream_mode_from_str(const char * mode)265 PHPAPI int php_stream_mode_from_str(const char *mode)
266 {
267 	if (strpbrk(mode, "a")) {
268 		return TEMP_STREAM_APPEND;
269 	} else if (strpbrk(mode, "w+")) {
270 		return TEMP_STREAM_DEFAULT;
271 	}
272 	return TEMP_STREAM_READONLY;
273 }
274 /* }}} */
275 
276 /* {{{ */
_php_stream_mode_to_str(int mode)277 PHPAPI const char *_php_stream_mode_to_str(int mode)
278 {
279 	if (mode == TEMP_STREAM_READONLY) {
280 		return "rb";
281 	} else if (mode == TEMP_STREAM_APPEND) {
282 		return "a+b";
283 	}
284 	return "w+b";
285 }
286 /* }}} */
287 
288 /* {{{ */
_php_stream_memory_create(int mode STREAMS_DC)289 PHPAPI php_stream *_php_stream_memory_create(int mode STREAMS_DC)
290 {
291 	php_stream_memory_data *self;
292 	php_stream *stream;
293 
294 	self = emalloc(sizeof(*self));
295 	self->data = ZSTR_EMPTY_ALLOC();
296 	self->fpos = 0;
297 	self->mode = mode;
298 
299 	stream = php_stream_alloc_rel(&php_stream_memory_ops, self, 0, _php_stream_mode_to_str(mode));
300 	stream->flags |= PHP_STREAM_FLAG_NO_BUFFER;
301 	return stream;
302 }
303 /* }}} */
304 
305 
306 /* {{{ */
_php_stream_memory_open(int mode,zend_string * buf STREAMS_DC)307 PHPAPI php_stream *_php_stream_memory_open(int mode, zend_string *buf STREAMS_DC)
308 {
309 	php_stream *stream;
310 	php_stream_memory_data *ms;
311 
312 	if ((stream = php_stream_memory_create_rel(mode)) != NULL) {
313 		ms = (php_stream_memory_data*)stream->abstract;
314 		ms->data = zend_string_copy(buf);
315 	}
316 	return stream;
317 }
318 /* }}} */
319 
320 
321 /* {{{ */
_php_stream_memory_get_buffer(php_stream * stream STREAMS_DC)322 PHPAPI zend_string *_php_stream_memory_get_buffer(php_stream *stream STREAMS_DC)
323 {
324 	php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
325 	ZEND_ASSERT(ms != NULL);
326 	return ms->data;
327 }
328 /* }}} */
329 
330 /* }}} */
331 
332 /* {{{ ------- TEMP stream implementation -------*/
333 
334 typedef struct {
335 	php_stream  *innerstream;
336 	size_t      smax;
337 	int			mode;
338 	zval        meta;
339 	char*		tmpdir;
340 } php_stream_temp_data;
341 
342 
343 /* {{{ */
php_stream_temp_write(php_stream * stream,const char * buf,size_t count)344 static ssize_t php_stream_temp_write(php_stream *stream, const char *buf, size_t count)
345 {
346 	php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
347 	assert(ts != NULL);
348 
349 	if (!ts->innerstream) {
350 		return -1;
351 	}
352 	if (php_stream_is(ts->innerstream, PHP_STREAM_IS_MEMORY)) {
353 		zend_off_t pos = php_stream_tell(ts->innerstream);
354 
355 		if (pos + count >= ts->smax) {
356 			zend_string *membuf = php_stream_memory_get_buffer(ts->innerstream);
357 			php_stream *file = php_stream_fopen_temporary_file(ts->tmpdir, "php", NULL);
358 			if (file == NULL) {
359 				php_error_docref(NULL, E_WARNING, "Unable to create temporary file, Check permissions in temporary files directory.");
360 				return 0;
361 			}
362 			php_stream_write(file, ZSTR_VAL(membuf), ZSTR_LEN(membuf));
363 			php_stream_free_enclosed(ts->innerstream, PHP_STREAM_FREE_CLOSE);
364 			ts->innerstream = file;
365 			php_stream_encloses(stream, ts->innerstream);
366 			php_stream_seek(ts->innerstream, pos, SEEK_SET);
367 		}
368 	}
369 	return php_stream_write(ts->innerstream, buf, count);
370 }
371 /* }}} */
372 
373 
374 /* {{{ */
php_stream_temp_read(php_stream * stream,char * buf,size_t count)375 static ssize_t php_stream_temp_read(php_stream *stream, char *buf, size_t count)
376 {
377 	php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
378 	size_t got;
379 
380 	assert(ts != NULL);
381 
382 	if (!ts->innerstream) {
383 		return -1;
384 	}
385 
386 	got = php_stream_read(ts->innerstream, buf, count);
387 
388 	stream->eof = ts->innerstream->eof;
389 
390 	return got;
391 }
392 /* }}} */
393 
394 
395 /* {{{ */
php_stream_temp_close(php_stream * stream,int close_handle)396 static int php_stream_temp_close(php_stream *stream, int close_handle)
397 {
398 	php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
399 	int ret;
400 
401 	assert(ts != NULL);
402 
403 	if (ts->innerstream) {
404 		ret = php_stream_free_enclosed(ts->innerstream, PHP_STREAM_FREE_CLOSE | (close_handle ? 0 : PHP_STREAM_FREE_PRESERVE_HANDLE));
405 	} else {
406 		ret = 0;
407 	}
408 
409 	zval_ptr_dtor(&ts->meta);
410 
411 	if (ts->tmpdir) {
412 		efree(ts->tmpdir);
413 	}
414 
415 	efree(ts);
416 
417 	return ret;
418 }
419 /* }}} */
420 
421 
422 /* {{{ */
php_stream_temp_flush(php_stream * stream)423 static int php_stream_temp_flush(php_stream *stream)
424 {
425 	php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
426 	assert(ts != NULL);
427 
428 	return ts->innerstream ? php_stream_flush(ts->innerstream) : -1;
429 }
430 /* }}} */
431 
432 
433 /* {{{ */
php_stream_temp_seek(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffs)434 static int php_stream_temp_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
435 {
436 	php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
437 	int ret;
438 
439 	assert(ts != NULL);
440 
441 	if (!ts->innerstream) {
442 		*newoffs = -1;
443 		return -1;
444 	}
445 	ret = php_stream_seek(ts->innerstream, offset, whence);
446 	*newoffs = php_stream_tell(ts->innerstream);
447 	stream->eof = ts->innerstream->eof;
448 
449 	return ret;
450 }
451 /* }}} */
452 
453 /* {{{ */
php_stream_temp_cast(php_stream * stream,int castas,void ** ret)454 static int php_stream_temp_cast(php_stream *stream, int castas, void **ret)
455 {
456 	php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
457 	php_stream *file;
458 	zend_string *membuf;
459 	zend_off_t pos;
460 
461 	assert(ts != NULL);
462 
463 	if (!ts->innerstream) {
464 		return FAILURE;
465 	}
466 	if (php_stream_is(ts->innerstream, PHP_STREAM_IS_STDIO)) {
467 		return php_stream_cast(ts->innerstream, castas, ret, 0);
468 	}
469 
470 	/* we are still using a memory based backing. If they are if we can be
471 	 * a FILE*, say yes because we can perform the conversion.
472 	 * If they actually want to perform the conversion, we need to switch
473 	 * the memory stream to a tmpfile stream */
474 
475 	if (ret == NULL && castas == PHP_STREAM_AS_STDIO) {
476 		return SUCCESS;
477 	}
478 
479 	/* say "no" to other stream forms */
480 	if (ret == NULL) {
481 		return FAILURE;
482 	}
483 
484 	file = php_stream_fopen_tmpfile();
485 	if (file == NULL) {
486 		php_error_docref(NULL, E_WARNING, "Unable to create temporary file.");
487 		return FAILURE;
488 	}
489 
490 	/* perform the conversion and then pass the request on to the innerstream */
491 	membuf = php_stream_memory_get_buffer(ts->innerstream);
492 	php_stream_write(file, ZSTR_VAL(membuf), ZSTR_LEN(membuf));
493 	pos = php_stream_tell(ts->innerstream);
494 
495 	php_stream_free_enclosed(ts->innerstream, PHP_STREAM_FREE_CLOSE);
496 	ts->innerstream = file;
497 	php_stream_encloses(stream, ts->innerstream);
498 	php_stream_seek(ts->innerstream, pos, SEEK_SET);
499 
500 	return php_stream_cast(ts->innerstream, castas, ret, 1);
501 }
502 /* }}} */
503 
php_stream_temp_stat(php_stream * stream,php_stream_statbuf * ssb)504 static int php_stream_temp_stat(php_stream *stream, php_stream_statbuf *ssb) /* {{{ */
505 {
506 	php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
507 
508 	if (!ts || !ts->innerstream) {
509 		return -1;
510 	}
511 	return php_stream_stat(ts->innerstream, ssb);
512 }
513 /* }}} */
514 
php_stream_temp_set_option(php_stream * stream,int option,int value,void * ptrparam)515 static int php_stream_temp_set_option(php_stream *stream, int option, int value, void *ptrparam) /* {{{ */
516 {
517 	php_stream_temp_data *ts = (php_stream_temp_data*)stream->abstract;
518 
519 	switch(option) {
520 		case PHP_STREAM_OPTION_META_DATA_API:
521 			if (Z_TYPE(ts->meta) != IS_UNDEF) {
522 				zend_hash_copy(Z_ARRVAL_P((zval*)ptrparam), Z_ARRVAL(ts->meta), zval_add_ref);
523 			}
524 			return PHP_STREAM_OPTION_RETURN_OK;
525 		default:
526 			if (ts->innerstream) {
527 				return php_stream_set_option(ts->innerstream, option, value, ptrparam);
528 			}
529 			return PHP_STREAM_OPTION_RETURN_NOTIMPL;
530 	}
531 }
532 /* }}} */
533 
534 PHPAPI const php_stream_ops	php_stream_temp_ops = {
535 	php_stream_temp_write, php_stream_temp_read,
536 	php_stream_temp_close, php_stream_temp_flush,
537 	"TEMP",
538 	php_stream_temp_seek,
539 	php_stream_temp_cast,
540 	php_stream_temp_stat,
541 	php_stream_temp_set_option
542 };
543 
544 /* }}} */
545 
546 /* {{{ _php_stream_temp_create_ex */
_php_stream_temp_create_ex(int mode,size_t max_memory_usage,const char * tmpdir STREAMS_DC)547 PHPAPI php_stream *_php_stream_temp_create_ex(int mode, size_t max_memory_usage, const char *tmpdir STREAMS_DC)
548 {
549 	php_stream_temp_data *self;
550 	php_stream *stream;
551 
552 	self = ecalloc(1, sizeof(*self));
553 	self->smax = max_memory_usage;
554 	self->mode = mode;
555 	ZVAL_UNDEF(&self->meta);
556 	if (tmpdir) {
557 		self->tmpdir = estrdup(tmpdir);
558 	}
559 	stream = php_stream_alloc_rel(&php_stream_temp_ops, self, 0, _php_stream_mode_to_str(mode));
560 	stream->flags |= PHP_STREAM_FLAG_NO_BUFFER;
561 	self->innerstream = php_stream_memory_create_rel(mode);
562 	php_stream_encloses(stream, self->innerstream);
563 
564 	return stream;
565 }
566 /* }}} */
567 
568 /* {{{ _php_stream_temp_create */
_php_stream_temp_create(int mode,size_t max_memory_usage STREAMS_DC)569 PHPAPI php_stream *_php_stream_temp_create(int mode, size_t max_memory_usage STREAMS_DC)
570 {
571 	return php_stream_temp_create_ex(mode, max_memory_usage, NULL);
572 }
573 /* }}} */
574 
575 /* {{{ _php_stream_temp_open */
_php_stream_temp_open(int mode,size_t max_memory_usage,const char * buf,size_t length STREAMS_DC)576 PHPAPI php_stream *_php_stream_temp_open(int mode, size_t max_memory_usage, const char *buf, size_t length STREAMS_DC)
577 {
578 	php_stream *stream;
579 	php_stream_temp_data *ts;
580 	zend_off_t newoffs;
581 
582 	if ((stream = php_stream_temp_create_rel(mode, max_memory_usage)) != NULL) {
583 		if (length) {
584 			assert(buf != NULL);
585 			php_stream_temp_write(stream, buf, length);
586 			php_stream_temp_seek(stream, 0, SEEK_SET, &newoffs);
587 		}
588 		ts = (php_stream_temp_data*)stream->abstract;
589 		assert(ts != NULL);
590 		ts->mode = mode;
591 	}
592 	return stream;
593 }
594 /* }}} */
595 
596 PHPAPI const php_stream_ops php_stream_rfc2397_ops = {
597 	NULL, php_stream_temp_read,
598 	php_stream_temp_close, php_stream_temp_flush,
599 	"RFC2397",
600 	php_stream_temp_seek,
601 	php_stream_temp_cast,
602 	php_stream_temp_stat,
603 	php_stream_temp_set_option
604 };
605 
php_stream_url_wrap_rfc2397(php_stream_wrapper * wrapper,const char * path,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)606 static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, const char *path,
607 												const char *mode, int options, zend_string **opened_path,
608 												php_stream_context *context STREAMS_DC) /* {{{ */
609 {
610 	php_stream *stream;
611 	php_stream_temp_data *ts;
612 	char *comma, *semi, *sep;
613 	size_t mlen, dlen, plen, vlen, ilen;
614 	zend_off_t newoffs;
615 	zval meta;
616 	int base64 = 0;
617 	zend_string *base64_comma = NULL;
618 
619 	ZVAL_NULL(&meta);
620 	if (memcmp(path, "data:", 5)) {
621 		return NULL;
622 	}
623 
624 	path += 5;
625 	dlen = strlen(path);
626 
627 	if (dlen >= 2 && path[0] == '/' && path[1] == '/') {
628 		dlen -= 2;
629 		path += 2;
630 	}
631 
632 	if ((comma = memchr(path, ',', dlen)) == NULL) {
633 		php_stream_wrapper_log_error(wrapper, options, "rfc2397: no comma in URL");
634 		return NULL;
635 	}
636 
637 	if (comma != path) {
638 		/* meta info */
639 		mlen = comma - path;
640 		dlen -= mlen;
641 		semi = memchr(path, ';', mlen);
642 		sep = memchr(path, '/', mlen);
643 
644 		if (!semi && !sep) {
645 			php_stream_wrapper_log_error(wrapper, options, "rfc2397: illegal media type");
646 			return NULL;
647 		}
648 
649 		array_init(&meta);
650 		if (!semi) { /* there is only a mime type */
651 			add_assoc_stringl(&meta, "mediatype", (char *) path, mlen);
652 			mlen = 0;
653 		} else if (sep && sep < semi) { /* there is a mime type */
654 			plen = semi - path;
655 			add_assoc_stringl(&meta, "mediatype", (char *) path, plen);
656 			mlen -= plen;
657 			path += plen;
658 		} else if (semi != path || mlen != sizeof(";base64")-1 || memcmp(path, ";base64", sizeof(";base64")-1)) { /* must be error since parameters are only allowed after mediatype */
659 			zval_ptr_dtor(&meta);
660 			php_stream_wrapper_log_error(wrapper, options, "rfc2397: illegal media type");
661 			return NULL;
662 		}
663 		/* get parameters and potentially ';base64' */
664 		while(semi && (semi == path)) {
665 			path++;
666 			mlen--;
667 			sep = memchr(path, '=', mlen);
668 			semi = memchr(path, ';', mlen);
669 			if (!sep || (semi && semi < sep)) { /* must be ';base64' or failure */
670 				if (mlen != sizeof("base64")-1 || memcmp(path, "base64", sizeof("base64")-1)) {
671 					/* must be error since parameters are only allowed after mediatype and we have no '=' sign */
672 					zval_ptr_dtor(&meta);
673 					php_stream_wrapper_log_error(wrapper, options, "rfc2397: illegal parameter");
674 					return NULL;
675 				}
676 				base64 = 1;
677 				mlen -= sizeof("base64") - 1;
678 				path += sizeof("base64") - 1;
679 				break;
680 			}
681 			/* found parameter ... the heart of cs ppl lies in +1/-1 or was it +2 this time? */
682 			plen = sep - path;
683 			vlen = (semi ? (size_t)(semi - sep) : (mlen - plen)) - 1 /* '=' */;
684 			if (plen != sizeof("mediatype")-1 || memcmp(path, "mediatype", sizeof("mediatype")-1)) {
685 				add_assoc_stringl_ex(&meta, path, plen, sep + 1, vlen);
686 			}
687 			plen += vlen + 1;
688 			mlen -= plen;
689 			path += plen;
690 		}
691 		if (mlen) {
692 			zval_ptr_dtor(&meta);
693 			php_stream_wrapper_log_error(wrapper, options, "rfc2397: illegal URL");
694 			return NULL;
695 		}
696 	} else {
697 		array_init(&meta);
698 	}
699 	add_assoc_bool(&meta, "base64", base64);
700 
701 	/* skip ',' */
702 	comma++;
703 	dlen--;
704 
705 	if (base64) {
706 		base64_comma = php_base64_decode_ex((const unsigned char *)comma, dlen, 1);
707 		if (!base64_comma) {
708 			zval_ptr_dtor(&meta);
709 			php_stream_wrapper_log_error(wrapper, options, "rfc2397: unable to decode");
710 			return NULL;
711 		}
712 		comma = ZSTR_VAL(base64_comma);
713 		ilen = ZSTR_LEN(base64_comma);
714 	} else {
715 		comma = estrndup(comma, dlen);
716 		dlen = php_url_decode(comma, dlen);
717 		ilen = dlen;
718 	}
719 
720 	if ((stream = php_stream_temp_create_rel(0, ~0u)) != NULL) {
721 		/* store data */
722 		php_stream_temp_write(stream, comma, ilen);
723 		php_stream_temp_seek(stream, 0, SEEK_SET, &newoffs);
724 		/* set special stream stuff (enforce exact mode) */
725 		vlen = strlen(mode);
726 		if (vlen >= sizeof(stream->mode)) {
727 			vlen = sizeof(stream->mode) - 1;
728 		}
729 		memcpy(stream->mode, mode, vlen);
730 		stream->mode[vlen] = '\0';
731 		stream->ops = &php_stream_rfc2397_ops;
732 		ts = (php_stream_temp_data*)stream->abstract;
733 		assert(ts != NULL);
734 		ts->mode = mode && mode[0] == 'r' && mode[1] != '+' ? TEMP_STREAM_READONLY : 0;
735 		ZVAL_COPY_VALUE(&ts->meta, &meta);
736 	}
737 	if (base64_comma) {
738 		zend_string_free(base64_comma);
739 	} else {
740 		efree(comma);
741 	}
742 
743 	return stream;
744 }
745 
746 PHPAPI const php_stream_wrapper_ops php_stream_rfc2397_wops = {
747 	php_stream_url_wrap_rfc2397,
748 	NULL, /* close */
749 	NULL, /* fstat */
750 	NULL, /* stat */
751 	NULL, /* opendir */
752 	"RFC2397",
753 	NULL, /* unlink */
754 	NULL, /* rename */
755 	NULL, /* mkdir */
756 	NULL, /* rmdir */
757 	NULL, /* stream_metadata */
758 };
759 
760 PHPAPI const php_stream_wrapper php_stream_rfc2397_wrapper =	{
761 	&php_stream_rfc2397_wops,
762 	NULL,
763 	1, /* is_url */
764 };
765