Completed
Push — master ( 79e63b...a5181c )
by Daniel
05:26 queued 02:19
created

code/MimeUploadValidator.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Adds an additional validation rule to Upload_Validator that attempts to detect
4
 * the file extension of an uploaded file matches it's contents, which is done
5
 * by detecting the MIME type and doing a fuzzy match.
6
 */
7
class MimeUploadValidator extends Upload_Validator
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
8
{
9
    /**
10
     * The preg_replace() pattern to use against MIME types. Used to strip out
11
     * useless characters so matching of MIME types can be fuzzy.
12
     *
13
     * @var string Regexp pattern
14
     */
15
    protected $filterPattern = '/.*[\/\.\-\+]/i';
16
17
    public function setFilterPattern($pattern)
18
    {
19
        $this->filterPattern = $pattern;
20
    }
21
22
    public function getFilterPattern()
23
    {
24
        return $this->filterPattern;
25
    }
26
27
    /**
28
     * Check if the temporary file has a valid MIME type for it's extension.
29
     *
30
     * @uses finfo php extension
31
     * @return boolean|null
32
     */
33
    public function isValidMime()
34
    {
35
        $extension = strtolower(pathinfo($this->tmpFile['name'], PATHINFO_EXTENSION));
36
37
                // we can't check filenames without an extension or no temp file path, let them pass validation.
38
                if (!$extension || !$this->tmpFile['tmp_name']) {
39
                    return true;
40
                }
41
42
        $expectedMimes = $this->getExpectedMimeTypes($this->tmpFile);
43
        if (empty($expectedMimes)) {
44
            throw new MimeUploadValidator_Exception(
45
                sprintf('Could not find a MIME type for extension %s', $extension)
46
            );
47
        }
48
49
        $finfo = new finfo(FILEINFO_MIME_TYPE);
50
        $foundMime = $finfo->file($this->tmpFile['tmp_name']);
51
        if (!$foundMime) {
52
            throw new MimeUploadValidator_Exception(
53
                sprintf('Could not find a MIME type for file %s', $this->tmpFile['tmp_name'])
54
            );
55
        }
56
57
        foreach ($expectedMimes as $expected) {
58
            if ($this->compareMime($foundMime, $expected)) {
59
                return true;
60
            }
61
        }
62
        return false;
63
    }
64
65
    /**
66
     * Fetches an array of valid mimetypes.
67
     *
68
     * @return array
69
     */
70
    public function getExpectedMimeTypes($tmpFile)
71
    {
72
        $extension = strtolower(pathinfo($tmpFile['name'], PATHINFO_EXTENSION));
73
74
        // if the finfo php extension isn't loaded, we can't complete this check.
75
        if (!class_exists('finfo')) {
76
            throw new MimeUploadValidator_Exception('PHP extension finfo is not loaded');
77
        }
78
79
        // Attempt to figure out which mime types are expected/acceptable here.
80
        $expectedMimes = array();
81
82
        // Get the mime types set in framework core
83
        $knownMimes = Config::inst()->get('HTTP', 'MimeTypes');
84
        if (isset($knownMimes[$extension])) {
85
            $expectedMimes[] = $knownMimes[$extension];
86
        }
87
88
        // Get the mime types and their variations from mimevalidator
89
        $knownMimes = Config::inst()->get(get_class($this), 'MimeTypes');
90
        if (isset($knownMimes[$extension])) {
91
            if (is_array($knownMimes[$extension])) {
92
                $expectedMimes += $knownMimes[$extension];
93
            } else {
94
                $expectedMimes[] = $knownMimes[$extension];
95
            }
96
        }
97
        return $expectedMimes;
98
    }
99
100
    /**
101
     * Check two MIME types roughly match eachother.
102
     *
103
     * Before we check MIME types, remove known prefixes "vnd.", "x-" etc.
104
     * If there is a suffix, we'll use that to compare. Examples:
105
     *
106
     * application/x-json = json
107
     * application/json = json
108
     * application/xhtml+xml = xml
109
     * application/xml = xml
110
     *
111
     * @param string $first The first MIME type to compare to the second
112
     * @param string $second The second MIME type to compare to the first
113
     * @return boolean
114
     */
115
    public function compareMime($first, $second)
116
    {
117
        return preg_replace($this->filterPattern, '', $first) === preg_replace($this->filterPattern, '', $second);
118
    }
119
120
    public function validate()
121
    {
122
        if (parent::validate() === false) {
123
            return false;
124
        }
125
126
        try {
127
            $result = $this->isValidMime();
128
            if ($result === false) {
129
                $this->errors[] = _t(
130
                    'File.INVALIDMIME',
131
                    'File extension does not match known MIME type'
132
                );
133
                return false;
134
            }
135
        } catch (MimeUploadValidator_Exception $e) {
136
            $this->errors[] = _t(
137
                'File.FAILEDMIMECHECK',
138
                'MIME validation failed: {message}',
139
                'Argument 1: Message about why MIME type detection failed',
140
                array('message' => $e->getMessage())
0 ignored issues
show
array('message' => $e->getMessage()) is of type array<string,string,{"message":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
141
            );
142
            return false;
143
        }
144
145
        return true;
146
    }
147
}
148
149
class MimeUploadValidator_Exception extends Exception
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
150
{
151
}
152