FileStorage.php 7.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

namespace Drupal\Component\PhpStorage;

/**
 * Stores the code as regular PHP files.
 */
class FileStorage implements PhpStorageInterface {

  /**
   * The directory where the files should be stored.
   *
   * @var string
   */
  protected $directory;

  /**
   * Constructs this FileStorage object.
   *
20
   * @param array $configuration
21
22
   *   An associative array, containing at least these two keys:
   *   - directory: The directory where the files should be stored.
23
24
   *   - bin: The storage bin. Multiple storage objects can be instantiated with
   *     the same configuration, but for different bins..
25
26
27
28
29
30
   */
  public function __construct(array $configuration) {
    $this->directory = $configuration['directory'] . '/' . $configuration['bin'];
  }

  /**
31
   * {@inheritdoc}
32
33
34
35
36
37
   */
  public function exists($name) {
    return file_exists($this->getFullPath($name));
  }

  /**
38
   * {@inheritdoc}
39
40
41
42
43
44
45
46
   */
  public function load($name) {
    // The FALSE returned on failure is enough for the caller to handle this,
    // we do not want a warning too.
    return (@include_once $this->getFullPath($name)) !== FALSE;
  }

  /**
47
   * {@inheritdoc}
48
49
50
   */
  public function save($name, $code) {
    $path = $this->getFullPath($name);
51
    $directory = dirname($path);
52
    $this->ensureDirectory($directory);
53
54
55
    return (bool) file_put_contents($path, $code);
  }

56
57
58
59
  /**
   * Returns the standard .htaccess lines that Drupal writes to file directories.
   *
   * @param bool $private
60
   *   (optional) Set to FALSE to return the .htaccess lines for an open and
61
62
63
64
65
   *   public directory. The default is TRUE, which returns the .htaccess lines
   *   for a private and protected directory.
   *
   * @return string
   *   The desired contents of the .htaccess file.
66
67
   *
   * @see file_create_htaccess()
68
69
70
71
   */
  public static function htaccessLines($private = TRUE) {
    $lines = <<<EOF
# Turn off all options we don't need.
72
Options -Indexes -ExecCGI -Includes -MultiViews
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

# Set the catch-all handler to prevent scripts from being executed.
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
<Files *>
  # Override the handler again if we're run later in the evaluation list.
  SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003
</Files>

# If we know how to do it safely, disable the PHP engine entirely.
<IfModule mod_php5.c>
  php_flag engine off
</IfModule>
EOF;

    if ($private) {
88
89
90
91
92
93
94
95
96
97
      $lines = <<<EOF
# Deny all requests from Apache 2.4+.
<IfModule mod_authz_core.c>
  Require all denied
</IfModule>

# Deny all requests from Apache 2.0-2.2.
<IfModule !mod_authz_core.c>
  Deny from all
</IfModule>
98
99
$lines
EOF;
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
    }

    return $lines;
  }

