PluginProbe
File Manager Pro – Filester / 2.1.3
File Manager Pro – Filester v2.1.3
2.1.3 2.1.2 2.1.1 trunk 1.6.1 1.7.6 1.8 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9 2.0 2.0.1 2.0.2 2.1.0
filester / includes / File_manager / lib / php / editors / editor.php
editor.php
409 lines 9.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Abstract class of editor plugins.
5 *
6 * @author Naoki Sawada
7 */
8 class elFinderEditor
9 {
10 /**
11 * Lifetime of callback states.
12 *
13 * @var int
14 */
15 protected $callbackStateTtl = 86400;
16
17 /**
18 * Dedicated directory name for callback states.
19 *
20 * @var string
21 */
22 protected $callbackStateDirName = 'elfinder_editor_callback_state';
23
24 /**
25 * Array of allowed method by request from client side.
26 *
27 * @var array
28 */
29 protected $allowed = array();
30
31 /**
32 * elFinder instance
33 *
34 * @var object elFinder instance
35 */
36 protected $elfinder;
37
38 /**
39 * Arguments
40 *
41 * @var array argValues
42 */
43 protected $args;
44
45 /**
46 * Constructor.
47 *
48 * @param object $elfinder
49 * @param array $args
50 */
51 public function __construct($elfinder, $args)
52 {
53 $this->elfinder = $elfinder;
54 $this->args = $args;
55 }
56
57 /**
58 * Return boolean that this plugin is enabled.
59 *
60 * @return bool
61 */
62 public function enabled()
63 {
64 return true;
65 }
66
67 /**
68 * Return boolean that $name method is allowed.
69 *
70 * @param string $name
71 *
72 * @return bool
73 */
74 public function isAllowedMethod($name)
75 {
76 $checker = array_flip($this->allowed);
77
78 return isset($checker[$name]);
79 }
80
81 /**
82 * Return $this->args value of the key
83 *
84 * @param string $key target key
85 * @param string $empty empty value
86 *
87 * @return mixed
88 */
89 public function argValue($key, $empty = '')
90 {
91 return isset($this->args[$key]) ? $this->args[$key] : $empty;
92 }
93
94 /**
95 * Create callback state and return token params for a callback request.
96 *
97 * @param string $method
98 * @param string $hash
99 * @param string $secret
100 * @param array $meta
101 * @param int $ttl
102 *
103 * @return array|false
104 */
105 protected function createCallbackState($method, $hash, $secret, $meta = array(), $ttl = null)
106 {
107 $this->gcCallbackStates();
108
109 $token = $this->createCallbackStateToken();
110 $expires = time() + max(60, is_null($ttl) ? (int)$this->callbackStateTtl : (int)$ttl);
111 $state = array(
112 'editor' => get_class($this),
113 'method' => (string)$method,
114 'token' => $token,
115 'hash' => $hash,
116 'expires' => $expires,
117 'meta' => is_array($meta) ? $meta : array()
118 );
119
120 if (!$this->writeCallbackState($token, $state)) {
121 return false;
122 }
123
124 return array(
125 'token' => $token,
126 'expires' => $expires,
127 'sig' => $this->createCallbackSignature($method, $token, $expires, $secret)
128 );
129 }
130
131 /**
132 * Verify callback request and return state.
133 *
134 * @param string $method
135 * @param array $post
136 * @param string $secret
137 * @param string $tokenKey
138 * @param string $expiresKey
139 * @param string $sigKey
140 *
141 * @return array|false
142 */
143 protected function verifyCallbackRequest($method, $post, $secret, $tokenKey = 'token', $expiresKey = 'expires', $sigKey = 'sig')
144 {
145 $this->gcCallbackStates();
146
147 if (!is_array($post)) {
148 return false;
149 }
150
151 $token = isset($post[$tokenKey]) ? (string)$post[$tokenKey] : '';
152 $expires = isset($post[$expiresKey]) ? (string)$post[$expiresKey] : '';
153 $sig = isset($post[$sigKey]) ? (string)$post[$sigKey] : '';
154
155 if ($token === '' || $expires === '' || $sig === '' || !ctype_digit($expires)) {
156 return false;
157 }
158
159 $expires = (int)$expires;
160 if ($expires < time()) {
161 $this->deleteCallbackState($token);
162 return false;
163 }
164
165 $expectedSig = $this->createCallbackSignature($method, $token, $expires, $secret);
166 if (!$this->hashEquals($expectedSig, $sig)) {
167 return false;
168 }
169
170 $state = $this->readCallbackState($token);
171 if (!$state) {
172 return false;
173 }
174
175 if (empty($state['editor']) || !$this->hashEquals($state['editor'], get_class($this))
176 || empty($state['method']) || !$this->hashEquals($state['method'], (string)$method)
177 || empty($state['token']) || !$this->hashEquals($state['token'], $token)
178 || !isset($state['expires']) || (int)$state['expires'] !== $expires
179 || empty($state['hash'])) {
180 return false;
181 }
182
183 return $state;
184 }
185
186 /**
187 * Consume callback state.
188 *
189 * @param string $token
190 *
191 * @return void
192 */
193 protected function consumeCallbackState($token)
194 {
195 $this->deleteCallbackState($token);
196 }
197
198 /**
199 * Garbage collect expired callback states.
200 *
201 * @return void
202 */
203 protected function gcCallbackStates()
204 {
205 $dir = $this->getCallbackStateDir(false);
206 if (!$dir) {
207 return;
208 }
209
210 $files = glob($dir . DIRECTORY_SEPARATOR . '*.json');
211 if (!$files) {
212 return;
213 }
214
215 $now = time();
216 foreach ($files as $path) {
217 if (!is_file($path)) {
218 continue;
219 }
220
221 $remove = false;
222 $json = file_get_contents($path);
223 if ($json === false || $json === '') {
224 $remove = true;
225 } else {
226 $state = json_decode($json, true);
227 if (!is_array($state) || empty($state['expires']) || (int)$state['expires'] < $now) {
228 $remove = true;
229 }
230 }
231
232 if ($remove) {
233 @unlink($path);
234 }
235 }
236 }
237
238 /**
239 * Return callback state directory.
240 *
241 * @param bool $create
242 *
243 * @return string|false
244 */
245 protected function getCallbackStateDir($create = false)
246 {
247 $base = elFinder::getCommonTempPath();
248 if (!$base) {
249 return false;
250 }
251
252 $dir = $base . DIRECTORY_SEPARATOR . $this->callbackStateDirName;
253 if (!is_dir($dir)) {
254 if (!$create || !@mkdir($dir, 0700, true)) {
255 return false;
256 }
257 }
258
259 return is_writable($dir) ? $dir : false;
260 }
261
262 /**
263 * Return callback state file path.
264 *
265 * @param string $token
266 * @param bool $create
267 *
268 * @return string|false
269 */
270 protected function getCallbackStatePath($token, $create = false)
271 {
272 $dir = $this->getCallbackStateDir($create);
273 if (!$dir || !is_string($token) || $token === '') {
274 return false;
275 }
276
277 return $dir . DIRECTORY_SEPARATOR . hash('sha256', $token) . '.json';
278 }
279
280 /**
281 * Persist callback state.
282 *
283 * @param string $token
284 * @param array $state
285 *
286 * @return bool
287 */
288 protected function writeCallbackState($token, $state)
289 {
290 $path = $this->getCallbackStatePath($token, true);
291 if (!$path) {
292 return false;
293 }
294
295 $json = json_encode($state);
296 if ($json === false) {
297 return false;
298 }
299
300 return file_put_contents($path, $json, LOCK_EX) !== false;
301 }
302
303 /**
304 * Read callback state.
305 *
306 * @param string $token
307 *
308 * @return array|false
309 */
310 protected function readCallbackState($token)
311 {
312 $path = $this->getCallbackStatePath($token, false);
313 if (!$path || !is_file($path)) {
314 return false;
315 }
316
317 $json = file_get_contents($path);
318 if ($json === false || $json === '') {
319 return false;
320 }
321
322 $state = json_decode($json, true);
323
324 return is_array($state) ? $state : false;
325 }
326
327 /**
328 * Delete callback state.
329 *
330 * @param string $token
331 *
332 * @return void
333 */
334 protected function deleteCallbackState($token)
335 {
336 $path = $this->getCallbackStatePath($token, false);
337 if ($path && is_file($path)) {
338 @unlink($path);
339 }
340 }
341
342 /**
343 * Create callback signature.
344 *
345 * @param string $method
346 * @param string $token
347 * @param int $expires
348 * @param string $secret
349 *
350 * @return string
351 */
352 protected function createCallbackSignature($method, $token, $expires, $secret)
353 {
354 $payload = implode('|', array(get_class($this), (string)$method, (string)$token, (string)$expires));
355
356 return hash_hmac('sha256', $payload, (string)$secret);
357 }
358
359 /**
360 * Create random callback state token.
361 *
362 * @return string
363 */
364 protected function createCallbackStateToken()
365 {
366 if (function_exists('random_bytes')) {
367 return bin2hex(random_bytes(32));
368 }
369
370 if (function_exists('openssl_random_pseudo_bytes')) {
371 $bytes = openssl_random_pseudo_bytes(32);
372 if ($bytes !== false) {
373 return bin2hex($bytes);
374 }
375 }
376
377 return md5(uniqid(mt_rand(), true)) . md5(uniqid(mt_rand(), true));
378 }
379
380 /**
381 * Constant-time string comparison.
382 *
383 * @param string $known
384 * @param string $user
385 *
386 * @return bool
387 */
388 protected function hashEquals($known, $user)
389 {
390 if (function_exists('hash_equals')) {
391 return hash_equals((string)$known, (string)$user);
392 }
393
394 $known = (string)$known;
395 $user = (string)$user;
396 if (strlen($known) !== strlen($user)) {
397 return false;
398 }
399
400 $result = 0;
401 $length = strlen($known);
402 for ($i = 0; $i < $length; $i++) {
403 $result |= ord($known[$i]) ^ ord($user[$i]);
404 }
405
406 return $result === 0;
407 }
408 }
409