Issues (80)

code/SecureFileController.php (9 issues)

1
<?php
2
/**
3
 * Handles requests for secure files by url.
4
 *
5
 * @author Hamish Campbell <[email protected]>
6
 * @copyright copyright (c) 2010, Hamish Campbell
7
 */
8
class SecureFileController extends Controller implements PermissionProvider
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
9
{
10
    /**
11
     * @var array Disallow all public actions on this controller
12
     */
13
    public static $allowed_actions = array();
14
15
    /**
16
     * @var string htaccess file as set by apache config
17
     */
18
    protected static $htaccess_file = '.htaccess';
19
20
    /**
21
     * @var int Size of output chunks in kb while in PHP fread mode.
22
     */
23
    protected static $chunck_size_kb = 32;
24
25
    /**
26
     * @var bool Flag use X-Sendfile header mode instead of PHP fread mode.
27
     */
28
    protected static $use_x_sendfile = false;
29
30
    /**
31
     * @var bool Flag use SilverStripe send file method.
32
     */
33
    protected static $use_ss_sendfile = false;
34
35
    /**
36
     * @var array i18n data for not authorized message as passed to _t
37
     */
38
    protected static $i18n_not_authorized = array('SecureFiles.NOTAUTHORIZED', 'Not Authorized');
39
40
    /**
41
     * @var array i18n data for not found message as passed to _t
42
     */
43
    protected static $i18n_not_found = array('SecureFiles.NOTFOUND', 'Not Found');
44
45
    /**
46
     * Use X-Sendfile headers to send files to the browser.
47
     * This is quicker than pushing files through PHP but
48
     * requires either Lighttpd or mod_xsendfile for Apache.
49
     *
50
     * @link http://tn123.ath.cx/mod_xsendfile/
51
     */
52
    public static function use_x_sendfile_method()
0 ignored issues
show
Method name "SecureFileController::use_x_sendfile_method" is not in camel caps format
Loading history...
53
    {
54
        self::use_default_sendfile_method();
55
        self::$use_x_sendfile = true;
56
    }
57
58
    /**
59
     * Use internal SilverStripe to send files to the browser.
60
     * This is the least efficient method but is useful for
61
     * testing. Not recommend for production
62
     * environments.
63
     */
64
    public static function use_ss_sendfile_method()
0 ignored issues
show
Method name "SecureFileController::use_ss_sendfile_method" is not in camel caps format
Loading history...
65
    {
66
        self::use_default_sendfile_method();
67
        self::$use_ss_sendfile = true;
68
    }
69
70
    /**
71
     * Use the default chuncked file method to send files to the browser.
72
     * This is the default method.
73
     */
74
    public static function use_default_sendfile_method()
0 ignored issues
show
Method name "SecureFileController::use_default_sendfile_method" is not in camel caps format
Loading history...
75
    {
76
        self::$use_ss_sendfile = false;
77
        self::$use_x_sendfile = false;
78
    }
79
80
    /**
81
     * Set the size of upload chunk in bytes.
82
     *
83
     * @param int $kilobytes
84
     */
85
    public static function set_chunk_size($kilobytes)
0 ignored issues
show
Method name "SecureFileController::set_chunk_size" is not in camel caps format
Loading history...
86
    {
87
        $kilobytes = max(0, (int) $kilobytes);
88
        if (!$kilobytes) {
89
            user_error('Invalid download chunk size', E_USER_ERROR);
90
        }
91
        self::$chunck_size_kb = $kilobytes;
92
    }
93
94
    /**
95
     * Set the Apache access file name (.htaccess by default)
96
     * as determined by the AccessFileName Apache directive.
97
     *
98
     * @param string $filename
99
     */
100
    public static function set_access_filename($filename)
0 ignored issues
show
Method name "SecureFileController::set_access_filename" is not in camel caps format
Loading history...
101
    {
102
        self::$htaccess_file = $filename;
103
    }
104
105
    /**
106
     * Get the Apache access file name.
107
     *
108
     * @return string
109
     */
110
    public static function get_access_filename()
0 ignored issues
show
Method name "SecureFileController::get_access_filename" is not in camel caps format
Loading history...
111
    {
112
        return self::$htaccess_file;
113
    }
114
115
    /**
116
     * Set a 'not authorized' message to replace the standard string.
117
     *
118
     * @param $message HTML body of 401 Not Authorized response
119
     * @param $i18n Reference to i18n path
120
     */
121
    public static function set_not_authorized_text($message = 'Not Authorized', $i18n = 'SecureFiles.NOTAUTHORIZED')
0 ignored issues
show
Method name "SecureFileController::set_not_authorized_text" is not in camel caps format
Loading history...
122
    {
123
        self::$i18n_not_authorized = array($i18n, $message);
124
    }
125
126
    /**
127
     * Set a 'not found' message to replace the standard string.
128
     *
129
     * @param $message HTML body of 404 Not Found response
130
     * @param $i18n Reference to i18n path
131
     */
132
    public static function set_not_found_text($message = 'Not Found', $i18n = 'SecureFiles.NOTFOUND')
0 ignored issues
show
Method name "SecureFileController::set_not_found_text" is not in camel caps format
Loading history...
133
    {
134
        self::$i18n_not_found = array($i18n, $message);
135
    }
136
137
    /**
138
     * Process incoming requests passed to this controller.
139
     *
140
     * @return HTTPResponse
141
     */
142
    protected function handleAction($request, $action)
143
    {
144
        $url = array_key_exists('url', $_GET) ? $_GET['url'] : $_SERVER['REQUEST_URI'];
145
        $file_path = Director::makeRelative($url);
146
        $file = File::find($file_path);
147
148
        if ($file instanceof File) {
149
            if ($file->canView()) {
150
                $file->extend('onAccessGranted');
151
152
                return $this->fileFound($file, $file_path);
153
            } else {
154
                $file->extend('onAccessDenied');
155
156
                return $this->fileNotAuthorized(call_user_func_array('_t', self::$i18n_not_authorized));
157
            }
158
        } else {
159
            return $this->fileNotFound(call_user_func_array('_t', self::$i18n_not_found));
160
        }
161
    }
162
163
    /**
164
     * File Not Found response.
165
     *
166
     * @param $body Optional message body
167
     *
168
     * @return HTTPResponse
169
     */
170
    public function fileNotFound($body = '')
171
    {
172
        if (ClassInfo::exists('SS_HTTPResponse')) {
173
            return new SS_HTTPResponse($body, 404);
174
        } else {
175
            return new HTTPResponse($body, 404);
176
        }
177
    }
178
179
    /**
180
     * File not authorized response.
181
     *
182
     * @param $body Optional message body
183
     *
184
     * @return HTTPResponse
185
     */
186
    public function fileNotAuthorized($body = '')
187
    {
188
        Security::permissionFailure($this, $body);
189
    }
190
191
    /**
192
     * File found response.
193
     *
194
     * @param $file File to send
195
     * @param $alternate_path string If supplied, return the file from this path instead, for
196
     * example, resampled images.
197
     */
198
    public function fileFound(File $file, $alternate_path = null)
199
    {
200
201
        // File properties
202
        $file_name = $file->Name;
203
        $file_path = Director::getAbsFile($alternate_path ? $alternate_path : $file->FullPath);
204
        $file_size = filesize($file_path);
205
206
        // Testing mode - return an HTTPResponse
207
        if (self::$use_ss_sendfile) {
208
            if (ClassInfo::exists('SS_HTTPRequest')) {
209
                return SS_HTTPRequest::send_file(file_get_contents($file_path), $file_name);
210
            } else {
211
                return HTTPRequest::send_file(file_get_contents($file_path), $file_name);
212
            }
213
        }
214
215
        // Normal operation:
216
        $mimeType = HTTP::getMimeType($file_name);
217
        header("Content-Type: {$mimeType}; name=\"".addslashes($file_name).'"');
218
        header('Content-Disposition: attachment; filename='.addslashes($file_name));
219
        header('Cache-Control: max-age=1, private');
220
        header("Content-Length: {$file_size}");
221
        header('Pragma: ');
222
223
        if (self::$use_x_sendfile) {
224
            session_write_close();
225
            header('X-Sendfile: '.$file_path);
226
            exit();
227
        } elseif ($filePointer = @fopen($file_path, 'rb')) {
228
            session_write_close();
229
            $this->flush();
230
            // Push the file while not EOF and connection exists
231
            while (!feof($filePointer) && !connection_aborted()) {
232
                echo fread($filePointer, 1024 * self::$chunck_size_kb);
233
                $this->flush();
234
            }
235
            fclose($filePointer);
236
            exit();
237
        } else {
238
            // Edge case - either not found anymore or can't read
239
            return $this->fileNotFound();
240
        }
241
    }
242
243
    /**
244
     * Flush the output buffer to the server (if possible).
245
     *
246
     * @see http://nz.php.net/manual/en/function.flush.php#93531
247
     */
248
    public function flush()
249
    {
250
        if (ob_get_length()) {
251
            @ob_flush();
252
            @flush();
253
            @ob_end_flush();
254
        }
255
        @ob_start();
256
    }
257
258
    /**
259
     * Permission provider for access to secure files.
260
     *
261
     * @return array
262
     */
263
    public function providePermissions()
264
    {
265
        return array(
266
            'SECURE_FILE_ACCESS' => _t('SecureFiles.SECUREFILEACCESS', 'Access to Secured Files'),
267
            'SECURE_FILE_SETTINGS' => _t('SecureFiles.SECUREFILESETTINGS', 'Manage File Security Settings'),
268
        );
269
    }
270
}
271