  /**
   * Ensures the directory exists, has the right permissions, and a .htaccess.
   *
   * For compatibility with open_basedir, the requested directory is created
   * using a recursion logic that is based on the relative directory path/tree:
   * It works from the end of the path recursively back towards the root
   * directory, until an existing parent directory is found. From there, the
   * subdirectories are created.
   *
   * @param string $directory
   *   The directory path.
   * @param int $mode
   *   The mode, permissions, the directory should have.
   */
  protected function ensureDirectory($directory, $mode = 0777) {
    if ($this->createDirectory($directory, $mode)) {
121
      $htaccess_path = $directory . '/.htaccess';
122
123
124
125
126
127
      if (!file_exists($htaccess_path) && file_put_contents($htaccess_path, static::htaccessLines())) {
        @chmod($htaccess_path, 0444);
      }
    }
  }

128
  /**
129
130
131
132
133
134
135
   * Ensures the requested directory exists and has the right permissions.
   *
   * For compatibility with open_basedir, the requested directory is created
   * using a recursion logic that is based on the relative directory path/tree:
   * It works from the end of the path recursively back towards the root
   * directory, until an existing parent directory is found. From there, the
   * subdirectories are created.
136
137
138
139
140
   *
   * @param string $directory
   *   The directory path.
   * @param int $mode
   *   The mode, permissions, the directory should have.
141
142
143
   *
   * @return bool
   *   TRUE if the directory exists or has been created, FALSE otherwise.
144
   */
145
  protected function createDirectory($directory, $mode = 0777) {
146
147
148
149
    // If the directory exists already, there's nothing to do.
    if (is_dir($directory)) {
      return TRUE;
    }
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173

    // If the parent directory doesn't exist, try to create it.
    $parent_exists = is_dir($parent = dirname($directory));
    if (!$parent_exists) {
      $parent_exists = $this->createDirectory($parent, $mode);
    }

    // If parent exists, try to create the directory and ensure to set its
    // permissions, because mkdir() obeys the umask of the current process.
    if ($parent_exists) {
      // We hide warnings and ignore the return because there may have been a
      // race getting here and the directory could already exist.
      @mkdir($directory);
      // Only try to chmod() if the subdirectory could be created.
      if (is_dir($directory)) {
        // Avoid writing permissions if possible.
        if (fileperms($directory) !== $mode) {
          return chmod($directory, $mode);
        }
        return TRUE;
      }
      else {
        // Something failed and the directory doesn't exist.
        trigger_error('mkdir(): Permission Denied', E_USER_WARNING);
174
      }
175
    }
176
    return FALSE;
177
178
  }

179
  /**
180
   * {@inheritdoc}
181
182
183
   */
  public function delete($name) {
    $path = $this->getFullPath($name);
184
185
186
187
    if (file_exists($path)) {
      return $this->unlink($path);
    }
    return FALSE;
188
189
190
  }

  /**
191
   * {@inheritdoc}
192
   */
193
  public function getFullPath($name) {
194
195
    return $this->directory . '/' . $name;
  }
196
197

  /**
198
   * {@inheritdoc}
199
   */
200
  public function writeable() {
201
202
203
204
    return TRUE;
  }

  /**
205
   * {@inheritdoc}
206
   */
207
208
  public function deleteAll() {
    return $this->unlink($this->directory);
209
210
211
  }

  /**
212
213
214
215
216
217
218
219
220
   * Deletes files and/or directories in the specified path.
   *
   * If the specified path is a directory the method will
   * call itself recursively to process the contents. Once the contents have
   * been removed the directory will also be removed.
   *
   * @param string $path
   *   A string containing either a file or directory path.
   *
221
   * @return bool
222
223
   *   TRUE for success or if path does not exist, FALSE in the event of an
   *   error.
224
   */
225
  protected function unlink($path) {
226
    if (file_exists($path)) {
227
      if (is_dir($path)) {
228
        // Ensure the folder is writable.
229
230
231
232
        @chmod($path, 0777);
        foreach (new \DirectoryIterator($path) as $fileinfo) {
          if (!$fileinfo->isDot()) {
            $this->unlink($fileinfo->getPathName());
233
234
235
236
          }
        }
        return @rmdir($path);
      }
237
238
      // Windows needs the file to be writable.
      @chmod($path, 0700);
239
      return @unlink($path);
240
    }
241
242
    // If there's nothing to delete return TRUE anyway.
    return TRUE;
243
  }
244
245
246
247
248

  /**
   * {@inheritdoc}
   */
  public function listAll() {
249
    $names = [];
250
251
252
253
254
255
256
257
258
259
260
261
262
    if (file_exists($this->directory)) {
      foreach (new \DirectoryIterator($this->directory) as $fileinfo) {
        if (!$fileinfo->isDot()) {
          $name = $fileinfo->getFilename();
          if ($name != '.htaccess') {
            $names[] = $name;
          }
        }
      }
    }
    return $names;
  }

263
264
265
266
267
268
  /**
   * {@inheritdoc}
   */
  public function garbageCollection() {
  }

269
}