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