AvailablePath.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. /**
  7. * Validator for check not protected/available path
  8. *
  9. * Mask symbols from path:
  10. * "?" - something directory with any name
  11. * "*" - something directory structure, which can not exist
  12. * Note: For set directory structure which must be exist, need to set mask "/?/{@*}"
  13. * Mask symbols from filename:
  14. * "*" - something symbols in file name
  15. * Example:
  16. * <code>
  17. * //set available path
  18. * $validator->setAvailablePath(array('/path/to/?/*fileMask.xml'));
  19. * $validator->isValid('/path/to/MyDir/Some-fileMask.xml'); //return true
  20. * $validator->setAvailablePath(array('/path/to/{@*}*.xml'));
  21. * $validator->isValid('/path/to/my.xml'); //return true, because directory structure can't exist
  22. * </code>
  23. *
  24. * @author Magento Core Team <core@magentocommerce.com>
  25. */
  26. namespace Magento\MediaStorage\Model\File\Validator;
  27. class AvailablePath extends \Zend_Validate_Abstract
  28. {
  29. const PROTECTED_PATH = 'protectedPath';
  30. const NOT_AVAILABLE_PATH = 'notAvailablePath';
  31. const PROTECTED_LFI = 'protectedLfi';
  32. /**
  33. * The path
  34. *
  35. * @var string
  36. */
  37. protected $_value;
  38. /**
  39. * Protected paths
  40. *
  41. * @var string[]
  42. */
  43. protected $_protectedPaths = [];
  44. /**
  45. * Available paths
  46. *
  47. * @var string[]
  48. */
  49. protected $_availablePaths = [];
  50. /**
  51. * Cache of made regular expressions from path masks
  52. *
  53. * @var array
  54. */
  55. protected $_pathsData;
  56. /**
  57. * Construct
  58. */
  59. public function __construct()
  60. {
  61. $this->_initMessageTemplates();
  62. }
  63. /**
  64. * Initialize message templates with translating
  65. *
  66. * @return $this
  67. */
  68. protected function _initMessageTemplates()
  69. {
  70. if (!$this->_messageTemplates) {
  71. $this->_messageTemplates = [
  72. self::PROTECTED_PATH => __('Path "%value%" is protected and cannot be used.'),
  73. self::NOT_AVAILABLE_PATH => __('Path "%value%" is not available and cannot be used.'),
  74. self::PROTECTED_LFI => __('Path "%value%" may not include parent directory traversal ("../", "..\\").'),
  75. ];
  76. }
  77. return $this;
  78. }
  79. /**
  80. * Set paths masks
  81. *
  82. * @param array $paths All paths masks types.
  83. * E.g.: array('available' => array(...), 'protected' => array(...))
  84. * @return $this
  85. */
  86. public function setPaths(array $paths)
  87. {
  88. if (isset($paths['available']) && is_array($paths['available'])) {
  89. $this->_availablePaths = $paths['available'];
  90. }
  91. if (isset($paths['protected']) && is_array($paths['protected'])) {
  92. $this->_protectedPaths = $paths['protected'];
  93. }
  94. return $this;
  95. }
  96. /**
  97. * Set protected paths masks
  98. *
  99. * @param array $paths
  100. * @return $this
  101. */
  102. public function setProtectedPaths(array $paths)
  103. {
  104. $this->_protectedPaths = $paths;
  105. return $this;
  106. }
  107. /**
  108. * Add protected paths masks
  109. *
  110. * @param string|string[] $path
  111. * @return $this
  112. */
  113. public function addProtectedPath($path)
  114. {
  115. if (is_array($path)) {
  116. $this->_protectedPaths = array_merge($this->_protectedPaths, $path);
  117. } else {
  118. $this->_protectedPaths[] = $path;
  119. }
  120. return $this;
  121. }
  122. /**
  123. * Get protected paths masks
  124. *
  125. * @return string[]
  126. */
  127. public function getProtectedPaths()
  128. {
  129. return $this->_protectedPaths;
  130. }
  131. /**
  132. * Set available paths masks
  133. *
  134. * @param array $paths
  135. * @return $this
  136. */
  137. public function setAvailablePaths(array $paths)
  138. {
  139. $this->_availablePaths = $paths;
  140. return $this;
  141. }
  142. /**
  143. * Add available paths mask
  144. *
  145. * @param string|string[] $path
  146. * @return $this
  147. */
  148. public function addAvailablePath($path)
  149. {
  150. if (is_array($path)) {
  151. $this->_availablePaths = array_merge($this->_availablePaths, $path);
  152. } else {
  153. $this->_availablePaths[] = $path;
  154. }
  155. return $this;
  156. }
  157. /**
  158. * Get available paths masks
  159. *
  160. * @return string[]
  161. */
  162. public function getAvailablePaths()
  163. {
  164. return $this->_availablePaths;
  165. }
  166. /**
  167. * Returns true if and only if $value meets the validation requirements
  168. *
  169. * If $value fails validation, then this method returns false, and
  170. * getMessages() will return an array of messages that explain why the
  171. * validation failed.
  172. *
  173. * @param string $value File/dir path
  174. * @return bool
  175. * @throws \Exception Throw exception on empty both paths masks types
  176. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  177. */
  178. public function isValid($value)
  179. {
  180. $value = trim($value);
  181. $this->_setValue($value);
  182. if (!$this->_availablePaths && !$this->_protectedPaths) {
  183. throw new \Exception(__('Please set available and/or protected paths list(s) before validation.'));
  184. }
  185. if (preg_match('#\.\.[\\\/]#', $this->_value)) {
  186. $this->_error(self::PROTECTED_LFI, $this->_value);
  187. return false;
  188. }
  189. //validation
  190. $value = str_replace('\\', '/', $this->_value);
  191. $valuePathInfo = pathinfo(ltrim($value, '\\/'));
  192. if ($valuePathInfo['dirname'] == '.' || $valuePathInfo['dirname'] == '/') {
  193. $valuePathInfo['dirname'] = '';
  194. }
  195. if ($this->_protectedPaths && !$this->_isValidByPaths($valuePathInfo, $this->_protectedPaths, true)) {
  196. $this->_error(self::PROTECTED_PATH, $this->_value);
  197. return false;
  198. }
  199. if ($this->_availablePaths && !$this->_isValidByPaths($valuePathInfo, $this->_availablePaths, false)) {
  200. $this->_error(self::NOT_AVAILABLE_PATH, $this->_value);
  201. return false;
  202. }
  203. return true;
  204. }
  205. /**
  206. * Validate value by path masks
  207. *
  208. * @param array $valuePathInfo Path info from value path
  209. * @param string[] $paths Protected/available paths masks
  210. * @param bool $protected Paths masks is protected?
  211. * @return bool
  212. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  213. * @SuppressWarnings(PHPMD.NPathComplexity)
  214. */
  215. protected function _isValidByPaths($valuePathInfo, $paths, $protected)
  216. {
  217. foreach ($paths as $path) {
  218. $path = ltrim($path, '\\/');
  219. if (!isset($this->_pathsData[$path]['regFilename'])) {
  220. $pathInfo = pathinfo($path);
  221. $options['file_mask'] = $pathInfo['basename'];
  222. if ($pathInfo['dirname'] == '.' || $pathInfo['dirname'] == '/') {
  223. $pathInfo['dirname'] = '';
  224. } else {
  225. $pathInfo['dirname'] = str_replace('\\', '/', $pathInfo['dirname']);
  226. }
  227. $options['dir_mask'] = $pathInfo['dirname'];
  228. $this->_pathsData[$path]['options'] = $options;
  229. } else {
  230. $options = $this->_pathsData[$path]['options'];
  231. }
  232. //file mask
  233. if (false !== strpos($options['file_mask'], '*')) {
  234. if (!isset($this->_pathsData[$path]['regFilename'])) {
  235. //make regular
  236. $reg = $options['file_mask'];
  237. $reg = str_replace('.', '\.', $reg);
  238. $reg = str_replace('*', '.*?', $reg);
  239. $reg = "/^({$reg})\$/";
  240. } else {
  241. $reg = $this->_pathsData[$path]['regFilename'];
  242. }
  243. $resultFile = preg_match($reg, $valuePathInfo['basename']);
  244. } else {
  245. $resultFile = $options['file_mask'] == $valuePathInfo['basename'];
  246. }
  247. //directory mask
  248. $reg = $options['dir_mask'] . '/';
  249. if (!isset($this->_pathsData[$path]['regDir'])) {
  250. //make regular
  251. $reg = str_replace('.', '\.', $reg);
  252. $reg = str_replace('*\\', '||', $reg);
  253. $reg = str_replace('*/', '||', $reg);
  254. //$reg = str_replace('*', '||', $reg);
  255. $reg = str_replace('/', '[\\/]', $reg);
  256. $reg = str_replace('?', '([^\\/]+)', $reg);
  257. $reg = str_replace('||', '(.*[\\/])?', $reg);
  258. $reg = "/^{$reg}\$/";
  259. } else {
  260. $reg = $this->_pathsData[$path]['regDir'];
  261. }
  262. $resultDir = preg_match($reg, $valuePathInfo['dirname'] . '/');
  263. if ($protected && ($resultDir && $resultFile)) {
  264. return false;
  265. } elseif (!$protected && ($resultDir && $resultFile)) {
  266. //return true because one match with available path mask
  267. return true;
  268. }
  269. }
  270. if ($protected) {
  271. return true;
  272. } else {
  273. //return false because no one match with available path mask
  274. return false;
  275. }
  276. }
  277. }