/src/php-src/main/streams/cast.c
Line | Count | Source (jump to first uncovered line) |
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 | | | Authors: Wez Furlong <wez@thebrainroom.com> | |
14 | | +----------------------------------------------------------------------+ |
15 | | */ |
16 | | |
17 | | #ifndef _GNU_SOURCE |
18 | | # define _GNU_SOURCE |
19 | | #endif |
20 | | #include "php.h" |
21 | | #include "php_globals.h" |
22 | | #include "php_network.h" |
23 | | #include "php_open_temporary_file.h" |
24 | | #include "ext/standard/file.h" |
25 | | #include <stddef.h> |
26 | | #include <fcntl.h> |
27 | | |
28 | | #include "php_streams_int.h" |
29 | | |
30 | | /* Under BSD, emulate fopencookie using funopen */ |
31 | | #if defined(HAVE_FUNOPEN) && !defined(HAVE_FOPENCOOKIE) |
32 | | |
33 | | /* NetBSD 6.0+ uses off_t instead of fpos_t in funopen */ |
34 | | # if defined(__NetBSD__) && (__NetBSD_Version__ >= 600000000) |
35 | | # define PHP_FPOS_T off_t |
36 | | # else |
37 | | # define PHP_FPOS_T fpos_t |
38 | | # endif |
39 | | |
40 | | typedef struct { |
41 | | int (*reader)(void *, char *, int); |
42 | | int (*writer)(void *, const char *, int); |
43 | | PHP_FPOS_T (*seeker)(void *, PHP_FPOS_T, int); |
44 | | int (*closer)(void *); |
45 | | } cookie_io_functions_t; |
46 | | |
47 | | FILE *fopencookie(void *cookie, const char *mode, cookie_io_functions_t *funcs) |
48 | | { |
49 | | FILE *file = funopen(cookie, funcs->reader, funcs->writer, funcs->seeker, funcs->closer); |
50 | | if (file) { |
51 | | /* Buffering of FILE handles is stateful. |
52 | | * A bailout during these can corrupt the state of the FILE handle |
53 | | * and cause memory corruption errors. See GH-11078. */ |
54 | | setvbuf(file, NULL, _IONBF, 0); |
55 | | } |
56 | | return file; |
57 | | } |
58 | | # define HAVE_FOPENCOOKIE 1 |
59 | | # define PHP_EMULATE_FOPENCOOKIE 1 |
60 | | # define PHP_STREAM_COOKIE_FUNCTIONS &stream_cookie_functions |
61 | | #elif defined(HAVE_FOPENCOOKIE) |
62 | 0 | # define PHP_STREAM_COOKIE_FUNCTIONS stream_cookie_functions |
63 | | #endif |
64 | | |
65 | | /* {{{ STDIO with fopencookie */ |
66 | | #if defined(PHP_EMULATE_FOPENCOOKIE) |
67 | | /* use our fopencookie emulation */ |
68 | | static int stream_cookie_reader(void *cookie, char *buffer, int size) |
69 | | { |
70 | | int ret; |
71 | | |
72 | | ret = php_stream_read((php_stream*)cookie, buffer, size); |
73 | | return ret; |
74 | | } |
75 | | |
76 | | static int stream_cookie_writer(void *cookie, const char *buffer, int size) |
77 | | { |
78 | | |
79 | | return php_stream_write((php_stream *)cookie, (char *)buffer, size); |
80 | | } |
81 | | |
82 | | static PHP_FPOS_T stream_cookie_seeker(void *cookie, zend_off_t position, int whence) |
83 | | { |
84 | | |
85 | | return (PHP_FPOS_T)php_stream_seek((php_stream *)cookie, position, whence); |
86 | | } |
87 | | |
88 | | static int stream_cookie_closer(void *cookie) |
89 | | { |
90 | | php_stream *stream = (php_stream*)cookie; |
91 | | |
92 | | /* prevent recursion */ |
93 | | stream->fclose_stdiocast = PHP_STREAM_FCLOSE_NONE; |
94 | | return php_stream_free(stream, |
95 | | PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_KEEP_RSRC | PHP_STREAM_FREE_RSRC_DTOR); |
96 | | } |
97 | | #elif defined(HAVE_FOPENCOOKIE) |
98 | | static ssize_t stream_cookie_reader(void *cookie, char *buffer, size_t size) |
99 | 0 | { |
100 | 0 | ssize_t ret; |
101 | |
|
102 | 0 | ret = php_stream_read(((php_stream *)cookie), buffer, size); |
103 | 0 | return ret; |
104 | 0 | } |
105 | | |
106 | | static ssize_t stream_cookie_writer(void *cookie, const char *buffer, size_t size) |
107 | 0 | { |
108 | |
|
109 | 0 | return php_stream_write(((php_stream *)cookie), (char *)buffer, size); |
110 | 0 | } |
111 | | |
112 | | # ifdef COOKIE_SEEKER_USES_OFF64_T |
113 | | static int stream_cookie_seeker(void *cookie, off64_t *position, int whence) |
114 | | # else |
115 | | static int stream_cookie_seeker(void *cookie, off_t *position, int whence) |
116 | | # endif |
117 | 0 | { |
118 | |
|
119 | 0 | *position = php_stream_seek((php_stream *)cookie, (zend_off_t)*position, whence); |
120 | |
|
121 | 0 | if (*position == -1) { |
122 | 0 | return -1; |
123 | 0 | } |
124 | 0 | return 0; |
125 | 0 | } |
126 | | |
127 | | static int stream_cookie_closer(void *cookie) |
128 | 0 | { |
129 | 0 | php_stream *stream = (php_stream*)cookie; |
130 | | |
131 | | /* prevent recursion */ |
132 | 0 | stream->fclose_stdiocast = PHP_STREAM_FCLOSE_NONE; |
133 | 0 | return php_stream_free(stream, |
134 | 0 | PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_KEEP_RSRC | PHP_STREAM_FREE_RSRC_DTOR); |
135 | 0 | } |
136 | | #endif /* elif defined(HAVE_FOPENCOOKIE) */ |
137 | | |
138 | | #ifdef HAVE_FOPENCOOKIE |
139 | | static cookie_io_functions_t stream_cookie_functions = |
140 | | { |
141 | | stream_cookie_reader, stream_cookie_writer, |
142 | | stream_cookie_seeker, stream_cookie_closer |
143 | | }; |
144 | | #else |
145 | | /* TODO: use socketpair() to emulate fopencookie, as suggested by Hartmut ? */ |
146 | | #endif |
147 | | /* }}} */ |
148 | | |
149 | | /* {{{ php_stream_mode_sanitize_fdopen_fopencookie |
150 | | * Result should have at least size 5, e.g. to write wbx+\0 */ |
151 | | void php_stream_mode_sanitize_fdopen_fopencookie(php_stream *stream, char *result) |
152 | 0 | { |
153 | | /* replace modes not supported by fdopen and fopencookie, but supported |
154 | | * by PHP's fread(), so that their calls won't fail */ |
155 | 0 | const char *cur_mode = stream->mode; |
156 | 0 | int has_plus = 0, |
157 | 0 | has_bin = 0, |
158 | 0 | i, |
159 | 0 | res_curs = 0; |
160 | |
|
161 | 0 | if (cur_mode[0] == 'r' || cur_mode[0] == 'w' || cur_mode[0] == 'a') { |
162 | 0 | result[res_curs++] = cur_mode[0]; |
163 | 0 | } else { |
164 | | /* assume cur_mode[0] is 'c' or 'x'; substitute by 'w', which should not |
165 | | * truncate anything in fdopen/fopencookie */ |
166 | 0 | result[res_curs++] = 'w'; |
167 | | |
168 | | /* x is allowed (at least by glibc & compat), but not as the 1st mode |
169 | | * as in PHP and in any case is (at best) ignored by fdopen and fopencookie */ |
170 | 0 | } |
171 | | |
172 | | /* assume current mode has at most length 4 (e.g. wbn+) */ |
173 | 0 | for (i = 1; i < 4 && cur_mode[i] != '\0'; i++) { |
174 | 0 | if (cur_mode[i] == 'b') { |
175 | 0 | has_bin = 1; |
176 | 0 | } else if (cur_mode[i] == '+') { |
177 | 0 | has_plus = 1; |
178 | 0 | } |
179 | | /* ignore 'n', 't' or other stuff */ |
180 | 0 | } |
181 | |
|
182 | 0 | if (has_bin) { |
183 | 0 | result[res_curs++] = 'b'; |
184 | 0 | } |
185 | 0 | if (has_plus) { |
186 | 0 | result[res_curs++] = '+'; |
187 | 0 | } |
188 | |
|
189 | 0 | result[res_curs] = '\0'; |
190 | 0 | } |
191 | | /* }}} */ |
192 | | |
193 | | /* {{{ php_stream_cast */ |
194 | | PHPAPI int _php_stream_cast(php_stream *stream, int castas, void **ret, int show_err) |
195 | 0 | { |
196 | 0 | int flags = castas & PHP_STREAM_CAST_MASK; |
197 | 0 | castas &= ~PHP_STREAM_CAST_MASK; |
198 | | |
199 | | /* synchronize our buffer (if possible) */ |
200 | 0 | if (ret && castas != PHP_STREAM_AS_FD_FOR_SELECT) { |
201 | 0 | php_stream_flush(stream); |
202 | 0 | if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) { |
203 | 0 | zend_off_t dummy; |
204 | |
|
205 | 0 | stream->ops->seek(stream, stream->position, SEEK_SET, &dummy); |
206 | 0 | stream->readpos = stream->writepos = 0; |
207 | 0 | } |
208 | 0 | } |
209 | | |
210 | | /* filtered streams can only be cast as stdio, and only when fopencookie is present */ |
211 | |
|
212 | 0 | if (castas == PHP_STREAM_AS_STDIO) { |
213 | 0 | if (stream->stdiocast) { |
214 | 0 | if (ret) { |
215 | 0 | *(FILE**)ret = stream->stdiocast; |
216 | 0 | } |
217 | 0 | goto exit_success; |
218 | 0 | } |
219 | | |
220 | | /* if the stream is a stdio stream let's give it a chance to respond |
221 | | * first, to avoid doubling up the layers of stdio with an fopencookie */ |
222 | 0 | if (php_stream_is(stream, PHP_STREAM_IS_STDIO) && |
223 | 0 | stream->ops->cast && |
224 | 0 | !php_stream_is_filtered(stream) && |
225 | 0 | stream->ops->cast(stream, castas, ret) == SUCCESS |
226 | 0 | ) { |
227 | 0 | goto exit_success; |
228 | 0 | } |
229 | | |
230 | 0 | #ifdef HAVE_FOPENCOOKIE |
231 | | /* if just checking, say yes we can be a FILE*, but don't actually create it yet */ |
232 | 0 | if (ret == NULL) { |
233 | 0 | goto exit_success; |
234 | 0 | } |
235 | | |
236 | 0 | { |
237 | 0 | char fixed_mode[5]; |
238 | 0 | php_stream_mode_sanitize_fdopen_fopencookie(stream, fixed_mode); |
239 | 0 | *(FILE**)ret = fopencookie(stream, fixed_mode, PHP_STREAM_COOKIE_FUNCTIONS); |
240 | 0 | } |
241 | |
|
242 | 0 | if (*ret != NULL) { |
243 | 0 | zend_off_t pos; |
244 | |
|
245 | 0 | stream->fclose_stdiocast = PHP_STREAM_FCLOSE_FOPENCOOKIE; |
246 | | |
247 | | /* If the stream position is not at the start, we need to force |
248 | | * the stdio layer to believe it's real location. */ |
249 | 0 | pos = php_stream_tell(stream); |
250 | 0 | if (pos > 0) { |
251 | 0 | zend_fseek(*ret, pos, SEEK_SET); |
252 | 0 | } |
253 | |
|
254 | 0 | goto exit_success; |
255 | 0 | } |
256 | | |
257 | | /* must be either: |
258 | | a) programmer error |
259 | | b) no memory |
260 | | -> lets bail |
261 | | */ |
262 | 0 | php_error_docref(NULL, E_ERROR, "fopencookie failed"); |
263 | 0 | return FAILURE; |
264 | 0 | #endif |
265 | | |
266 | 0 | if (!php_stream_is_filtered(stream) && stream->ops->cast && stream->ops->cast(stream, castas, NULL) == SUCCESS) { |
267 | 0 | if (FAILURE == stream->ops->cast(stream, castas, ret)) { |
268 | 0 | return FAILURE; |
269 | 0 | } |
270 | 0 | goto exit_success; |
271 | 0 | } else if (flags & PHP_STREAM_CAST_TRY_HARD) { |
272 | 0 | php_stream *newstream; |
273 | |
|
274 | 0 | newstream = php_stream_fopen_tmpfile(); |
275 | 0 | if (newstream) { |
276 | 0 | int retcopy = php_stream_copy_to_stream_ex(stream, newstream, PHP_STREAM_COPY_ALL, NULL); |
277 | |
|
278 | 0 | if (retcopy != SUCCESS) { |
279 | 0 | php_stream_close(newstream); |
280 | 0 | } else { |
281 | 0 | int retcast = php_stream_cast(newstream, castas | flags, (void **)ret, show_err); |
282 | |
|
283 | 0 | if (retcast == SUCCESS) { |
284 | 0 | rewind(*(FILE**)ret); |
285 | 0 | } |
286 | | |
287 | | /* do some specialized cleanup */ |
288 | 0 | if ((flags & PHP_STREAM_CAST_RELEASE)) { |
289 | 0 | php_stream_free(stream, PHP_STREAM_FREE_CLOSE_CASTED); |
290 | 0 | } |
291 | | |
292 | | /* TODO: we probably should be setting .stdiocast and .fclose_stdiocast or |
293 | | * we may be leaking the FILE*. Needs investigation, though. */ |
294 | 0 | return retcast; |
295 | 0 | } |
296 | 0 | } |
297 | 0 | } |
298 | 0 | } |
299 | | |
300 | 0 | if (php_stream_is_filtered(stream)) { |
301 | 0 | if (show_err) { |
302 | 0 | php_error_docref(NULL, E_WARNING, "Cannot cast a filtered stream on this system"); |
303 | 0 | } |
304 | 0 | return FAILURE; |
305 | 0 | } else if (stream->ops->cast && stream->ops->cast(stream, castas, ret) == SUCCESS) { |
306 | 0 | goto exit_success; |
307 | 0 | } |
308 | | |
309 | 0 | if (show_err) { |
310 | | /* these names depend on the values of the PHP_STREAM_AS_XXX defines in php_streams.h */ |
311 | 0 | static const char *cast_names[4] = { |
312 | 0 | "STDIO FILE*", |
313 | 0 | "File Descriptor", |
314 | 0 | "Socket Descriptor", |
315 | 0 | "select()able descriptor" |
316 | 0 | }; |
317 | |
|
318 | 0 | php_error_docref(NULL, E_WARNING, "Cannot represent a stream of type %s as a %s", stream->ops->label, cast_names[castas]); |
319 | 0 | } |
320 | |
|
321 | 0 | return FAILURE; |
322 | | |
323 | 0 | exit_success: |
324 | |
|
325 | 0 | if ((stream->writepos - stream->readpos) > 0 && |
326 | 0 | stream->fclose_stdiocast != PHP_STREAM_FCLOSE_FOPENCOOKIE && |
327 | 0 | (flags & PHP_STREAM_CAST_INTERNAL) == 0 |
328 | 0 | ) { |
329 | | /* the data we have buffered will be lost to the third party library that |
330 | | * will be accessing the stream. Emit a warning so that the end-user will |
331 | | * know that they should try something else */ |
332 | |
|
333 | 0 | php_error_docref(NULL, E_WARNING, ZEND_LONG_FMT " bytes of buffered data lost during stream conversion!", (zend_long)(stream->writepos - stream->readpos)); |
334 | 0 | } |
335 | |
|
336 | 0 | if (castas == PHP_STREAM_AS_STDIO && ret) { |
337 | 0 | stream->stdiocast = *(FILE**)ret; |
338 | 0 | } |
339 | |
|
340 | 0 | if (flags & PHP_STREAM_CAST_RELEASE) { |
341 | 0 | php_stream_free(stream, PHP_STREAM_FREE_CLOSE_CASTED); |
342 | 0 | } |
343 | |
|
344 | 0 | return SUCCESS; |
345 | |
|
346 | 0 | } |
347 | | /* }}} */ |
348 | | |
349 | | /* {{{ php_stream_open_wrapper_as_file */ |
350 | | PHPAPI FILE * _php_stream_open_wrapper_as_file(char *path, char *mode, int options, zend_string **opened_path STREAMS_DC) |
351 | 0 | { |
352 | 0 | FILE *fp = NULL; |
353 | 0 | php_stream *stream = NULL; |
354 | |
|
355 | 0 | stream = php_stream_open_wrapper_rel(path, mode, options|STREAM_WILL_CAST, opened_path); |
356 | |
|
357 | 0 | if (stream == NULL) { |
358 | 0 | return NULL; |
359 | 0 | } |
360 | | |
361 | 0 | if (php_stream_cast(stream, PHP_STREAM_AS_STDIO|PHP_STREAM_CAST_TRY_HARD|PHP_STREAM_CAST_RELEASE, (void**)&fp, REPORT_ERRORS) == FAILURE) { |
362 | 0 | php_stream_close(stream); |
363 | 0 | if (opened_path && *opened_path) { |
364 | 0 | zend_string_release_ex(*opened_path, 0); |
365 | 0 | } |
366 | 0 | return NULL; |
367 | 0 | } |
368 | 0 | return fp; |
369 | 0 | } |
370 | | /* }}} */ |
371 | | |
372 | | /* {{{ php_stream_make_seekable */ |
373 | | PHPAPI int _php_stream_make_seekable(php_stream *origstream, php_stream **newstream, int flags STREAMS_DC) |
374 | 0 | { |
375 | 0 | if (newstream == NULL) { |
376 | 0 | return PHP_STREAM_FAILED; |
377 | 0 | } |
378 | 0 | *newstream = NULL; |
379 | |
|
380 | 0 | if (((flags & PHP_STREAM_FORCE_CONVERSION) == 0) && origstream->ops->seek != NULL) { |
381 | 0 | *newstream = origstream; |
382 | 0 | return PHP_STREAM_UNCHANGED; |
383 | 0 | } |
384 | | |
385 | | /* Use a tmpfile and copy the old streams contents into it */ |
386 | | |
387 | 0 | if (flags & PHP_STREAM_PREFER_STDIO) { |
388 | 0 | *newstream = php_stream_fopen_tmpfile(); |
389 | 0 | } else { |
390 | 0 | *newstream = php_stream_temp_new(); |
391 | 0 | } |
392 | |
|
393 | 0 | if (*newstream == NULL) { |
394 | 0 | return PHP_STREAM_FAILED; |
395 | 0 | } |
396 | | |
397 | 0 | #if ZEND_DEBUG |
398 | 0 | (*newstream)->open_filename = origstream->open_filename; |
399 | 0 | (*newstream)->open_lineno = origstream->open_lineno; |
400 | 0 | #endif |
401 | |
|
402 | 0 | if (php_stream_copy_to_stream_ex(origstream, *newstream, PHP_STREAM_COPY_ALL, NULL) != SUCCESS) { |
403 | 0 | php_stream_close(*newstream); |
404 | 0 | *newstream = NULL; |
405 | 0 | return PHP_STREAM_CRITICAL; |
406 | 0 | } |
407 | | |
408 | 0 | php_stream_close(origstream); |
409 | 0 | php_stream_seek(*newstream, 0, SEEK_SET); |
410 | |
|
411 | 0 | return PHP_STREAM_RELEASED; |
412 | 0 | } |
413 | | /* }}} */ |