1: <?php
2: /**
3: * The Horde_Mime_Magic:: class provides an interface to determine a MIME type
4: * for various content, if it provided with different levels of information.
5: *
6: * Copyright 1999-2012 Horde LLC (http://www.horde.org/)
7: *
8: * See the enclosed file COPYING for license information (LGPL). If you
9: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
10: *
11: * @author Anil Madhavapeddy <anil@recoil.org>
12: * @author Michael Slusarz <slusarz@horde.org>
13: * @category Horde
14: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
15: * @package Mime
16: */
17: class Horde_Mime_Magic
18: {
19: /**
20: * The MIME extension map.
21: *
22: * @var array
23: */
24: static protected $_map = null;
25:
26: /**
27: * Returns a copy of the MIME extension map.
28: *
29: * @return array The MIME extension map.
30: */
31: static protected function _getMimeExtensionMap()
32: {
33: if (is_null(self::$_map)) {
34: require dirname(__FILE__) . '/mime.mapping.php';
35: self::$_map = $mime_extension_map;
36: }
37:
38: return self::$_map;
39: }
40:
41: /**
42: * Attempt to convert a file extension to a MIME type, based
43: * on the global Horde and application specific config files.
44: *
45: * If we cannot map the file extension to a specific type, then
46: * we fall back to a custom MIME handler 'x-extension/$ext', which
47: * can be used as a normal MIME type internally throughout Horde.
48: *
49: * @param string $ext The file extension to be mapped to a MIME type.
50: *
51: * @return string The MIME type of the file extension.
52: */
53: static public function extToMime($ext)
54: {
55: if (empty($ext)) {
56: return 'application/octet-stream';
57: }
58:
59: $ext = Horde_String::lower($ext);
60: $map = self::_getMimeExtensionMap();
61: $pos = 0;
62:
63: while (!isset($map[$ext])) {
64: if (($pos = strpos($ext, '.')) === false) {
65: break;
66: }
67: $ext = substr($ext, $pos + 1);
68: }
69:
70: return isset($map[$ext])
71: ? $map[$ext]
72: : 'x-extension/' . $ext;
73: }
74:
75: /**
76: * Attempt to convert a filename to a MIME type, based on the global Horde
77: * and application specific config files.
78: *
79: * @param string $filename The filename to be mapped to a MIME type.
80: * @param boolean $unknown How should unknown extensions be handled? If
81: * true, will return 'x-extension/*' types. If
82: * false, will return 'application/octet-stream'.
83: *
84: * @return string The MIME type of the filename.
85: */
86: static public function filenameToMime($filename, $unknown = true)
87: {
88: $pos = strlen($filename) + 1;
89: $type = '';
90:
91: $map = self::_getMimeExtensionMap();
92: for ($i = 0; $i <= $map['__MAXPERIOD__']; ++$i) {
93: $npos = strrpos(substr($filename, 0, $pos - 1), '.');
94: if ($npos === false) {
95: break;
96: }
97: $pos = $npos + 1;
98: }
99:
100: $type = ($pos === false) ? '' : self::extToMime(substr($filename, $pos));
101:
102: return (empty($type) || (!$unknown && (strpos($type, 'x-extension') !== false)))
103: ? 'application/octet-stream'
104: : $type;
105: }
106:
107: /**
108: * Attempt to convert a MIME type to a file extension, based
109: * on the global Horde and application specific config files.
110: *
111: * If we cannot map the type to a file extension, we return false.
112: *
113: * @param string $type The MIME type to be mapped to a file extension.
114: *
115: * @return string The file extension of the MIME type.
116: */
117: static public function mimeToExt($type)
118: {
119: if (empty($type)) {
120: return false;
121: }
122:
123: if (($key = array_search($type, self::_getMimeExtensionMap())) === false) {
124: list($major, $minor) = explode('/', $type);
125: if ($major == 'x-extension') {
126: return $minor;
127: }
128: if (strpos($minor, 'x-') === 0) {
129: return substr($minor, 2);
130: }
131: return false;
132: }
133:
134: return $key;
135: }
136:
137: /**
138: * Attempt to determine the MIME type of an unknown file.
139: *
140: * @param string $path The path to the file to analyze.
141: * @param string $magic_db Path to the mime magic database.
142: * @param array $opts Additional options:
143: * <pre>
144: * 'nostrip' - (boolean) Don't strip parameter information from MIME
145: * type string.
146: * DEFAULT: false
147: * </pre>
148: *
149: * @return mixed The MIME type of the file. Returns false if the file
150: * type can not be determined.
151: */
152: static public function analyzeFile($path, $magic_db = null,
153: $opts = array())
154: {
155: if (Horde_Util::extensionExists('fileinfo')) {
156: $res = empty($magic_db)
157: ? finfo_open(FILEINFO_MIME)
158: : finfo_open(FILEINFO_MIME, $magic_db);
159:
160: if ($res) {
161: $type = trim(finfo_file($res, $path));
162: finfo_close($res);
163:
164: /* Remove any additional information. */
165: if (empty($opts['nostrip'])) {
166: foreach (array(';', ',', '\\0') as $separator) {
167: if (($pos = strpos($type, $separator)) !== false) {
168: $type = rtrim(substr($type, 0, $pos));
169: }
170: }
171:
172: if (preg_match('|^[a-z0-9]+/[.-a-z0-9]+$|i', $type)) {
173: return $type;
174: }
175: } else {
176: return $type;
177: }
178: }
179: }
180:
181: return false;
182: }
183:
184: /**
185: * Attempt to determine the MIME type of an unknown byte stream.
186: *
187: * @param string $data The file data to analyze.
188: * @param string $magic_db Path to the mime magic database.
189: * @param array $opts Additional options:
190: * <pre>
191: * 'nostrip' - (boolean) Don't strip parameter information from MIME
192: * type string.
193: * DEFAULT: false
194: * </pre>
195: *
196: * @return mixed The MIME type of the file. Returns false if the file
197: * type can not be determined.
198: */
199: static public function analyzeData($data, $magic_db = null,
200: $opts = array())
201: {
202: /* If the PHP Mimetype extension is available, use that. */
203: if (Horde_Util::extensionExists('fileinfo')) {
204: $res = empty($magic_db)
205: ? @finfo_open(FILEINFO_MIME)
206: : @finfo_open(FILEINFO_MIME, $magic_db);
207:
208: if (!$res) {
209: return false;
210: }
211:
212: $type = trim(finfo_buffer($res, $data));
213: finfo_close($res);
214:
215: /* Remove any additional information. */
216: if (empty($opts['nostrip'])) {
217: if (($pos = strpos($type, ';')) !== false) {
218: $type = rtrim(substr($type, 0, $pos));
219: }
220:
221: if (($pos = strpos($type, ',')) !== false) {
222: $type = rtrim(substr($type, 0, $pos));
223: }
224: }
225:
226: return $type;
227: }
228:
229: return false;
230: }
231:
232: }
233: