PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.1
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.1
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
← All changes | back/Libraries/Imap2/Roundcube/Utils.php +0 -1462 10.2.2 → 11.1.1 View file →
@@ -1,1462 +1,0 @@
1 -<?php
2 -
3 -/**
4 - * +-----------------------------------------------------------------------+
5 - * | This file is part of the Roundcube Webmail client |
6 - * | |
7 - * | Copyright (C) The Roundcube Dev Team |
8 - * | Copyright (C) Kolab Systems AG |
9 - * | |
10 - * | Licensed under the GNU General Public License version 3 or |
11 - * | any later version with exceptions for skins & plugins. |
12 - * | See the README file for a full license statement. |
13 - * | |
14 - * | PURPOSE: |
15 - * | Utility class providing common functions |
16 - * +-----------------------------------------------------------------------+
17 - * | Author: Thomas Bruederli <[email protected]> |
18 - * | Author: Aleksander Machniak <[email protected]> |
19 - * +-----------------------------------------------------------------------+
20 - */
21 -
22 -namespace AcyMailing\Libraries\Imap2\Roundcube;
23 -
24 -use AcyMailing\Libraries\Imap2\DateTime;
25 -use AcyMailing\Libraries\Imap2\DateTimeZone;
26 -use AcyMailing\Libraries\Imap2\Exception;
27 -use AcyMailing\Libraries\Imap2\rcube;
28 -use AcyMailing\Libraries\Imap2\rcube_charset;
29 -use AcyMailing\Libraries\Imap2\rcube_string_replacer;
30 -
31 -/**
32 - * Utility class providing common functions
33 - *
34 - * @package Framework
35 - * @subpackage Utils
36 - */
37 -class Utils
38 -{
39 - // define constants for input reading
40 - const INPUT_GET = 1;
41 - const INPUT_POST = 2;
42 - const INPUT_COOKIE = 4;
43 - const INPUT_GP = 3; // GET + POST
44 - const INPUT_GPC = 7; // GET + POST + COOKIE
45 -
46 -
47 - /**
48 - * Helper method to set a cookie with the current path and host settings
49 - *
50 - * @param string $name Cookie name
51 - * @param string $value Cookie value
52 - * @param int $exp Expiration time
53 - * @param bool $http_only HTTP Only
54 - */
55 - public static function setcookie($name, $value, $exp = 0, $http_only = true)
56 - {
57 - if (headers_sent()) {
58 - return;
59 - }
60 -
61 - $cookie = session_get_cookie_params();
62 - $secure = $cookie['secure'] || self::https_check();
63 -
64 - setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'], $secure, $http_only);
65 - }
66 -
67 - /**
68 - * E-mail address validation.
69 - *
70 - * @param string $email Email address
71 - * @param bool $dns_check True to check dns
72 - *
73 - * @return bool True on success, False if address is invalid
74 - */
75 - public static function check_email($email, $dns_check = true)
76 - {
77 - // Check for invalid (control) characters
78 - if (preg_match('/\p{Cc}/u', $email)) {
79 - return false;
80 - }
81 -
82 - // Check for length limit specified by RFC 5321 (#1486453)
83 - if (strlen($email) > 254) {
84 - return false;
85 - }
86 -
87 - $pos = strrpos($email, '@');
88 - if (!$pos) {
89 - return false;
90 - }
91 -
92 - $domain_part = substr($email, $pos + 1);
93 - $local_part = substr($email, 0, $pos);
94 -
95 - // quoted-string, make sure all backslashes and quotes are
96 - // escaped
97 - if (substr($local_part, 0, 1) == '"') {
98 - $local_quoted = preg_replace('/\\\\(\\\\|\")/', '', substr($local_part, 1, -1));
99 - if (preg_match('/\\\\|"/', $local_quoted)) {
100 - return false;
101 - }
102 - } // dot-atom portion, make sure there's no prohibited characters
103 - elseif (preg_match('/(^\.|\.\.|\.$)/', $local_part)
104 - || preg_match('/[\\ ",:;<>@]/', $local_part)
105 - ) {
106 - return false;
107 - }
108 -
109 - // Validate domain part
110 - if (preg_match('/^\[((IPv6:[0-9a-f:.]+)|([0-9.]+))\]$/i', $domain_part, $matches)) {
111 - return self::check_ip(preg_replace('/^IPv6:/i', '', $matches[1])); // valid IPv4 or IPv6 address
112 - } else {
113 - // If not an IP address
114 - $domain_array = explode('.', $domain_part);
115 - // Not enough parts to be a valid domain
116 - if (count($domain_array) < 2) {
117 - return false;
118 - }
119 -
120 - foreach ($domain_array as $part) {
121 - if (!preg_match('/^((xn--)?([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part)) {
122 - return false;
123 - }
124 - }
125 -
126 - // last domain part
127 - $last_part = array_pop($domain_array);
128 - if (strpos($last_part, 'xn--') !== 0 && preg_match('/[^a-zA-Z]/', $last_part)) {
129 - return false;
130 - }
131 -
132 - $rcube = rcube::get_instance();
133 -
134 - if (!$dns_check || !function_exists('checkdnsrr') || !$rcube->config->get('email_dns_check')) {
135 - return true;
136 - }
137 -
138 - // Check DNS record(s)
139 - // Note: We can't use ANY (#6581)
140 - foreach (['A', 'MX', 'CNAME', 'AAAA'] as $type) {
141 - if (checkdnsrr($domain_part, $type)) {
142 - return true;
143 - }
144 - }
145 - }
146 -
147 - return false;
148 - }
149 -
150 - /**
151 - * Validates IPv4 or IPv6 address
152 - *
153 - * @param string $ip IP address in v4 or v6 format
154 - *
155 - * @return bool True if the address is valid
156 - */
157 - public static function check_ip($ip)
158 - {
159 - return filter_var($ip, FILTER_VALIDATE_IP) !== false;
160 - }
161 -
162 - /**
163 - * Replacing specials characters to a specific encoding type
164 - *
165 - * @param string $str Input string
166 - * @param string $enctype Encoding type: text|html|xml|js|url
167 - * @param string $mode Replace mode for tags: show|remove|strict
168 - * @param bool $newlines Convert newlines
169 - *
170 - * @return string The quoted string
171 - */
172 - public static function rep_specialchars_output($str, $enctype = '', $mode = '', $newlines = true)
173 - {
174 - static $html_encode_arr = false;
175 - static $js_rep_table = false;
176 - static $xml_rep_table = false;
177 -
178 - if (!is_string($str)) {
179 - $str = strval($str);
180 - }
181 -
182 - // encode for HTML output
183 - if ($enctype == 'html') {
184 - if (!$html_encode_arr) {
185 - $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
186 - unset($html_encode_arr['?']);
187 - }
188 -
189 - $encode_arr = $html_encode_arr;
190 -
191 - if ($mode == 'remove') {
192 - $str = strip_tags($str);
193 - } elseif ($mode != 'strict') {
194 - // don't replace quotes and html tags
195 - $ltpos = strpos($str, '<');
196 - if ($ltpos !== false && strpos($str, '>', $ltpos) !== false) {
197 - unset($encode_arr['"']);
198 - unset($encode_arr['<']);
199 - unset($encode_arr['>']);
200 - unset($encode_arr['&']);
201 - }
202 - }
203 -
204 - $out = strtr($str, $encode_arr);
205 -
206 - return $newlines ? nl2br($out) : $out;
207 - }
208 -
209 - // if the replace tables for XML and JS are not yet defined
210 - if ($js_rep_table === false) {
211 - $js_rep_table = $xml_rep_table = [];
212 - $xml_rep_table['&'] = '&amp;';
213 -
214 - // can be increased to support more charsets
215 - for ($c = 160 ; $c < 256 ; $c++) {
216 - $xml_rep_table[chr($c)] = "&#$c;";
217 - }
218 -
219 - $xml_rep_table['"'] = '&quot;';
220 - $js_rep_table['"'] = '\\"';
221 - $js_rep_table["'"] = "\\'";
222 - $js_rep_table["\\"] = "\\\\";
223 - // Unicode line and paragraph separators (#1486310)
224 - $js_rep_table[chr(hexdec('E2')).chr(hexdec('80')).chr(hexdec('A8'))] = '&#8232;';
225 - $js_rep_table[chr(hexdec('E2')).chr(hexdec('80')).chr(hexdec('A9'))] = '&#8233;';
226 - }
227 -
228 - // encode for javascript use
229 - if ($enctype == 'js') {
230 - return preg_replace(["/\r?\n/", "/\r/", '/<\\//'], ['\n', '\n', '<\\/'], strtr($str, $js_rep_table));
231 - }
232 -
233 - // encode for plaintext
234 - if ($enctype == 'text') {
235 - return str_replace("\r\n", "\n", $mode == 'remove' ? strip_tags($str) : $str);
236 - }
237 -
238 - if ($enctype == 'url') {
239 - return rawurlencode($str);
240 - }
241 -
242 - // encode for XML
243 - if ($enctype == 'xml') {
244 - return strtr($str, $xml_rep_table);
245 - }
246 -
247 - // no encoding given -> return original string
248 - return $str;
249 - }
250 -
251 - /**
252 - * Read input value and convert it for internal use
253 - * Performs stripslashes() and charset conversion if necessary
254 - *
255 - * @param string $fname Field name to read
256 - * @param int $source Source to get value from (see self::INPUT_*)
257 - * @param bool $allow_html Allow HTML tags in field value
258 - * @param string $charset Charset to convert into
259 - *
260 - * @return string Field value or NULL if not available
261 - */
262 - public static function get_input_value($fname, $source, $allow_html = false, $charset = null)
263 - {
264 - $value = null;
265 -
266 - if (($source & self::INPUT_GET) && isset($_GET[$fname])) {
267 - $value = $_GET[$fname];
268 - }
269 -
270 - if (($source & self::INPUT_POST) && isset($_POST[$fname])) {
271 - $value = $_POST[$fname];
272 - }
273 -
274 - if (($source & self::INPUT_COOKIE) && isset($_COOKIE[$fname])) {
275 - $value = $_COOKIE[$fname];
276 - }
277 -
278 - return self::parse_input_value($value, $allow_html, $charset);
279 - }
280 -
281 - /**
282 - * Parse/validate input value. See self::get_input_value()
283 - * Performs stripslashes() and charset conversion if necessary
284 - *
285 - * @param string $value Input value
286 - * @param bool $allow_html Allow HTML tags in field value
287 - * @param string $charset Charset to convert into
288 - *
289 - * @return string Parsed value
290 - */
291 - public static function parse_input_value($value, $allow_html = false, $charset = null)
292 - {
293 - global $OUTPUT;
294 -
295 - if (empty($value)) {
296 - return $value;
297 - }
298 -
299 - if (is_array($value)) {
300 - foreach ($value as $idx => $val) {
301 - $value[$idx] = self::parse_input_value($val, $allow_html, $charset);
302 - }
303 -
304 - return $value;
305 - }
306 -
307 - // remove HTML tags if not allowed
308 - if (!$allow_html) {
309 - $value = strip_tags($value);
310 - }
311 -
312 - $output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null;
313 -
314 - // remove invalid characters (#1488124)
315 - if ($output_charset == 'UTF-8') {
316 - $value = rcube_charset::clean($value);
317 - }
318 -
319 - // convert to internal charset
320 - if ($charset && $output_charset) {
321 - $value = rcube_charset::convert($value, $output_charset, $charset);
322 - }
323 -
324 - return $value;
325 - }
326 -
327 - /**
328 - * Convert array of request parameters (prefixed with _)
329 - * to a regular array with non-prefixed keys.
330 - *
331 - * @param int $mode Source to get value from (GPC)
332 - * @param string $ignore PCRE expression to skip parameters by name
333 - * @param bool $allow_html Allow HTML tags in field value
334 - *
335 - * @return array Hash array with all request parameters
336 - */
337 - public static function request2param($mode = null, $ignore = 'task|action', $allow_html = false)
338 - {
339 - $out = [];
340 - $src = $mode == self::INPUT_GET ? $_GET : ($mode == self::INPUT_POST ? $_POST : $_REQUEST);
341 -
342 - foreach (array_keys($src) as $key) {
343 - $fname = $key[0] == '_' ? substr($key, 1) : $key;
344 - if ($ignore && !preg_match('/^('.$ignore.')$/', $fname)) {
345 - $out[$fname] = self::get_input_value($key, $mode, $allow_html);
346 - }
347 - }
348 -
349 - return $out;
350 - }
351 -
352 - /**
353 - * Convert the given string into a valid HTML identifier
354 - * Same functionality as done in app.js with rcube_webmail.html_identifier()
355 - *
356 - * @param string $str String input
357 - * @param bool $encode Use base64 encoding
358 - *
359 - * @param string Valid HTML identifier
360 - */
361 - public static function html_identifier($str, $encode = false)
362 - {
363 - if ($encode) {
364 - return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
365 - }
366 -
367 - return asciiwords($str, true, '_');
368 - }
369 -
370 - /**
371 - * Replace all css definitions with #container [def]
372 - * and remove css-inlined scripting, make position style safe
373 - *
374 - * @param string $source CSS source code
375 - * @param string $container_id Container ID to use as prefix
376 - * @param bool $allow_remote Allow remote content
377 - * @param string $prefix Prefix to be added to id/class identifier
378 - *
379 - * @return string Modified CSS source
380 - */
381 - public static function mod_css_styles($source, $container_id, $allow_remote = false, $prefix = '')
382 - {
383 - $last_pos = 0;
384 - $replacements = new rcube_string_replacer;
385 -
386 - // ignore the whole block if evil styles are detected
387 - $source = self::xss_entity_decode($source);
388 - $stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
389 - $evilexpr = 'expression|behavior|javascript:|import[^a]'.(!$allow_remote ? '|url\((?!data:image)' : '');
390 -
391 - if (preg_match("/$evilexpr/i", $stripped)) {
392 - return '/* evil! */';
393 - }
394 -
395 - $strict_url_regexp = '!url\s*\(\s*["\']?(https?:)//[a-z0-9/._+-]+["\']?\s*\)!Uims';
396 -
397 - // cut out all contents between { and }
398 - while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
399 - $nested = strpos($source, '{', $pos + 1);
400 - if ($nested && $nested < $pos2) // when dealing with nested blocks (e.g. @media), take the inner one
401 - {
402 - $pos = $nested;
403 - }
404 - $length = $pos2 - $pos - 1;
405 - $styles = substr($source, $pos + 1, $length);
406 -
407 - // Convert position:fixed to position:absolute (#5264)
408 - $styles = preg_replace('/position[^a-z]*:[\s\r\n]*fixed/i', 'position: absolute', $styles);
409 -
410 - // check every line of a style block...
411 - if ($allow_remote) {
412 - $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY);
413 -
414 - for ($i = 0, $len = count($a_styles) ; $i < $len ; $i++) {
415 - $line = $a_styles[$i];
416 - $stripped = preg_replace('/[^a-z\(:;]/i', '', $line);
417 -
418 - // allow data:image uri, join with continuation
419 - if (stripos($stripped, 'url(data:image')) {
420 - $a_styles[$i] .= ';'.$a_styles[$i + 1];
421 - unset($a_styles[$i + 1]);
422 - } // allow strict url() values only
423 - elseif (stripos($stripped, 'url(') && !preg_match($strict_url_regexp, $line)) {
424 - $a_styles = ['/* evil! */'];
425 - break;
426 - }
427 - }
428 -
429 - $styles = implode(";\n", $a_styles);
430 - }
431 -
432 - $key = $replacements->add($styles);
433 - $repl = $replacements->get_replacement($key);
434 - $source = substr_replace($source, $repl, $pos + 1, $length);
435 - $last_pos = $pos2 - ($length - strlen($repl));
436 - }
437 -
438 - // remove html comments
439 - $source = preg_replace('/(^\s*<\!--)|(-->\s*$)/m', '', $source);
440 -
441 - // add #container to each tag selector and prefix to id/class identifiers
442 - if ($container_id || $prefix) {
443 - // (?!##str) below is to not match with ##str_replacement_0##
444 - // from rcube_string_replacer used above, this is needed for
445 - // cases like @media { body { position: fixed; } } (#5811)
446 - $regexp = '/(^\s*|,\s*|\}\s*|\{\s*)((?!##str):?[a-z0-9\._#\*\[][a-z0-9\._:\(\)#=~ \[\]"\|\>\+\$\^-]*)/im';
447 - $callback = function ($matches) use ($container_id, $prefix) {
448 - $replace = $matches[2];
449 -
450 - if (stripos($replace, ':root') === 0) {
451 - $replace = substr($replace, 5);
452 - }
453 -
454 - if ($prefix) {
455 - $replace = str_replace(['.', '#'], [".$prefix", "#$prefix"], $replace);
456 - }
457 -
458 - if ($container_id) {
459 - $replace = "#$container_id ".$replace;
460 - }
461 -
462 - // Remove redundant spaces (for simpler testing)
463 - $replace = preg_replace('/\s+/', ' ', $replace);
464 -
465 - return str_replace($matches[2], $replace, $matches[0]);
466 - };
467 -
468 - $source = preg_replace_callback($regexp, $callback, $source);
469 - }
470 -
471 - // replace body definition because we also stripped off the <body> tag
472 - if ($container_id) {
473 - $regexp = '/#'.preg_quote($container_id, '/').'\s+body/i';
474 - $source = preg_replace($regexp, "#$container_id", $source);
475 - }
476 -
477 - // put block contents back in
478 - $source = $replacements->resolve($source);
479 -
480 - return $source;
481 - }
482 -
483 - /**
484 - * Generate CSS classes from mimetype and filename extension
485 - *
486 - * @param string $mimetype Mimetype
487 - * @param string $filename Filename
488 - *
489 - * @return string CSS classes separated by space
490 - */
491 - public static function file2class($mimetype, $filename)
492 - {
493 - $mimetype = strtolower($mimetype);
494 - $filename = strtolower($filename);
495 -
496 - [$primary, $secondary] = explode('/', $mimetype);
497 -
498 - $classes = [$primary ? : 'unknown'];
499 -
500 - if ($secondary) {
501 - $classes[] = $secondary;
502 - }
503 -
504 - if (preg_match('/\.([a-z0-9]+)$/', $filename, $m)) {
505 - if (!in_array($m[1], $classes)) {
506 - $classes[] = $m[1];
507 - }
508 - }
509 -
510 - return implode(' ', $classes);
511 - }
512 -
513 - /**
514 - * Decode escaped entities used by known XSS exploits.
515 - * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
516 - *
517 - * @param string $content CSS content to decode
518 - *
519 - * @return string Decoded string
520 - */
521 - public static function xss_entity_decode($content)
522 - {
523 - $callback = function ($matches) {
524 - return chr(hexdec($matches[1]));
525 - };
526 -
527 - $out = html_entity_decode(html_entity_decode($content));
528 - $out = trim(preg_replace('/(^<!--|-->$)/', '', trim($out)));
529 - $out = preg_replace_callback('/\\\([0-9a-f]{2,6})\s*/i', $callback, $out);
530 - $out = preg_replace('/\\\([^0-9a-f])/i', '\\1', $out);
531 - $out = preg_replace('#/\*.*\*/#Ums', '', $out);
532 - $out = strip_tags($out);
533 -
534 - return $out;
535 - }
536 -
537 - /**
538 - * Check if we can process not exceeding memory_limit
539 - *
540 - * @param integer $need Required amount of memory
541 - *
542 - * @return bool True if memory won't be exceeded, False otherwise
543 - */
544 - public static function mem_check($need)
545 - {
546 - $mem_limit = parse_bytes(ini_get('memory_limit'));
547 - $memory = function_exists('memory_get_usage') ? memory_get_usage() : 16 * 1024 * 1024; // safe value: 16MB
548 -
549 - return $mem_limit > 0 && $memory + $need > $mem_limit ? false : true;
550 - }
551 -
552 - /**
553 - * Check if working in SSL mode
554 - *
555 - * @param int $port HTTPS port number
556 - * @param bool $use_https Enables 'use_https' option checking
557 - *
558 - * @return bool True in SSL mode, False otherwise
559 - */
560 - public static function https_check($port = null, $use_https = true)
561 - {
562 - if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off') {
563 - return true;
564 - }
565 -
566 - if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])
567 - && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https'
568 - && in_array($_SERVER['REMOTE_ADDR'], rcube::get_instance()->config->get('proxy_whitelist', []))
569 - ) {
570 - return true;
571 - }
572 -
573 - if ($port && $_SERVER['SERVER_PORT'] == $port) {
574 - return true;
575 - }
576 -
577 - if ($use_https && rcube::get_instance()->config->get('use_https')) {
578 - return true;
579 - }
580 -
581 - return false;
582 - }
583 -
584 - /**
585 - * Replaces hostname variables.
586 - *
587 - * @param string $name Hostname
588 - * @param string $host Optional IMAP hostname
589 - *
590 - * @return string Hostname
591 - */
592 - public static function parse_host($name, $host = '')
593 - {
594 - if (!is_string($name)) {
595 - return $name;
596 - }
597 -
598 - // %n - host
599 - $n = self::server_name();
600 - // %t - host name without first part, e.g. %n=mail.domain.tld, %t=domain.tld
601 - // If %n=domain.tld then %t=domain.tld as well (remains valid)
602 - $t = preg_replace('/^[^.]+\.(?![^.]+$)/', '', $n);
603 - // %d - domain name without first part (up to domain.tld)
604 - $d = preg_replace('/^[^.]+\.(?![^.]+$)/', '', self::server_name('HTTP_HOST'));
605 - // %h - IMAP host
606 - $h = $_SESSION['storage_host'] ? : $host;
607 - // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
608 - // If %h=domain.tld then %z=domain.tld as well (remains valid)
609 - $z = preg_replace('/^[^.]+\.(?![^.]+$)/', '', $h);
610 - // %s - domain name after the '@' from e-mail address provided at login screen.
611 - // Returns FALSE if an invalid email is provided
612 - if (strpos($name, '%s') !== false) {
613 - $user_email = self::idn_to_ascii(self::get_input_value('_user', self::INPUT_POST));
614 - $matches = preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s);
615 - if ($matches < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false) {
616 - return false;
617 - }
618 - }
619 -
620 - return str_replace(['%n', '%t', '%d', '%h', '%z', '%s'], [$n, $t, $d, $h, $z, $s[2]], $name);
621 - }
622 -
623 - /**
624 - * Returns the server name after checking it against trusted hostname patterns.
625 - *
626 - * Returns 'localhost' and logs a warning when the hostname is not trusted.
627 - *
628 - * @param string $type The $_SERVER key, e.g. 'HTTP_HOST', Default: 'SERVER_NAME'.
629 - * @param bool $strip_port Strip port from the host name
630 - *
631 - * @return string Server name
632 - */
633 - public static function server_name($type = null, $strip_port = true)
634 - {
635 - $name = $_SERVER[$type ? : 'SERVER_NAME'];
636 - $rcube = rcube::get_instance();
637 - $patterns = (array)$rcube->config->get('trusted_host_patterns');
638 -
639 - if ($strip_port) {
640 - $name = preg_replace('/:\d+$/', '', $name);
641 - }
642 -
643 - if (empty($patterns) || in_array_nocase($name, $patterns)) {
644 - return $name;
645 - }
646 -
647 - if (!empty($name)) {
648 - foreach ($patterns as $pattern) {
649 - if (preg_match("/$pattern/", $name)) {
650 - return $name;
651 - }
652 - }
653 -
654 - $rcube->raise_error([
655 - 'file' => __FILE__,
656 - 'line' => __LINE__,
657 - 'message' => "Specified host is not trusted. Using 'localhost'.",
658 - ], true, false);
659 - }
660 -
661 - return 'localhost';
662 - }
663 -
664 - /**
665 - * Returns remote IP address and forwarded addresses if found
666 - *
667 - * @return string Remote IP address(es)
668 - */
669 - public static function remote_ip()
670 - {
671 - $address = $_SERVER['REMOTE_ADDR'];
672 -
673 - // append the NGINX X-Real-IP header, if set
674 - if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
675 - $remote_ip[] = 'X-Real-IP: '.$_SERVER['HTTP_X_REAL_IP'];
676 - }
677 -
678 - // append the X-Forwarded-For header, if set
679 - if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
680 - $remote_ip[] = 'X-Forwarded-For: '.$_SERVER['HTTP_X_FORWARDED_FOR'];
681 - }
682 -
683 - if (!empty($remote_ip)) {
684 - $address .= '('.implode(',', $remote_ip).')';
685 - }
686 -
687 - return $address;
688 - }
689 -
690 - /**
691 - * Returns the real remote IP address
692 - *
693 - * @return string Remote IP address
694 - */
695 - public static function remote_addr()
696 - {
697 - // Check if any of the headers are set first to improve performance
698 - if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) || !empty($_SERVER['HTTP_X_REAL_IP'])) {
699 - $proxy_whitelist = rcube::get_instance()->config->get('proxy_whitelist', []);
700 - if (in_array($_SERVER['REMOTE_ADDR'], $proxy_whitelist)) {
701 - if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
702 - foreach (array_reverse(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])) as $forwarded_ip) {
703 - $forwarded_ip = trim($forwarded_ip);
704 - if (!in_array($forwarded_ip, $proxy_whitelist)) {
705 - return $forwarded_ip;
706 - }
707 - }
708 - }
709 -
710 - if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
711 - return $_SERVER['HTTP_X_REAL_IP'];
712 - }
713 - }
714 - }
715 -
716 - if (!empty($_SERVER['REMOTE_ADDR'])) {
717 - return $_SERVER['REMOTE_ADDR'];
718 - }
719 -
720 - return '';
721 - }
722 -
723 - /**
724 - * Read a specific HTTP request header.
725 - *
726 - * @param string $name Header name
727 - *
728 - * @return string|null Header value or null if not available
729 - */
730 - public static function request_header($name)
731 - {
732 - if (function_exists('apache_request_headers')) {
733 - $headers = apache_request_headers();
734 - $key = strtoupper($name);
735 - } else {
736 - $headers = $_SERVER;
737 - $key = 'HTTP_'.strtoupper(strtr($name, '-', '_'));
738 - }
739 -
740 - if (!empty($headers)) {
741 - $headers = array_change_key_case($headers, CASE_UPPER);
742 -
743 - return $headers[$key];
744 - }
745 - }
746 -
747 - /**
748 - * Explode quoted string
749 - *
750 - * @param string $delimiter Delimiter expression string for preg_match()
751 - * @param string $string Input string
752 - *
753 - * @return array String items
754 - */
755 - public static function explode_quoted_string($delimiter, $string)
756 - {
757 - $result = [];
758 - $strlen = strlen($string);
759 -
760 - for ($q = $p = $i = 0 ; $i < $strlen ; $i++) {
761 - if ($string[$i] == "\"" && $string[$i - 1] != "\\") {
762 - $q = $q ? false : true;
763 - } elseif (!$q && preg_match("/$delimiter/", $string[$i])) {
764 - $result[] = substr($string, $p, $i - $p);
765 - $p = $i + 1;
766 - }
767 - }
768 -
769 - $result[] = (string)substr($string, $p);
770 -
771 - return $result;
772 - }
773 -
774 - /**
775 - * Improved equivalent to strtotime()
776 - *
777 - * @param string $date Date string
778 - * @param DateTimeZone $timezone Timezone to use for DateTime object
779 - *
780 - * @return int Unix timestamp
781 - */
782 - public static function strtotime($date, $timezone = null)
783 - {
784 - $date = self::clean_datestr($date);
785 - $tzname = $timezone ? ' '.$timezone->getName() : '';
786 -
787 - // unix timestamp
788 - if (is_numeric($date)) {
789 - return (int)$date;
790 - }
791 -
792 - // It can be very slow when provided string is not a date and very long
793 - if (strlen($date) > 128) {
794 - $date = substr($date, 0, 128);
795 - }
796 -
797 - // if date parsing fails, we have a date in non-rfc format.
798 - // remove token from the end and try again
799 - while (($ts = @strtotime($date.$tzname)) === false || $ts < 0) {
800 - if (($pos = strrpos($date, ' ')) === false) {
801 - break;
802 - }
803 -
804 - $date = rtrim(substr($date, 0, $pos));
805 - }
806 -
807 - return (int)$ts;
808 - }
809 -
810 - /**
811 - * Date parsing function that turns the given value into a DateTime object
812 - *
813 - * @param string $date Date string
814 - * @param DateTimeZone $timezone Timezone to use for DateTime object
815 - *
816 - * @return DateTime|false DateTime object or False on failure
817 - */
818 - public static function anytodatetime($date, $timezone = null)
819 - {
820 - if ($date instanceof DateTime) {
821 - return $date;
822 - }
823 -
824 - $dt = false;
825 - $date = self::clean_datestr($date);
826 -
827 - // try to parse string with DateTime first
828 - if (!empty($date)) {
829 - try {
830 - $_date = preg_match('/^[0-9]+$/', $date) ? "@$date" : $date;
831 - $dt = $timezone ? new DateTime($_date, $timezone) : new DateTime($_date);
832 - } catch (Exception $e) {
833 - // ignore
834 - }
835 - }
836 -
837 - // try our advanced strtotime() method
838 - if (!$dt && ($timestamp = self::strtotime($date, $timezone))) {
839 - try {
840 - $dt = new DateTime("@".$timestamp);
841 - if ($timezone) {
842 - $dt->setTimezone($timezone);
843 - }
844 - } catch (Exception $e) {
845 - // ignore
846 - }
847 - }
848 -
849 - return $dt;
850 - }
851 -
852 - /**
853 - * Clean up date string for strtotime() input
854 - *
855 - * @param string $date Date string
856 - *
857 - * @return string Date string
858 - */
859 - public static function clean_datestr($date)
860 - {
861 - $date = trim($date);
862 -
863 - // check for MS Outlook vCard date format YYYYMMDD
864 - if (preg_match('/^([12][90]\d\d)([01]\d)([0123]\d)$/', $date, $m)) {
865 - return sprintf('%04d-%02d-%02d 00:00:00', intval($m[1]), intval($m[2]), intval($m[3]));
866 - }
867 -
868 - // Clean malformed data
869 - $date = preg_replace(
870 - [
871 - '/\(.*\)/', // remove RFC comments
872 - '/GMT\s*([+-][0-9]+)/', // support non-standard "GMTXXXX" literal
873 - '/[^a-z0-9\x20\x09:\/\.+-]/i', // remove any invalid characters
874 - '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i', // remove weekday names
875 - ],
876 - [
877 - '',
878 - '\\1',
879 - '',
880 - '',
881 - ],
882 - $date
883 - );
884 -
885 - $date = trim($date);
886 -
887 - // try to fix dd/mm vs. mm/dd discrepancy, we can't do more here
888 - if (preg_match('/^(\d{1,2})[.\/-](\d{1,2})[.\/-](\d{4})(\s.*)?$/', $date, $m)) {
889 - $mdy = $m[2] > 12 && $m[1] <= 12;
890 - $day = $mdy ? $m[2] : $m[1];
891 - $month = $mdy ? $m[1] : $m[2];
892 - $date = sprintf('%04d-%02d-%02d%s', $m[3], $month, $day, $m[4] ? : ' 00:00:00');
893 - } // I've found that YYYY.MM.DD is recognized wrong, so here's a fix
894 - elseif (preg_match('/^(\d{4})\.(\d{1,2})\.(\d{1,2})(\s.*)?$/', $date, $m)) {
895 - $date = sprintf('%04d-%02d-%02d%s', $m[1], $m[2], $m[3], $m[4] ? : ' 00:00:00');
896 - }
897 -
898 - return $date;
899 - }
900 -
901 - /**
902 - * Turns the given date-only string in defined format into YYYY-MM-DD format.
903 - *
904 - * Supported formats: 'Y/m/d', 'Y.m.d', 'd-m-Y', 'd/m/Y', 'd.m.Y', 'j.n.Y'
905 - *
906 - * @param string $date Date string
907 - * @param string $format Input date format
908 - *
909 - * @return string Date string in YYYY-MM-DD format, or the original string
910 - * if format is not supported
911 - */
912 - public static function format_datestr($date, $format)
913 - {
914 - $format_items = preg_split('/[.-\/\\\\]/', $format);
915 - $date_items = preg_split('/[.-\/\\\\]/', $date);
916 - $iso_format = '%04d-%02d-%02d';
917 -
918 - if (count($format_items) == 3 && count($date_items) == 3) {
919 - if ($format_items[0] == 'Y') {
920 - $date = sprintf($iso_format, $date_items[0], $date_items[1], $date_items[2]);
921 - } elseif (strpos('dj', $format_items[0]) !== false) {
922 - $date = sprintf($iso_format, $date_items[2], $date_items[1], $date_items[0]);
923 - } elseif (strpos('mn', $format_items[0]) !== false) {
924 - $date = sprintf($iso_format, $date_items[2], $date_items[0], $date_items[1]);
925 - }
926 - }
927 -
928 - return $date;
929 - }
930 -
931 - /**
932 - * Wrapper for idn_to_ascii with support for e-mail address.
933 - *
934 - * Warning: Domain names may be lowercase'd.
935 - * Warning: An empty string may be returned on invalid domain.
936 - *
937 - * @param string $str Decoded e-mail address
938 - *
939 - * @return string Encoded e-mail address
940 - */
941 - public static function idn_to_ascii($str)
942 - {
943 - return self::idn_convert($str, true);
944 - }
945 -
946 - /**
947 - * Wrapper for idn_to_utf8 with support for e-mail address
948 - *
949 - * @param string $str Decoded e-mail address
950 - *
951 - * @return string Encoded e-mail address
952 - */
953 - public static function idn_to_utf8($str)
954 - {
955 - return self::idn_convert($str, false);
956 - }
957 -
958 - /**
959 - * Convert a string to ascii or utf8 (using IDNA standard)
960 - *
961 - * @param string $input Decoded e-mail address
962 - * @param boolean $is_utf Convert by idn_to_ascii if true and idn_to_utf8 if false
963 - *
964 - * @return string Encoded e-mail address
965 - */
966 - public static function idn_convert($input, $is_utf = false)
967 - {
968 - if ($at = strpos($input, '@')) {
969 - $user = substr($input, 0, $at);
970 - $domain = substr($input, $at + 1);
971 - } else {
972 - $user = '';
973 - $domain = $input;
974 - }
975 -
976 - // Note that in PHP 7.2/7.3 calling idn_to_* functions with default arguments
977 - // throws a warning, so we have to set the variant explicitely (#6075)
978 - $variant = defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : null;
979 - $options = 0;
980 -
981 - // Because php-intl extension lowercases domains and return false
982 - // on invalid input (#6224), we skip conversion when not needed
983 - // for compatibility with our Net_IDNA2 wrappers in bootstrap.php
984 -
985 - if ($is_utf) {
986 - if (preg_match('/[^\x20-\x7E]/', $domain)) {
987 - $options = defined('IDNA_NONTRANSITIONAL_TO_ASCII') ? IDNA_NONTRANSITIONAL_TO_ASCII : 0;
988 - $domain = idn_to_ascii($domain, $options, $variant);
989 - }
990 - } elseif (preg_match('/(^|\.)xn--/i', $domain)) {
991 - $options = defined('IDNA_NONTRANSITIONAL_TO_UNICODE') ? IDNA_NONTRANSITIONAL_TO_UNICODE : 0;
992 - $domain = idn_to_utf8($domain, $options, $variant);
993 - }
994 -
995 - if ($domain === false) {
996 - return '';
997 - }
998 -
999 - return $at ? $user.'@'.$domain : $domain;
1000 - }
1001 -
1002 - /**
1003 - * Split the given string into word tokens
1004 - *
1005 - * @param string $str Input to tokenize
1006 - * @param int $minlen Minimum length of a single token
1007 - *
1008 - * @return array List of tokens
1009 - */
1010 - public static function tokenize_string($str, $minlen = 2)
1011 - {
1012 - $expr = ['/[\s;,"\'\/+-]+/ui', '/(\d)[-.\s]+(\d)/u'];
1013 - $repl = [' ', '\\1\\2'];
1014 -
1015 - if ($minlen > 1) {
1016 - $minlen--;
1017 - $expr[] = "/(^|\s+)\w{1,$minlen}(\s+|$)/u";
1018 - $repl[] = ' ';
1019 - }
1020 -
1021 - return array_filter(explode(" ", preg_replace($expr, $repl, $str)));
1022 - }
1023 -
1024 - /**
1025 - * Normalize the given string for fulltext search.
1026 - * Currently only optimized for ISO-8859-1 and ISO-8859-2 characters; to be extended
1027 - *
1028 - * @param string $str Input string (UTF-8)
1029 - * @param bool $as_array True to return list of words as array
1030 - * @param int $minlen Minimum length of tokens
1031 - *
1032 - * @return string|array Normalized string or a list of normalized tokens
1033 - */
1034 - public static function normalize_string($str, $as_array = false, $minlen = 2)
1035 - {
1036 - // replace 4-byte unicode characters with '?' character,
1037 - // these are not supported in default utf-8 charset on mysql,
1038 - // the chance we'd need them in searching is very low
1039 - $str = preg_replace(
1040 - '/('
1041 - .'\xF0[\x90-\xBF][\x80-\xBF]{2}'
1042 - .'|[\xF1-\xF3][\x80-\xBF]{3}'
1043 - .'|\xF4[\x80-\x8F][\x80-\xBF]{2}'
1044 - .')/',
1045 - '?',
1046 - $str
1047 - );
1048 -
1049 - // split by words
1050 - $arr = self::tokenize_string($str, $minlen);
1051 -
1052 - // detect character set
1053 - if (utf8_encode(utf8_decode($str)) == $str) {
1054 - // ISO-8859-1 (or ASCII)
1055 - preg_match_all('/./u', 'äâàåáãæçéêëèïîìíñöôòøõóüûùúýÿ', $keys);
1056 - preg_match_all('/./', 'aaaaaaaceeeeiiiinoooooouuuuyy', $values);
1057 -
1058 - $mapping = array_combine($keys[0], $values[0]);
1059 - $mapping = array_merge($mapping, ['ß' => 'ss', 'ae' => 'a', 'oe' => 'o', 'ue' => 'u']);
1060 - } elseif (rcube_charset::convert(rcube_charset::convert($str, 'UTF-8', 'ISO-8859-2'), 'ISO-8859-2', 'UTF-8') == $str) {
1061 - // ISO-8859-2
1062 - preg_match_all('/./u', 'ąáâäćçčéęëěíîłľĺńňóôöŕřśšşťţůúűüźžżý', $keys);
1063 - preg_match_all('/./', 'aaaaccceeeeiilllnnooorrsssttuuuuzzzy', $values);
1064 -
1065 - $mapping = array_combine($keys[0], $values[0]);
1066 - $mapping = array_merge($mapping, ['ß' => 'ss', 'ae' => 'a', 'oe' => 'o', 'ue' => 'u']);
1067 - }
1068 -
1069 - foreach ($arr as $i => $part) {
1070 - $part = mb_strtolower($part);
1071 -
1072 - if (!empty($mapping)) {
1073 - $part = strtr($part, $mapping);
1074 - }
1075 -
1076 - $arr[$i] = $part;
1077 - }
1078 -
1079 - return $as_array ? $arr : implode(' ', $arr);
1080 - }
1081 -
1082 - /**
1083 - * Compare two strings for matching words (order not relevant)
1084 - *
1085 - * @param string $haystack Haystack
1086 - * @param string $needle Needle
1087 - *
1088 - * @return bool True if match, False otherwise
1089 - */
1090 - public static function words_match($haystack, $needle)
1091 - {
1092 - $a_needle = self::tokenize_string($needle, 1);
1093 - $_haystack = implode(' ', self::tokenize_string($haystack, 1));
1094 - $valid = strlen($_haystack) > 0;
1095 - $hits = 0;
1096 -
1097 - foreach ($a_needle as $w) {
1098 - if ($valid) {
1099 - if (stripos($_haystack, $w) !== false) {
1100 - $hits++;
1101 - }
1102 - } elseif (stripos($haystack, $w) !== false) {
1103 - $hits++;
1104 - }
1105 - }
1106 -
1107 - return $hits >= count($a_needle);
1108 - }
1109 -
1110 - /**
1111 - * Parse commandline arguments into a hash array
1112 - *
1113 - * @param array $aliases Argument alias names
1114 - *
1115 - * @return array Argument values hash
1116 - */
1117 - public static function get_opt($aliases = [])
1118 - {
1119 - $args = [];
1120 - $bool = [];
1121 -
1122 - // find boolean (no value) options
1123 - foreach ($aliases as $key => $alias) {
1124 - if ($pos = strpos($alias, ':')) {
1125 - $aliases[$key] = substr($alias, 0, $pos);
1126 - $bool[] = $key;
1127 - $bool[] = $aliases[$key];
1128 - }
1129 - }
1130 -
1131 - for ($i = 1 ; $i < count($_SERVER['argv']) ; $i++) {
1132 - $arg = $_SERVER['argv'][$i];
1133 - $value = true;
1134 - $key = null;
1135 -
1136 - if ($arg[0] == '-') {
1137 - $key = preg_replace('/^-+/', '', $arg);
1138 - $sp = strpos($arg, '=');
1139 -
1140 - if ($sp > 0) {
1141 - $key = substr($key, 0, $sp - 2);
1142 - $value = substr($arg, $sp + 1);
1143 - } elseif (in_array($key, $bool)) {
1144 - $value = true;
1145 - } elseif (strlen($_SERVER['argv'][$i + 1]) && $_SERVER['argv'][$i + 1][0] != '-') {
1146 - $value = $_SERVER['argv'][++$i];
1147 - }
1148 -
1149 - $args[$key] = is_string($value) ? preg_replace(['/^["\']/', '/["\']$/'], '', $value) : $value;
1150 - } else {
1151 - $args[] = $arg;
1152 - }
1153 -
1154 - if ($alias = $aliases[$key]) {
1155 - $args[$alias] = $args[$key];
1156 - }
1157 - }
1158 -
1159 - return $args;
1160 - }
1161 -
1162 - /**
1163 - * Safe password prompt for command line
1164 - * from http://blogs.sitepoint.com/2009/05/01/interactive-cli-password-prompt-in-php/
1165 - *
1166 - * @param string $prompt Prompt text
1167 - *
1168 - * @return string Password
1169 - */
1170 - public static function prompt_silent($prompt = "Password:")
1171 - {
1172 - if (preg_match('/^win/i', PHP_OS)) {
1173 - $vbscript = sys_get_temp_dir().'prompt_password.vbs';
1174 - $vbcontent = 'wscript.echo(InputBox("'.addslashes($prompt).'", "", "password here"))';
1175 - #file_put_contents($vbscript, $vbcontent);
1176 -
1177 - $command = "cscript //nologo ".escapeshellarg($vbscript);
1178 - $password = rtrim(shell_exec($command));
1179 - unlink($vbscript);
1180 -
1181 - return $password;
1182 - }
1183 -
1184 - $command = "/usr/bin/env bash -c 'echo OK'";
1185 -
1186 - if (rtrim(shell_exec($command)) !== 'OK') {
1187 - echo $prompt;
1188 - $pass = trim(fgets(STDIN));
1189 - echo chr(8)."\r".$prompt.str_repeat("*", strlen($pass))."\n";
1190 -
1191 - return $pass;
1192 - }
1193 -
1194 - $command = "/usr/bin/env bash -c 'read -s -p \"".addslashes($prompt)."\" mypassword && echo \$mypassword'";
1195 - $password = rtrim(shell_exec($command));
1196 - echo "\n";
1197 -
1198 - return $password;
1199 - }
1200 -
1201 - /**
1202 - * Find out if the string content means true or false
1203 - *
1204 - * @param string $str Input value
1205 - *
1206 - * @return bool Boolean value
1207 - */
1208 - public static function get_boolean($str)
1209 - {
1210 - $str = strtolower($str);
1211 -
1212 - return !in_array($str, ['false', '0', 'no', 'off', 'nein', ''], true);
1213 - }
1214 -
1215 - /**
1216 - * OS-dependent absolute path detection
1217 - *
1218 - * @param string $path File path
1219 - *
1220 - * @return bool True if the path is absolute, False otherwise
1221 - */
1222 - public static function is_absolute_path($path)
1223 - {
1224 - if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
1225 - return (bool)preg_match('!^[a-z]:[\\\\/]!i', $path);
1226 - }
1227 -
1228 - return $path[0] == '/';
1229 - }
1230 -
1231 - /**
1232 - * Resolve relative URL
1233 - *
1234 - * @param string $url Relative URL
1235 - *
1236 - * @return string Absolute URL
1237 - */
1238 - public static function resolve_url($url)
1239 - {
1240 - // prepend protocol://hostname:port
1241 - if (!preg_match('|^https?://|', $url)) {
1242 - $schema = 'http';
1243 - $default_port = 80;
1244 -
1245 - if (self::https_check()) {
1246 - $schema = 'https';
1247 - $default_port = 443;
1248 - }
1249 -
1250 - $prefix = $schema.'://'.preg_replace('/:\d+$/', '', $_SERVER['HTTP_HOST']);
1251 - if ($_SERVER['SERVER_PORT'] != $default_port && $_SERVER['SERVER_PORT'] != 80) {
1252 - $prefix .= ':'.$_SERVER['SERVER_PORT'];
1253 - }
1254 -
1255 - $url = $prefix.($url[0] == '/' ? '' : '/').$url;
1256 - }
1257 -
1258 - return $url;
1259 - }
1260 -
1261 - /**
1262 - * Generate a random string
1263 - *
1264 - * @param int $length String length
1265 - * @param bool $raw Return RAW data instead of ascii
1266 - *
1267 - * @return string The generated random string
1268 - */
1269 - public static function random_bytes($length, $raw = false)
1270 - {
1271 - $hextab = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1272 - $tabsize = strlen($hextab);
1273 -
1274 - // Use PHP7 true random generator
1275 - if ($raw && function_exists('random_bytes')) {
1276 - return random_bytes($length);
1277 - }
1278 -
1279 - if (!$raw && function_exists('random_int')) {
1280 - $result = '';
1281 - while ($length-- > 0) {
1282 - $result .= $hextab[random_int(0, $tabsize - 1)];
1283 - }
1284 -
1285 - return $result;
1286 - }
1287 -
1288 - $random = openssl_random_pseudo_bytes($length);
1289 -
1290 - if ($random === false && $length > 0) {
1291 - throw new Exception("Failed to get random bytes");
1292 - }
1293 -
1294 - if (!$raw) {
1295 - for ($x = 0 ; $x < $length ; $x++) {
1296 - $random[$x] = $hextab[ord($random[$x]) % $tabsize];
1297 - }
1298 - }
1299 -
1300 - return $random;
1301 - }
1302 -
1303 - /**
1304 - * Convert binary data into readable form (containing a-zA-Z0-9 characters)
1305 - *
1306 - * @param string $input Binary input
1307 - *
1308 - * @return string Readable output (Base62)
1309 - * @deprecated since 1.3.1
1310 - */
1311 - public static function bin2ascii($input)
1312 - {
1313 - $hextab = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1314 - $result = '';
1315 -
1316 - for ($x = 0 ; $x < strlen($input) ; $x++) {
1317 - $result .= $hextab[ord($input[$x]) % 62];
1318 - }
1319 -
1320 - return $result;
1321 - }
1322 -
1323 - /**
1324 - * Format current date according to specified format.
1325 - * This method supports microseconds (u).
1326 - *
1327 - * @param string $format Date format (default: 'd-M-Y H:i:s O')
1328 - *
1329 - * @return string Formatted date
1330 - */
1331 - public static function date_format($format = null)
1332 - {
1333 - if (empty($format)) {
1334 - $format = 'd-M-Y H:i:s O';
1335 - }
1336 -
1337 - if (strpos($format, 'u') !== false) {
1338 - $dt = number_format(microtime(true), 6, '.', '');
1339 - $dt .= '.'.date_default_timezone_get();
1340 -
1341 - if ($date = date_create_from_format('U.u.e', $dt)) {
1342 - return $date->format($format);
1343 - }
1344 - }
1345 -
1346 - return date($format);
1347 - }
1348 -
1349 - /**
1350 - * Parses socket options and returns options for specified hostname.
1351 - *
1352 - * @param array &$options Configured socket options
1353 - * @param string $host Hostname
1354 - */
1355 - public static function parse_socket_options(&$options, $host = null)
1356 - {
1357 - if (empty($host) || empty($options)) {
1358 - return;
1359 - }
1360 -
1361 - // get rid of schema and port from the hostname
1362 - $host_url = parse_url($host);
1363 - if (isset($host_url['host'])) {
1364 - $host = $host_url['host'];
1365 - }
1366 -
1367 - // find per-host options
1368 - if ($host && array_key_exists($host, $options)) {
1369 - $options = $options[$host];
1370 - }
1371 - }
1372 -
1373 - /**
1374 - * Get maximum upload size
1375 - *
1376 - * @return int Maximum size in bytes
1377 - */
1378 - public static function max_upload_size()
1379 - {
1380 - // find max filesize value
1381 - $max_filesize = parse_bytes(ini_get('upload_max_filesize'));
1382 - $max_postsize = parse_bytes(ini_get('post_max_size'));
1383 -
1384 - if ($max_postsize && $max_postsize < $max_filesize) {
1385 - $max_filesize = $max_postsize;
1386 - }
1387 -
1388 - return $max_filesize;
1389 - }
1390 -
1391 - /**
1392 - * Detect and log last PREG operation error
1393 - *
1394 - * @param array $error Error data (line, file, code, message)
1395 - * @param bool $terminate Stop script execution
1396 - *
1397 - * @return bool True on error, False otherwise
1398 - */
1399 - public static function preg_error($error = [], $terminate = false)
1400 - {
1401 - if (($preg_error = preg_last_error()) != PREG_NO_ERROR) {
1402 - $errstr = "PCRE Error: $preg_error.";
1403 -
1404 - if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR) {
1405 - $errstr .= " Consider raising pcre.backtrack_limit!";
1406 - }
1407 - if ($preg_error == PREG_RECURSION_LIMIT_ERROR) {
1408 - $errstr .= " Consider raising pcre.recursion_limit!";
1409 - }
1410 -
1411 - $error = array_merge(['code' => 620, 'line' => __LINE__, 'file' => __FILE__], $error);
1412 -
1413 - if (!empty($error['message'])) {
1414 - $error['message'] .= ' '.$errstr;
1415 - } else {
1416 - $error['message'] = $errstr;
1417 - }
1418 -
1419 - rcube::raise_error($error, true, $terminate);
1420 -
1421 - return true;
1422 - }
1423 -
1424 - return false;
1425 - }
1426 -
1427 - /**
1428 - * Generate a temporary file path in the Roundcube temp directory
1429 - *
1430 - * @param string $file_name String identifier for the type of temp file
1431 - * @param bool $unique Generate unique file names based on $file_name
1432 - * @param bool $create Create the temp file or not
1433 - *
1434 - * @return string temporary file path
1435 - */
1436 - public static function temp_filename($file_name, $unique = true, $create = true)
1437 - {
1438 - $temp_dir = rcube::get_instance()->config->get('temp_dir');
1439 -
1440 - // Fall back to system temp dir if configured dir is not writable
1441 - if (!is_writable($temp_dir)) {
1442 - $temp_dir = sys_get_temp_dir();
1443 - }
1444 -
1445 - // On Windows tempnam() uses only the first three characters of prefix so use uniqid() and manually add the prefix
1446 - // Full prefix is required for garbage collection to recognise the file
1447 - $temp_file = $unique ? str_replace('.', '', uniqid($file_name, true)) : $file_name;
1448 - $temp_path = unslashify($temp_dir).'/'.RCUBE_TEMP_FILE_PREFIX.$temp_file;
1449 -
1450 - // Sanity check for unique file name
1451 - if ($unique && file_exists($temp_path)) {
1452 - return self::temp_filename($file_name, $unique, $create);
1453 - }
1454 -
1455 - // Create the file to prevent possible race condition like tempnam() does
1456 - if ($create) {
1457 - touch($temp_path);
1458 - }
1459 -
1460 - return $temp_path;
1461 - }
1462 -}