1<?php 2// base.php -- HotCRP base helper functions 3// Copyright (c) 2006-2018 Eddie Kohler; see LICENSE. 4 5// string helpers 6 7function str_starts_with($haystack, $needle) { 8 $nl = strlen($needle); 9 return $nl <= strlen($haystack) && substr($haystack, 0, $nl) === $needle; 10} 11 12function str_ends_with($haystack, $needle) { 13 $p = strlen($haystack) - strlen($needle); 14 return $p >= 0 && substr($haystack, $p) === $needle; 15} 16 17function stri_ends_with($haystack, $needle) { 18 $p = strlen($haystack) - strlen($needle); 19 return $p >= 0 && strcasecmp(substr($haystack, $p), $needle) == 0; 20} 21 22function preg_matchpos($pattern, $subject) { 23 if (preg_match($pattern, $subject, $m, PREG_OFFSET_CAPTURE)) 24 return $m[0][1]; 25 else 26 return false; 27} 28 29function cleannl($text) { 30 if (substr($text, 0, 3) === "\xEF\xBB\xBF") 31 $text = substr($text, 3); 32 if (strpos($text, "\r") !== false) { 33 $text = str_replace("\r\n", "\n", $text); 34 $text = strtr($text, "\r", "\n"); 35 } 36 if (strlen($text) && $text[strlen($text) - 1] !== "\n") 37 $text .= "\n"; 38 return $text; 39} 40 41function space_join(/* $str_or_array, ... */) { 42 $t = ""; 43 foreach (func_get_args() as $arg) 44 if (is_array($arg)) { 45 foreach ($arg as $x) 46 if ($x !== "" && $x !== false && $x !== null) 47 $t .= ($t === "" ? "" : " ") . $x; 48 } else if ($arg !== "" && $arg !== false && $arg !== null) 49 $t .= ($t === "" ? "" : " ") . $arg; 50 return $t; 51} 52 53function is_valid_utf8($str) { 54 return !!preg_match('//u', $str); 55} 56 57if (function_exists("iconv")) { 58 function windows_1252_to_utf8($str) { 59 return iconv("Windows-1252", "UTF-8//IGNORE", $str); 60 } 61 function mac_os_roman_to_utf8($str) { 62 return iconv("Mac", "UTF-8//IGNORE", $str); 63 } 64} else if (function_exists("mb_convert_encoding")) { 65 function windows_1252_to_utf8($str) { 66 return mb_convert_encoding($str, "UTF-8", "Windows-1252"); 67 } 68} 69if (!function_exists("windows_1252_to_utf8")) { 70 function windows_1252_to_utf8($str) { 71 return UnicodeHelper::windows_1252_to_utf8($str); 72 } 73} 74if (!function_exists("mac_os_roman_to_utf8")) { 75 function mac_os_roman_to_utf8($str) { 76 return UnicodeHelper::mac_os_roman_to_utf8($str); 77 } 78} 79 80function convert_to_utf8($str) { 81 if (substr($str, 0, 3) === "\xEF\xBB\xBF") 82 $str = substr($str, 3); 83 if (is_valid_utf8($str)) 84 return $str; 85 $pfx = substr($str, 0, 5000); 86 if (substr_count($pfx, "\r") > 1.5 * substr_count($pfx, "\n")) 87 return mac_os_roman_to_utf8($str); 88 else 89 return windows_1252_to_utf8($str); 90} 91 92function simplify_whitespace($x) { 93 // Replace invisible Unicode space-type characters with true spaces, 94 // including control characters and DEL. 95 return trim(preg_replace('/(?:[\x00-\x20\x7F]|\xC2[\x80-\xA0]|\xE2\x80[\x80-\x8A\xA8\xA9\xAF]|\xE2\x81\x9F|\xE3\x80\x80)+/', " ", $x)); 96} 97 98function prefix_word_wrap($prefix, $text, $indent = 18, $totWidth = 75) { 99 if (is_int($indent)) { 100 $indentlen = $indent; 101 $indent = str_pad("", $indent); 102 } else 103 $indentlen = strlen($indent); 104 105 $out = ""; 106 if ($prefix !== false) { 107 while ($text !== "" && ctype_space($text[0])) { 108 $out .= $text[0]; 109 $text = substr($text, 1); 110 } 111 } else if (($line = UnicodeHelper::utf8_line_break($text, $totWidth)) !== false) 112 $out .= $line . "\n"; 113 114 while (($line = UnicodeHelper::utf8_line_break($text, $totWidth - $indentlen)) !== false) 115 $out .= $indent . preg_replace('/^\pZ+/u', '', $line) . "\n"; 116 117 if ($prefix === false) 118 /* skip */; 119 else if (strlen($prefix) <= $indentlen) { 120 $prefix = str_pad($prefix, $indentlen, " ", STR_PAD_LEFT); 121 $out = $prefix . substr($out, $indentlen); 122 } else 123 $out = $prefix . "\n" . $out; 124 125 if (!str_ends_with($out, "\n")) 126 $out .= "\n"; 127 return $out; 128} 129 130function center_word_wrap($text, $totWidth = 75, $multi_center = false) { 131 if (strlen($text) <= $totWidth && !preg_match('/[\200-\377]/', $text)) 132 return str_pad($text, (int) (($totWidth + strlen($text)) / 2), " ", STR_PAD_LEFT) . "\n"; 133 $out = ""; 134 while (($line = UnicodeHelper::utf8_line_break($text, $totWidth)) !== false) { 135 $linelen = UnicodeHelper::utf8_glyphlen($line); 136 $out .= str_pad($line, (int) (($totWidth + $linelen) / 2), " ", STR_PAD_LEFT) . "\n"; 137 } 138 return $out; 139} 140 141function count_words($text) { 142 return preg_match_all('/[^-\s.,;:<>!?*_~`#|]\S*/', $text); 143} 144 145function friendly_boolean($x) { 146 if (is_bool($x)) 147 return $x; 148 else if (is_string($x) || is_int($x)) 149 return filter_var($x, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); 150 else 151 return null; 152} 153 154interface Abbreviator { 155 public function abbreviations_for($name, $data); 156} 157 158 159// email and MIME helpers 160 161function validate_email($email) { 162 // Allow @_.com email addresses. Simpler than RFC822 validation. 163 if (!preg_match(':\A[-!#$%&\'*+./0-9=?A-Z^_`a-z{|}~]+@(.+)\z:', $email, $m)) 164 return false; 165 if ($m[1][0] === "_") 166 return preg_match(':\A_\.[0-9A-Za-z]+\z:', $m[1]); 167 else 168 return preg_match(':\A([-0-9A-Za-z]+\.)+[0-9A-Za-z]+\z:', $m[1]); 169} 170 171function mime_quote_string($word) { 172 return '"' . preg_replace('_(?=[\x00-\x1F\\"])_', '\\', $word) . '"'; 173} 174 175function mime_token_quote($word) { 176 if (preg_match('_\A[^][\x00-\x20\x80-\xFF()<>@,;:\\"/?=]+\z_', $word)) 177 return $word; 178 else 179 return mime_quote_string($word); 180} 181 182function rfc2822_words_quote($words) { 183 if (preg_match(':\A[-A-Za-z0-9!#$%&\'*+/=?^_`{|}~ \t]*\z:', $words)) 184 return $words; 185 else 186 return mime_quote_string($words); 187} 188 189 190// encoders and decoders 191 192function html_id_encode($text) { 193 $x = preg_split('_([^-a-zA-Z0-9])_', $text, -1, PREG_SPLIT_DELIM_CAPTURE); 194 for ($i = 1; $i < count($x); $i += 2) 195 $x[$i] = "_" . dechex(ord($x[$i])); 196 return join("", $x); 197} 198 199function html_id_decode($text) { 200 $x = preg_split(',(_[0-9A-Fa-f][0-9A-Fa-f]),', $text, -1, PREG_SPLIT_DELIM_CAPTURE); 201 for ($i = 1; $i < count($x); $i += 2) 202 $x[$i] = chr(hexdec(substr($x[$i], 1))); 203 return join("", $x); 204} 205 206function base64url_encode($text) { 207 return rtrim(strtr(base64_encode($text), '+/', '-_'), '='); 208} 209 210function base64url_decode($data) { 211 return base64_decode(strtr($text, '-_', '+/')); 212} 213 214 215// JSON encoding helpers 216 217if (!function_exists("json_encode") || !function_exists("json_decode")) 218 require_once("$ConfSitePATH/lib/json.php"); 219if (!function_exists("json_last_error_msg")) { 220 function json_last_error_msg() { 221 return false; 222 } 223} 224if (defined("JSON_UNESCAPED_LINE_TERMINATORS")) { 225 // JSON_UNESCAPED_UNICODE is only safe to send to the browser if 226 // JSON_UNESCAPED_LINE_TERMINATORS is defined. 227 function json_encode_browser($x, $flags = 0) { 228 return json_encode($x, $flags | JSON_UNESCAPED_UNICODE); 229 } 230} else { 231 function json_encode_browser($x, $flags = 0) { 232 return json_encode($x, $flags); 233 } 234} 235function json_encode_db($x, $flags = 0) { 236 return json_encode($x, $flags | JSON_UNESCAPED_UNICODE); 237} 238 239 240// array and object helpers 241 242function get($var, $idx, $default = null) { 243 if (is_array($var)) 244 return array_key_exists($idx, $var) ? $var[$idx] : $default; 245 else if (is_object($var)) 246 return property_exists($var, $idx) ? $var->$idx : $default; 247 else if ($var === null) 248 return $default; 249 else { 250 error_log("inappropriate get: " . var_export($var, true) . ": " . json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS))); 251 return $default; 252 } 253} 254 255function get_s($var, $idx, $default = null) { 256 return (string) get($var, $idx, $default); 257} 258 259function get_i($var, $idx, $default = null) { 260 return (int) get($var, $idx, $default); 261} 262 263function get_f($var, $idx, $default = null) { 264 return (float) get($var, $idx, $default); 265} 266 267function opt($idx, $default = null) { 268 global $Conf, $Opt; 269 return get($Conf ? $Conf->opt : $Opt, $idx, $default); 270} 271 272function uploaded_file_error($finfo) { 273 $e = $finfo["error"]; 274 $name = get($finfo, "name") ? "<span class=\"lineno\">" . htmlspecialchars($finfo["name"]) . ":</span> " : ""; 275 if ($e == UPLOAD_ERR_INI_SIZE || $e == UPLOAD_ERR_FORM_SIZE) 276 return $name . "Uploaded file too big. The maximum upload size is " . ini_get("upload_max_filesize") . "B."; 277 else if ($e == UPLOAD_ERR_PARTIAL) 278 return $name . "Upload process interrupted."; 279 else if ($e != UPLOAD_ERR_NO_FILE) 280 return $name . "Unknown upload error."; 281 else 282 return false; 283} 284 285function make_qreq() { 286 $qreq = new Qrequest($_SERVER["REQUEST_METHOD"]); 287 foreach ($_GET as $k => $v) 288 $qreq->set_req($k, $v); 289 foreach ($_POST as $k => $v) 290 $qreq->set_req($k, $v); 291 if (empty($_POST)) 292 $qreq->set_post_empty(); 293 294 // $_FILES requires special processing since we want error messages. 295 $errors = []; 296 foreach ($_FILES as $nx => $fix) { 297 if (is_array($fix["error"])) { 298 $fis = []; 299 foreach (array_keys($fix["error"]) as $i) { 300 $fis[$i ? "$nx.$i" : $nx] = ["name" => $fix["name"][$i], "type" => $fix["type"][$i], "size" => $fix["size"][$i], "tmp_name" => $fix["tmp_name"][$i], "error" => $fix["error"][$i]]; 301 } 302 } else { 303 $fis = [$nx => $fix]; 304 } 305 foreach ($fis as $n => $fi) { 306 if ($fi["error"] == UPLOAD_ERR_OK) { 307 if (is_uploaded_file($fi["tmp_name"])) 308 $qreq->set_file($n, $fi); 309 } else if (($err = uploaded_file_error($fi))) 310 $errors[] = $err; 311 } 312 } 313 if (!empty($errors) && Conf::$g) 314 Conf::msg_error("<div class=\"parseerr\"><p>" . join("</p>\n<p>", $errors) . "</p></div>"); 315 316 return $qreq; 317} 318 319function defval($var, $idx, $defval = null) { 320 if (is_array($var)) 321 return (isset($var[$idx]) ? $var[$idx] : $defval); 322 else 323 return (isset($var->$idx) ? $var->$idx : $defval); 324} 325 326function is_associative_array($a) { 327 // this method is suprisingly fast 328 return is_array($a) && array_values($a) !== $a; 329} 330 331function array_to_object_recursive($a) { 332 if (is_associative_array($a)) { 333 $o = (object) array(); 334 foreach ($a as $k => $v) 335 if ($k !== "") 336 $o->$k = array_to_object_recursive($v); 337 return $o; 338 } else 339 return $a; 340} 341 342function object_replace($a, $b) { 343 foreach (is_object($b) ? get_object_vars($b) : $b as $k => $v) 344 if ($v === null) 345 unset($a->$k); 346 else 347 $a->$k = $v; 348} 349 350function object_replace_recursive($a, $b) { 351 foreach (is_object($b) ? get_object_vars($b) : $b as $k => $v) 352 if ($v === null) 353 unset($a->$k); 354 else if (!property_exists($a, $k) 355 || !is_object($a->$k) 356 || !is_object($v)) 357 $a->$k = $v; 358 else 359 object_replace_recursive($a->$k, $v); 360} 361 362function json_object_replace($j, $updates, $nullable = false) { 363 if ($j === null) 364 $j = (object) []; 365 else if (is_array($j)) 366 $j = (object) $j; 367 object_replace($j, $updates); 368 if ($nullable) { 369 $x = get_object_vars($j); 370 if (empty($x)) 371 $j = null; 372 } 373 return $j; 374} 375 376 377// debug helpers 378 379function caller_landmark($position = 1, $skipfunction_re = null) { 380 if (is_string($position)) 381 list($position, $skipfunction_re) = array(1, $position); 382 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); 383 $fname = null; 384 for (++$position; isset($trace[$position]); ++$position) { 385 $fname = get_s($trace[$position], "class"); 386 $fname .= ($fname ? "::" : "") . $trace[$position]["function"]; 387 if ((!$skipfunction_re || !preg_match($skipfunction_re, $fname)) 388 && ($fname !== "call_user_func" || get($trace[$position - 1], "file"))) 389 break; 390 } 391 $t = ""; 392 if ($position > 0 && ($pi = $trace[$position - 1]) && isset($pi["file"])) 393 $t = $pi["file"] . ":" . $pi["line"]; 394 if ($fname) 395 $t .= ($t ? ":" : "") . $fname; 396 return $t ? : "<unknown>"; 397} 398 399function assert_callback() { 400 trigger_error("Assertion backtrace: " . json_encode(array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), 2)), E_USER_WARNING); 401} 402//assert_options(ASSERT_CALLBACK, "assert_callback"); 403 404 405// pcntl helpers 406 407if (function_exists("pcntl_wifexited") && pcntl_wifexited(0) !== null) { 408 function pcntl_wifexitedsuccess($status) { 409 return pcntl_wifexited($status) && pcntl_wexitstatus($status) == 0; 410 } 411} else { 412 function pcntl_wifexitedsuccess($status) { 413 return ($status & 0x7f) == 0 && (($status & 0xff00) >> 8) == 0; 414 } 415} 416