1. sfValidatorFile.class.php
  2. /** * sfValidatorFile validates an uploaded file. * * @package symfony * @subpackage validator * @author Fabien Potencier * @version SVN: $Id: sfValidatorFile.class.php 23951 2009-11-14 20:44:22Z FabianLange $ */
  3. class sfValidatorFile extends sfValidatorBase
  4. {
  5. /**
  6. * Configures the current validator.
  7. *
  8. * Available options:
  9. *
  10. * * max_size: The maximum file size in bytes (cannot exceed upload_max_filesize in php.ini)
  11. * * mime_types: Allowed mime types array or category (available categories: web_images)
  12. * * mime_type_guessers: An array of mime type guesser PHP callables (must return the mime type or null)
  13. * * mime_categories: An array of mime type categories (web_images is defined by default)
  14. * * path: The path where to save the file - as used by the sfValidatedFile class (optional)
  15. * * validated_file_class: Name of the class that manages the cleaned uploaded file (optional)
  16. *
  17. * There are 3 built-in mime type guessers:
  18. *
  19. * * guessFromFileinfo: Uses the finfo_open() function (from the Fileinfo PECL extension)
  20. * * guessFromMimeContentType: Uses the mime_content_type() function (deprecated)
  21. * * guessFromFileBinary: Uses the file binary (only works on *nix system)
  22. *
  23. * Available error codes:
  24. *
  25. * * max_size
  26. * * mime_types
  27. * * partial
  28. * * no_tmp_dir
  29. * * cant_write
  30. * * extension
  31. *
  32. * @param array $options An array of options
  33. * @param array $messages An array of error messages
  34. *
  35. * @see sfValidatorBase
  36. */
  37. protected function configure($options = array(), $messages = array())
  38. {
  39. if (!ini_get('file_uploads'))
  40. {
  41. throw new LogicException(sprintf('Unable to use a file validator as "file_uploads" is disabled in your php.ini file (%s)', get_cfg_var('cfg_file_path')));
  42. }
  43. $this->addOption('max_size');
  44. $this->addOption('mime_types');
  45. $this->addOption('mime_type_guessers', array(
  46. array($this, 'guessFromFileinfo'),
  47. array($this, 'guessFromMimeContentType'),
  48. array($this, 'guessFromFileBinary'),
  49. ));
  50. $this->addOption('mime_categories', array(
  51. 'web_images' => array(
  52. 'image/jpeg',
  53. 'image/pjpeg',
  54. 'image/png',
  55. 'image/x-png',
  56. 'image/gif',
  57. )));
  58. $this->addOption('validated_file_class', 'sfValidatedFile');
  59. $this->addOption('path', null);
  60. $this->addMessage('max_size', 'File is too large (maximum is %max_size% bytes).');
  61. $this->addMessage('mime_types', 'Invalid mime type (%mime_type%).');
  62. $this->addMessage('partial', 'The uploaded file was only partially uploaded.');
  63. $this->addMessage('no_tmp_dir', 'Missing a temporary folder.');
  64. $this->addMessage('cant_write', 'Failed to write file to disk.');
  65. $this->addMessage('extension', 'File upload stopped by extension.');
  66. }
  67. /**
  68. * This validator always returns a sfValidatedFile object.
  69. *
  70. * The input value must be an array with the following keys:
  71. *
  72. * * tmp_name: The absolute temporary path to the file
  73. * * name: The original file name (optional)
  74. * * type: The file content type (optional)
  75. * * error: The error code (optional)
  76. * * size: The file size in bytes (optional)
  77. *
  78. * @see sfValidatorBase
  79. */
  80. protected function doClean($value)
  81. {
  82. if (!is_array($value) || !isset($value['tmp_name']))
  83. {
  84. throw new sfValidatorError($this, 'invalid', array('value' => (string) $value));
  85. }
  86. if (!isset($value['name']))
  87. {
  88. $value['name'] = '';
  89. }
  90. if (!isset($value['error']))
  91. {
  92. $value['error'] = UPLOAD_ERR_OK;
  93. }
  94. if (!isset($value['size']))
  95. {
  96. $value['size'] = filesize($value['tmp_name']);
  97. }
  98. if (!isset($value['type']))
  99. {
  100. $value['type'] = 'application/octet-stream';
  101. }
  102. switch ($value['error'])
  103. {
  104. case UPLOAD_ERR_INI_SIZE:
  105. $max = ini_get('upload_max_filesize');
  106. if ($this->getOption('max_size'))
  107. {
  108. $max = min($max, $this->getOption('max_size'));
  109. }
  110. throw new sfValidatorError($this, 'max_size', array('max_size' => $max, 'size' => (int) $value['size']));
  111. case UPLOAD_ERR_FORM_SIZE:
  112. throw new sfValidatorError($this, 'max_size', array('max_size' => 0, 'size' => (int) $value['size']));
  113. case UPLOAD_ERR_PARTIAL:
  114. throw new sfValidatorError($this, 'partial');
  115. case UPLOAD_ERR_NO_TMP_DIR:
  116. throw new sfValidatorError($this, 'no_tmp_dir');
  117. case UPLOAD_ERR_CANT_WRITE:
  118. throw new sfValidatorError($this, 'cant_write');
  119. case UPLOAD_ERR_EXTENSION:
  120. throw new sfValidatorError($this, 'extension');
  121. }
  122. // check file size
  123. if ($this->hasOption('max_size') && $this->getOption('max_size') < (int) $value['size'])
  124. {
  125. throw new sfValidatorError($this, 'max_size', array('max_size' => $this->getOption('max_size'), 'size' => (int) $value['size']));
  126. }
  127. $mimeType = $this->getMimeType((string) $value['tmp_name'], (string) $value['type']);
  128. // check mime type
  129. if ($this->hasOption('mime_types'))
  130. {
  131. $mimeTypes = is_array($this->getOption('mime_types')) ? $this->getOption('mime_types') : $this->getMimeTypesFromCategory($this->getOption('mime_types'));
  132. if (!in_array($mimeType, array_map('strtolower', $mimeTypes)))
  133. {
  134. throw new sfValidatorError($this, 'mime_types', array('mime_types' => $mimeTypes, 'mime_type' => $mimeType));
  135. }
  136. }
  137. $class = $this->getOption('validated_file_class');
  138. return new $class($value['name'], $mimeType, $value['tmp_name'], $value['size'], $this->getOption('path'));
  139. }
  140. /**
  141. * Returns the mime type of a file.
  142. *
  143. * This methods call each mime_type_guessers option callables to
  144. * guess the mime type.
  145. *
  146. * This method always returns a lower-cased string as mime types are case-insensitive
  147. * as per the RFC 2616 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7).
  148. *
  149. * @param string $file The absolute path of a file
  150. * @param string $fallback The default mime type to return if not guessable
  151. *
  152. * @return string The mime type of the file (fallback is returned if not guessable)
  153. */
  154. protected function getMimeType($file, $fallback)
  155. {
  156. foreach ($this->getOption('mime_type_guessers') as $method)
  157. {
  158. $type = call_user_func($method, $file);
  159. if (null !== $type && $type !== false)
  160. {
  161. return strtolower($type);
  162. }
  163. }
  164. return strtolower($fallback);
  165. }
  166. /**
  167. * Guess the file mime type with PECL Fileinfo extension
  168. *
  169. * @param string $file The absolute path of a file
  170. *
  171. * @return string The mime type of the file (null if not guessable)
  172. */
  173. protected function guessFromFileinfo($file)
  174. {
  175. if (!function_exists('finfo_open') || !is_readable($file))
  176. {
  177. return null;
  178. }
  179. if (!$finfo = new finfo(FILEINFO_MIME))
  180. {
  181. return null;
  182. }
  183. $type = $finfo->file($file);
  184. // remove charset (added as of PHP 5.3)
  185. if (false !== $pos = strpos($type, ';'))
  186. {
  187. $type = substr($type, 0, $pos);
  188. }
  189. return $type;
  190. }
  191. /**
  192. * Guess the file mime type with mime_content_type function (deprecated)
  193. *
  194. * @param string $file The absolute path of a file
  195. *
  196. * @return string The mime type of the file (null if not guessable)
  197. */
  198. protected function guessFromMimeContentType($file)
  199. {
  200. if (!function_exists('mime_content_type') || !is_readable($file))
  201. {
  202. return null;
  203. }
  204. return mime_content_type($file);
  205. }
  206. /**
  207. * Guess the file mime type with the file binary (only available on *nix)
  208. *
  209. * @param string $file The absolute path of a file
  210. *
  211. * @return string The mime type of the file (null if not guessable)
  212. */
  213. protected function guessFromFileBinary($file)
  214. {
  215. ob_start();
  216. //need to use --mime instead of -i. see #6641
  217. passthru(sprintf('file -b --mime %s 2>/dev/null', escapeshellarg($file)), $return);
  218. if ($return > 0)
  219. {
  220. ob_end_clean();
  221. return null;
  222. }
  223. $type = trim(ob_get_clean());
  224. if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-]+)#i', $type, $match))
  225. {
  226. // it's not a type, but an error message
  227. return null;
  228. }
  229. return $match[1];
  230. }
  231. protected function getMimeTypesFromCategory($category)
  232. {
  233. $categories = $this->getOption('mime_categories');
  234. if (!isset($categories[$category]))
  235. {
  236. throw new InvalidArgumentException(sprintf('Invalid mime type category "%s".', $category));
  237. }
  238. return $categories[$category];
  239. }
  240. /**
  241. * @see sfValidatorBase
  242. */
  243. protected function isEmpty($value)
  244. {
  245. // empty if the value is not an array
  246. // or if the value comes from PHP with an error of UPLOAD_ERR_NO_FILE
  247. return
  248. (!is_array($value))
  249. ||
  250. (is_array($value) && isset($value['error']) && UPLOAD_ERR_NO_FILE === $value['error']);
  251. }
  252. }

Debug toolbar