1 : /*
2 : +----------------------------------------------------------------------+
3 : | PHP Version 5 |
4 : +----------------------------------------------------------------------+
5 : | Copyright (c) 1997-2009 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: Wez Furlong <wez@thebrainroom.com> |
16 : | Borrowed code from: |
17 : | Rasmus Lerdorf <rasmus@lerdorf.on.ca> |
18 : | Jim Winstead <jimw@php.net> |
19 : +----------------------------------------------------------------------+
20 : */
21 :
22 : /* $Id: streams.c 290803 2009-11-16 10:36:27Z felipe $ */
23 :
24 : #define _GNU_SOURCE
25 : #include "php.h"
26 : #include "php_globals.h"
27 : #include "php_network.h"
28 : #include "php_open_temporary_file.h"
29 : #include "ext/standard/file.h"
30 : #include "ext/standard/basic_functions.h" /* for BG(mmap_file) (not strictly required) */
31 : #include "ext/standard/php_string.h" /* for php_memnstr, used by php_stream_get_record() */
32 : #include <stddef.h>
33 : #include <fcntl.h>
34 : #include "php_streams_int.h"
35 :
36 : /* {{{ resource and registration code */
37 : /* Global wrapper hash, copied to FG(stream_wrappers) on registration of volatile wrapper */
38 : static HashTable url_stream_wrappers_hash;
39 : static int le_stream = FAILURE; /* true global */
40 : static int le_pstream = FAILURE; /* true global */
41 : static int le_stream_filter = FAILURE; /* true global */
42 :
43 : PHPAPI int php_file_le_stream(void)
44 9462853 : {
45 9462853 : return le_stream;
46 : }
47 :
48 : PHPAPI int php_file_le_pstream(void)
49 9400839 : {
50 9400839 : return le_pstream;
51 : }
52 :
53 : PHPAPI int php_file_le_stream_filter(void)
54 86 : {
55 86 : return le_stream_filter;
56 : }
57 :
58 : PHPAPI HashTable *_php_stream_get_url_stream_wrappers_hash(TSRMLS_D)
59 31 : {
60 31 : return (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
61 : }
62 :
63 : PHPAPI HashTable *php_stream_get_url_stream_wrappers_hash_global(void)
64 1 : {
65 1 : return &url_stream_wrappers_hash;
66 : }
67 :
68 : static int _php_stream_release_context(zend_rsrc_list_entry *le, void *pContext TSRMLS_DC)
69 0 : {
70 0 : if (le->ptr == pContext) {
71 0 : return --le->refcount == 0;
72 : }
73 0 : return 0;
74 : }
75 :
76 : static int forget_persistent_resource_id_numbers(zend_rsrc_list_entry *rsrc TSRMLS_DC)
77 328 : {
78 : php_stream *stream;
79 :
80 328 : if (Z_TYPE_P(rsrc) != le_pstream) {
81 322 : return 0;
82 : }
83 :
84 6 : stream = (php_stream*)rsrc->ptr;
85 :
86 : #if STREAM_DEBUG
87 : fprintf(stderr, "forget_persistent: %s:%p\n", stream->ops->label, stream);
88 : #endif
89 :
90 6 : stream->rsrc_id = FAILURE;
91 :
92 6 : if (stream->context) {
93 0 : zend_hash_apply_with_argument(&EG(regular_list),
94 : (apply_func_arg_t) _php_stream_release_context,
95 : stream->context TSRMLS_CC);
96 0 : stream->context = NULL;
97 : }
98 :
99 6 : return 0;
100 : }
101 :
102 : PHP_RSHUTDOWN_FUNCTION(streams)
103 17651 : {
104 17651 : zend_hash_apply(&EG(persistent_list), (apply_func_t)forget_persistent_resource_id_numbers TSRMLS_CC);
105 17651 : return SUCCESS;
106 : }
107 :
108 : PHPAPI int php_stream_from_persistent_id(const char *persistent_id, php_stream **stream TSRMLS_DC)
109 1006 : {
110 : zend_rsrc_list_entry *le;
111 :
112 1006 : if (zend_hash_find(&EG(persistent_list), (char*)persistent_id, strlen(persistent_id)+1, (void*) &le) == SUCCESS) {
113 0 : if (Z_TYPE_P(le) == le_pstream) {
114 0 : if (stream) {
115 0 : *stream = (php_stream*)le->ptr;
116 0 : le->refcount++;
117 0 : (*stream)->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, *stream, le_pstream);
118 : }
119 0 : return PHP_STREAM_PERSISTENT_SUCCESS;
120 : }
121 0 : return PHP_STREAM_PERSISTENT_FAILURE;
122 : }
123 1006 : return PHP_STREAM_PERSISTENT_NOT_EXIST;
124 : }
125 :
126 : /* }}} */
127 :
128 : /* {{{ wrapper error reporting */
129 : void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption TSRMLS_DC)
130 440 : {
131 440 : char *tmp = estrdup(path);
132 : char *msg;
133 440 : int free_msg = 0;
134 :
135 440 : if (wrapper) {
136 420 : if (wrapper->err_count > 0) {
137 : int i;
138 : size_t l;
139 : int brlen;
140 : char *br;
141 :
142 86 : if (PG(html_errors)) {
143 0 : brlen = 7;
144 0 : br = "<br />\n";
145 : } else {
146 86 : brlen = 1;
147 86 : br = "\n";
148 : }
149 :
150 178 : for (i = 0, l = 0; i < wrapper->err_count; i++) {
151 92 : l += strlen(wrapper->err_stack[i]);
152 92 : if (i < wrapper->err_count - 1) {
153 6 : l += brlen;
154 : }
155 : }
156 86 : msg = emalloc(l + 1);
157 86 : msg[0] = '\0';
158 178 : for (i = 0; i < wrapper->err_count; i++) {
159 92 : strcat(msg, wrapper->err_stack[i]);
160 92 : if (i < wrapper->err_count - 1) {
161 6 : strcat(msg, br);
162 : }
163 : }
164 :
165 86 : free_msg = 1;
166 : } else {
167 334 : if (wrapper == &php_plain_files_wrapper) {
168 333 : msg = strerror(errno);
169 : } else {
170 1 : msg = "operation failed";
171 : }
172 : }
173 : } else {
174 20 : msg = "no suitable wrapper could be found";
175 : }
176 :
177 440 : php_strip_url_passwd(tmp);
178 440 : php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "%s: %s", caption, msg);
179 440 : efree(tmp);
180 440 : if (free_msg) {
181 86 : efree(msg);
182 : }
183 440 : }
184 :
185 : void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper TSRMLS_DC)
186 116899 : {
187 116899 : if (wrapper) {
188 : /* tidy up the error stack */
189 : int i;
190 :
191 116971 : for (i = 0; i < wrapper->err_count; i++) {
192 92 : efree(wrapper->err_stack[i]);
193 : }
194 116879 : if (wrapper->err_stack) {
195 86 : efree(wrapper->err_stack);
196 : }
197 116879 : wrapper->err_stack = NULL;
198 116879 : wrapper->err_count = 0;
199 : }
200 116899 : }
201 :
202 : PHPAPI void php_stream_wrapper_log_error(php_stream_wrapper *wrapper, int options TSRMLS_DC, const char *fmt, ...)
203 126 : {
204 : va_list args;
205 126 : char *buffer = NULL;
206 :
207 126 : va_start(args, fmt);
208 126 : vspprintf(&buffer, 0, fmt, args);
209 126 : va_end(args);
210 :
211 160 : if (options & REPORT_ERRORS || wrapper == NULL) {
212 34 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", buffer);
213 34 : efree(buffer);
214 : } else {
215 : /* append to stack */
216 92 : wrapper->err_stack = erealloc(wrapper->err_stack, (wrapper->err_count + 1) * sizeof(char *));
217 92 : if (wrapper->err_stack) {
218 92 : wrapper->err_stack[wrapper->err_count++] = buffer;
219 : }
220 : }
221 126 : }
222 :
223 :
224 : /* }}} */
225 :
226 : /* allocate a new stream for a particular ops */
227 : PHPAPI php_stream *_php_stream_alloc(php_stream_ops *ops, void *abstract, const char *persistent_id, const char *mode STREAMS_DC TSRMLS_DC) /* {{{ */
228 193754 : {
229 : php_stream *ret;
230 :
231 193754 : ret = (php_stream*) pemalloc_rel_orig(sizeof(php_stream), persistent_id ? 1 : 0);
232 :
233 193754 : memset(ret, 0, sizeof(php_stream));
234 :
235 193754 : ret->readfilters.stream = ret;
236 193754 : ret->writefilters.stream = ret;
237 :
238 : #if STREAM_DEBUG
239 : fprintf(stderr, "stream_alloc: %s:%p persistent=%s\n", ops->label, ret, persistent_id);
240 : #endif
241 :
242 193754 : ret->ops = ops;
243 193754 : ret->abstract = abstract;
244 193754 : ret->is_persistent = persistent_id ? 1 : 0;
245 193754 : ret->chunk_size = FG(def_chunk_size);
246 :
247 : #if ZEND_DEBUG
248 : ret->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename;
249 : ret->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno;
250 : #endif
251 :
252 193754 : if (FG(auto_detect_line_endings)) {
253 0 : ret->flags |= PHP_STREAM_FLAG_DETECT_EOL;
254 : }
255 :
256 193754 : if (persistent_id) {
257 : zend_rsrc_list_entry le;
258 :
259 1006 : Z_TYPE(le) = le_pstream;
260 1006 : le.ptr = ret;
261 1006 : le.refcount = 0;
262 :
263 1006 : if (FAILURE == zend_hash_update(&EG(persistent_list), (char *)persistent_id,
264 : strlen(persistent_id) + 1,
265 : (void *)&le, sizeof(le), NULL)) {
266 :
267 0 : pefree(ret, 1);
268 0 : return NULL;
269 : }
270 : }
271 :
272 193754 : ret->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, ret, persistent_id ? le_pstream : le_stream);
273 193754 : strlcpy(ret->mode, mode, sizeof(ret->mode));
274 :
275 193754 : return ret;
276 : }
277 : /* }}} */
278 :
279 : static int _php_stream_free_persistent(zend_rsrc_list_entry *le, void *pStream TSRMLS_DC)
280 36 : {
281 36 : return le->ptr == pStream;
282 : }
283 :
284 : PHPAPI int _php_stream_free(php_stream *stream, int close_options TSRMLS_DC) /* {{{ */
285 257834 : {
286 257834 : int ret = 1;
287 257834 : int remove_rsrc = 1;
288 257834 : int preserve_handle = close_options & PHP_STREAM_FREE_PRESERVE_HANDLE ? 1 : 0;
289 257834 : int release_cast = 1;
290 257834 : php_stream_context *context = stream->context;
291 :
292 257834 : if (stream->flags & PHP_STREAM_FLAG_NO_CLOSE) {
293 0 : preserve_handle = 1;
294 : }
295 :
296 : #if STREAM_DEBUG
297 : fprintf(stderr, "stream_free: %s:%p[%s] in_free=%d opts=%08x\n", stream->ops->label, stream, stream->orig_path, stream->in_free, close_options);
298 : #endif
299 :
300 : /* recursion protection */
301 257834 : if (stream->in_free) {
302 63947 : return 1;
303 : }
304 :
305 193887 : stream->in_free++;
306 :
307 : /* if we are releasing the stream only (and preserving the underlying handle),
308 : * we need to do things a little differently.
309 : * We are only ever called like this when the stream is cast to a FILE*
310 : * for include (or other similar) purposes.
311 : * */
312 193887 : if (preserve_handle) {
313 0 : if (stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
314 : /* If the stream was fopencookied, we must NOT touch anything
315 : * here, as the cookied stream relies on it all.
316 : * Instead, mark the stream as OK to auto-clean */
317 : php_stream_auto_cleanup(stream);
318 0 : stream->in_free--;
319 0 : return 0;
320 : }
321 : /* otherwise, make sure that we don't close the FILE* from a cast */
322 0 : release_cast = 0;
323 : }
324 :
325 : #if STREAM_DEBUG
326 : fprintf(stderr, "stream_free: %s:%p[%s] preserve_handle=%d release_cast=%d remove_rsrc=%d\n",
327 : stream->ops->label, stream, stream->orig_path, preserve_handle, release_cast, remove_rsrc);
328 : #endif
329 :
330 : /* make sure everything is saved */
331 193887 : _php_stream_flush(stream, 1 TSRMLS_CC);
332 :
333 : /* If not called from the resource dtor, remove the stream from the resource list. */
334 193887 : if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) == 0 && remove_rsrc) {
335 63015 : zend_list_delete(stream->rsrc_id);
336 : }
337 :
338 : /* Remove stream from any context link list */
339 193887 : if (stream->context && stream->context->links) {
340 0 : php_stream_context_del_link(stream->context, stream);
341 : }
342 :
343 193887 : if (close_options & PHP_STREAM_FREE_CALL_DTOR) {
344 193887 : if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
345 : /* calling fclose on an fopencookied stream will ultimately
346 : call this very same function. If we were called via fclose,
347 : the cookie_closer unsets the fclose_stdiocast flags, so
348 : we can be sure that we only reach here when PHP code calls
349 : php_stream_free.
350 : Lets let the cookie code clean it all up.
351 : */
352 0 : stream->in_free = 0;
353 0 : return fclose(stream->stdiocast);
354 : }
355 :
356 193887 : ret = stream->ops->close(stream, preserve_handle ? 0 : 1 TSRMLS_CC);
357 193885 : stream->abstract = NULL;
358 :
359 : /* tidy up any FILE* that might have been fdopened */
360 193885 : if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FDOPEN && stream->stdiocast) {
361 0 : fclose(stream->stdiocast);
362 0 : stream->stdiocast = NULL;
363 0 : stream->fclose_stdiocast = PHP_STREAM_FCLOSE_NONE;
364 : }
365 : }
366 :
367 193885 : if (close_options & PHP_STREAM_FREE_RELEASE_STREAM) {
368 387829 : while (stream->readfilters.head) {
369 59 : php_stream_filter_remove(stream->readfilters.head, 1 TSRMLS_CC);
370 : }
371 387815 : while (stream->writefilters.head) {
372 45 : php_stream_filter_remove(stream->writefilters.head, 1 TSRMLS_CC);
373 : }
374 :
375 193885 : if (stream->wrapper && stream->wrapper->wops && stream->wrapper->wops->stream_closer) {
376 0 : stream->wrapper->wops->stream_closer(stream->wrapper, stream TSRMLS_CC);
377 0 : stream->wrapper = NULL;
378 : }
379 :
380 193885 : if (stream->wrapperdata) {
381 65 : zval_ptr_dtor(&stream->wrapperdata);
382 65 : stream->wrapperdata = NULL;
383 : }
384 :
385 193885 : if (stream->readbuf) {
386 43748 : pefree(stream->readbuf, stream->is_persistent);
387 43748 : stream->readbuf = NULL;
388 : }
389 :
390 193885 : if (stream->is_persistent && (close_options & PHP_STREAM_FREE_PERSISTENT)) {
391 : /* we don't work with *stream but need its value for comparison */
392 1008 : zend_hash_apply_with_argument(&EG(persistent_list), (apply_func_arg_t) _php_stream_free_persistent, stream TSRMLS_CC);
393 : }
394 : #if ZEND_DEBUG
395 : if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) && (stream->__exposed == 0) && (EG(error_reporting) & E_WARNING)) {
396 : /* it leaked: Lets deliberately NOT pefree it so that the memory manager shows it
397 : * as leaked; it will log a warning, but lets help it out and display what kind
398 : * of stream it was. */
399 : char *leakinfo;
400 : spprintf(&leakinfo, 0, __FILE__ "(%d) : Stream of type '%s' %p (path:%s) was not closed\n", __LINE__, stream->ops->label, stream, stream->orig_path);
401 :
402 : if (stream->orig_path) {
403 : pefree(stream->orig_path, stream->is_persistent);
404 : stream->orig_path = NULL;
405 : }
406 :
407 : # if defined(PHP_WIN32)
408 : OutputDebugString(leakinfo);
409 : # else
410 : fprintf(stderr, "%s", leakinfo);
411 : # endif
412 : efree(leakinfo);
413 : } else {
414 : if (stream->orig_path) {
415 : pefree(stream->orig_path, stream->is_persistent);
416 : stream->orig_path = NULL;
417 : }
418 :
419 : pefree(stream, stream->is_persistent);
420 : }
421 : #else
422 193885 : if (stream->orig_path) {
423 135275 : pefree(stream->orig_path, stream->is_persistent);
424 135275 : stream->orig_path = NULL;
425 : }
426 :
427 193885 : pefree(stream, stream->is_persistent);
428 : #endif
429 : }
430 :
431 193885 : if (context) {
432 194 : zend_list_delete(context->rsrc_id);
433 : }
434 :
435 193885 : return ret;
436 : }
437 : /* }}} */
438 :
439 : /* {{{ generic stream operations */
440 :
441 : static void php_stream_fill_read_buffer(php_stream *stream, size_t size TSRMLS_DC)
442 838234 : {
443 : /* allocate/fill the buffer */
444 :
445 838234 : if (stream->readfilters.head) {
446 : char *chunk_buf;
447 69 : int err_flag = 0;
448 69 : php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
449 69 : php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap;
450 :
451 : /* Invalidate the existing cache, otherwise reads can fail, see note in
452 : main/streams/filter.c::_php_stream_filter_append */
453 69 : stream->writepos = stream->readpos = 0;
454 :
455 : /* allocate a buffer for reading chunks */
456 69 : chunk_buf = emalloc(stream->chunk_size);
457 :
458 185 : while (!stream->eof && !err_flag && (stream->writepos - stream->readpos < (off_t)size)) {
459 62 : size_t justread = 0;
460 : int flags;
461 : php_stream_bucket *bucket;
462 62 : php_stream_filter_status_t status = PSFS_ERR_FATAL;
463 : php_stream_filter *filter;
464 :
465 : /* read a chunk into a bucket */
466 62 : justread = stream->ops->read(stream, chunk_buf, stream->chunk_size TSRMLS_CC);
467 109 : if (justread && justread != (size_t)-1) {
468 47 : bucket = php_stream_bucket_new(stream, chunk_buf, justread, 0, 0 TSRMLS_CC);
469 :
470 : /* after this call, bucket is owned by the brigade */
471 47 : php_stream_bucket_append(brig_inp, bucket TSRMLS_CC);
472 :
473 47 : flags = PSFS_FLAG_NORMAL;
474 : } else {
475 15 : flags = stream->eof ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC;
476 : }
477 :
478 : /* wind the handle... */
479 113 : for (filter = stream->readfilters.head; filter; filter = filter->next) {
480 68 : status = filter->fops->filter(stream, filter, brig_inp, brig_outp, NULL, flags TSRMLS_CC);
481 :
482 68 : if (status != PSFS_PASS_ON) {
483 17 : break;
484 : }
485 :
486 : /* brig_out becomes brig_in.
487 : * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets
488 : * to its own brigade */
489 51 : brig_swap = brig_inp;
490 51 : brig_inp = brig_outp;
491 51 : brig_outp = brig_swap;
492 51 : memset(brig_outp, 0, sizeof(*brig_outp));
493 : }
494 :
495 62 : switch (status) {
496 : case PSFS_PASS_ON:
497 : /* we get here when the last filter in the chain has data to pass on.
498 : * in this situation, we are passing the brig_in brigade into the
499 : * stream read buffer */
500 123 : while (brig_inp->head) {
501 33 : bucket = brig_inp->head;
502 : /* grow buffer to hold this bucket
503 : * TODO: this can fail for persistent streams */
504 33 : if (stream->readbuflen - stream->writepos < bucket->buflen) {
505 24 : stream->readbuflen += bucket->buflen;
506 24 : stream->readbuf = perealloc(stream->readbuf, stream->readbuflen,
507 : stream->is_persistent);
508 : }
509 33 : memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen);
510 33 : stream->writepos += bucket->buflen;
511 :
512 33 : php_stream_bucket_unlink(bucket TSRMLS_CC);
513 33 : php_stream_bucket_delref(bucket TSRMLS_CC);
514 : }
515 45 : break;
516 :
517 : case PSFS_FEED_ME:
518 : /* when a filter needs feeding, there is no brig_out to deal with.
519 : * we simply continue the loop; if the caller needs more data,
520 : * we will read again, otherwise out job is done here */
521 2 : if (justread == 0) {
522 : /* there is no data */
523 2 : err_flag = 1;
524 2 : break;
525 : }
526 0 : continue;
527 :
528 : case PSFS_ERR_FATAL:
529 : /* some fatal error. Theoretically, the stream is borked, so all
530 : * further reads should fail. */
531 15 : err_flag = 1;
532 : break;
533 : }
534 :
535 62 : if (justread == 0 || justread == (size_t)-1) {
536 : break;
537 : }
538 : }
539 :
540 69 : efree(chunk_buf);
541 :
542 : } else {
543 : /* is there enough data in the buffer ? */
544 838165 : if (stream->writepos - stream->readpos < (off_t)size) {
545 838163 : size_t justread = 0;
546 :
547 : /* reduce buffer memory consumption if possible, to avoid a realloc */
548 838163 : if (stream->readbuf && stream->readbuflen - stream->writepos < stream->chunk_size) {
549 773005 : memmove(stream->readbuf, stream->readbuf + stream->readpos, stream->readbuflen - stream->readpos);
550 773005 : stream->writepos -= stream->readpos;
551 773005 : stream->readpos = 0;
552 : }
553 :
554 : /* grow the buffer if required
555 : * TODO: this can fail for persistent streams */
556 838163 : if (stream->readbuflen - stream->writepos < stream->chunk_size) {
557 43743 : stream->readbuflen += stream->chunk_size;
558 43743 : stream->readbuf = perealloc(stream->readbuf, stream->readbuflen,
559 : stream->is_persistent);
560 : }
561 :
562 838163 : justread = stream->ops->read(stream, stream->readbuf + stream->writepos,
563 : stream->readbuflen - stream->writepos
564 : TSRMLS_CC);
565 :
566 838163 : if (justread != (size_t)-1) {
567 837861 : stream->writepos += justread;
568 : }
569 : }
570 : }
571 838234 : }
572 :
573 : PHPAPI size_t _php_stream_read(php_stream *stream, char *buf, size_t size TSRMLS_DC)
574 1648238 : {
575 1648238 : size_t toread = 0, didread = 0;
576 :
577 3391467 : while (size > 0) {
578 :
579 : /* take from the read buffer first.
580 : * It is possible that a buffered stream was switched to non-buffered, so we
581 : * drain the remainder of the buffer before using the "raw" read mode for
582 : * the excess */
583 1652296 : if (stream->writepos > stream->readpos) {
584 :
585 797330 : toread = stream->writepos - stream->readpos;
586 797330 : if (toread > size) {
587 751230 : toread = size;
588 : }
589 :
590 797330 : memcpy(buf, stream->readbuf + stream->readpos, toread);
591 797330 : stream->readpos += toread;
592 797330 : size -= toread;
593 797330 : buf += toread;
594 797330 : didread += toread;
595 : }
596 :
597 : /* ignore eof here; the underlying state might have changed */
598 1652296 : if (size == 0) {
599 791094 : break;
600 : }
601 :
602 930131 : if (!stream->readfilters.head && (stream->flags & PHP_STREAM_FLAG_NO_BUFFER || stream->chunk_size == 1)) {
603 68929 : toread = stream->ops->read(stream, buf, size TSRMLS_CC);
604 : } else {
605 792273 : php_stream_fill_read_buffer(stream, size TSRMLS_CC);
606 :
607 792273 : toread = stream->writepos - stream->readpos;
608 792273 : if (toread > size) {
609 55841 : toread = size;
610 : }
611 :
612 792273 : if (toread > 0) {
613 757976 : memcpy(buf, stream->readbuf + stream->readpos, toread);
614 757976 : stream->readpos += toread;
615 : }
616 : }
617 861202 : if (toread > 0) {
618 823463 : didread += toread;
619 823463 : buf += toread;
620 823463 : size -= toread;
621 : } else {
622 : /* EOF, or temporary end of data (for non-blocking mode). */
623 37739 : break;
624 : }
625 :
626 : /* just break anyway, to avoid greedy read */
627 823463 : if (stream->wrapper != &php_plain_files_wrapper) {
628 728472 : break;
629 : }
630 : }
631 :
632 1648238 : if (didread > 0) {
633 1617681 : stream->position += didread;
634 : }
635 :
636 1648238 : return didread;
637 : }
638 :
639 : PHPAPI int _php_stream_eof(php_stream *stream TSRMLS_DC)
640 1076130 : {
641 : /* if there is data in the buffer, it's not EOF */
642 1076130 : if (stream->writepos - stream->readpos > 0) {
643 1034939 : return 0;
644 : }
645 :
646 : /* use the configured timeout when checking eof */
647 41191 : if (!stream->eof && PHP_STREAM_OPTION_RETURN_ERR ==
648 : php_stream_set_option(stream, PHP_STREAM_OPTION_CHECK_LIVENESS,
649 : 0, NULL)) {
650 4 : stream->eof = 1;
651 : }
652 :
653 41191 : return stream->eof;
654 : }
655 :
656 : PHPAPI int _php_stream_putc(php_stream *stream, int c TSRMLS_DC)
657 49 : {
658 49 : unsigned char buf = c;
659 :
660 49 : if (php_stream_write(stream, &buf, 1) > 0) {
661 49 : return 1;
662 : }
663 0 : return EOF;
664 : }
665 :
666 : PHPAPI int _php_stream_getc(php_stream *stream TSRMLS_DC)
667 533762 : {
668 : char buf;
669 :
670 533762 : if (php_stream_read(stream, &buf, 1) > 0) {
671 533733 : return buf & 0xff;
672 : }
673 29 : return EOF;
674 : }
675 :
676 : PHPAPI int _php_stream_puts(php_stream *stream, char *buf TSRMLS_DC)
677 0 : {
678 : int len;
679 0 : char newline[2] = "\n"; /* is this OK for Win? */
680 0 : len = strlen(buf);
681 :
682 0 : if (len > 0 && php_stream_write(stream, buf, len) && php_stream_write(stream, newline, 1)) {
683 0 : return 1;
684 : }
685 0 : return 0;
686 : }
687 :
688 : PHPAPI int _php_stream_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
689 33410 : {
690 33410 : memset(ssb, 0, sizeof(*ssb));
691 :
692 : /* if the stream was wrapped, allow the wrapper to stat it */
693 33410 : if (stream->wrapper && stream->wrapper->wops->stream_stat != NULL) {
694 0 : return stream->wrapper->wops->stream_stat(stream->wrapper, stream, ssb TSRMLS_CC);
695 : }
696 :
697 : /* if the stream doesn't directly support stat-ing, return with failure.
698 : * We could try and emulate this by casting to a FD and fstat-ing it,
699 : * but since the fd might not represent the actual underlying content
700 : * this would give bogus results. */
701 33410 : if (stream->ops->stat == NULL) {
702 12 : return -1;
703 : }
704 :
705 33398 : return (stream->ops->stat)(stream, ssb TSRMLS_CC);
706 : }
707 :
708 : PHPAPI char *php_stream_locate_eol(php_stream *stream, char *buf, size_t buf_len TSRMLS_DC)
709 1383687 : {
710 : size_t avail;
711 1383687 : char *cr, *lf, *eol = NULL;
712 : char *readptr;
713 :
714 1383687 : if (!buf) {
715 1383546 : readptr = stream->readbuf + stream->readpos;
716 1383546 : avail = stream->writepos - stream->readpos;
717 : } else {
718 141 : readptr = buf;
719 141 : avail = buf_len;
720 : }
721 :
722 : /* Look for EOL */
723 1383687 : if (stream->flags & PHP_STREAM_FLAG_DETECT_EOL) {
724 0 : cr = memchr(readptr, '\r', avail);
725 0 : lf = memchr(readptr, '\n', avail);
726 :
727 0 : if (cr && lf != cr + 1 && !(lf && lf < cr)) {
728 : /* mac */
729 0 : stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL;
730 0 : stream->flags |= PHP_STREAM_FLAG_EOL_MAC;
731 0 : eol = cr;
732 0 : } else if ((cr && lf && cr == lf - 1) || (lf)) {
733 : /* dos or unix endings */
734 0 : stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL;
735 0 : eol = lf;
736 : }
737 1383687 : } else if (stream->flags & PHP_STREAM_FLAG_EOL_MAC) {
738 0 : eol = memchr(readptr, '\r', avail);
739 : } else {
740 : /* unix (and dos) line endings */
741 1383687 : eol = memchr(readptr, '\n', avail);
742 : }
743 :
744 1383687 : return eol;
745 : }
746 :
747 : /* If buf == NULL, the buffer will be allocated automatically and will be of an
748 : * appropriate length to hold the line, regardless of the line length, memory
749 : * permitting */
750 : PHPAPI char *_php_stream_get_line(php_stream *stream, char *buf, size_t maxlen,
751 : size_t *returned_len TSRMLS_DC)
752 1378121 : {
753 1378121 : size_t avail = 0;
754 1378121 : size_t current_buf_size = 0;
755 1378121 : size_t total_copied = 0;
756 1378121 : int grow_mode = 0;
757 1378121 : char *bufstart = buf;
758 :
759 1378121 : if (buf == NULL) {
760 1051933 : grow_mode = 1;
761 326188 : } else if (maxlen == 0) {
762 0 : return NULL;
763 : }
764 :
765 : /*
766 : * If the underlying stream operations block when no new data is readable,
767 : * we need to take extra precautions.
768 : *
769 : * If there is buffered data available, we check for a EOL. If it exists,
770 : * we pass the data immediately back to the caller. This saves a call
771 : * to the read implementation and will not block where blocking
772 : * is not necessary at all.
773 : *
774 : * If the stream buffer contains more data than the caller requested,
775 : * we can also avoid that costly step and simply return that data.
776 : */
777 :
778 : for (;;) {
779 1429785 : avail = stream->writepos - stream->readpos;
780 :
781 1429785 : if (avail > 0) {
782 1383546 : size_t cpysz = 0;
783 : char *readptr;
784 : char *eol;
785 1383546 : int done = 0;
786 :
787 1383546 : readptr = stream->readbuf + stream->readpos;
788 1383546 : eol = php_stream_locate_eol(stream, NULL, 0 TSRMLS_CC);
789 :
790 1383546 : if (eol) {
791 1364724 : cpysz = eol - readptr + 1;
792 1364724 : done = 1;
793 : } else {
794 18822 : cpysz = avail;
795 : }
796 :
797 1383546 : if (grow_mode) {
798 : /* allow room for a NUL. If this realloc is really a realloc
799 : * (ie: second time around), we get an extra byte. In most
800 : * cases, with the default chunk size of 8K, we will only
801 : * incur that overhead once. When people have lines longer
802 : * than 8K, we waste 1 byte per additional 8K or so.
803 : * That seems acceptable to me, to avoid making this code
804 : * hard to follow */
805 1041765 : bufstart = erealloc(bufstart, current_buf_size + cpysz + 1);
806 1041765 : current_buf_size += cpysz + 1;
807 1041765 : buf = bufstart + total_copied;
808 : } else {
809 341781 : if (cpysz >= maxlen - 1) {
810 590 : cpysz = maxlen - 1;
811 590 : done = 1;
812 : }
813 : }
814 :
815 1383546 : memcpy(buf, readptr, cpysz);
816 :
817 1383546 : stream->position += cpysz;
818 1383546 : stream->readpos += cpysz;
819 1383546 : buf += cpysz;
820 1383546 : maxlen -= cpysz;
821 1383546 : total_copied += cpysz;
822 :
823 1383546 : if (done) {
824 1364817 : break;
825 : }
826 46239 : } else if (stream->eof) {
827 310 : break;
828 : } else {
829 : /* XXX: Should be fine to always read chunk_size */
830 : size_t toread;
831 :
832 45929 : if (grow_mode) {
833 25502 : toread = stream->chunk_size;
834 : } else {
835 20427 : toread = maxlen - 1;
836 20427 : if (toread > stream->chunk_size) {
837 0 : toread = stream->chunk_size;
838 : }
839 : }
840 :
841 45929 : php_stream_fill_read_buffer(stream, toread TSRMLS_CC);
842 :
843 45929 : if (stream->writepos - stream->readpos == 0) {
844 12994 : break;
845 : }
846 : }
847 51664 : }
848 :
849 1378121 : if (total_copied == 0) {
850 : if (grow_mode) {
851 : assert(bufstart == NULL);
852 : }
853 11307 : return NULL;
854 : }
855 :
856 1366814 : buf[0] = '\0';
857 1366814 : if (returned_len) {
858 1365338 : *returned_len = total_copied;
859 : }
860 :
861 1366814 : return bufstart;
862 : }
863 :
864 : PHPAPI char *php_stream_get_record(php_stream *stream, size_t maxlen, size_t *returned_len, char *delim, size_t delim_len TSRMLS_DC)
865 37 : {
866 : char *e, *buf;
867 : size_t toread, len;
868 37 : int skip = 0;
869 :
870 37 : len = stream->writepos - stream->readpos;
871 :
872 82 : while (len < maxlen) {
873 :
874 : size_t just_read;
875 32 : toread = MIN(maxlen - len, stream->chunk_size);
876 :
877 32 : php_stream_fill_read_buffer(stream, len + toread TSRMLS_CC);
878 :
879 32 : just_read = (stream->writepos - stream->readpos) - len;
880 32 : len += just_read;
881 :
882 32 : if (just_read < toread) {
883 24 : break;
884 : }
885 : }
886 :
887 37 : if (delim_len == 0 || !delim) {
888 0 : toread = maxlen;
889 : } else {
890 : size_t seek_len;
891 :
892 37 : seek_len = stream->writepos - stream->readpos;
893 37 : if (seek_len > maxlen) {
894 11 : seek_len = maxlen;
895 : }
896 :
897 37 : if (delim_len == 1) {
898 12 : e = memchr(stream->readbuf + stream->readpos, *delim, seek_len);
899 : } else {
900 25 : e = php_memnstr(stream->readbuf + stream->readpos, delim, delim_len, (stream->readbuf + stream->readpos + seek_len));
901 : }
902 :
903 37 : if (!e) {
904 14 : if (seek_len < maxlen && !stream->eof) {
905 6 : return NULL;
906 : }
907 8 : toread = maxlen;
908 : } else {
909 23 : toread = e - (char *) stream->readbuf - stream->readpos;
910 23 : skip = 1;
911 : }
912 : }
913 :
914 31 : if (toread > maxlen && maxlen > 0) {
915 0 : toread = maxlen;
916 : }
917 :
918 31 : buf = emalloc(toread + 1);
919 31 : *returned_len = php_stream_read(stream, buf, toread);
920 :
921 : if (*returned_len >= 0) {
922 31 : if (skip) {
923 23 : stream->readpos += delim_len;
924 23 : stream->position += delim_len;
925 : }
926 31 : buf[*returned_len] = '\0';
927 31 : return buf;
928 : } else {
929 : efree(buf);
930 : return NULL;
931 : }
932 : }
933 :
934 : /* Writes a buffer directly to a stream, using multiple of the chunk size */
935 : static size_t _php_stream_write_buffer(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
936 203960 : {
937 203960 : size_t didwrite = 0, towrite, justwrote;
938 :
939 : /* if we have a seekable stream we need to ensure that data is written at the
940 : * current stream->position. This means invalidating the read buffer and then
941 : * performing a low-level seek */
942 203960 : if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && stream->readpos != stream->writepos) {
943 1 : stream->readpos = stream->writepos = 0;
944 :
945 1 : stream->ops->seek(stream, stream->position, SEEK_SET, &stream->position TSRMLS_CC);
946 : }
947 :
948 :
949 612451 : while (count > 0) {
950 204604 : towrite = count;
951 204604 : if (towrite > stream->chunk_size)
952 640 : towrite = stream->chunk_size;
953 :
954 204604 : justwrote = stream->ops->write(stream, buf, towrite TSRMLS_CC);
955 :
956 : /* convert justwrote to an integer, since normally it is unsigned */
957 204604 : if ((int)justwrote > 0) {
958 204531 : buf += justwrote;
959 204531 : count -= justwrote;
960 204531 : didwrite += justwrote;
961 :
962 : /* Only screw with the buffer if we can seek, otherwise we lose data
963 : * buffered from fifos and sockets */
964 204531 : if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
965 174194 : stream->position += justwrote;
966 : }
967 : } else {
968 73 : break;
969 : }
970 : }
971 203960 : return didwrite;
972 :
973 : }
974 :
975 : /* push some data through the write filter chain.
976 : * buf may be NULL, if flags are set to indicate a flush.
977 : * This may trigger a real write to the stream.
978 : * Returns the number of bytes consumed from buf by the first filter in the chain.
979 : * */
980 : static size_t _php_stream_write_filtered(php_stream *stream, const char *buf, size_t count, int flags TSRMLS_DC)
981 265 : {
982 265 : size_t consumed = 0;
983 : php_stream_bucket *bucket;
984 265 : php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
985 265 : php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap;
986 265 : php_stream_filter_status_t status = PSFS_ERR_FATAL;
987 : php_stream_filter *filter;
988 :
989 265 : if (buf) {
990 150 : bucket = php_stream_bucket_new(stream, (char *)buf, count, 0, 0 TSRMLS_CC);
991 150 : php_stream_bucket_append(&brig_in, bucket TSRMLS_CC);
992 : }
993 :
994 387 : for (filter = stream->writefilters.head; filter; filter = filter->next) {
995 : /* for our return value, we are interested in the number of bytes consumed from
996 : * the first filter in the chain */
997 276 : status = filter->fops->filter(stream, filter, brig_inp, brig_outp,
998 : filter == stream->writefilters.head ? &consumed : NULL, flags TSRMLS_CC);
999 :
1000 276 : if (status != PSFS_PASS_ON) {
1001 154 : break;
1002 : }
1003 : /* brig_out becomes brig_in.
1004 : * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets
1005 : * to its own brigade */
1006 122 : brig_swap = brig_inp;
1007 122 : brig_inp = brig_outp;
1008 122 : brig_outp = brig_swap;
1009 122 : memset(brig_outp, 0, sizeof(*brig_outp));
1010 : }
1011 :
1012 265 : switch (status) {
1013 : case PSFS_PASS_ON:
1014 : /* filter chain generated some output; push it through to the
1015 : * underlying stream */
1016 482 : while (brig_inp->head) {
1017 260 : bucket = brig_inp->head;
1018 260 : _php_stream_write_buffer(stream, bucket->buf, bucket->buflen TSRMLS_CC);
1019 : /* Potential error situation - eg: no space on device. Perhaps we should keep this brigade
1020 : * hanging around and try to write it later.
1021 : * At the moment, we just drop it on the floor
1022 : * */
1023 :
1024 260 : php_stream_bucket_unlink(bucket TSRMLS_CC);
1025 260 : php_stream_bucket_delref(bucket TSRMLS_CC);
1026 : }
1027 : break;
1028 : case PSFS_FEED_ME:
1029 : /* need more data before we can push data through to the stream */
1030 : break;
1031 :
1032 : case PSFS_ERR_FATAL:
1033 : /* some fatal error. Theoretically, the stream is borked, so all
1034 : * further writes should fail. */
1035 : break;
1036 : }
1037 :
1038 265 : return consumed;
1039 : }
1040 :
1041 : PHPAPI int _php_stream_flush(php_stream *stream, int closing TSRMLS_DC)
1042 194926 : {
1043 194926 : int ret = 0;
1044 :
1045 194926 : if (stream->writefilters.head) {
1046 115 : _php_stream_write_filtered(stream, NULL, 0, closing ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC TSRMLS_CC);
1047 : }
1048 :
1049 194926 : if (stream->ops->flush) {
1050 191738 : ret = stream->ops->flush(stream TSRMLS_CC);
1051 : }
1052 :
1053 194926 : return ret;
1054 : }
1055 :
1056 : PHPAPI size_t _php_stream_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
1057 211821 : {
1058 211821 : if (buf == NULL || count == 0 || stream->ops->write == NULL) {
1059 7971 : return 0;
1060 : }
1061 :
1062 203850 : if (stream->writefilters.head) {
1063 150 : return _php_stream_write_filtered(stream, buf, count, PSFS_FLAG_NORMAL TSRMLS_CC);
1064 : } else {
1065 203700 : return _php_stream_write_buffer(stream, buf, count TSRMLS_CC);
1066 : }
1067 : }
1068 :
1069 : PHPAPI size_t _php_stream_printf(php_stream *stream TSRMLS_DC, const char *fmt, ...)
1070 134 : {
1071 : size_t count;
1072 : char *buf;
1073 : va_list ap;
1074 :
1075 134 : va_start(ap, fmt);
1076 134 : count = vspprintf(&buf, 0, fmt, ap);
1077 134 : va_end(ap);
1078 :
1079 134 : if (!buf) {
1080 0 : return 0; /* error condition */
1081 : }
1082 :
1083 134 : count = php_stream_write(stream, buf, count);
1084 134 : efree(buf);
1085 :
1086 134 : return count;
1087 : }
1088 :
1089 : PHPAPI off_t _php_stream_tell(php_stream *stream TSRMLS_DC)
1090 61985 : {
1091 61985 : return stream->position;
1092 : }
1093 :
1094 : PHPAPI int _php_stream_seek(php_stream *stream, off_t offset, int whence TSRMLS_DC)
1095 72778 : {
1096 : /* handle the case where we are in the buffer */
1097 72778 : if ((stream->flags & PHP_STREAM_FLAG_NO_BUFFER) == 0) {
1098 72104 : switch(whence) {
1099 : case SEEK_CUR:
1100 17987 : if (offset > 0 && offset < stream->writepos - stream->readpos) {
1101 436 : stream->readpos += offset;
1102 436 : stream->position += offset;
1103 436 : stream->eof = 0;
1104 436 : return 0;
1105 : }
1106 17551 : break;
1107 : case SEEK_SET:
1108 51898 : if (offset > stream->position &&
1109 : offset < stream->position + stream->writepos - stream->readpos) {
1110 811 : stream->readpos += offset - stream->position;
1111 811 : stream->position = offset;
1112 811 : stream->eof = 0;
1113 811 : return 0;
1114 : }
1115 : break;
1116 : }
1117 : }
1118 :
1119 :
1120 71531 : if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
1121 : int ret;
1122 :
1123 71529 : if (stream->writefilters.head) {
1124 1 : _php_stream_flush(stream, 0 TSRMLS_CC);
1125 : }
1126 :
1127 71529 : switch(whence) {
1128 : case SEEK_CUR:
1129 17559 : offset = stream->position + offset;
1130 17559 : whence = SEEK_SET;
1131 : break;
1132 : }
1133 71529 : ret = stream->ops->seek(stream, offset, whence, &stream->position TSRMLS_CC);
1134 :
1135 71529 : if (((stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) || ret == 0) {
1136 71529 : if (ret == 0) {
1137 71321 : stream->eof = 0;
1138 : }
1139 :
1140 : /* invalidate the buffer contents */
1141 71529 : stream->readpos = stream->writepos = 0;
1142 :
1143 71529 : return ret;
1144 : }
1145 : /* else the stream has decided that it can't support seeking after all;
1146 : * fall through to attempt emulation */
1147 : }
1148 :
1149 : /* emulate forward moving seeks with reads */
1150 2 : if (whence == SEEK_CUR && offset > 0) {
1151 : char tmp[1024];
1152 0 : while(offset >= sizeof(tmp)) {
1153 0 : if (php_stream_read(stream, tmp, sizeof(tmp)) == 0) {
1154 0 : return -1;
1155 : }
1156 0 : offset -= sizeof(tmp);
1157 : }
1158 0 : if (offset && (php_stream_read(stream, tmp, offset) == 0)) {
1159 0 : return -1;
1160 : }
1161 0 : stream->eof = 0;
1162 0 : return 0;
1163 : }
1164 :
1165 2 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream does not support seeking");
1166 :
1167 2 : return -1;
1168 : }
1169 :
1170 : PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC)
1171 108400 : {
1172 108400 : int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL;
1173 :
1174 108400 : if (stream->ops->set_option) {
1175 107703 : ret = stream->ops->set_option(stream, option, value, ptrparam TSRMLS_CC);
1176 : }
1177 :
1178 108400 : if (ret == PHP_STREAM_OPTION_RETURN_NOTIMPL) {
1179 28679 : switch(option) {
1180 : case PHP_STREAM_OPTION_SET_CHUNK_SIZE:
1181 0 : ret = stream->chunk_size;
1182 0 : stream->chunk_size = value;
1183 0 : return ret;
1184 :
1185 : case PHP_STREAM_OPTION_READ_BUFFER:
1186 : /* try to match the buffer mode as best we can */
1187 0 : if (value == PHP_STREAM_BUFFER_NONE) {
1188 0 : stream->flags |= PHP_STREAM_FLAG_NO_BUFFER;
1189 : } else {
1190 0 : stream->flags ^= PHP_STREAM_FLAG_NO_BUFFER;
1191 : }
1192 0 : ret = PHP_STREAM_OPTION_RETURN_OK;
1193 : break;
1194 :
1195 : default:
1196 : ;
1197 : }
1198 : }
1199 :
1200 108400 : return ret;
1201 : }
1202 :
1203 : PHPAPI int _php_stream_truncate_set_size(php_stream *stream, size_t newsize TSRMLS_DC)
1204 398 : {
1205 398 : return php_stream_set_option(stream, PHP_STREAM_OPTION_TRUNCATE_API, PHP_STREAM_TRUNCATE_SET_SIZE, &newsize);
1206 : }
1207 :
1208 : PHPAPI size_t _php_stream_passthru(php_stream * stream STREAMS_DC TSRMLS_DC)
1209 539 : {
1210 539 : size_t bcount = 0;
1211 : char buf[8192];
1212 : int b;
1213 :
1214 539 : if (php_stream_mmap_possible(stream)) {
1215 : char *p;
1216 : size_t mapped;
1217 :
1218 478 : p = php_stream_mmap_range(stream, php_stream_tell(stream), PHP_STREAM_MMAP_ALL, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
1219 :
1220 478 : if (p) {
1221 372 : PHPWRITE(p, mapped);
1222 :
1223 372 : php_stream_mmap_unmap_ex(stream, mapped);
1224 :
1225 372 : return mapped;
1226 : }
1227 : }
1228 :
1229 412 : while ((b = php_stream_read(stream, buf, sizeof(buf))) > 0) {
1230 78 : PHPWRITE(buf, b);
1231 78 : bcount += b;
1232 : }
1233 :
1234 167 : return bcount;
1235 : }
1236 :
1237 :
1238 : PHPAPI size_t _php_stream_copy_to_mem(php_stream *src, char **buf, size_t maxlen, int persistent STREAMS_DC TSRMLS_DC)
1239 2633 : {
1240 2633 : size_t ret = 0;
1241 : char *ptr;
1242 2633 : size_t len = 0, max_len;
1243 2633 : int step = CHUNK_SIZE;
1244 2633 : int min_room = CHUNK_SIZE / 4;
1245 : php_stream_statbuf ssbuf;
1246 :
1247 2633 : if (maxlen == 0) {
1248 10 : return 0;
1249 : }
1250 :
1251 2623 : if (maxlen == PHP_STREAM_COPY_ALL) {
1252 2550 : maxlen = 0;
1253 : }
1254 :
1255 2623 : if (maxlen > 0) {
1256 73 : ptr = *buf = pemalloc_rel_orig(maxlen + 1, persistent);
1257 218 : while ((len < maxlen) && !php_stream_eof(src)) {
1258 72 : ret = php_stream_read(src, ptr, maxlen - len);
1259 72 : len += ret;
1260 72 : ptr += ret;
1261 : }
1262 73 : *ptr = '\0';
1263 73 : return len;
1264 : }
1265 :
1266 : /* avoid many reallocs by allocating a good sized chunk to begin with, if
1267 : * we can. Note that the stream may be filtered, in which case the stat
1268 : * result may be inaccurate, as the filter may inflate or deflate the
1269 : * number of bytes that we can read. In order to avoid an upsize followed
1270 : * by a downsize of the buffer, overestimate by the step size (which is
1271 : * 2K). */
1272 4573 : if (php_stream_stat(src, &ssbuf) == 0 && ssbuf.sb.st_size > 0) {
1273 2023 : max_len = ssbuf.sb.st_size + step;
1274 : } else {
1275 527 : max_len = step;
1276 : }
1277 :
1278 2550 : ptr = *buf = pemalloc_rel_orig(max_len, persistent);
1279 :
1280 8608 : while((ret = php_stream_read(src, ptr, max_len - len))) {
1281 3508 : len += ret;
1282 3508 : if (len + min_room >= max_len) {
1283 14 : *buf = perealloc_rel_orig(*buf, max_len + step, persistent);
1284 14 : max_len += step;
1285 14 : ptr = *buf + len;
1286 : } else {
1287 3494 : ptr += ret;
1288 : }
1289 : }
1290 2550 : if (len) {
1291 2137 : *buf = perealloc_rel_orig(*buf, len + 1, persistent);
1292 2137 : (*buf)[len] = '\0';
1293 : } else {
1294 413 : pefree(*buf, persistent);
1295 413 : *buf = NULL;
1296 : }
1297 2550 : return len;
1298 : }
1299 :
1300 : /* Returns SUCCESS/FAILURE and sets *len to the number of bytes moved */
1301 : PHPAPI size_t _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t *len STREAMS_DC TSRMLS_DC)
1302 26738 : {
1303 : char buf[CHUNK_SIZE];
1304 : size_t readchunk;
1305 26738 : size_t haveread = 0;
1306 : size_t didread;
1307 : size_t dummy;
1308 : php_stream_statbuf ssbuf;
1309 :
1310 26738 : if (!len) {
1311 7223 : len = &dummy;
1312 : }
1313 :
1314 26738 : if (maxlen == 0) {
1315 4125 : *len = 0;
1316 4125 : return SUCCESS;
1317 : }
1318 :
1319 22613 : if (maxlen == PHP_STREAM_COPY_ALL) {
1320 12355 : maxlen = 0;
1321 : }
1322 :
1323 22613 : if (php_stream_stat(src, &ssbuf) == 0) {
1324 22611 : if (ssbuf.sb.st_size == 0
1325 : #ifdef S_ISREG
1326 : && S_ISREG(ssbuf.sb.st_mode)
1327 : #endif
1328 : ) {
1329 4122 : *len = 0;
1330 4122 : return SUCCESS;
1331 : }
1332 : }
1333 :
1334 18491 : if (php_stream_mmap_possible(src)) {
1335 : char *p;
1336 : size_t mapped;
1337 :
1338 18467 : p = php_stream_mmap_range(src, php_stream_tell(src), maxlen, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
1339 :
1340 18467 : if (p) {
1341 13924 : mapped = php_stream_write(dest, p, mapped);
1342 :
1343 13924 : php_stream_mmap_unmap_ex(src, mapped);
1344 :
1345 13924 : *len = mapped;
1346 :
1347 : /* we've got at least 1 byte to read.
1348 : * less than 1 is an error */
1349 :
1350 13924 : if (mapped > 0) {
1351 13924 : return SUCCESS;
1352 : }
1353 0 : return FAILURE;
1354 : }
1355 : }
1356 :
1357 : while(1) {
1358 4599 : readchunk = sizeof(buf);
1359 :
1360 4599 : if (maxlen && (maxlen - haveread) < readchunk) {
1361 4558 : readchunk = maxlen - haveread;
1362 : }
1363 :
1364 4599 : didread = php_stream_read(src, buf, readchunk);
1365 :
1366 4599 : if (didread) {
1367 : /* extra paranoid */
1368 : size_t didwrite, towrite;
1369 : char *writeptr;
1370 :
1371 4589 : towrite = didread;
1372 4589 : writeptr = buf;
1373 4589 : haveread += didread;
1374 :
1375 13767 : while(towrite) {
1376 4589 : didwrite = php_stream_write(dest, writeptr, towrite);
1377 4589 : if (didwrite == 0) {
1378 0 : *len = haveread - (didread - towrite);
1379 0 : return FAILURE;
1380 : }
1381 :
1382 4589 : towrite -= didwrite;
1383 4589 : writeptr += didwrite;
1384 : }
1385 : } else {
1386 10 : break;
1387 : }
1388 :
1389 4589 : if (maxlen - haveread == 0) {
1390 4557 : break;
1391 : }
1392 32 : }
1393 :
1394 4567 : *len = haveread;
1395 :
1396 : /* we've got at least 1 byte to read.
1397 : * less than 1 is an error */
1398 :
1399 4567 : if (haveread > 0) {
1400 4565 : return SUCCESS;
1401 : }
1402 2 : return FAILURE;
1403 : }
1404 :
1405 : /* Returns the number of bytes moved.
1406 : * Returns 1 when source len is 0.
1407 : * Deprecated in favor of php_stream_copy_to_stream_ex() */
1408 : ZEND_ATTRIBUTE_DEPRECATED
1409 : PHPAPI size_t _php_stream_copy_to_stream(php_stream *src, php_stream *dest, size_t maxlen STREAMS_DC TSRMLS_DC)
1410 24 : {
1411 : size_t len;
1412 24 : int ret = _php_stream_copy_to_stream_ex(src, dest, maxlen, &len STREAMS_REL_CC TSRMLS_CC);
1413 24 : if (ret == SUCCESS && len == 0 && maxlen != 0) {
1414 0 : return 1;
1415 : }
1416 24 : return len;
1417 : }
1418 : /* }}} */
1419 :
1420 : /* {{{ wrapper init and registration */
1421 :
1422 : static void stream_resource_regular_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
1423 192820 : {
1424 192820 : php_stream *stream = (php_stream*)rsrc->ptr;
1425 : /* set the return value for pclose */
1426 192820 : FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
1427 192819 : }
1428 :
1429 : static void stream_resource_persistent_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
1430 1006 : {
1431 1006 : php_stream *stream = (php_stream*)rsrc->ptr;
1432 1006 : FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
1433 1006 : }
1434 :
1435 : void php_shutdown_stream_hashes(TSRMLS_D)
1436 17651 : {
1437 17651 : if (FG(stream_wrappers)) {
1438 26 : zend_hash_destroy(FG(stream_wrappers));
1439 26 : efree(FG(stream_wrappers));
1440 26 : FG(stream_wrappers) = NULL;
1441 : }
1442 :
1443 17651 : if (FG(stream_filters)) {
1444 9 : zend_hash_destroy(FG(stream_filters));
1445 9 : efree(FG(stream_filters));
1446 9 : FG(stream_filters) = NULL;
1447 : }
1448 17651 : }
1449 :
1450 : int php_init_stream_wrappers(int module_number TSRMLS_DC)
1451 17633 : {
1452 17633 : le_stream = zend_register_list_destructors_ex(stream_resource_regular_dtor, NULL, "stream", module_number);
1453 17633 : le_pstream = zend_register_list_destructors_ex(NULL, stream_resource_persistent_dtor, "persistent stream", module_number);
1454 :
1455 : /* Filters are cleaned up by the streams they're attached to */
1456 17633 : le_stream_filter = zend_register_list_destructors_ex(NULL, NULL, "stream filter", module_number);
1457 :
1458 17633 : return (
1459 : zend_hash_init(&url_stream_wrappers_hash, 0, NULL, NULL, 1) == SUCCESS
1460 : &&
1461 : zend_hash_init(php_get_stream_filters_hash_global(), 0, NULL, NULL, 1) == SUCCESS
1462 : &&
1463 : zend_hash_init(php_stream_xport_get_hash(), 0, NULL, NULL, 1) == SUCCESS
1464 : &&
1465 : php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1466 : &&
1467 : php_stream_xport_register("udp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1468 : #if defined(AF_UNIX) && !(defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE))
1469 : &&
1470 : php_stream_xport_register("unix", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1471 : &&
1472 : php_stream_xport_register("udg", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1473 : #endif
1474 : ) ? SUCCESS : FAILURE;
1475 : }
1476 :
1477 : int php_shutdown_stream_wrappers(int module_number TSRMLS_DC)
1478 17665 : {
1479 17665 : zend_hash_destroy(&url_stream_wrappers_hash);
1480 17665 : zend_hash_destroy(php_get_stream_filters_hash_global());
1481 17665 : zend_hash_destroy(php_stream_xport_get_hash());
1482 17665 : return SUCCESS;
1483 : }
1484 :
1485 : /* Validate protocol scheme names during registration
1486 : * Must conform to /^[a-zA-Z0-9+.-]+$/
1487 : */
1488 : static inline int php_stream_wrapper_scheme_validate(char *protocol, int protocol_len)
1489 317427 : {
1490 : int i;
1491 :
1492 1939848 : for(i = 0; i < protocol_len; i++) {
1493 1622421 : if (!isalnum((int)protocol[i]) &&
1494 : protocol[i] != '+' &&
1495 : protocol[i] != '-' &&
1496 : protocol[i] != '.') {
1497 0 : return FAILURE;
1498 : }
1499 : }
1500 :
1501 317427 : return SUCCESS;
1502 : }
1503 :
1504 : /* API for registering GLOBAL wrappers */
1505 : PHPAPI int php_register_url_stream_wrapper(char *protocol, php_stream_wrapper *wrapper TSRMLS_DC)
1506 317394 : {
1507 317394 : int protocol_len = strlen(protocol);
1508 :
1509 317394 : if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) {
1510 0 : return FAILURE;
1511 : }
1512 :
1513 317394 : return zend_hash_add(&url_stream_wrappers_hash, protocol, protocol_len + 1, &wrapper, sizeof(wrapper), NULL);
1514 : }
1515 :
1516 : PHPAPI int php_unregister_url_stream_wrapper(char *protocol TSRMLS_DC)
1517 335379 : {
1518 335379 : return zend_hash_del(&url_stream_wrappers_hash, protocol, strlen(protocol) + 1);
1519 : }
1520 :
1521 : static void clone_wrapper_hash(TSRMLS_D)
1522 26 : {
1523 : php_stream_wrapper *tmp;
1524 :
1525 26 : ALLOC_HASHTABLE(FG(stream_wrappers));
1526 26 : zend_hash_init(FG(stream_wrappers), zend_hash_num_elements(&url_stream_wrappers_hash), NULL, NULL, 1);
1527 26 : zend_hash_copy(FG(stream_wrappers), &url_stream_wrappers_hash, NULL, &tmp, sizeof(tmp));
1528 26 : }
1529 :
1530 : /* API for registering VOLATILE wrappers */
1531 : PHPAPI int php_register_url_stream_wrapper_volatile(char *protocol, php_stream_wrapper *wrapper TSRMLS_DC)
1532 33 : {
1533 33 : int protocol_len = strlen(protocol);
1534 :
1535 33 : if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) {
1536 0 : return FAILURE;
1537 : }
1538 :
1539 33 : if (!FG(stream_wrappers)) {
1540 25 : clone_wrapper_hash(TSRMLS_C);
1541 : }
1542 :
1543 33 : return zend_hash_add(FG(stream_wrappers), protocol, protocol_len + 1, &wrapper, sizeof(wrapper), NULL);
1544 : }
1545 :
1546 : PHPAPI int php_unregister_url_stream_wrapper_volatile(char *protocol TSRMLS_DC)
1547 2 : {
1548 2 : if (!FG(stream_wrappers)) {
1549 1 : clone_wrapper_hash(TSRMLS_C);
1550 : }
1551 :
1552 2 : return zend_hash_del(FG(stream_wrappers), protocol, strlen(protocol) + 1);
1553 : }
1554 : /* }}} */
1555 :
1556 : /* {{{ php_stream_locate_url_wrapper */
1557 : PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, char **path_for_open, int options TSRMLS_DC)
1558 474385 : {
1559 474385 : HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
1560 474385 : php_stream_wrapper **wrapperpp = NULL;
1561 474385 : const char *p, *protocol = NULL;
1562 474385 : int n = 0;
1563 :
1564 474385 : if (path_for_open) {
1565 301080 : *path_for_open = (char*)path;
1566 : }
1567 :
1568 474385 : if (options & IGNORE_URL) {
1569 5682 : return (options & STREAM_LOCATE_WRAPPERS_ONLY) ? NULL : &php_plain_files_wrapper;
1570 : }
1571 :
1572 675779 : for (p = path; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
1573 207076 : n++;
1574 : }
1575 :
1576 523012 : if ((*p == ':') && (n > 1) && (!strncmp("//", p+1, 2) || (n == 4 && !memcmp("data:", path, 5)))) {
1577 54309 : protocol = path;
1578 414394 : } else if (n == 5 && strncasecmp(path, "zlib:", 5) == 0) {
1579 : /* BC with older php scripts and zlib wrapper */
1580 0 : protocol = "compress.zlib";
1581 0 : n = 13;
1582 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Use of \"zlib:\" wrapper is deprecated; please use \"compress.zlib://\" instead");
1583 : }
1584 :
1585 468703 : if (protocol) {
1586 54309 : char *tmp = estrndup(protocol, n);
1587 54309 : if (FAILURE == zend_hash_find(wrapper_hash, (char*)tmp, n + 1, (void**)&wrapperpp)) {
1588 3 : php_strtolower(tmp, n);
1589 3 : if (FAILURE == zend_hash_find(wrapper_hash, (char*)tmp, n + 1, (void**)&wrapperpp)) {
1590 : char wrapper_name[32];
1591 :
1592 2 : if (n >= sizeof(wrapper_name)) {
1593 0 : n = sizeof(wrapper_name) - 1;
1594 : }
1595 2 : PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n);
1596 :
1597 2 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to find the wrapper \"%s\" - did you forget to enable it when you configured PHP?", wrapper_name);
1598 :
1599 2 : wrapperpp = NULL;
1600 2 : protocol = NULL;
1601 : }
1602 : }
1603 54309 : efree(tmp);
1604 : }
1605 : /* TODO: curl based streams probably support file:// properly */
1606 468703 : if (!protocol || !strncasecmp(protocol, "file", n)) {
1607 : /* fall back on regular file access */
1608 414441 : php_stream_wrapper *plain_files_wrapper = &php_plain_files_wrapper;
1609 :
1610 414441 : if (protocol) {
1611 45 : int localhost = 0;
1612 :
1613 45 : if (!strncasecmp(path, "file://localhost/", 17)) {
1614 0 : localhost = 1;
1615 : }
1616 :
1617 : #ifdef PHP_WIN32
1618 : if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/' && path[n+4] != ':') {
1619 : #else
1620 45 : if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/') {
1621 : #endif
1622 18 : if (options & REPORT_ERRORS) {
1623 12 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "remote host file access not supported, %s", path);
1624 : }
1625 18 : return NULL;
1626 : }
1627 :
1628 27 : if (path_for_open) {
1629 : /* skip past protocol and :/, but handle windows correctly */
1630 26 : *path_for_open = (char*)path + n + 1;
1631 26 : if (localhost == 1) {
1632 0 : (*path_for_open) += 11;
1633 : }
1634 79 : while (*(++*path_for_open)=='/');
1635 : #ifdef PHP_WIN32
1636 : if (*(*path_for_open + 1) != ':')
1637 : #endif
1638 26 : (*path_for_open)--;
1639 : }
1640 : }
1641 :
1642 414423 : if (options & STREAM_LOCATE_WRAPPERS_ONLY) {
1643 584 : return NULL;
1644 : }
1645 :
1646 413839 : if (FG(stream_wrappers)) {
1647 : /* The file:// wrapper may have been disabled/overridden */
1648 :
1649 11 : if (wrapperpp) {
1650 : /* It was found so go ahead and provide it */
1651 1 : return *wrapperpp;
1652 : }
1653 :
1654 : /* Check again, the original check might have not known the protocol name */
1655 10 : if (zend_hash_find(wrapper_hash, "file", sizeof("file"), (void**)&wrapperpp) == SUCCESS) {
1656 9 : return *wrapperpp;
1657 : }
1658 :
1659 1 : if (options & REPORT_ERRORS) {
1660 1 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "file:// wrapper is disabled in the server configuration");
1661 : }
1662 1 : return NULL;
1663 : }
1664 :
1665 413828 : return plain_files_wrapper;
1666 : }
1667 :
1668 54262 : if (wrapperpp && (*wrapperpp)->is_url &&
1669 : (options & STREAM_DISABLE_URL_PROTECTION) == 0 &&
1670 : (!PG(allow_url_fopen) ||
1671 : (((options & STREAM_OPEN_FOR_INCLUDE) ||
1672 : PG(in_user_include)) && !PG(allow_url_include)))) {
1673 10 : if (options & REPORT_ERRORS) {
1674 : /* protocol[n] probably isn't '\0' */
1675 7 : char *protocol_dup = estrndup(protocol, n);
1676 7 : if (!PG(allow_url_fopen)) {
1677 4 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_fopen=0", protocol_dup);
1678 : } else {
1679 3 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_include=0", protocol_dup);
1680 : }
1681 7 : efree(protocol_dup);
1682 : }
1683 10 : return NULL;
1684 : }
1685 :
1686 54252 : return *wrapperpp;
1687 : }
1688 : /* }}} */
1689 :
1690 : /* {{{ _php_stream_mkdir
1691 : */
1692 : PHPAPI int _php_stream_mkdir(char *path, int mode, int options, php_stream_context *context TSRMLS_DC)
1693 1675 : {
1694 1675 : php_stream_wrapper *wrapper = NULL;
1695 :
1696 1675 : wrapper = php_stream_locate_url_wrapper(path, NULL, ENFORCE_SAFE_MODE TSRMLS_CC);
1697 1675 : if (!wrapper || !wrapper->wops || !wrapper->wops->stream_mkdir) {
1698 1 : return 0;
1699 : }
1700 :
1701 1674 : return wrapper->wops->stream_mkdir(wrapper, path, mode, options, context TSRMLS_CC);
1702 : }
1703 : /* }}} */
1704 :
1705 : /* {{{ _php_stream_rmdir
1706 : */
1707 : PHPAPI int _php_stream_rmdir(char *path, int options, php_stream_context *context TSRMLS_DC)
1708 1693 : {
1709 1693 : php_stream_wrapper *wrapper = NULL;
1710 :
1711 1693 : wrapper = php_stream_locate_url_wrapper(path, NULL, ENFORCE_SAFE_MODE TSRMLS_CC);
1712 1693 : if (!wrapper || !wrapper->wops || !wrapper->wops->stream_rmdir) {
1713 1 : return 0;
1714 : }
1715 :
1716 1692 : return wrapper->wops->stream_rmdir(wrapper, path, options, context TSRMLS_CC);
1717 : }
1718 : /* }}} */
1719 :
1720 : /* {{{ _php_stream_stat_path */
1721 : PHPAPI int _php_stream_stat_path(char *path, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
1722 87621 : {
1723 87621 : php_stream_wrapper *wrapper = NULL;
1724 87621 : char *path_to_open = path;
1725 : int ret;
1726 :
1727 : /* Try to hit the cache first */
1728 87621 : if (flags & PHP_STREAM_URL_STAT_LINK) {
1729 3194 : if (BG(CurrentLStatFile) && strcmp(path, BG(CurrentLStatFile)) == 0) {
1730 8 : memcpy(ssb, &BG(lssb), sizeof(php_stream_statbuf));
1731 8 : return 0;
1732 : }
1733 : } else {
1734 84427 : if (BG(CurrentStatFile) && strcmp(path, BG(CurrentStatFile)) == 0) {
1735 4969 : memcpy(ssb, &BG(ssb), sizeof(php_stream_statbuf));
1736 4969 : return 0;
1737 : }
1738 : }
1739 :
1740 82644 : wrapper = php_stream_locate_url_wrapper(path, &path_to_open, ENFORCE_SAFE_MODE TSRMLS_CC);
1741 82644 : if (wrapper && wrapper->wops->url_stat) {
1742 82631 : ret = wrapper->wops->url_stat(wrapper, path_to_open, flags, ssb, context TSRMLS_CC);
1743 82631 : if (ret == 0) {
1744 : /* Drop into cache */
1745 81496 : if (flags & PHP_STREAM_URL_STAT_LINK) {
1746 3161 : if (BG(CurrentLStatFile)) {
1747 3085 : efree(BG(CurrentLStatFile));
1748 : }
1749 3161 : BG(CurrentLStatFile) = estrdup(path);
1750 3161 : memcpy(&BG(lssb), ssb, sizeof(php_stream_statbuf));
1751 : } else {
1752 78335 : if (BG(CurrentStatFile)) {
1753 59001 : efree(BG(CurrentStatFile));
1754 : }
1755 78335 : BG(CurrentStatFile) = estrdup(path);
1756 78335 : memcpy(&BG(ssb), ssb, sizeof(php_stream_statbuf));
1757 : }
1758 : }
1759 82631 : return ret;
1760 : }
1761 13 : return -1;
1762 : }
1763 : /* }}} */
1764 :
1765 : /* {{{ php_stream_opendir */
1766 : PHPAPI php_stream *_php_stream_opendir(char *path, int options,
1767 : php_stream_context *context STREAMS_DC TSRMLS_DC)
1768 3749 : {
1769 3749 : php_stream *stream = NULL;
1770 3749 : php_stream_wrapper *wrapper = NULL;
1771 : char *path_to_open;
1772 :
1773 3749 : if (!path || !*path) {
1774 16 : return NULL;
1775 : }
1776 :
1777 3733 : path_to_open = path;
1778 :
1779 3733 : wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC);
1780 :
1781 7463 : if (wrapper && wrapper->wops->dir_opener) {
1782 3730 : stream = wrapper->wops->dir_opener(wrapper,
1783 : path_to_open, "r", options ^ REPORT_ERRORS, NULL,
1784 : context STREAMS_REL_CC TSRMLS_CC);
1785 :
1786 3730 : if (stream) {
1787 3638 : stream->wrapper = wrapper;
1788 3638 : stream->flags |= PHP_STREAM_FLAG_NO_BUFFER | PHP_STREAM_FLAG_IS_DIR;
1789 : }
1790 3 : } else if (wrapper) {
1791 3 : php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC, "not implemented");
1792 : }
1793 3733 : if (stream == NULL && (options & REPORT_ERRORS)) {
1794 95 : php_stream_display_wrapper_errors(wrapper, path, "failed to open dir" TSRMLS_CC);
1795 : }
1796 3733 : php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC);
1797 :
1798 3733 : return stream;
1799 : }
1800 : /* }}} */
1801 :
1802 : /* {{{ _php_stream_readdir */
1803 : PHPAPI php_stream_dirent *_php_stream_readdir(php_stream *dirstream, php_stream_dirent *ent TSRMLS_DC)
1804 68217 : {
1805 :
1806 68217 : if (sizeof(php_stream_dirent) == php_stream_read(dirstream, (char*)ent, sizeof(php_stream_dirent))) {
1807 64916 : return ent;
1808 : }
1809 :
1810 3301 : return NULL;
1811 : }
1812 : /* }}} */
1813 :
1814 : /* {{{ php_stream_open_wrapper_ex */
1815 : PHPAPI php_stream *_php_stream_open_wrapper_ex(char *path, char *mode, int options,
1816 : char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
1817 122182 : {
1818 122182 : php_stream *stream = NULL;
1819 122182 : php_stream_wrapper *wrapper = NULL;
1820 : char *path_to_open;
1821 122182 : int persistent = options & STREAM_OPEN_PERSISTENT;
1822 122182 : char *resolved_path = NULL;
1823 122182 : char *copy_of_path = NULL;
1824 :
1825 :
1826 122182 : if (opened_path) {
1827 11874 : *opened_path = NULL;
1828 : }
1829 :
1830 122182 : if (!path || !*path) {
1831 69 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Filename cannot be empty");
1832 69 : return NULL;
1833 : }
1834 :
1835 122113 : if (options & USE_PATH) {
1836 8132 : resolved_path = zend_resolve_path(path, strlen(path) TSRMLS_CC);
1837 8132 : if (resolved_path) {
1838 7772 : path = resolved_path;
1839 : /* we've found this file, don't re-check include_path or run realpath */
1840 7772 : options |= STREAM_ASSUME_REALPATH;
1841 7772 : options &= ~USE_PATH;
1842 : }
1843 : }
1844 :
1845 122113 : path_to_open = path;
1846 :
1847 122113 : wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC);
1848 122113 : if (options & STREAM_USE_URL && (!wrapper || !wrapper->is_url)) {
1849 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function may only be used against URLs");
1850 0 : if (resolved_path) {
1851 0 : efree(resolved_path);
1852 : }
1853 0 : return NULL;
1854 : }
1855 :
1856 122113 : if (wrapper) {
1857 122093 : if (!wrapper->wops->stream_opener) {
1858 0 : php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC,
1859 : "wrapper does not support stream open");
1860 : } else {
1861 122093 : stream = wrapper->wops->stream_opener(wrapper,
1862 : path_to_open, mode, options ^ REPORT_ERRORS,
1863 : opened_path, context STREAMS_REL_CC TSRMLS_CC);
1864 : }
1865 :
1866 : /* if the caller asked for a persistent stream but the wrapper did not
1867 : * return one, force an error here */
1868 122092 : if (stream && (options & STREAM_OPEN_PERSISTENT) && !stream->is_persistent) {
1869 0 : php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC,
1870 : "wrapper does not support persistent streams");
1871 0 : php_stream_close(stream);
1872 0 : stream = NULL;
1873 : }
1874 :
1875 122092 : if (stream) {
1876 121349 : stream->wrapper = wrapper;
1877 : }
1878 : }
1879 :
1880 122112 : if (stream) {
1881 121349 : if (opened_path && !*opened_path && resolved_path) {
1882 2 : *opened_path = resolved_path;
1883 2 : resolved_path = NULL;
1884 : }
1885 121349 : if (stream->orig_path) {
1886 1 : pefree(stream->orig_path, persistent);
1887 : }
1888 121349 : copy_of_path = pestrdup(path, persistent);
1889 121349 : stream->orig_path = copy_of_path;
1890 : #if ZEND_DEBUG
1891 : stream->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename;
1892 : stream->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno;
1893 : #endif
1894 : }
1895 :
1896 122112 : if (stream != NULL && (options & STREAM_MUST_SEEK)) {
1897 : php_stream *newstream;
1898 :
1899 8946 : switch(php_stream_make_seekable_rel(stream, &newstream,
1900 : (options & STREAM_WILL_CAST)
1901 : ? PHP_STREAM_PREFER_STDIO : PHP_STREAM_NO_PREFERENCE)) {
1902 : case PHP_STREAM_UNCHANGED:
1903 8946 : if (resolved_path) {
1904 42 : efree(resolved_path);
1905 : }
1906 8946 : return stream;
1907 : case PHP_STREAM_RELEASED:
1908 0 : if (newstream->orig_path) {
1909 0 : pefree(newstream->orig_path, persistent);
1910 : }
1911 0 : newstream->orig_path = pestrdup(path, persistent);
1912 0 : if (resolved_path) {
1913 0 : efree(resolved_path);
1914 : }
1915 0 : return newstream;
1916 : default:
1917 0 : php_stream_close(stream);
1918 0 : stream = NULL;
1919 0 : if (options & REPORT_ERRORS) {
1920 0 : char *tmp = estrdup(path);
1921 0 : php_strip_url_passwd(tmp);
1922 0 : php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "could not make seekable - %s",
1923 : tmp);
1924 0 : efree(tmp);
1925 :
1926 0 : options ^= REPORT_ERRORS;
1927 : }
1928 : }
1929 : }
1930 :
1931 113166 : if (stream && stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && strchr(mode, 'a') && stream->position == 0) {
1932 1367 : off_t newpos = 0;
1933 :
1934 : /* if opened for append, we need to revise our idea of the initial file position */
1935 1367 : if (0 == stream->ops->seek(stream, 0, SEEK_CUR, &newpos TSRMLS_CC)) {
1936 1367 : stream->position = newpos;
1937 : }
1938 : }
1939 :
1940 113166 : if (stream == NULL && (options & REPORT_ERRORS)) {
1941 345 : php_stream_display_wrapper_errors(wrapper, path, "failed to open stream" TSRMLS_CC);
1942 345 : if (opened_path && *opened_path) {
1943 0 : efree(*opened_path);
1944 0 : *opened_path = NULL;
1945 : }
1946 : }
1947 113166 : php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC);
1948 : #if ZEND_DEBUG
1949 : if (stream == NULL && copy_of_path != NULL) {
1950 : pefree(copy_of_path, persistent);
1951 : }
1952 : #endif
1953 113166 : if (resolved_path) {
1954 7728 : efree(resolved_path);
1955 : }
1956 113166 : return stream;
1957 : }
1958 : /* }}} */
1959 :
1960 : /* {{{ context API */
1961 : PHPAPI php_stream_context *php_stream_context_set(php_stream *stream, php_stream_context *context)
1962 1829 : {
1963 1829 : php_stream_context *oldcontext = stream->context;
1964 : TSRMLS_FETCH();
1965 :
1966 1829 : stream->context = context;
1967 :
1968 1829 : if (context) {
1969 125 : zend_list_addref(context->rsrc_id);
1970 : }
1971 1829 : if (oldcontext) {
1972 0 : zend_list_delete(oldcontext->rsrc_id);
1973 : }
1974 :
1975 1829 : return oldcontext;
1976 : }
1977 :
1978 : PHPAPI void php_stream_notification_notify(php_stream_context *context, int notifycode, int severity,
1979 : char *xmsg, int xcode, size_t bytes_sofar, size_t bytes_max, void * ptr TSRMLS_DC)
1980 0 : {
1981 0 : if (context && context->notifier)
1982 0 : context->notifier->func(context, notifycode, severity, xmsg, xcode, bytes_sofar, bytes_max, ptr TSRMLS_CC);
1983 0 : }
1984 :
1985 : PHPAPI void php_stream_context_free(php_stream_context *context)
1986 3545 : {
1987 3545 : if (context->options) {
1988 0 : zval_ptr_dtor(&context->options);
1989 0 : context->options = NULL;
1990 : }
1991 3545 : if (context->notifier) {
1992 1 : php_stream_notification_free(context->notifier);
1993 1 : context->notifier = NULL;
1994 : }
1995 3545 : if (context->links) {
1996 0 : zval_ptr_dtor(&context->links);
1997 0 : context->links = NULL;
1998 : }
1999 3545 : efree(context);
2000 3545 : }
2001 :
2002 : PHPAPI php_stream_context *php_stream_context_alloc(void)
2003 3519 : {
2004 : php_stream_context *context;
2005 :
2006 3519 : context = ecalloc(1, sizeof(php_stream_context));
2007 3519 : context->notifier = NULL;
2008 3519 : MAKE_STD_ZVAL(context->options);
2009 3519 : array_init(context->options);
2010 :
2011 3519 : context->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, context, php_le_stream_context());
2012 3519 : return context;
2013 : }
2014 :
2015 : PHPAPI php_stream_notifier *php_stream_notification_alloc(void)
2016 2 : {
2017 2 : return ecalloc(1, sizeof(php_stream_notifier));
2018 : }
2019 :
2020 : PHPAPI void php_stream_notification_free(php_stream_notifier *notifier)
2021 2 : {
2022 2 : if (notifier->dtor) {
2023 2 : notifier->dtor(notifier);
2024 : }
2025 2 : efree(notifier);
2026 2 : }
2027 :
2028 : PHPAPI int php_stream_context_get_option(php_stream_context *context,
2029 : const char *wrappername, const char *optionname, zval ***optionvalue)
2030 926 : {
2031 : zval **wrapperhash;
2032 :
2033 926 : if (FAILURE == zend_hash_find(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&wrapperhash)) {
2034 796 : return FAILURE;
2035 : }
2036 130 : return zend_hash_find(Z_ARRVAL_PP(wrapperhash), (char*)optionname, strlen(optionname)+1, (void**)optionvalue);
2037 : }
2038 :
2039 : PHPAPI int php_stream_context_set_option(php_stream_context *context,
2040 : const char *wrappername, const char *optionname, zval *optionvalue)
2041 1481 : {
2042 : zval **wrapperhash;
2043 : zval *category, *copied_val;
2044 :
2045 1481 : ALLOC_INIT_ZVAL(copied_val);
2046 1481 : *copied_val = *optionvalue;
2047 1481 : zval_copy_ctor(copied_val);
2048 1481 : INIT_PZVAL(copied_val);
2049 :
2050 1481 : if (FAILURE == zend_hash_find(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&wrapperhash)) {
2051 744 : MAKE_STD_ZVAL(category);
2052 744 : array_init(category);
2053 744 : if (FAILURE == zend_hash_update(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&category, sizeof(zval *), NULL)) {
2054 0 : return FAILURE;
2055 : }
2056 :
2057 744 : wrapperhash = &category;
2058 : }
2059 1481 : return zend_hash_update(Z_ARRVAL_PP(wrapperhash), (char*)optionname, strlen(optionname)+1, (void**)&copied_val, sizeof(zval *), NULL);
2060 : }
2061 :
2062 : PHPAPI int php_stream_context_get_link(php_stream_context *context,
2063 : const char *hostent, php_stream **stream)
2064 0 : {
2065 : php_stream **pstream;
2066 :
2067 0 : if (!stream || !hostent || !context || !(context->links)) {
2068 0 : return FAILURE;
2069 : }
2070 0 : if (SUCCESS == zend_hash_find(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1, (void**)&pstream)) {
2071 0 : *stream = *pstream;
2072 0 : return SUCCESS;
2073 : }
2074 0 : return FAILURE;
2075 : }
2076 :
2077 : PHPAPI int php_stream_context_set_link(php_stream_context *context,
2078 : const char *hostent, php_stream *stream)
2079 0 : {
2080 0 : if (!context) {
2081 0 : return FAILURE;
2082 : }
2083 0 : if (!context->links) {
2084 0 : ALLOC_INIT_ZVAL(context->links);
2085 0 : array_init(context->links);
2086 : }
2087 0 : if (!stream) {
2088 : /* Delete any entry for <hostent> */
2089 0 : return zend_hash_del(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1);
2090 : }
2091 0 : return zend_hash_update(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1, (void**)&stream, sizeof(php_stream *), NULL);
2092 : }
2093 :
2094 : PHPAPI int php_stream_context_del_link(php_stream_context *context,
2095 : php_stream *stream)
2096 0 : {
2097 : php_stream **pstream;
2098 : char *hostent;
2099 0 : int ret = SUCCESS;
2100 :
2101 0 : if (!context || !context->links || !stream) {
2102 0 : return FAILURE;
2103 : }
2104 :
2105 0 : for(zend_hash_internal_pointer_reset(Z_ARRVAL_P(context->links));
2106 0 : SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(context->links), (void**)&pstream);
2107 0 : zend_hash_move_forward(Z_ARRVAL_P(context->links))) {
2108 0 : if (*pstream == stream) {
2109 0 : if (SUCCESS == zend_hash_get_current_key(Z_ARRVAL_P(context->links), &hostent, NULL, 0)) {
2110 0 : if (FAILURE == zend_hash_del(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1)) {
2111 0 : ret = FAILURE;
2112 : }
2113 : } else {
2114 0 : ret = FAILURE;
2115 : }
2116 : }
2117 : }
2118 :
2119 0 : return ret;
2120 : }
2121 : /* }}} */
2122 :
2123 : /* {{{ php_stream_dirent_alphasort
2124 : */
2125 : PHPAPI int php_stream_dirent_alphasort(const char **a, const char **b)
2126 124 : {
2127 124 : return strcoll(*a, *b);
2128 : }
2129 : /* }}} */
2130 :
2131 : /* {{{ php_stream_dirent_alphasortr
2132 : */
2133 : PHPAPI int php_stream_dirent_alphasortr(const char **a, const char **b)
2134 24 : {
2135 24 : return strcoll(*b, *a);
2136 : }
2137 : /* }}} */
2138 :
2139 : /* {{{ php_stream_scandir
2140 : */
2141 : PHPAPI int _php_stream_scandir(char *dirname, char **namelist[], int flags, php_stream_context *context,
2142 : int (*compare) (const char **a, const char **b) TSRMLS_DC)
2143 76 : {
2144 : php_stream *stream;
2145 : php_stream_dirent sdp;
2146 76 : char **vector = NULL;
2147 76 : int vector_size = 0;
2148 76 : int nfiles = 0;
2149 :
2150 76 : if (!namelist) {
2151 0 : return FAILURE;
2152 : }
2153 :
2154 76 : stream = php_stream_opendir(dirname, ENFORCE_SAFE_MODE | REPORT_ERRORS, context);
2155 76 : if (!stream) {
2156 31 : return FAILURE;
2157 : }
2158 :
2159 229 : while (php_stream_readdir(stream, &sdp)) {
2160 139 : if (nfiles == vector_size) {
2161 46 : if (vector_size == 0) {
2162 45 : vector_size = 10;
2163 : } else {
2164 1 : vector_size *= 2;
2165 : }
2166 46 : vector = (char **) erealloc(vector, vector_size * sizeof(char *));
2167 : }
2168 :
2169 139 : vector[nfiles] = estrdup(sdp.d_name);
2170 :
2171 139 : nfiles++;
2172 : }
2173 45 : php_stream_closedir(stream);
2174 :
2175 45 : *namelist = vector;
2176 :
2177 45 : if (compare) {
2178 45 : qsort(*namelist, nfiles, sizeof(char *), (int(*)(const void *, const void *))compare);
2179 : }
2180 45 : return nfiles;
2181 : }
2182 : /* }}} */
2183 :
2184 : /*
2185 : * Local variables:
2186 : * tab-width: 4
2187 : * c-basic-offset: 4
2188 : * End:
2189 : * vim600: noet sw=4 ts=4 fdm=marker
2190 : * vim<600: noet sw=4 ts=4
2191 : */
|