1 /*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.0 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_0.txt. |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Author: Ilia Alshanetsky <ilia@php.net> |
14 +----------------------------------------------------------------------+
15 */
16
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 #include "php.h"
21
22 #include "libmagic/magic.h"
23 /*
24 * HOWMANY specifies the maximum offset libmagic will look at
25 * this is currently hardcoded in the libmagic source but not exported
26 */
27 #ifndef HOWMANY
28 #define HOWMANY 65536
29 #endif
30
31 #include "php_ini.h"
32 #include "ext/standard/info.h"
33 #include "ext/standard/file.h" /* needed for context stuff */
34 #include "php_fileinfo.h"
35 #include "fileinfo_arginfo.h"
36 #include "fopen_wrappers.h" /* needed for is_url */
37 #include "Zend/zend_exceptions.h"
38 #include "Zend/zend_interfaces.h"
39
40 /* {{{ macros and type definitions */
41 typedef struct _php_fileinfo {
42 zend_long options;
43 struct magic_set *magic;
44 } php_fileinfo;
45
46 static zend_object_handlers finfo_object_handlers;
47 zend_class_entry *finfo_class_entry;
48
49 typedef struct _finfo_object {
50 php_fileinfo *ptr;
51 zend_object zo;
52 } finfo_object;
53
54 #define FILEINFO_DECLARE_INIT_OBJECT(object) \
55 zval *object = getThis();
56
php_finfo_fetch_object(zend_object * obj)57 static inline finfo_object *php_finfo_fetch_object(zend_object *obj) {
58 return (finfo_object *)((char*)(obj) - XtOffsetOf(finfo_object, zo));
59 }
60
61 #define Z_FINFO_P(zv) php_finfo_fetch_object(Z_OBJ_P((zv)))
62
63 #define FILEINFO_REGISTER_OBJECT(_object, _ptr) \
64 { \
65 finfo_object *obj; \
66 obj = Z_FINFO_P(_object); \
67 obj->ptr = _ptr; \
68 }
69
70 #define FILEINFO_FROM_OBJECT(finfo, object) \
71 { \
72 finfo_object *obj = Z_FINFO_P(object); \
73 finfo = obj->ptr; \
74 if (!finfo) { \
75 zend_throw_error(NULL, "Invalid finfo object"); \
76 RETURN_THROWS(); \
77 } \
78 }
79
80 /* {{{ finfo_objects_free */
finfo_objects_free(zend_object * object)81 static void finfo_objects_free(zend_object *object)
82 {
83 finfo_object *intern = php_finfo_fetch_object(object);
84
85 if (intern->ptr) {
86 magic_close(intern->ptr->magic);
87 efree(intern->ptr);
88 }
89
90 zend_object_std_dtor(&intern->zo);
91 }
92 /* }}} */
93
94 /* {{{ finfo_objects_new */
finfo_objects_new(zend_class_entry * class_type)95 PHP_FILEINFO_API zend_object *finfo_objects_new(zend_class_entry *class_type)
96 {
97 finfo_object *intern;
98
99 intern = zend_object_alloc(sizeof(finfo_object), class_type);
100
101 zend_object_std_init(&intern->zo, class_type);
102 object_properties_init(&intern->zo, class_type);
103 intern->zo.handlers = &finfo_object_handlers;
104
105 return &intern->zo;
106 }
107 /* }}} */
108
109 #define FINFO_SET_OPTION(magic, options) \
110 if (magic_setflags(magic, options) == -1) { \
111 php_error_docref(NULL, E_WARNING, "Failed to set option '" ZEND_LONG_FMT "' %d:%s", \
112 options, magic_errno(magic), magic_error(magic)); \
113 RETURN_FALSE; \
114 }
115
116 /* True global resources - no need for thread safety here */
117 static int le_fileinfo;
118 /* }}} */
119
finfo_resource_destructor(zend_resource * rsrc)120 void finfo_resource_destructor(zend_resource *rsrc) /* {{{ */
121 {
122 if (rsrc->ptr) {
123 php_fileinfo *finfo = (php_fileinfo *) rsrc->ptr;
124 magic_close(finfo->magic);
125 efree(rsrc->ptr);
126 rsrc->ptr = NULL;
127 }
128 }
129 /* }}} */
130
131 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(finfo)132 PHP_MINIT_FUNCTION(finfo)
133 {
134 zend_class_entry _finfo_class_entry;
135 INIT_CLASS_ENTRY(_finfo_class_entry, "finfo", class_finfo_methods);
136 finfo_class_entry = zend_register_internal_class(&_finfo_class_entry);
137 finfo_class_entry->create_object = finfo_objects_new;
138 finfo_class_entry->serialize = zend_class_serialize_deny;
139 finfo_class_entry->unserialize = zend_class_unserialize_deny;
140
141 /* copy the standard object handlers to you handler table */
142 memcpy(&finfo_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
143 finfo_object_handlers.offset = XtOffsetOf(finfo_object, zo);
144 finfo_object_handlers.free_obj = finfo_objects_free;
145 finfo_object_handlers.clone_obj = NULL;
146
147 le_fileinfo = zend_register_list_destructors_ex(finfo_resource_destructor, NULL, "file_info", module_number);
148
149 REGISTER_LONG_CONSTANT("FILEINFO_NONE", MAGIC_NONE, CONST_CS|CONST_PERSISTENT);
150 REGISTER_LONG_CONSTANT("FILEINFO_SYMLINK", MAGIC_SYMLINK, CONST_CS|CONST_PERSISTENT);
151 REGISTER_LONG_CONSTANT("FILEINFO_MIME", MAGIC_MIME, CONST_CS|CONST_PERSISTENT);
152 REGISTER_LONG_CONSTANT("FILEINFO_MIME_TYPE", MAGIC_MIME_TYPE, CONST_CS|CONST_PERSISTENT);
153 REGISTER_LONG_CONSTANT("FILEINFO_MIME_ENCODING",MAGIC_MIME_ENCODING, CONST_CS|CONST_PERSISTENT);
154 /* REGISTER_LONG_CONSTANT("FILEINFO_COMPRESS", MAGIC_COMPRESS, CONST_CS|CONST_PERSISTENT); disabled, as it does fork now */
155 REGISTER_LONG_CONSTANT("FILEINFO_DEVICES", MAGIC_DEVICES, CONST_CS|CONST_PERSISTENT);
156 REGISTER_LONG_CONSTANT("FILEINFO_CONTINUE", MAGIC_CONTINUE, CONST_CS|CONST_PERSISTENT);
157 #ifdef MAGIC_PRESERVE_ATIME
158 REGISTER_LONG_CONSTANT("FILEINFO_PRESERVE_ATIME", MAGIC_PRESERVE_ATIME, CONST_CS|CONST_PERSISTENT);
159 #endif
160 #ifdef MAGIC_RAW
161 REGISTER_LONG_CONSTANT("FILEINFO_RAW", MAGIC_RAW, CONST_CS|CONST_PERSISTENT);
162 #endif
163 #if 0
164 /* seems not usable yet. */
165 REGISTER_LONG_CONSTANT("FILEINFO_APPLE", MAGIC_APPLE, CONST_CS|CONST_PERSISTENT);
166 #endif
167 REGISTER_LONG_CONSTANT("FILEINFO_EXTENSION", MAGIC_EXTENSION, CONST_CS|CONST_PERSISTENT);
168
169 return SUCCESS;
170 }
171 /* }}} */
172
173 /* {{{ fileinfo_module_entry */
174 zend_module_entry fileinfo_module_entry = {
175 STANDARD_MODULE_HEADER,
176 "fileinfo",
177 ext_functions,
178 PHP_MINIT(finfo),
179 NULL,
180 NULL,
181 NULL,
182 PHP_MINFO(fileinfo),
183 PHP_FILEINFO_VERSION,
184 STANDARD_MODULE_PROPERTIES
185 };
186 /* }}} */
187
188 #ifdef COMPILE_DL_FILEINFO
189 ZEND_GET_MODULE(fileinfo)
190 #endif
191
192 /* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(fileinfo)193 PHP_MINFO_FUNCTION(fileinfo)
194 {
195 char magic_ver[5];
196
197 (void)snprintf(magic_ver, 4, "%d", magic_version());
198 magic_ver[4] = '\0';
199
200 php_info_print_table_start();
201 php_info_print_table_row(2, "fileinfo support", "enabled");
202 php_info_print_table_row(2, "libmagic", magic_ver);
203 php_info_print_table_end();
204 }
205 /* }}} */
206
207 /* {{{ Create a new fileinfo resource. */
PHP_FUNCTION(finfo_open)208 PHP_FUNCTION(finfo_open)
209 {
210 zend_long options = MAGIC_NONE;
211 char *file = NULL;
212 size_t file_len = 0;
213 php_fileinfo *finfo;
214 FILEINFO_DECLARE_INIT_OBJECT(object)
215 char resolved_path[MAXPATHLEN];
216 zend_error_handling zeh;
217
218 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|lp!", &options, &file, &file_len) == FAILURE) {
219 RETURN_THROWS();
220 }
221
222 if (object) {
223 finfo_object *finfo_obj = Z_FINFO_P(object);
224
225 zend_replace_error_handling(EH_THROW, NULL, &zeh);
226
227 if (finfo_obj->ptr) {
228 magic_close(finfo_obj->ptr->magic);
229 efree(finfo_obj->ptr);
230 finfo_obj->ptr = NULL;
231 }
232 }
233
234 if (file_len == 0) {
235 file = NULL;
236 } else if (file && *file) { /* user specified file, perform open_basedir checks */
237
238 if (php_check_open_basedir(file)) {
239 if (object) {
240 zend_restore_error_handling(&zeh);
241 if (!EG(exception)) {
242 zend_throw_exception(NULL, "Constructor failed", 0);
243 }
244 }
245 RETURN_FALSE;
246 }
247 if (!expand_filepath_with_mode(file, resolved_path, NULL, 0, CWD_EXPAND)) {
248 if (object) {
249 zend_restore_error_handling(&zeh);
250 if (!EG(exception)) {
251 zend_throw_exception(NULL, "Constructor failed", 0);
252 }
253 }
254 RETURN_FALSE;
255 }
256 file = resolved_path;
257 }
258
259 finfo = emalloc(sizeof(php_fileinfo));
260
261 finfo->options = options;
262 finfo->magic = magic_open(options);
263
264 if (finfo->magic == NULL) {
265 efree(finfo);
266 php_error_docref(NULL, E_WARNING, "Invalid mode '" ZEND_LONG_FMT "'.", options);
267 if (object) {
268 zend_restore_error_handling(&zeh);
269 if (!EG(exception)) {
270 zend_throw_exception(NULL, "Constructor failed", 0);
271 }
272 }
273 RETURN_FALSE;
274 }
275
276 if (magic_load(finfo->magic, file) == -1) {
277 php_error_docref(NULL, E_WARNING, "Failed to load magic database at \"%s\"", file);
278 magic_close(finfo->magic);
279 efree(finfo);
280 if (object) {
281 zend_restore_error_handling(&zeh);
282 if (!EG(exception)) {
283 zend_throw_exception(NULL, "Constructor failed", 0);
284 }
285 }
286 RETURN_FALSE;
287 }
288
289 if (object) {
290 zend_restore_error_handling(&zeh);
291 FILEINFO_REGISTER_OBJECT(object, finfo);
292 } else {
293 RETURN_RES(zend_register_resource(finfo, le_fileinfo));
294 }
295 }
296 /* }}} */
297
298 /* {{{ Close fileinfo resource. */
PHP_FUNCTION(finfo_close)299 PHP_FUNCTION(finfo_close)
300 {
301 php_fileinfo *finfo;
302 zval *zfinfo;
303
304 if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zfinfo) == FAILURE) {
305 RETURN_THROWS();
306 }
307
308 if ((finfo = (php_fileinfo *)zend_fetch_resource(Z_RES_P(zfinfo), "file_info", le_fileinfo)) == NULL) {
309 RETURN_THROWS();
310 }
311
312 zend_list_close(Z_RES_P(zfinfo));
313
314 RETURN_TRUE;
315 }
316 /* }}} */
317
318 /* {{{ Set libmagic configuration options. */
PHP_FUNCTION(finfo_set_flags)319 PHP_FUNCTION(finfo_set_flags)
320 {
321 zend_long options;
322 php_fileinfo *finfo;
323 zval *zfinfo;
324 FILEINFO_DECLARE_INIT_OBJECT(object)
325
326 if (object) {
327 if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &options) == FAILURE) {
328 RETURN_THROWS();
329 }
330 FILEINFO_FROM_OBJECT(finfo, object);
331 } else {
332 if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &zfinfo, &options) == FAILURE) {
333 RETURN_THROWS();
334 }
335 if ((finfo = (php_fileinfo *)zend_fetch_resource(Z_RES_P(zfinfo), "file_info", le_fileinfo)) == NULL) {
336 RETURN_THROWS();
337 }
338 }
339
340 FINFO_SET_OPTION(finfo->magic, options)
341 finfo->options = options;
342
343 RETURN_TRUE;
344 }
345 /* }}} */
346
347 #define FILEINFO_MODE_BUFFER 0
348 #define FILEINFO_MODE_STREAM 1
349 #define FILEINFO_MODE_FILE 2
350
_php_finfo_get_type(INTERNAL_FUNCTION_PARAMETERS,int mode,int mimetype_emu)351 static void _php_finfo_get_type(INTERNAL_FUNCTION_PARAMETERS, int mode, int mimetype_emu) /* {{{ */
352 {
353 zend_long options = 0;
354 char *ret_val = NULL, *buffer = NULL;
355 size_t buffer_len;
356 php_fileinfo *finfo = NULL;
357 zval *zfinfo, *zcontext = NULL;
358 zval *what;
359 char mime_directory[] = "directory";
360
361 struct magic_set *magic = NULL;
362 FILEINFO_DECLARE_INIT_OBJECT(object)
363
364 if (mimetype_emu) {
365
366 /* mime_content_type(..) emulation */
367 if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &what) == FAILURE) {
368 RETURN_THROWS();
369 }
370
371 switch (Z_TYPE_P(what)) {
372 case IS_STRING:
373 buffer = Z_STRVAL_P(what);
374 buffer_len = Z_STRLEN_P(what);
375 mode = FILEINFO_MODE_FILE;
376 break;
377
378 case IS_RESOURCE:
379 mode = FILEINFO_MODE_STREAM;
380 break;
381
382 default:
383 zend_argument_type_error(1, "must be of type resource|string, %s given", zend_zval_type_name(what));
384 RETURN_THROWS();
385 }
386
387 magic = magic_open(MAGIC_MIME_TYPE);
388 if (magic_load(magic, NULL) == -1) {
389 php_error_docref(NULL, E_WARNING, "Failed to load magic database");
390 goto common;
391 }
392 } else if (object) {
393 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|lr!", &buffer, &buffer_len, &options, &zcontext) == FAILURE) {
394 RETURN_THROWS();
395 }
396 FILEINFO_FROM_OBJECT(finfo, object);
397 magic = finfo->magic;
398 } else {
399 if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs|lr!", &zfinfo, &buffer, &buffer_len, &options, &zcontext) == FAILURE) {
400 RETURN_THROWS();
401 }
402 if ((finfo = (php_fileinfo *)zend_fetch_resource(Z_RES_P(zfinfo), "file_info", le_fileinfo)) == NULL) {
403 RETURN_THROWS();
404 }
405 magic = finfo->magic;
406 }
407
408 /* Set options for the current file/buffer. */
409 if (options) {
410 FINFO_SET_OPTION(magic, options)
411 }
412
413 switch (mode) {
414 case FILEINFO_MODE_BUFFER:
415 {
416 ret_val = (char *) magic_buffer(magic, buffer, buffer_len);
417 break;
418 }
419
420 case FILEINFO_MODE_STREAM:
421 {
422 php_stream *stream;
423 zend_off_t streampos;
424
425 php_stream_from_zval_no_verify(stream, what);
426 if (!stream) {
427 goto common;
428 }
429
430 streampos = php_stream_tell(stream); /* remember stream position for restoration */
431 php_stream_seek(stream, 0, SEEK_SET);
432
433 ret_val = (char *) magic_stream(magic, stream);
434
435 php_stream_seek(stream, streampos, SEEK_SET);
436 break;
437 }
438
439 case FILEINFO_MODE_FILE:
440 {
441 /* determine if the file is a local file or remote URL */
442 const char *tmp2;
443 php_stream_wrapper *wrap;
444 php_stream_statbuf ssb;
445
446 if (buffer == NULL || buffer_len == 0) {
447 zend_argument_value_error(1, "cannot be empty");
448 goto clean;
449 }
450 if (CHECK_NULL_PATH(buffer, buffer_len)) {
451 zend_argument_type_error(1, "must not contain any null bytes");
452 goto clean;
453 }
454
455 wrap = php_stream_locate_url_wrapper(buffer, &tmp2, 0);
456
457 if (wrap) {
458 php_stream *stream;
459 php_stream_context *context = php_stream_context_from_zval(zcontext, 0);
460
461 #ifdef PHP_WIN32
462 if (php_stream_stat_path_ex(buffer, 0, &ssb, context) == SUCCESS) {
463 if (ssb.sb.st_mode & S_IFDIR) {
464 ret_val = mime_directory;
465 goto common;
466 }
467 }
468 #endif
469
470 stream = php_stream_open_wrapper_ex(buffer, "rb", REPORT_ERRORS, NULL, context);
471
472 if (!stream) {
473 RETVAL_FALSE;
474 goto clean;
475 }
476
477 if (php_stream_stat(stream, &ssb) == SUCCESS) {
478 if (ssb.sb.st_mode & S_IFDIR) {
479 ret_val = mime_directory;
480 } else {
481 ret_val = (char *)magic_stream(magic, stream);
482 }
483 }
484
485 php_stream_close(stream);
486 }
487 break;
488 }
489 EMPTY_SWITCH_DEFAULT_CASE()
490 }
491
492 common:
493 if (ret_val) {
494 RETVAL_STRING(ret_val);
495 } else {
496 php_error_docref(NULL, E_WARNING, "Failed identify data %d:%s", magic_errno(magic), magic_error(magic));
497 RETVAL_FALSE;
498 }
499
500 clean:
501 if (mimetype_emu) {
502 magic_close(magic);
503 }
504
505 /* Restore options */
506 if (options) {
507 FINFO_SET_OPTION(magic, finfo->options)
508 }
509 return;
510 }
511 /* }}} */
512
513 /* {{{ Return information about a file. */
PHP_FUNCTION(finfo_file)514 PHP_FUNCTION(finfo_file)
515 {
516 _php_finfo_get_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, FILEINFO_MODE_FILE, 0);
517 }
518 /* }}} */
519
520 /* {{{ Return information about a string buffer. */
PHP_FUNCTION(finfo_buffer)521 PHP_FUNCTION(finfo_buffer)
522 {
523 _php_finfo_get_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, FILEINFO_MODE_BUFFER, 0);
524 }
525 /* }}} */
526
527 /* {{{ Return content-type for file */
PHP_FUNCTION(mime_content_type)528 PHP_FUNCTION(mime_content_type)
529 {
530 _php_finfo_get_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, -1, 1);
531 }
532 /* }}} */
533