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

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

Below you find some examples:

// Incorrect placement according to PSR2
class MyClass
{
    public function foo()
    {

    }
    // This blank line is not allowed.

}

// Correct
class MyClass
{
    public function foo()
    {

    } // No blank lines after this line.
}
Loading history...
137
138
class MimeUploadValidator_Exception extends Exception {
0 ignored issues
show
As per PSR2, the opening brace for this class should be on a new line.
Loading history...
139
140
}
141
142