Passed
Pull Request — master (#29)
by
unknown
04:17
created

MimeUploadValidator::setFilterPattern()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\MimeValidator;
4
5
use finfo;
6
use Exception;
7
use SilverStripe\Core\Config\Config;
8
use SilverStripe\Control\HTTP;
9
use SilverStripe\Assets\Upload_Validator;
10
11
12
/**
13
 * Adds an additional validation rule to Upload_Validator that attempts to detect
14
 * the file extension of an uploaded file matches it's contents, which is done
15
 * by detecting the MIME type and doing a fuzzy match.
16
 *
17
 * Class MimeUploadValidator
18
 * @package SilverStripe\MimeValidator
19
 */
20
class MimeUploadValidator extends Upload_Validator
21
{
22
    /**
23
     * The preg_replace() pattern to use against MIME types. Used to strip out
24
     * useless characters so matching of MIME types can be fuzzy.
25
     *
26
     * @var string Regexp pattern
27
     */
28
    protected $filterPattern = '/.*[\/\.\-\+]/i';
29
30
    /**
31
     * @param string $pattern
32
     */
33
    public function setFilterPattern($pattern)
34
    {
35
        $this->filterPattern = $pattern;
36
    }
37
38
    /**
39
     * @return string
40
     */
41
    public function getFilterPattern()
42
    {
43
        return $this->filterPattern;
44
    }
45
46
    /**
47
     * Check if the temporary file has a valid MIME type for it's extension.
48
     *
49
     * @uses finfo php extension
50
     * @return bool|null
51
     * @throws MimeUploadValidator_Exception
52
     */
53
    public function isValidMime()
54
    {
55
        $extension = strtolower(pathinfo($this->tmpFile['name'], PATHINFO_EXTENSION));
56
57
        // we can't check filenames without an extension or no temp file path, let them pass validation.
58
        if (!$extension || !$this->tmpFile['tmp_name']) {
59
            return true;
60
        }
61
62
        $expectedMimes = $this->getExpectedMimeTypes($this->tmpFile);
63
        if (empty($expectedMimes)) {
64
            throw new MimeUploadValidator_Exception(
65
                sprintf('Could not find a MIME type for extension %s', $extension)
66
            );
67
        }
68
69
        $fileInfo = new finfo(FILEINFO_MIME_TYPE);
0 ignored issues
show
Bug introduced by
The call to finfo::finfo() has too few arguments starting with arg. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

69
        $fileInfo = /** @scrutinizer ignore-call */ new finfo(FILEINFO_MIME_TYPE);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
70
        $foundMime = $fileInfo->file($this->tmpFile['tmp_name']);
71
        if (!$foundMime) {
72
            throw new MimeUploadValidator_Exception(
73
                sprintf('Could not find a MIME type for file %s', $this->tmpFile['tmp_name'])
74
            );
75
        }
76
77
        foreach ($expectedMimes as $expected) {
78
            if ($this->compareMime($foundMime, $expected)) {
79
                return true;
80
            }
81
        }
82
83
        return false;
84
    }
85
86
    /**
87
     * Fetches an array of valid mimetypes.
88
     *
89
     * @param $file
90
     * @return array
91
     * @throws MimeUploadValidator_Exception
92
     */
93
    public function getExpectedMimeTypes($file)
94
    {
95
        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
96
97
        // if the finfo php extension isn't loaded, we can't complete this check.
98
        if (!class_exists('finfo')) {
99
            throw new MimeUploadValidator_Exception('PHP extension finfo is not loaded');
100
        }
101
102
        // Attempt to figure out which mime types are expected/acceptable here.
103
        $expectedMimes = array();
104
105
        // Get the mime types set in framework core
106
        $knownMimes = Config::inst()->get(HTTP::class, 'MimeTypes');
107
        if (isset($knownMimes[$extension])) {
108
            $expectedMimes[] = $knownMimes[$extension];
109
        }
110
111
        // Get the mime types and their variations from mime validator
112
        $knownMimes = $this->config()->get('MimeTypes');
113
        if (isset($knownMimes[$extension])) {
114
          $mimes = (array) $knownMimes[$extension];
115
116
          foreach($mimes as $mime){
117
              if (!in_array($mime,$expectedMimes)) {
118
                  $expectedMimes[] = $mime;
119
              }
120
          }
121
        }
122
123
        return $expectedMimes;
124
    }
125
126
    /**
127
     * Check two MIME types roughly match eachother.
128
     *
129
     * Before we check MIME types, remove known prefixes "vnd.", "x-" etc.
130
     * If there is a suffix, we'll use that to compare. Examples:
131
     *
132
     * application/x-json = json
133
     * application/json = json
134
     * application/xhtml+xml = xml
135
     * application/xml = xml
136
     *
137
     * @param string $first The first MIME type to compare to the second
138
     * @param string $second The second MIME type to compare to the first
139
     * @return boolean
140
     */
141
    public function compareMime($first, $second)
142
    {
143
        return preg_replace($this->filterPattern, '', $first) === preg_replace($this->filterPattern, '', $second);
144
    }
145
146
    public function validate()
147
    {
148
        if (parent::validate() === false) {
149
            return false;
150
        }
151
152
        try {
153
            $result = $this->isValidMime();
154
155
            if ($result === false) {
156
                $this->errors[] = _t(
157
                    __CLASS__ . '.INVALIDMIME',
158
                    'File extension does not match known MIME type'
159
                );
160
161
                return false;
162
            }
163
        } catch (MimeUploadValidator_Exception $e) {
164
            $this->errors[] = _t(
165
                __CLASS__ . '.FAILEDMIMECHECK',
166
                'MIME validation failed: {message}',
167
                'Argument 1: Message about why MIME type detection failed',
168
                ['message' => $e->getMessage()]
169
            );
170
171
            return false;
172
        }
173
174
        return true;
175
    }
176
}
177