xref: /PHP-8.1/ext/fileinfo/fileinfo.c (revision 16c98d48)
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   | 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 
39 /* {{{ macros and type definitions */
40 typedef struct _php_fileinfo {
41 	zend_long options;
42 	struct magic_set *magic;
43 } php_fileinfo;
44 
45 static zend_object_handlers finfo_object_handlers;
46 zend_class_entry *finfo_class_entry;
47 
48 typedef struct _finfo_object {
49 	php_fileinfo *ptr;
50 	zend_object zo;
51 } finfo_object;
52 
php_finfo_fetch_object(zend_object * obj)53 static inline finfo_object *php_finfo_fetch_object(zend_object *obj) {
54 	return (finfo_object *)((char*)(obj) - XtOffsetOf(finfo_object, zo));
55 }
56 
57 #define Z_FINFO_P(zv) php_finfo_fetch_object(Z_OBJ_P((zv)))
58 
59 #define FILEINFO_FROM_OBJECT(finfo, object) \
60 { \
61 	finfo_object *obj = Z_FINFO_P(object); \
62 	finfo = obj->ptr; \
63 	if (!finfo) { \
64 		zend_throw_error(NULL, "Invalid finfo object"); \
65 		RETURN_THROWS(); \
66 	} \
67 }
68 
69 /* {{{ finfo_objects_free */
finfo_objects_free(zend_object * object)70 static void finfo_objects_free(zend_object *object)
71 {
72 	finfo_object *intern = php_finfo_fetch_object(object);
73 
74 	if (intern->ptr) {
75 		magic_close(intern->ptr->magic);
76 		efree(intern->ptr);
77 	}
78 
79 	zend_object_std_dtor(&intern->zo);
80 }
81 /* }}} */
82 
83 /* {{{ finfo_objects_new */
finfo_objects_new(zend_class_entry * class_type)84 PHP_FILEINFO_API zend_object *finfo_objects_new(zend_class_entry *class_type)
85 {
86 	finfo_object *intern;
87 
88 	intern = zend_object_alloc(sizeof(finfo_object), class_type);
89 
90 	zend_object_std_init(&intern->zo, class_type);
91 	object_properties_init(&intern->zo, class_type);
92 	intern->zo.handlers = &finfo_object_handlers;
93 
94 	return &intern->zo;
95 }
96 /* }}} */
97 
98 #define FINFO_SET_OPTION(magic, options) \
99 	if (magic_setflags(magic, options) == -1) { \
100 		php_error_docref(NULL, E_WARNING, "Failed to set option '" ZEND_LONG_FMT "' %d:%s", \
101 				options, magic_errno(magic), magic_error(magic)); \
102 		RETURN_FALSE; \
103 	}
104 /* }}} */
105 
106 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(finfo)107 PHP_MINIT_FUNCTION(finfo)
108 {
109 	finfo_class_entry = register_class_finfo();
110 	finfo_class_entry->create_object = finfo_objects_new;
111 
112 	/* copy the standard object handlers to you handler table */
113 	memcpy(&finfo_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
114 	finfo_object_handlers.offset = XtOffsetOf(finfo_object, zo);
115 	finfo_object_handlers.free_obj = finfo_objects_free;
116 	finfo_object_handlers.clone_obj = NULL;
117 
118 	REGISTER_LONG_CONSTANT("FILEINFO_NONE",			MAGIC_NONE, CONST_CS|CONST_PERSISTENT);
119 	REGISTER_LONG_CONSTANT("FILEINFO_SYMLINK",		MAGIC_SYMLINK, CONST_CS|CONST_PERSISTENT);
120 	REGISTER_LONG_CONSTANT("FILEINFO_MIME",			MAGIC_MIME, CONST_CS|CONST_PERSISTENT);
121 	REGISTER_LONG_CONSTANT("FILEINFO_MIME_TYPE",	MAGIC_MIME_TYPE, CONST_CS|CONST_PERSISTENT);
122 	REGISTER_LONG_CONSTANT("FILEINFO_MIME_ENCODING",MAGIC_MIME_ENCODING, CONST_CS|CONST_PERSISTENT);
123 /*	REGISTER_LONG_CONSTANT("FILEINFO_COMPRESS",		MAGIC_COMPRESS, CONST_CS|CONST_PERSISTENT); disabled, as it does fork now */
124 	REGISTER_LONG_CONSTANT("FILEINFO_DEVICES",		MAGIC_DEVICES, CONST_CS|CONST_PERSISTENT);
125 	REGISTER_LONG_CONSTANT("FILEINFO_CONTINUE",		MAGIC_CONTINUE, CONST_CS|CONST_PERSISTENT);
126 #ifdef MAGIC_PRESERVE_ATIME
127 	REGISTER_LONG_CONSTANT("FILEINFO_PRESERVE_ATIME",	MAGIC_PRESERVE_ATIME, CONST_CS|CONST_PERSISTENT);
128 #endif
129 #ifdef MAGIC_RAW
130 	REGISTER_LONG_CONSTANT("FILEINFO_RAW",			MAGIC_RAW, CONST_CS|CONST_PERSISTENT);
131 #endif
132 #if 0
133 	/* seems not usable yet. */
134 	REGISTER_LONG_CONSTANT("FILEINFO_APPLE",		MAGIC_APPLE, CONST_CS|CONST_PERSISTENT);
135 #endif
136 	REGISTER_LONG_CONSTANT("FILEINFO_EXTENSION",	MAGIC_EXTENSION, CONST_CS|CONST_PERSISTENT);
137 
138 	return SUCCESS;
139 }
140 /* }}} */
141 
142 /* {{{ fileinfo_module_entry */
143 zend_module_entry fileinfo_module_entry = {
144 	STANDARD_MODULE_HEADER,
145 	"fileinfo",
146 	ext_functions,
147 	PHP_MINIT(finfo),
148 	NULL,
149 	NULL,
150 	NULL,
151 	PHP_MINFO(fileinfo),
152 	PHP_FILEINFO_VERSION,
153 	STANDARD_MODULE_PROPERTIES
154 };
155 /* }}} */
156 
157 #ifdef COMPILE_DL_FILEINFO
158 ZEND_GET_MODULE(fileinfo)
159 #endif
160 
161 /* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(fileinfo)162 PHP_MINFO_FUNCTION(fileinfo)
163 {
164 	char magic_ver[5];
165 
166 	(void)snprintf(magic_ver, 4, "%d", magic_version());
167 	magic_ver[4] = '\0';
168 
169 	php_info_print_table_start();
170 	php_info_print_table_row(2, "fileinfo support", "enabled");
171 	php_info_print_table_row(2, "libmagic", magic_ver);
172 	php_info_print_table_end();
173 }
174 /* }}} */
175 
176 /* {{{ Construct a new fileinfo object. */
PHP_FUNCTION(finfo_open)177 PHP_FUNCTION(finfo_open)
178 {
179 	zend_long options = MAGIC_NONE;
180 	char *file = NULL;
181 	size_t file_len = 0;
182 	php_fileinfo *finfo;
183 	zval *object = getThis();
184 	char resolved_path[MAXPATHLEN];
185 	zend_error_handling zeh;
186 
187 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|lp!", &options, &file, &file_len) == FAILURE) {
188 		RETURN_THROWS();
189 	}
190 
191 	if (object) {
192 		finfo_object *finfo_obj = Z_FINFO_P(object);
193 
194 		zend_replace_error_handling(EH_THROW, NULL, &zeh);
195 
196 		if (finfo_obj->ptr) {
197 			magic_close(finfo_obj->ptr->magic);
198 			efree(finfo_obj->ptr);
199 			finfo_obj->ptr = NULL;
200 		}
201 	}
202 
203 	if (file_len == 0) {
204 		file = NULL;
205 	} else if (file && *file) { /* user specified file, perform open_basedir checks */
206 
207 		if (php_check_open_basedir(file)) {
208 			if (object) {
209 				zend_restore_error_handling(&zeh);
210 				if (!EG(exception)) {
211 					zend_throw_exception(NULL, "Constructor failed", 0);
212 				}
213 			}
214 			RETURN_FALSE;
215 		}
216 		if (!expand_filepath_with_mode(file, resolved_path, NULL, 0, CWD_EXPAND)) {
217 			if (object) {
218 				zend_restore_error_handling(&zeh);
219 				if (!EG(exception)) {
220 					zend_throw_exception(NULL, "Constructor failed", 0);
221 				}
222 			}
223 			RETURN_FALSE;
224 		}
225 		file = resolved_path;
226 	}
227 
228 	finfo = emalloc(sizeof(php_fileinfo));
229 
230 	finfo->options = options;
231 	finfo->magic = magic_open(options);
232 
233 	if (finfo->magic == NULL) {
234 		efree(finfo);
235 		php_error_docref(NULL, E_WARNING, "Invalid mode '" ZEND_LONG_FMT "'.", options);
236 		if (object) {
237 			zend_restore_error_handling(&zeh);
238 			if (!EG(exception)) {
239 				zend_throw_exception(NULL, "Constructor failed", 0);
240 			}
241 		}
242 		RETURN_FALSE;
243 	}
244 
245 	if (magic_load(finfo->magic, file) == -1) {
246 		php_error_docref(NULL, E_WARNING, "Failed to load magic database at \"%s\"", file);
247 		magic_close(finfo->magic);
248 		efree(finfo);
249 		if (object) {
250 			zend_restore_error_handling(&zeh);
251 			if (!EG(exception)) {
252 				zend_throw_exception(NULL, "Constructor failed", 0);
253 			}
254 		}
255 		RETURN_FALSE;
256 	}
257 
258 	if (object) {
259 		finfo_object *obj;
260 		zend_restore_error_handling(&zeh);
261 		obj = Z_FINFO_P(object);
262 		obj->ptr = finfo;
263 	} else {
264 		zend_object *zobj = finfo_objects_new(finfo_class_entry);
265 		finfo_object *obj = php_finfo_fetch_object(zobj);
266 		obj->ptr = finfo;
267 		RETURN_OBJ(zobj);
268 	}
269 }
270 /* }}} */
271 
272 /* {{{ Close fileinfo object - a NOP. */
PHP_FUNCTION(finfo_close)273 PHP_FUNCTION(finfo_close)
274 {
275 	zval *self;
276 
277 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &self, finfo_class_entry) == FAILURE) {
278 		RETURN_THROWS();
279 	}
280 
281 	RETURN_TRUE;
282 }
283 /* }}} */
284 
285 /* {{{ Set libmagic configuration options. */
PHP_FUNCTION(finfo_set_flags)286 PHP_FUNCTION(finfo_set_flags)
287 {
288 	zend_long options;
289 	php_fileinfo *finfo;
290 	zval *self;
291 
292 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Ol", &self, finfo_class_entry, &options) == FAILURE) {
293 		RETURN_THROWS();
294 	}
295 	FILEINFO_FROM_OBJECT(finfo, self);
296 
297 	FINFO_SET_OPTION(finfo->magic, options)
298 	finfo->options = options;
299 
300 	RETURN_TRUE;
301 }
302 /* }}} */
303 
304 #define FILEINFO_MODE_BUFFER 0
305 #define FILEINFO_MODE_STREAM 1
306 #define FILEINFO_MODE_FILE 2
307 
_php_finfo_get_type(INTERNAL_FUNCTION_PARAMETERS,int mode,int mimetype_emu)308 static void _php_finfo_get_type(INTERNAL_FUNCTION_PARAMETERS, int mode, int mimetype_emu) /* {{{ */
309 {
310 	zend_long options = 0;
311 	char *ret_val = NULL, *buffer = NULL;
312 	size_t buffer_len;
313 	php_fileinfo *finfo = NULL;
314 	zval *zcontext = NULL;
315 	zval *what;
316 	char mime_directory[] = "directory";
317 	struct magic_set *magic = NULL;
318 
319 	if (mimetype_emu) {
320 
321 		/* mime_content_type(..) emulation */
322 		if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &what) == FAILURE) {
323 			RETURN_THROWS();
324 		}
325 
326 		switch (Z_TYPE_P(what)) {
327 			case IS_STRING:
328 				buffer = Z_STRVAL_P(what);
329 				buffer_len = Z_STRLEN_P(what);
330 				mode = FILEINFO_MODE_FILE;
331 				break;
332 
333 			case IS_RESOURCE:
334 				mode = FILEINFO_MODE_STREAM;
335 				break;
336 
337 			default:
338 				zend_argument_type_error(1, "must be of type resource|string, %s given", zend_zval_type_name(what));
339 				RETURN_THROWS();
340 		}
341 
342 		magic = magic_open(MAGIC_MIME_TYPE);
343 		if (magic_load(magic, NULL) == -1) {
344 			php_error_docref(NULL, E_WARNING, "Failed to load magic database");
345 			goto common;
346 		}
347 	} else {
348 		zval *self;
349 		if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Os|lr!", &self, finfo_class_entry, &buffer, &buffer_len, &options, &zcontext) == FAILURE) {
350 			RETURN_THROWS();
351 		}
352 		FILEINFO_FROM_OBJECT(finfo, self);
353 		magic = finfo->magic;
354 	}
355 
356 	/* Set options for the current file/buffer. */
357 	if (options) {
358 		FINFO_SET_OPTION(magic, options)
359 	}
360 
361 	switch (mode) {
362 		case FILEINFO_MODE_BUFFER:
363 		{
364 			ret_val = (char *) magic_buffer(magic, buffer, buffer_len);
365 			break;
366 		}
367 
368 		case FILEINFO_MODE_STREAM:
369 		{
370 				php_stream *stream;
371 				zend_off_t streampos;
372 
373 				php_stream_from_zval_no_verify(stream, what);
374 				if (!stream) {
375 					goto common;
376 				}
377 
378 				streampos = php_stream_tell(stream); /* remember stream position for restoration */
379 				php_stream_seek(stream, 0, SEEK_SET);
380 
381 				ret_val = (char *) magic_stream(magic, stream);
382 
383 				php_stream_seek(stream, streampos, SEEK_SET);
384 				break;
385 		}
386 
387 		case FILEINFO_MODE_FILE:
388 		{
389 			/* determine if the file is a local file or remote URL */
390 			const char *tmp2;
391 			php_stream_wrapper *wrap;
392 			php_stream_statbuf ssb;
393 
394 			if (buffer == NULL || buffer_len == 0) {
395 				zend_argument_value_error(1, "cannot be empty");
396 				goto clean;
397 			}
398 			if (CHECK_NULL_PATH(buffer, buffer_len)) {
399 				zend_argument_type_error(1, "must not contain any null bytes");
400 				goto clean;
401 			}
402 
403 			wrap = php_stream_locate_url_wrapper(buffer, &tmp2, 0);
404 
405 			if (wrap) {
406 				php_stream *stream;
407 				php_stream_context *context = php_stream_context_from_zval(zcontext, 0);
408 
409 #ifdef PHP_WIN32
410 				if (php_stream_stat_path_ex(buffer, 0, &ssb, context) == SUCCESS) {
411 					if (ssb.sb.st_mode & S_IFDIR) {
412 						ret_val = mime_directory;
413 						goto common;
414 					}
415 				}
416 #endif
417 
418 				stream = php_stream_open_wrapper_ex(buffer, "rb", REPORT_ERRORS, NULL, context);
419 
420 				if (!stream) {
421 					RETVAL_FALSE;
422 					goto clean;
423 				}
424 
425 				if (php_stream_stat(stream, &ssb) == SUCCESS) {
426 					if (ssb.sb.st_mode & S_IFDIR) {
427 						ret_val = mime_directory;
428 					} else {
429 						ret_val = (char *)magic_stream(magic, stream);
430 					}
431 				}
432 
433 				php_stream_close(stream);
434 			}
435 			break;
436 		}
437 		EMPTY_SWITCH_DEFAULT_CASE()
438 	}
439 
440 common:
441 	if (ret_val) {
442 		RETVAL_STRING(ret_val);
443 	} else {
444 		php_error_docref(NULL, E_WARNING, "Failed identify data %d:%s", magic_errno(magic), magic_error(magic));
445 		RETVAL_FALSE;
446 	}
447 
448 clean:
449 	if (mimetype_emu) {
450 		magic_close(magic);
451 	}
452 
453 	/* Restore options */
454 	if (options) {
455 		FINFO_SET_OPTION(magic, finfo->options)
456 	}
457 	return;
458 }
459 /* }}} */
460 
461 /* {{{ Return information about a file. */
PHP_FUNCTION(finfo_file)462 PHP_FUNCTION(finfo_file)
463 {
464 	_php_finfo_get_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, FILEINFO_MODE_FILE, 0);
465 }
466 /* }}} */
467 
468 /* {{{ Return information about a string buffer. */
PHP_FUNCTION(finfo_buffer)469 PHP_FUNCTION(finfo_buffer)
470 {
471 	_php_finfo_get_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, FILEINFO_MODE_BUFFER, 0);
472 }
473 /* }}} */
474 
475 /* {{{ Return content-type for file */
PHP_FUNCTION(mime_content_type)476 PHP_FUNCTION(mime_content_type)
477 {
478 	_php_finfo_get_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, -1, 1);
479 }
480 /* }}} */
481