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