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