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

code/MimeUploadValidator.php (1 issue)

not multiple classes are defined in the same file.

Coding Style Compatibility Minor

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
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())
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...
150
{
151
}
152