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