FileStorage.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <?php
  2. namespace Drupal\Component\PhpStorage;
  3. /**
  4. * Stores the code as regular PHP files.
  5. */
  6. class FileStorage implements PhpStorageInterface {
  7. /**
  8. * The directory where the files should be stored.
  9. *
  10. * @var string
  11. */
  12. protected $directory;
  13. /**
  14. * Constructs this FileStorage object.
  15. *
  16. * @param array $configuration
  17. * An associative array, containing at least these two keys:
  18. * - directory: The directory where the files should be stored.
  19. * - bin: The storage bin. Multiple storage objects can be instantiated with
  20. * the same configuration, but for different bins..
  21. */
  22. public function __construct(array $configuration) {
  23. $this->directory = $configuration['directory'] . '/' . $configuration['bin'];
  24. }
  25. /**
  26. * {@inheritdoc}
  27. */
  28. public function exists($name) {
  29. return file_exists($this->getFullPath($name));
  30. }
  31. /**
  32. * {@inheritdoc}
  33. */
  34. public function load($name) {
  35. // The FALSE returned on failure is enough for the caller to handle this,
  36. // we do not want a warning too.
  37. return (@include_once $this->getFullPath($name)) !== FALSE;
  38. }
  39. /**
  40. * {@inheritdoc}
  41. */
  42. public function save($name, $code) {
  43. $path = $this->getFullPath($name);
  44. $directory = dirname($path);
  45. $this->ensureDirectory($directory);
  46. return (bool) file_put_contents($path, $code);
  47. }
  48. /**
  49. * Returns the standard .htaccess lines that Drupal writes to file directories.
  50. *
  51. * @param bool $private
  52. * (optional) Set to FALSE to return the .htaccess lines for an open and
  53. * public directory. The default is TRUE, which returns the .htaccess lines
  54. * for a private and protected directory.
  55. *
  56. * @return string
  57. * The desired contents of the .htaccess file.
  58. *
  59. * @see file_create_htaccess()
  60. */
  61. public static function htaccessLines($private = TRUE) {
  62. $lines = <<<EOF
  63. # Turn off all options we don't need.
  64. Options -Indexes -ExecCGI -Includes -MultiViews
  65. # Set the catch-all handler to prevent scripts from being executed.
  66. SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
  67. <Files *>
  68. # Override the handler again if we're run later in the evaluation list.
  69. SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003
  70. </Files>
  71. # If we know how to do it safely, disable the PHP engine entirely.
  72. <IfModule mod_php5.c>
  73. php_flag engine off
  74. </IfModule>
  75. EOF;
  76. if ($private) {
  77. $lines = <<<EOF
  78. # Deny all requests from Apache 2.4+.
  79. <IfModule mod_authz_core.c>
  80. Require all denied
  81. </IfModule>
  82. # Deny all requests from Apache 2.0-2.2.
  83. <IfModule !mod_authz_core.c>
  84. Deny from all
  85. </IfModule>
  86. $lines
  87. EOF;
  88. }
  89. return $lines;
  90. }
  91. /**
  92. * Ensures the directory exists, has the right permissions, and a .htaccess.
  93. *
  94. * For compatibility with open_basedir, the requested directory is created
  95. * using a recursion logic that is based on the relative directory path/tree:
  96. * It works from the end of the path recursively back towards the root
  97. * directory, until an existing parent directory is found. From there, the
  98. * subdirectories are created.
  99. *
  100. * @param string $directory
  101. * The directory path.
  102. * @param int $mode
  103. * The mode, permissions, the directory should have.
  104. */
  105. protected function ensureDirectory($directory, $mode = 0777) {
  106. if ($this->createDirectory($directory, $mode)) {
  107. $htaccess_path = $directory . '/.htaccess';
  108. if (!file_exists($htaccess_path) && file_put_contents($htaccess_path, static::htaccessLines())) {
  109. @chmod($htaccess_path, 0444);
  110. }
  111. }
  112. }
  113. /**
  114. * Ensures the requested directory exists and has the right permissions.
  115. *
  116. * For compatibility with open_basedir, the requested directory is created
  117. * using a recursion logic that is based on the relative directory path/tree:
  118. * It works from the end of the path recursively back towards the root
  119. * directory, until an existing parent directory is found. From there, the
  120. * subdirectories are created.
  121. *
  122. * @param string $directory
  123. * The directory path.
  124. * @param int $mode
  125. * The mode, permissions, the directory should have.
  126. *
  127. * @return bool
  128. * TRUE if the directory exists or has been created, FALSE otherwise.
  129. */
  130. protected function createDirectory($directory, $mode = 0777) {
  131. // If the directory exists already, there's nothing to do.
  132. if (is_dir($directory)) {
  133. return TRUE;
  134. }
  135. // If the parent directory doesn't exist, try to create it.
  136. $parent_exists = is_dir($parent = dirname($directory));
  137. if (!$parent_exists) {
  138. $parent_exists = $this->createDirectory($parent, $mode);
  139. }
  140. // If parent exists, try to create the directory and ensure to set its
  141. // permissions, because mkdir() obeys the umask of the current process.
  142. if ($parent_exists) {
  143. // We hide warnings and ignore the return because there may have been a
  144. // race getting here and the directory could already exist.
  145. @mkdir($directory);
  146. // Only try to chmod() if the subdirectory could be created.
  147. if (is_dir($directory)) {
  148. // Avoid writing permissions if possible.
  149. if (fileperms($directory) !== $mode) {
  150. return chmod($directory, $mode);
  151. }
  152. return TRUE;
  153. }
  154. else {
  155. // Something failed and the directory doesn't exist.
  156. trigger_error('mkdir(): Permission Denied', E_USER_WARNING);
  157. }
  158. }
  159. return FALSE;
  160. }
  161. /**
  162. * {@inheritdoc}
  163. */
  164. public function delete($name) {
  165. $path = $this->getFullPath($name);
  166. if (file_exists($path)) {
  167. return $this->unlink($path);
  168. }
  169. return FALSE;
  170. }
  171. /**
  172. * {@inheritdoc}
  173. */
  174. public function getFullPath($name) {
  175. return $this->directory . '/' . $name;
  176. }
  177. /**
  178. * {@inheritdoc}
  179. */
  180. public function writeable() {
  181. return TRUE;
  182. }
  183. /**
  184. * {@inheritdoc}
  185. */
  186. public function deleteAll() {
  187. return $this->unlink($this->directory);
  188. }
  189. /**
  190. * Deletes files and/or directories in the specified path.
  191. *
  192. * If the specified path is a directory the method will
  193. * call itself recursively to process the contents. Once the contents have
  194. * been removed the directory will also be removed.
  195. *
  196. * @param string $path
  197. * A string containing either a file or directory path.
  198. *
  199. * @return bool
  200. * TRUE for success or if path does not exist, FALSE in the event of an
  201. * error.
  202. */
  203. protected function unlink($path) {
  204. if (file_exists($path)) {
  205. if (is_dir($path)) {
  206. // Ensure the folder is writable.
  207. @chmod($path, 0777);
  208. foreach (new \DirectoryIterator($path) as $fileinfo) {
  209. if (!$fileinfo->isDot()) {
  210. $this->unlink($fileinfo->getPathName());
  211. }
  212. }
  213. return @rmdir($path);
  214. }
  215. // Windows needs the file to be writable.
  216. @chmod($path, 0700);
  217. return @unlink($path);
  218. }
  219. // If there's nothing to delete return TRUE anyway.
  220. return TRUE;
  221. }
  222. /**
  223. * {@inheritdoc}
  224. */
  225. public function listAll() {
  226. $names = [];
  227. if (file_exists($this->directory)) {
  228. foreach (new \DirectoryIterator($this->directory) as $fileinfo) {
  229. if (!$fileinfo->isDot()) {
  230. $name = $fileinfo->getFilename();
  231. if ($name != '.htaccess') {
  232. $names[] = $name;
  233. }
  234. }
  235. }
  236. }
  237. return $names;
  238. }
  239. /**
  240. * {@inheritdoc}
  241. */
  242. public function garbageCollection() {
  243. }
  244. }