PageRenderTime 34ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Lib/Flourish/fUpload.php

https://gitlab.com/foodsharing-dev/foodsharing
PHP | 290 lines | 134 code | 36 blank | 120 comment | 38 complexity | fbb2e22003e2b8b3104c250a70923302 MD5 | raw file
  1. <?php
  2. namespace Flourish;
  3. /**
  4. * Provides validation and movement of uploaded files.
  5. *
  6. * @copyright Copyright (c) 2007-2012 Will Bond, others
  7. * @author Will Bond [wb] <will@flourishlib.com>
  8. * @author Will Bond, iMarc LLC [wb-imarc] <will@imarc.net>
  9. * @license http://flourishlib.com/license
  10. *
  11. * @see http://flourishlib.com/fUpload
  12. *
  13. * @version 1.0.0b15
  14. * @changes 1.0.0b15 Fixed an undefined variable error in ::setMaxSize() [wb, 2012-09-16]
  15. * @changes 1.0.0b14 Fixed some method signatures [wb, 2011-08-24]
  16. * @changes 1.0.0b13 Changed the class to throw fValidationException objects instead of fProgrammerException objects when the form is improperly configured - this is to prevent error logs when bad requests are sent by scanners/hackers [wb, 2011-08-24]
  17. * @changes 1.0.0b12 Fixed the ::filter() callback constant [wb, 2010-11-24]
  18. * @changes 1.0.0b11 Added ::setImageDimensions() and ::setImageRatio() [wb-imarc, 2010-11-11]
  19. * @changes 1.0.0b10 BackwardsCompatibilityBreak - renamed ::setMaxFilesize() to ::setMaxSize() to be consistent with fFile::getSize() [wb, 2010-05-30]
  20. * @changes 1.0.0b9 BackwardsCompatibilityBreak - the class no longer accepts uploaded files that start with a `.` unless ::allowDotFiles() is called - added ::setOptional() [wb, 2010-05-30]
  21. * @changes 1.0.0b8 BackwardsCompatibilityBreak - ::validate() no longer returns the `$_FILES` array for the file being validated - added `$return_message` parameter to ::validate(), fixed a bug with detection of mime type for text files [wb, 2010-05-26]
  22. * @changes 1.0.0b7 Added ::filter() to allow for ignoring array file upload field entries that did not have a file uploaded [wb, 2009-10-06]
  23. * @changes 1.0.0b6 Updated ::move() to use the new fFilesystem::createObject() method [wb, 2009-01-21]
  24. * @changes 1.0.0b5 Removed some unnecessary error suppression operators from ::move() [wb, 2009-01-05]
  25. * @changes 1.0.0b4 Updated ::validate() so it properly handles upload max filesize specified in human-readable notation [wb, 2009-01-05]
  26. * @changes 1.0.0b3 Removed the dependency on fRequest [wb, 2009-01-05]
  27. * @changes 1.0.0b2 Fixed a bug with validating filesizes [wb, 2008-11-25]
  28. * @changes 1.0.0b The initial implementation [wb, 2007-06-14]
  29. */
  30. class fUpload
  31. {
  32. // The following constants allow for nice looking callbacks to static methods
  33. const check = 'fUpload::check';
  34. /**
  35. * Checks to see if the field specified is a valid file upload field.
  36. *
  37. * @throws fValidationException If `$throw_exception` is `TRUE` and the request was not a POST or the content type is not multipart/form-data
  38. *
  39. * @param string $field The field to check
  40. * @param bool $throw_exception If an exception should be thrown when there are issues with the form
  41. *
  42. * @return bool If the field is a valid file upload field
  43. */
  44. public static function check($field, $throw_exception = true)
  45. {
  46. if (isset($_GET[$field]) && $_SERVER['REQUEST_METHOD'] != 'POST') {
  47. if ($throw_exception) {
  48. throw new fValidationException(
  49. 'Missing method="post" attribute in form tag'
  50. );
  51. }
  52. return false;
  53. }
  54. if (isset($_POST[$field]) && (!isset($_SERVER['CONTENT_TYPE']) || stripos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') === false)) {
  55. if ($throw_exception) {
  56. throw new fValidationException(
  57. 'Missing enctype="multipart/form-data" attribute in form tag'
  58. );
  59. }
  60. return false;
  61. }
  62. return isset($_FILES) && isset($_FILES[$field]) && is_array($_FILES[$field]);
  63. }
  64. /**
  65. * Composes text using fText if loaded.
  66. *
  67. * @param string $message The message to compose
  68. * @param mixed $component A string or number to insert into the message
  69. * @param mixed ...
  70. *
  71. * @return string The composed and possible translated message
  72. */
  73. protected static function compose($message)
  74. {
  75. $args = array_slice(func_get_args(), 1);
  76. return vsprintf($message, $args);
  77. }
  78. /**
  79. * The error message to display if the mime types do not match.
  80. *
  81. * @var string
  82. */
  83. private $mime_type_message = null;
  84. /**
  85. * The mime types of files accepted.
  86. *
  87. * @var array
  88. */
  89. private $mime_types = array();
  90. /**
  91. * All requests that hit this method should be requests for callbacks.
  92. *
  93. * @internal
  94. *
  95. * @param string $method The method to create a callback for
  96. *
  97. * @return callback The callback for the method requested
  98. */
  99. public function __get($method)
  100. {
  101. return array($this, $method);
  102. }
  103. /**
  104. * Returns the `$_FILES` array for the field specified.
  105. *
  106. * @param string $field The field to get the file array for
  107. * @param mixed $index If the field is an array file upload field, use this to specify which array index to return
  108. *
  109. * @return array The file info array from `$_FILES`
  110. */
  111. private function extractFileUploadArray($field, $index = null)
  112. {
  113. if ($index === null) {
  114. return $_FILES[$field];
  115. }
  116. if (!is_array($_FILES[$field]['name'])) {
  117. throw new fValidationException(
  118. 'The field specified, %s, does not appear to be an array file upload field',
  119. $field
  120. );
  121. }
  122. if (!isset($_FILES[$field]['name'][$index])) {
  123. throw new fValidationException(
  124. 'The index specified, %1$s, is invalid for the field %2$s',
  125. $index,
  126. $field
  127. );
  128. }
  129. $file_array = array();
  130. $file_array['name'] = $_FILES[$field]['name'][$index];
  131. $file_array['type'] = $_FILES[$field]['type'][$index];
  132. $file_array['tmp_name'] = $_FILES[$field]['tmp_name'][$index];
  133. $file_array['error'] = $_FILES[$field]['error'][$index];
  134. $file_array['size'] = $_FILES[$field]['size'][$index];
  135. return $file_array;
  136. }
  137. /**
  138. * Moves an uploaded file from the temp directory to a permanent location.
  139. *
  140. * @throws fValidationException When the form is not setup for file uploads, the `$directory` is somehow invalid or ::validate() thows an exception
  141. *
  142. * @param string|fDirectory $directory The directory to upload the file to
  143. * @param string $field The file upload field to get the file from
  144. * @param mixed $index If the field was an array file upload field, upload the file corresponding to this index
  145. *
  146. * @return fFile|null An fFile (or fImage) object, or `NULL` if no file was uploaded
  147. */
  148. public function move($directory, $field, $index = null)
  149. {
  150. if (!is_object($directory)) {
  151. $directory = new fDirectory($directory);
  152. }
  153. if (!$directory->isWritable()) {
  154. throw new fEnvironmentException(
  155. 'The directory specified, %s, is not writable',
  156. $directory->getPath()
  157. );
  158. }
  159. if (!self::check($field)) {
  160. throw new fValidationException(
  161. 'The field specified, %s, does not appear to be a file upload field',
  162. $field
  163. );
  164. }
  165. $file_array = $this->extractFileUploadArray($field, $index);
  166. $error = $this->validateField($file_array);
  167. if ($error) {
  168. throw new fValidationException($error);
  169. }
  170. // This will only ever be true if the file is optional
  171. if ($file_array['name'] == '' || $file_array['tmp_name'] == '' || $file_array['size'] == 0) {
  172. return null;
  173. }
  174. $file_name = fFilesystem::makeURLSafe($file_array['name']);
  175. $file_name = $directory->getPath() . $file_name;
  176. $file_name = fFilesystem::makeUniqueName($file_name);
  177. if (!move_uploaded_file($file_array['tmp_name'], $file_name)) {
  178. throw new fEnvironmentException('There was an error moving the uploaded file');
  179. }
  180. if (!chmod($file_name, 0644)) {
  181. throw new fEnvironmentException('Unable to change permissions on the uploaded file');
  182. }
  183. return fFilesystem::createObject($file_name);
  184. }
  185. /**
  186. * Sets the file mime types accepted.
  187. *
  188. * @param array $mime_types The mime types to accept
  189. * @param string $message The message to display if the uploaded file is not one of the mime type specified
  190. */
  191. public function setMIMETypes($mime_types, $message)
  192. {
  193. $this->mime_types = $mime_types;
  194. $this->mime_type_message = $message;
  195. }
  196. /**
  197. * Validates a $_FILES array against the upload configuration.
  198. *
  199. * @param array $file_array The $_FILES array for a single file
  200. *
  201. * @return string The validation error message
  202. */
  203. private function validateField($file_array)
  204. {
  205. if (empty($file_array['name'])) {
  206. return self::compose('Please upload a file');
  207. }
  208. if ($file_array['error'] == UPLOAD_ERR_FORM_SIZE || $file_array['error'] == UPLOAD_ERR_INI_SIZE) {
  209. $max_size = (!empty($_POST['MAX_FILE_SIZE'])) ? $_POST['MAX_FILE_SIZE'] : ini_get('upload_max_filesize');
  210. $max_size = (!is_numeric($max_size)) ? fFilesystem::convertToBytes($max_size) : $max_size;
  211. return self::compose(
  212. 'The file uploaded is over the limit of %s',
  213. fFilesystem::formatFilesize($max_size)
  214. );
  215. }
  216. if (empty($file_array['tmp_name']) || empty($file_array['size'])) {
  217. return self::compose('Please upload a file');
  218. }
  219. if (!empty($this->mime_types) && file_exists($file_array['tmp_name'])) {
  220. $contents = file_get_contents($file_array['tmp_name'], false, null, 0, 4096);
  221. if (!in_array(fFile::determineMimeType($file_array['name'], $contents), $this->mime_types)) {
  222. return self::compose($this->mime_type_message);
  223. }
  224. }
  225. $file_info = fFilesystem::getPathInfo($file_array['name']);
  226. if (in_array(strtolower($file_info['extension']), array('php', 'php4', 'php5'))) {
  227. return self::compose('The file uploaded is a PHP file, but those are not permitted');
  228. }
  229. if (substr($file_array['name'], 0, 1) == '.') {
  230. return self::compose('The name of the uploaded file may not being with a .');
  231. }
  232. }
  233. }
  234. /*
  235. * Copyright (c) 2007-2012 Will Bond <will@flourishlib.com>, others
  236. *
  237. * Permission is hereby granted, free of charge, to any person obtaining a copy
  238. * of this software and associated documentation files (the "Software"), to deal
  239. * in the Software without restriction, including without limitation the rights
  240. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  241. * copies of the Software, and to permit persons to whom the Software is
  242. * furnished to do so, subject to the following conditions:
  243. *
  244. * The above copyright notice and this permission notice shall be included in
  245. * all copies or substantial portions of the Software.
  246. *
  247. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  248. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  249. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  250. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  251. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  252. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  253. * THE SOFTWARE.
  254. */