xref: /PHP-8.0/ext/fileinfo/fileinfo.c (revision 0ea38b95)
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