<?php require_once($INC . "/prof.inc.php"); require_once($INC . "/log.inc.php"); set_error_handler("rg_error_handler"); register_shutdown_function("rg_error_shutdown"); define('RG_SOCKET_NO_WAIT', 0x01); if (!isset($rg_util_debug)) $rg_util_debug = FALSE; if (strcmp(getenv('ROCKETGIT_UTIL_DEBUG'), '1') == 0) $rg_util_debug = TRUE; $rg_util_error = ""; function rg_util_set_error($str) { global $rg_util_error; $rg_util_error = $str; rg_log('util_set_error: ' . $str); } function rg_util_error() { global $rg_util_error; return $rg_util_error; } /* * This array will keep all registered function for templates */ $rg_template_functions = array(); /* * */ function rg_php_err() { global $rg_php_err; //TODO: we are using our error handler! //$a = error_get_last(); //if ($a === NULL) { $ret = $rg_php_err; $rg_php_err = ''; //} else { // $ret = $a['message']; // error_clear_last(); //} return $ret; } /* * Register a function to be called when a @@func:func_name:var@@ construction * is found in a template. * Please note that the function is called once per template. */ function rg_template_func($name, $real_func_name) { global $rg_template_functions; $rg_template_functions[$name] = $real_func_name; } function sha512($m) { return hash('sha512', $m); } /* * TODO: 8192KiB must be 8MiB */ function rg_1024($v) { if ($v <= 9999) return number_format($v) . 'B'; $v /= 1024; if ($v <= 9999) return number_format($v) . "KiB"; $v /= 1024; if ($v <= 9999) return number_format($v) . "MiB"; $v /= 1024; if ($v <= 9999) return number_format($v) . "GiB"; $v /= 1024; return number_format($v) . "TiB"; } /* * Transforms a kilo/mega/giga into bytes * Example: 8M -> 8388608 */ function rg_mega2bytes($s) { $r = intval($s); if (stristr($s, 'k')) return $r * 1024; if (stristr($s, 'm')) return $r * 1024 * 1024; if (stristr($s, 'g')) return $r * 1024 * 1024 * 1024; return $r; } /* * Returns a binary string of random bytes */ function rg_random_bytes($len) { static $buf = ''; static $buf_len = 0; rg_prof_start('random_bytes'); $ret = FALSE; if ($len > $buf_len) { $f = @fopen('/dev/urandom', 'r'); if ($f === NULL) rg_fatal('cannot open urandom'); while ($len > $buf_len) { $r = @fread($f, max(512, $len)); if ($r === FALSE) rg_fatal('cannot read from urandom'); $buf .= $r; $buf_len += strlen($r); } fclose($f); } $ret = substr($buf, $len); $buf = substr($buf, $len); $buf_len -= $len; rg_prof_end('random_bytes'); return $ret; } /* * Unique ID generator */ function rg_id($len) { rg_prof_start('id'); $id = rg_random_bytes(($len + 1) / 2); $id = bin2hex($id); $id = substr($id, 0, $len); rg_prof_end('id'); return $id; } /* * Locks a file */ $_lock = array(); function rg_lock($file) { global $_lock; global $rg_lock_dir; if (!isset($rg_lock_dir)) $rg_lock_dir = "/var/lib/rocketgit/locks"; // Double locking? if (isset($_lock[$file])) { rg_internal_error("Double locking [$file]: " . rg_array2string($_lock)); return FALSE; } $f = @fopen($rg_lock_dir . "/" . $file, "w"); if ($f === FALSE) { rg_internal_error('Cannot open lock: ' . rg_php_err() . '.'); return FALSE; } if (!@flock($f, LOCK_EX | LOCK_NB, $wouldblock)) { if ($wouldblock == 1) rg_log('Cannot lock: already locked'); else rg_log('Cannot lock: ' . rg_php_err()); fclose($f); return FALSE; } fwrite($f, getmypid() . "\n"); $_lock[$file] = $f; rg_log('Lock [' . $file . '] acquired.'); return TRUE; } function rg_lock_or_exit($file) { if (rg_lock($file) === FALSE) exit(0); } function rg_unlock($file) { global $_lock; if (!isset($_lock[$file])) { rg_internal_error('Lock [' . $file . '] not taken!'); return FALSE; } fclose($_lock[$file]); rg_log('Lock [' . $file . '] unlocked.'); } /* * Returns information bout memory usage of the OS */ function rg_memory() { $r = @file('/proc/meminfo'); if ($r === FALSE) return FALSE; $ret = array(); $found = 0; foreach ($r as $line) { $t = explode(':', $line); $k = rtrim($t[0], ' '); $t = explode(' ', trim($t[1])); $v = $t[0]; if (strcmp($k, 'MemTotal') == 0) { $ret['total'] = $v; $found++; } else if (strcmp($k, 'MemAvailable') == 0) { $ret['avail'] = $v; $found++; } if ($found == 2) break; } return $ret; } /* * Taks a list (example: 0-4,6-7) and return exploded elements (0,1,2,3,4,6,7) */ function rg_list_expand($s) { $t = explode(',', trim($s)); $ret = array(); $i = 0; while (isset($t[$i])) { $x = explode('-', $t[$i]); if (!isset($x[1])) { $ret[] = $t[$i]; } else { for ($j = $x[0]; $j <= $x[1]; $j++) $ret[] = $j; } $i++; } return $ret; } /* * Returns the number of online cores */ function rg_cores() { $r = @file_get_contents('/sys/devices/system/cpu/online'); if ($r === FALSE) return 1; $online_cores = rg_list_expand($r); return count($online_cores); } /* * Returns the load of the system multiplied by 100 and divided by the number of cores * Example: 4 cores, load 8 will return 2 */ function rg_load() { // Number of cores $r = @file_get_contents('/proc/loadavg'); if ($r === FALSE) return 0; $t = explode(' ', $r); $v = 100 * $t[0]; return intval($v / rg_cores()); } /* * Outputs a string to browser, XSS safe * Thanks OWASP! */ function rg_xss_safe($str) { if (!defined('ENT_HTML401')) define('ENT_HTML401', 0); return htmlspecialchars($str, ENT_QUOTES | ENT_HTML401, 'UTF-8'); } /* * Builds URLs */ function rg_re_url($area) { return $area; } function rg_re_userpage($ui) { if (isset($ui['uid']) && ($ui['uid'] == 0)) return ''; if (!isset($ui['organization'])) { rg_internal_error("rg_re_userpage called with wrong ui (no org)!"); rg_log("ui: " . print_r($ui, TRUE)); exit(1); } $prefix = ''; if ($ui['organization'] == 0) $prefix = '/user'; $s = $prefix . '/' . rawurlencode($ui['username']); return rg_re_url($s); } function rg_re_repopage($ui, $repo_name) { $s = rg_re_userpage($ui) . "/" . rawurlencode($repo_name); return rg_re_url($s); } function rg_re_bugpage($ui, $repo_name, $bug_id) { return rg_re_repopage($ui, $repo_name) . "/bug/" . $bug_id; } /* * Returns the correct URL to the current virtual host */ function rg_base_url() { global $rg_base_url; return $rg_base_url; } /* * Builds a host[+port] */ function rg_base_url_host_port($hostname, $port, $default_port) { if ($port != $default_port) return $hostname . ':' . $port; return $hostname; } /* * Builds a correct URL to refer to the current virtualhost * @http(s)_allow: '0' if not allowed, FALSE if unknown, else port number * Note: at least one of http_allow or https_allow will be set to a port number. */ function rg_base_url_build($hostname, $http_allow, $https_allow) { global $rg_base_url; // We are forced to use something if we cannot get them from cache/db if (($hostname === FALSE) || empty($hostname)) $hostname = php_uname('n'); // Prefer httpS if (intval($https_allow) > 0) { $rg_base_url = 'https://' . rg_base_url_host_port($hostname, $https_allow, 443); return; } $rg_base_url = 'http://' . rg_base_url_host_port($hostname, $http_allow, 80); } function rg_re_repo_ssh($organization, $user, $repo) { global $rg_ssh_host; global $rg_ssh_port; if ($rg_ssh_port == 22) $port = ""; else $port = ":" . $rg_ssh_port; $prefix = ""; if ($organization == 0) $prefix = "/user"; return "ssh://rocketgit@" . $rg_ssh_host . $port . $prefix . "/" . rawurlencode($user) . "/" . rawurlencode($repo); } function rg_re_repo_git($organization, $user, $repo) { global $rg_git_host; global $rg_git_port; if ($rg_git_port == 9418) $port = ""; else $port = ":" . $rg_git_port; $prefix = ""; if ($organization == 0) $prefix = "/user"; return "git://" . $rg_git_host . $port . $prefix . "/" . rawurlencode($user) . "/" . rawurlencode($repo); } function rg_re_repo_http($organization, $user, $repo) { $prefix = ''; if ($organization == 0) $prefix = '/user'; return $prefix . "/" . rawurlencode($user) . "/" . rawurlencode($repo); } function rg_var_get($name) { if (isset($_SERVER[$name])) $ret = $_SERVER[$name]; else if (isset($_POST[$name])) $ret = $_POST[$name]; else if (isset($_GET[$name])) $ret = $_GET[$name]; else return FALSE; return str_replace("\r", "", $ret); } function rg_var_is_set($name) { $c = rg_var_get($name); if ($c === FALSE) return 0; return 1; } function rg_var_str($name) { $c = rg_var_get($name); if ($c === FALSE) return ''; return $c; } function rg_var_str_nocr($name) { $k = array("\n", "\r"); $v = array('', ''); return str_replace($k, $v, rg_var_str($name)); } function rg_var_int($name) { $r = rg_var_str($name); if (is_array($r)) { $ret2 = array(); foreach ($r as $k => $v) $ret2[$k] = sprintf("%d", $v); return $ret2; } return sprintf("%d", $r); } function rg_var_uint($name) { $r = rg_var_str($name); if (is_array($r)) { $ret2 = array(); foreach ($r as $k => $v) $ret2[$k] = sprintf("%u", $v); return $ret2; } return sprintf("%u", $r); } function rg_var_bool($name) { $r = rg_var_str($name); if (strcmp($r, '1') == 0) return 1; return 0; } function rg_var_email($name) { return trim(rg_var_str_nocr($name)); } /* * Allow only @re chars */ function rg_var_re($name, $re) { $a = rg_var_str($name); return preg_replace('/[^' . $re . ']/', '', $a); } /* * Extract a cookie from $_COOKIE */ function rg_var_cookie_re($name, $re) { if (!isset($_COOKIE[$name])) return ""; return preg_replace($re, '', $_COOKIE[$name]); } /* * Gets data from request and transforms it into an array * Usefull for checkboxes. * (a2s = array2string) */ function rg_var_a2s($var) { $r = rg_var_str($var); if (!is_array($r)) return $r; $ret = ''; foreach ($r as $s => $junk) $ret .= $s; return $ret; } /* * Enforce chars in a name. It is used for user and repo. * Returns: -1 on error, 0 = no match, 1 = match */ function rg_chars_allow($name, $allowed_regexp, &$invalid) { // Modifiers: u = UTF8, D = PCRE_DOLLAR_ENDONLY // http://php.net/manual/en/reference.pcre.pattern.modifiers.php $r = preg_match('/^[' . $allowed_regexp . ']*$/uD', $name); if ($r === FALSE) { $m = 'cannot match [' . $allowed_regexp . '] in [' . $name . ']'; rg_internal_error($m); rg_util_set_error($m); return -1; } if ($r !== 1) { $invalid = preg_replace('/[' . $allowed_regexp . ']/', '', $name); rg_util_set_error('chars_allow: [' . $name . ']' . ' does not match [' . $allowed_regexp . ']'); return 0; } return 1; } /* * Deletes a folder and the files inside it */ function rg_rmdir($dir) { if (!is_dir($dir)) return TRUE; $scan = glob($dir . "/*"); if ($scan === FALSE) { rg_util_set_error("invalid pattern [$dir/*]"); return FALSE; } if (count($scan) > 0) { $all_good = TRUE; foreach ($scan as $junk => $path) { if (is_dir($path)) { $r = rg_rmdir($path); if ($r !== TRUE) return FALSE; continue; } if (!unlink($path)) { rg_util_set_error('cannot remove path: ' . rg_php_err()); return FALSE; } } } if (!rmdir($dir)) { rg_util_set_error('cannot remove main dir: ' . rg_php_err()); return FALSE; } return TRUE; } /* * Lookup a path in the current theme with fallback to default * Returns the correct path */ function rg_theme_resolve_path($path) { global $rg_theme, $rg_theme_dir; $url = "/themes/" . $rg_theme . "/" . $path; $xfile = $rg_theme_dir . "/" . $rg_theme . "/" . $path; if (!is_file($xfile)) $url = "/themes/default/" . $path; return $url; } /* * Loads a file if exists, else return "" */ function rg_file_get_contents($f) { if (!file_exists($f)) return ""; $c = @file_get_contents($f); if ($c === FALSE) { rg_internal_error('Could not load file: ' . rg_php_err() . '.'); return ""; } return $c; } /* * Merges an array (a) into another (src), using a namespace * Protects modifiers (HTML: etc.). */ function rg_array_merge($src, $namespace, $a) { $ret = $src; if (!empty($namespace)) if (!isset($ret[$namespace])) $ret[$namespace] = array(); foreach ($a as $k => $v) { if (empty($namespace)) $ret[$k] = $v; else $ret[$namespace][$k] = $v; } return $ret; } /* * Performs a lookup of a var of type 'a::b::c' into an array and * returns FALSE or the value */ function rg_template_tree_lookup($var, &$data, $xss_protection) { $tree = &$data; $t = explode('::', $var); $v = array_pop($t); foreach ($t as $token) { if (!isset($tree[$token])) return FALSE; $tree = &$tree[$token]; } // We prefer the HTML version $hv = 'HTML:' . $v; if (isset($tree[$hv])) return $tree[$hv]; if (isset($tree[$v])) { if ($xss_protection) return rg_xss_safe($tree[$v]); else return $tree[$v]; } return FALSE; } /* * Evaluates a condition * Returns FALSE on error, 0 for TRUE and 1 for FALSE */ function rg_template_eval_cond($cond, &$data) { while (strpos($cond, ' ')) $cond = str_replace(' ', ' ', $cond); $t = explode(' ', $cond); if (count($t) != 3) { rg_util_set_error('invalid condition: ' . $cond . ' (not 3 tokens)'); return FALSE; } $left = rg_template_string($t[0], 0, $data, FALSE); $op = $t[1]; $right = rg_template_string($t[2], 0, $data, FALSE); if (strcmp($op, '==') == 0) return strcmp($left, $right) == 0 ? 1 : 0; if (strcmp($op, '!=') == 0) return strcmp($left, $right) != 0 ? 1 : 0; $ileft = intval($left); $iright = intval($right); if (strcmp($op, '>=') == 0) return $ileft >= $iright ? 1 : 0; if (strcmp($op, '>') == 0) return $ileft > $iright ? 1 : 0; if (strcmp($op, '<=') == 0) return $ileft <= $iright ? 1 : 0; if (strcmp($op, '<') == 0) return $ileft < $iright ? 1 : 0; rg_util_set_error('unknown condition: ' . $cond); return FALSE; } /* * Finds matching }} for an {{ * We assume @off points to the byte after '{{' * Returns the offset of the byte before '}}' */ function rg_template_find_closing(&$s, $off) { $nesting_level = 0; while (1) { $end = strpos($s, '}}', $off); if ($end === FALSE) return -1; $start = strpos($s, '{{', $off); if (($start === FALSE) || ($start >= $end)) { if ($nesting_level == 0) return $end - 1; $nesting_level--; $off = $end + 2; } else { $nesting_level++; $off = $start + 2; } } } /* * "Decodes" an 'if', returning 'true_start/end' and false_start/end' * s + off must point after ')' * Returns -1 on error, 0 on success */ function rg_template_find_true_and_false(&$s, $off, &$true_start, &$true_end, &$false_start, &$false_end) { //rg_log_enter("DEBUG: template_find_true_and_false s+off=[" . substr($s, $off) . "]"); $true_start = strpos($s, '{{', $off); if ($true_start === FALSE) { //rg_log("DEBUG: no '{{'!"); //rg_log_exit(); return -1; } $true_start += 2; if (strncmp(substr($s, $true_start, 1), "\n", 1) == 0) { //rg_log("DEBUG: true starts with CR, remove it"); $true_start++; } $true_end = rg_template_find_closing($s, $true_start); if ($true_end == -1) { //rg_log("DEBUG: no true_end!"); //rg_log_exit(); return -1; } //rg_log("DEBUG: true_start=$true_start true_end=$true_end [" . substr($s, $true_end, 3) . "...]" // . " true=[" . substr($s, $true_start, $true_end - $true_start + 1) . "]"); // We try to detect if we have an else $false_start = -1; $false_end = -1; $x = strpos($s, '{{', $true_end); if ($x !== FALSE) { $gap = substr($s, $true_end + 3, $x - $true_end - 3); $gap = trim($gap); //rg_log("DEBUG: gap = [$gap]"); if (empty($gap)) { $false_start = $x + 2; if (strncmp(substr($s, $false_start, 1), "\n", 1) == 0) { //rg_log("DEBUG: false starts with CR, remove it"); $false_start++; } $false_end = rg_template_find_closing($s, $x + 2); //rg_log("DEBUG: false=[" . substr($s, $false_start, $false_end - $false_start + 1) . "]"); } else { //rg_log("DEBUG: gap prevents parsing stuff as false, we have only true part"); } } else { //rg_log("DEBUG: cannot find '{{'"); } //rg_log_exit(); return 0; } /* * Helper for rg_tempalte_string to deal with 'if's * Returns how many bytes used from string @s in @next */ function rg_template_string_if(&$s, $off, &$data, &$next, $xss_protection) { rg_prof_start("template_string_if"); //rg_log_enter("DEBUG: template_string_if s+off=[" . substr($s, $off) . "]"); $ret = ''; $next = $off; $off += 5; /* skip '@@if(' */ $pos = strpos($s, ')', $off); if ($pos === FALSE) { rg_log("no closing ')' in [" . substr($s, $off) . "]!"); rg_log_exit(); rg_prof_end("template_string_if"); return ''; } $cond = substr($s, $off, $pos - $off); $off = $pos + 1; $eval_cond = rg_template_eval_cond($cond, $data); if ($eval_cond === FALSE) { rg_prof_end('template_string_if'); return -1; } // TODO: Between ')' and '{{' must be only space, else ignore anything?? $r = rg_template_find_true_and_false($s, $off, $true_start, $true_end, $false_start, $false_end); if ($r == -1) { rg_log("no if skeleton found [" . substr($s, $off) . "]!"); //rg_log_exit(); rg_prof_end("template_string_if"); return -1; } $x = ''; if ($eval_cond === 1) { $x = substr($s, $true_start, $true_end - $true_start + 1); } else { if ($false_start != -1) $x = substr($s, $false_start, $false_end - $false_start + 1); } //rg_log("DEBUG: x=[$x]"); $ret .= rg_template_string($x, 0, $data, $xss_protection); if ($false_start != -1) $next = $false_end + 3; else $next = $true_end + 3; if (strncmp(substr($s, $next, 1), "\n", 1) == 0) $next++; //rg_log("DEBUG: next: [" . substr($s, $next) . "]"); //rg_log_exit(); rg_prof_end("template_string_if"); return $ret; } /* * Replace all known variables in string @s * Example @data: a->a2->a3, b->b2; @s='@@a::a2@@ @@b@@' => 'a3 b2' * @xss_protection - TRUE if you want to apply rg_xss_safe on the value of vars */ function rg_template_string(&$s, $off, &$data, $xss_protection) { global $rg_template_functions; rg_prof_start('template_string'); //rg_log_enter("DEBUG: template_string: s+off=[" . substr($s, $off) . "]"); $ret = ''; while (strlen(substr($s, $off, 1)) == 1) { //rg_log("DEBUG: template_string: s+off=[" . substr($s, $off) . "]"); $pos = strpos($s, '@@', $off); if ($pos === FALSE) { $ret .= substr($s, $off); break; } $var_start = $pos + 2; // copy everything before '@@' $ret .= substr($s, $off, $pos - $off); //rg_log("DEBUG: after copy all before @@, ret=[$ret]"); $off = $pos; $s2 = substr($s, $off, 5); if (strcmp($s2, '@@if(') == 0) { $r = rg_template_string_if($s, $off, $data, $next, $xss_protection); if ($r == -1) { $ret .= 'Cannot evaluate condition: ' . rg_util_error(); break; } $ret .= $r; $off = $next; continue; } $off += 2; /* skip start '@@' */ $pos2 = strpos($s, '@@', $off); if ($pos2 === FALSE) { // We have only start '@@' $ret .= substr($s, $off); break; } $var_end = $pos2 - 1; $off = $pos2 + 2; $var = substr($s, $var_start, $var_end - $var_start + 1); //rg_log("DEBUG: var=[$var]"); $value = rg_template_tree_lookup($var, $data, $xss_protection); if ($value === FALSE) { $value = '@@' . $var . '@@'; if (strncmp($var, 'IMG:', 4) == 0) { $path = substr($var, 4); $path = rg_template_string($path, 0, $data, $xss_protection); //rg_log("DEBUG: found an img tag path=[$path]!"); $value = rg_theme_resolve_path($path); } else if (strncmp($var, 'FUNC:', 5) == 0) { $rest = substr($var, 5); //rg_log("DEBUG: found a function call rest=[$rest]!"); $fpos = strpos($rest, ':'); if ($fpos === FALSE) { // no params $_param = ''; } else { $_param = '@@' . substr($rest, $fpos + 1) . '@@'; } $func = substr($rest, 0, $fpos); //rg_log("DEBUG: func=$func _param=$_param"); // out var may be with '@@' $param = rg_template_string($_param, 0, $data, $xss_protection); if (isset($rg_template_functions[$func])) $value = $rg_template_functions[$func]($param); } else if (strncmp($var, 'URL_ESCAPE:', 11) == 0) { $rest = substr($var, 11); $value = rg_template_tree_lookup($rest, $data, FALSE /*xss*/); $value = rawurlencode($value); } else if (strncmp($var, 'TEMPLATE:', 9) == 0) { $path = substr($var, 9); rg_log("DEBUG: found a template path=[$path]!"); $value = rg_template($path, $data, $xss_protection); } else if (strncmp($var, 'SET:', 4) == 0) { $rest = substr($var, 4); rg_log("DEBUG: found a set rest=[$rest]!"); $_t = explode('=', $rest, 2); if (isset($_t[1])) $data[$_t[0]] = $_t[1]; $value = ''; } else if (strcmp($var, 'DUMP') == 0) { $value = rg_xss_safe(print_r($data, TRUE)); } else { //rg_log("DEBUG: VAR [$var] NOT FOUND!"); } } //rg_log("DEBUG: var=[$var] value=[$value]"); $ret .= $value; } //rg_log("DEBUG: ret=[$ret]"); //rg_log_exit(); rg_prof_end('template_string'); return $ret; } /* * Loads a template from disk without replacing vars or acting on 'ifs' */ function rg_template_blind($file) { global $rg_theme_dir; global $rg_theme; rg_prof_start('template_blind'); //rg_log_enter('template_blind: ' . $file); $ret = ''; while (1) { $xfile = $rg_theme_dir . "/" . $rg_theme . "/" . $file; if (!is_file($xfile)) { $xfile = $rg_theme_dir . "/default/" . $file; if (!is_file($xfile)) $xfile = $file; if (!is_file($xfile)) { rg_internal_error('Cannot find ' . $file . '!'); break; } } $ret = rg_file_get_contents($xfile); break; } rg_prof_end('template_blind'); return $ret; } /* * Loads a template from disk and replase all known variables * @xss_protection - TRUE if you want to apply rg_xss_safe on the value of vars */ function rg_template($file, &$data, $xss_protection) { rg_prof_start('template'); //rg_log_enter('template: ' . $file); $ret = ''; while (1) { $body = rg_template_blind($file); if (empty($body)) break; $ret = rg_template_string($body, 0, $data, $xss_protection); break; } //rg_log("DEBUG: rg_template returns [$ret]"); //rg_log_exit(); rg_prof_end('template'); return $ret; } /* * Builds a html output based on a template with header, footer and line * @data - in array of data for every out line: index 0 is line 1, index 1 is line 2... */ function rg_template_table($dir, &$data, $more) { global $rg_theme_dir; global $rg_theme; rg_prof_start('template_table'); //rg_log('template_table: ' . $dir); $xdir = $rg_theme_dir . "/" . $rg_theme . "/" . $dir; if (!is_dir($xdir)) { rg_log("$xdir not found."); $xdir = $rg_theme_dir . "/default/" . $dir; rg_log("Using [$xdir]"); } if (!is_array($data) || empty($data)) { $ret = rg_template($xdir . "/nodata.html", $more, TRUE /* xss */); rg_prof_end('template_table'); return $ret; } $head = rg_template($xdir . "/header.html", $more, TRUE /* xss */); $foot = rg_template($xdir . "/footer.html", $more, TRUE /* xss */); $line = rg_file_get_contents($xdir . "/line.html"); $body = ''; foreach ($data as $index => $info) { $more2 = array_merge($more, $info); $more2['index'] = $index; $body .= rg_template_string($line, 0, $more2, TRUE /*xss*/); } rg_prof_end('template_table'); return $head . $body . $foot; } /* * Outputs a numbered list */ function rg_template_list($c) { $a = explode("\n", $c); if (count($a) == 0) return ""; $ret = ""; $i = 1; $add = ""; foreach ($a as $line) { $ret .= $add . $i . " " . rg_xss_safe($line); $add = "<br />"; $i++; } return $ret; } /* * Show errors using a template */ function rg_template_errmsg($a) { if (empty($a)) return ""; $b = array(); foreach ($a as $junk => $err) $b[] = array("error" => $err); return rg_template_table("errmsg", $b, array()); } /* * Show a warning using a template */ function rg_warning($msg) { if (empty($msg)) return ""; rg_log("Warning: $msg"); $x = array("msg" => $msg); return rg_template("warning.html", $x, TRUE /* xss */); } /* * Show an OK message using a template * TODO: OBSOLETE? Because we want the files to be in templates? */ function rg_ok($msg) { if (empty($msg)) return ""; $x = array("msg" => $msg); return rg_template("ok.html", $x, TRUE /* xss */); } /* * Helper for reading input from a fd for rg_exec2. * Used as a 'cb_output' function. */ function rg_exec2_helper_read_fd($index, &$info) { $ret = -1; while (1) { $r = @fread($info['cb_output_fd'], 16 * 4096); if ($r === FALSE) { $err = 'cannot read from cb_output_fd: ' . rg_php_err(); rg_internal_error($err); break; } $len = strlen($r); if ($len === 0) { fclose($info['cb_output_fd']); $ret = 0; break; } $info['out_buf'] .= $r; $ret = $len; break; } return $ret; } /* * Helper for decompressing input for rg_exec2. * On error, it returns FALSE and $err contains the error message. */ function rg_exec2_helper_gzip_in(&$info) { global $rg_util_debug; $rg_util_debug && rg_log('exec2_helper_gzip_in...'); if (empty($info['out_buf'])) return TRUE; if (!isset($info['gzip_in_ctx'])) { $info['gzip_in_ctx'] = inflate_init(ZLIB_ENCODING_GZIP); if ($info['gzip_in_ctx'] === FALSE) { $err = 'cannot initialize context'; return FALSE; } $info['gzip_in_bytes'] = 0; } $do_exit = FALSE; do { $rg_util_debug && rg_log(' calling inflate_add with buf[0-31]=' . substr($info['out_buf'], 0, 32) . (!empty($info['out_buf']) ? '' : ' and ZLIB_FINISH')); if (!empty($info['out_buf'])) $r = inflate_add($info['gzip_in_ctx'], $info['out_buf']); else $r = inflate_add($info['gzip_in_ctx'], $info['out_buf'], ZLIB_FINISH); if ($r === FALSE) { $err = 'error in decompression: ' . rg_php_err(); rg_internal_error($err); return FALSE; } $s = inflate_get_status($info['gzip_in_ctx']); if ($s === ZLIB_STREAM_END) { $rg_util_debug && rg_log(' Got ZLIB_STREAM_END; set out_buf_done to 1 and do_exit to TRUE'); $info['out_buf'] = ''; // because zlib may finish before consuming all input $info['out_buf_done'] = 1; unset($info['gzip_in_ctx']); $do_exit = TRUE; } else if ($s === ZLIB_OK) { $rg_util_debug && rg_log(' Got ZLIB_OK'); $rl = inflate_get_read_len($info['gzip_in_ctx']); if ($rl === FALSE) { $err = 'cannot retrieve read_len'; return FALSE; } $rg_util_debug && rg_log(' gzip_in_bytes=' . $info['gzip_in_bytes'] . ' read_len=' . $rl); $diff = $rl - $info['gzip_in_bytes']; $info['gzip_in_bytes'] = $rl; $info['out_buf'] = substr($info['out_buf'], $diff); if (!empty($r)) $do_exit = TRUE; } else { unset($info['gzip_in_ctx']); if ($s === FALSE) rg_log(' status is FALSE'); else if ($s === ZLIB_BUF_ERROR) rg_log(' status is ZLIB_BUF_ERROR'); else rg_log(' status is unknown [' . $s . ']'); return FALSE; } $rg_util_debug && rg_log(' decompression produced: [' . $r . '] out_buf=' . $info['out_buf']); } while (!$do_exit); $info['out_buf_real'] .= $r; return TRUE; } /* * Helper for rg_exec to init the structure */ function rg_exec2_helper_init(&$info) { $info['last_activity'] = 0; $info['last_errmsg'] = ''; $info['start_ts'] = time(); $info['last_failure'] = 0; if (!isset($info['out_buf'])) $info['out_buf'] = ''; $info['out_buf_real'] = ''; $info['in_buf'] = ''; $info['err_buf'] = ''; $info['done'] = FALSE; $info['started'] = 0; if (!isset($info['idle_time'])) $info['idle_time'] = 5; if (isset($info['input_fd'])) $info['input_fd_done'] = 0; else $info['input_fd_done'] = 1; if (isset($info['cb_output'])) $info['cb_output_done'] = 0; else $info['cb_output_done'] = 1; // TODO: what this is doing, rename it to something better if (!isset($info['out_buf_done'])) $info['out_buf_done'] = 1; // m2p = 'me' to 'program' $info['m2p_bytes'] = 0; // w2m = 'web' to 'me' $info['w2m_bytes'] = 0; } /* * Helper for rg_exec to close a stream */ function rg_exec2_helper_close_stream($index, $fd, &$info, $stream) { global $rg_util_debug; $rg_util_debug && rg_log_enter($index . ' DEBUG: exec2_helper_close_stream: stream ' . $stream); if ($stream == -1) { $info['input_fd_done'] = 1; unset($info['input_fd']); } else { unset($info['pipes'][$stream]); } $r = @fclose($fd); if ($r === FALSE) rg_log($index . ' Error closing stream ' . $stream . ' (' . rg_php_err() . ')'); if (($stream == 0) && isset($info['input_fd'])) { $rg_util_debug && rg_log($index . ' DEBUG: closing stream 0 =>' . ' closing input_fd ' . $info['input_fd']); rg_exec2_helper_close_stream($index, $info['input_fd'], $info, -1); } $rg_util_debug && rg_log_exit(); } /* * Tries to procude data to be sent to the peer process */ function rg_exec2_helper_populate_out_buf($index, &$info) { global $rg_util_debug; $rg_util_debug && rg_log_enter($index . ': exec2_helper_populate_out_buf'); // cb_output can populate $info['out_buf'] if ($info['cb_output_done'] == 0) { $r = $info['cb_output']($index, $info); if ($r == -1) { // TODO } else if ($r == 0) { $info['cb_output_done'] = 1; } else { $info['w2m_bytes'] += $r; } } if (!isset($info['out_buf_helper'])) { $info['out_buf_real'] .= $info['out_buf']; $info['out_buf'] = ''; } else { $info['out_buf_helper']($info); } $rg_util_debug && rg_log_exit(); } /* * This will replace rg_exec function * Returns the array of the commands with the last status. */ function rg_exec2($a) { global $rg_util_debug; $_id = ''; $_add = ''; foreach ($a['cmds'] as $_junk => $_i) { $_id .= $_add . '[' . $_i['cmd'] . ']'; $_add = ' '; } rg_prof_start('exec2 ' . $_id); rg_log_enter('exec2 ' . $_id); //$rg_util_debug && //rg_log_ml('DEBUG: a: ' . print_r($a, TRUE)); $ret = array(); $ret['ok'] = 0; $ret['cmds'] = $a['cmds']; // some status initialization $s = array(); foreach ($ret['cmds'] as $cmd => &$info) { $info['stopped'] = TRUE; $info['wait_for_stop'] = FALSE; rg_exec2_helper_init($info); } while (1) { // check if all commands are started $now = time(); $rx = array(); $wx = array(); $lut = array(); $done = TRUE; foreach ($ret['cmds'] as $index => &$info) { if ($info['done']) continue; if ($info['started'] == 0) { // nothing } else if (empty($info['pipes'])) { $rg_util_debug && rg_log($index . ' DEBUG: All streams are closed.' . ' Set wait_for_stop to TRUE'); $info['wait_for_stop'] = TRUE; } else { $rg_util_debug && rg_log_ml($index . ' DEBUG: Cannot set wait_for_stop to' . ' true because something is not closed; pipes:' . ' ' . rg_array2string($info['pipes'])); } if ($info['wait_for_stop'] == TRUE) { $rg_util_debug && rg_log($index . ' DEBUG: needs stop is TRUE!'); for ($i = 0; $i <= 2; $i++) if (isset($info['pipes'][$i])) fclose($info['pipes'][$i]); if (isset($info['input_fd'])) { @fclose($info['input_fd']); unset($info['input_fd']); } $info['ps'] = proc_get_status($info['a']); $rg_util_debug && rg_log_ml($index . ' DEBUG: info[ps]: ' . print_r($info['ps'], TRUE)); if ($info['ps']['running'] !== FALSE) { $done = FALSE; continue; } @proc_close($info['a']); $info['exitcode'] = $info['ps']['exitcode']; if (($info['exitcode'] != 0) && empty($info['last_errmsg'])) $info['last_errmsg'] = 'child exited with' . ' error code ' . $info['exitcode']; if (isset($info['cb_finish'])) $info['cb_finish']($index, $info, $info['exitcode']); unset($info['pipes']); unset($info['a']); $info['wait_for_stop'] = FALSE; $info['stopped'] = TRUE; if (isset($info['restart_delay'])) { rg_exec2_helper_init($info); } else { $rg_util_debug && rg_log($index . ' DEBUG: no restart flag => done = TRUE'); $info['done'] = TRUE; } } $rg_util_debug && rg_log($index . ' DEBUG:' . ' info[done]=' . ($info['done'] === TRUE ? 'true' : 'false')); if ($info['done']) continue; $rg_util_debug && rg_log($index . ' DEBUG: set done to FALSE'); $done = FALSE; if ($info['started'] == 0) { $restart_delay = isset($info['restart_delay']) ? $info['restart_delay'] : 0; if ($info['last_failure'] + $restart_delay > $now) continue; $rg_util_debug && rg_log($index . ' DEBUG: Running [' . $info['cmd'] . ']'); $desc = array( 0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array("pipe", "w") ); $info['a'] = proc_open($info['cmd'], $desc, $info['pipes']); if ($info['a'] === FALSE) { $info['last_errmsg'] = 'cannot do proc_open'; if (isset($info['cb_error'])) $info['cb_error']($index, $info, $info['last_errmsg']); $info['last_failure'] = $now; continue; } $rg_util_debug && rg_log($index . ' DEBUG: proc_open pipes: ' . rg_array2string($info['pipes'])); $info['stopped'] = FALSE; $info['started'] = time(); } if ($info['stopped']) { $rg_util_debug && rg_log($index . ' DEBUG: stopped is TRUE'); continue; } rg_exec2_helper_populate_out_buf($index, $info); // Check idle if (isset($info['cb_idle']) && ($info['idle_time'] > 0) && ($info['last_activity'] > 0) && ($info['last_activity'] + $info['idle_time'] <= time())) { $rg_util_debug && rg_log($index . ' DEBUG: IDLE:' . ' last_activity=' . $info['last_activity'] . ' now=' . time()); $info['cb_idle']($index, $info); } if (!empty($info['out_buf_real'])) { $rg_util_debug && rg_log($index . ' DEBUG: out_buf_real not empty =>' . ' enable write notification!'); $wx[] = $info['pipes'][0]; } else if (($info['input_fd_done'] == 1) && ($info['out_buf_done'] == 1) && (isset($info['pipes'][0]))) { $rg_util_debug && rg_log($index . ' DEBUG: empty(out_buf_real)' . ' && input_fd_done == 1' . ' && out_buf_done == 1' . ': close output ' . $info['pipes'][0]); rg_exec2_helper_close_stream($index, $info['pipes'][0], $info, 0); } if (isset($info['pipes'][1])) $rx[] = $info['pipes'][1]; if (isset($info['pipes'][2])) $rx[] = $info['pipes'][2]; if (isset($info['input_fd'])) $rx[] = $info['input_fd']; for ($i = 0; $i <= 2; $i++) { if (!isset($info['pipes'][$i])) continue; $k = intval($info['pipes'][$i]); $lut[$k] = array('index' => $index, 'stream' => $i); } if (isset($info['input_fd'])) { $k = intval($info['input_fd']); $lut[$k] = array('index' => $index, 'stream' => -1); } } if ($done) { $rg_util_debug && rg_log('DEBUG: No work to do anymore! Exit!'); $ret['ok'] = 1; break; } if (empty($rx) && empty($wx)) { $rg_util_debug && rg_log('DEBUG: No r/w events; sleeping...'); sleep(1); continue; } $revents = $rx; $wevents = $wx; $ex = NULL; $rg_util_debug && rg_log('DEBUG: before stream_select:' . (empty($revents) ? '' : ' revents:' . rg_array2string($revents)) . (empty($wevents) ? '' : ' wevents:' . rg_array2string($wevents))); $r = stream_select($revents, $wevents, $ex, 0, 100 * 1000); if ($r === FALSE) { $ret['errmsg'] = "cannot select"; break; } $rg_util_debug && (!empty($revents) || !empty($wevents) || !empty($ex)) && rg_log('DEBUG: after stream_select:' . (empty($revents) ? '' : ' revents:' . rg_array2string($revents)) . (empty($wevents) ? '' : ' wevents:' . rg_array2string($wevents)) . (empty($ex) ? '' : ' ex:' . rg_array2string($ex))); if ($r === 0) continue; foreach ($wevents as $fd) { $ifd = intval($fd); $index = $lut[$ifd]['index']; $rg_util_debug && rg_log($index . ' DEBUG: write event on ifd ' . $ifd); $info = &$ret['cmds'][$index]; rg_exec2_helper_populate_out_buf($index, $info); if (empty($info['out_buf_real'])) { $rg_util_debug && rg_log_ml($index . ' DEBUG: out_buf_real is empty!' . ' info: ' . print_r($info, TRUE)); continue; } //$rg_util_debug && //rg_log($index . ' DEBUG: sending: [' . $info['out_buf_real'] . ']'); $r = @fwrite($fd, $info['out_buf_real']); if ($r === FALSE) { $info['last_errmsg'] = 'cannot write'; if (isset($info['cb_error'])) $info['cb_error']($index, $info, $info['last_errmsg']); else rg_log($index . ' fwrite returned FALSE'); $info['wait_for_stop'] = TRUE; continue; } $info['out_buf_real'] = substr($info['out_buf_real'], $r); $info['m2p_bytes'] += $r; } foreach ($revents as $fd) { $ifd = intval($fd); $index = $lut[$ifd]['index']; $stream = $lut[$ifd]['stream']; $rg_util_debug && rg_log($index . ' DEBUG: read event on ifd ' . $ifd . ', stream=' . $stream . '!'); $info = &$ret['cmds'][$index]; $_d = @fread($fd, 32 * 4096); if ($_d === FALSE) { $rg_util_debug && rg_log($index . ' DEBUG: ifd ' . $ifd . ' fread returned FALSE'); $info['last_errmsg'] = 'cannot read'; if (isset($info['cb_error'])) $info['cb_error']($index, $info, $info['last_errmsg']); $info['wait_for_stop'] = TRUE; continue; } if (empty($_d)) { $rg_util_debug && rg_log($index . ' DEBUG: ifd ' . $ifd . ' returned no data.'); rg_exec2_helper_close_stream($index, $fd, $info, $stream); continue; } $rg_util_debug && rg_log($index . ' DEBUG: fread [stream ' . $stream . '] returned [' . $_d . ']'); $info['last_activity'] = time(); switch ($stream) { case -1: $info['out_buf'] .= $_d; break; case 1: $info['in_buf'] .= $_d; break; case 2: $info['err_buf'] .= $_d; break; } if (isset($info['cb_input'])) $info['cb_input']($index, $info, $stream); } foreach ($ret['cmds'] as $index => &$info) { if (!isset($info['cb_tick'])) continue; if ($info['done']) continue; $info['cb_tick']($index, $info); } } rg_log_exit(); rg_prof_end('exec2 ' . $_id); return $ret; } /* * Helper for rg_exec */ function rg_exec_helper_data($index, &$a, $stream) { switch ($stream) { case 1: $a['my_data_out']($a['in_buf']); $a['in_buf'] = ''; break; case 1: $a['my_err_out']($a['err_buf']); $a['err_buf'] = ''; break; } } /* * Helper for rg_exec */ function rg_exec_helper_output($index, &$a) { return $a['my_data_in'](); } /* * Execute $cmd and returns the output as a string, binary safe * @input: some data to be sent to the process and received as stdin * @cb_stdin - call back used to pass data frou our stdin to the process * ifa @cb_stdin is false, it is not called; if is 0, STDIN will be used. * @cb_stdout - call back called when there is something to be send to our stdout * if @cb_stdout is FALSE, stdout output will be returned in $ret['data'] * cb_stderr - call back called when there is something to be sent to our stderr * if @cb_stderr is FALSE, stderr output will be returned in $ret['stderr'] */ function rg_exec($cmd, $input, $cb_stdin, $cb_stdout, $cb_stderr) { $ret = array(); $ret['ok'] = 0; while(1) { $ret['errmsg'] = ''; $ret['code'] = 65000; // fake code $ret['data'] = ''; $ret['stderr'] = ''; $a = array('cmds' => array( 'cmd1' => array( 'cmd' => $cmd, 'out_buf' => $input ) ) ); if ($cb_stdin === 0) { $a['cmds']['cmd1']['input_fd'] = @fopen('php://stdin', 'rb'); if ($a['cmds']['cmd1']['input_fd'] === FALSE) { $ret['errmsg'] = 'cannot open stdin: ' . rg_php_err(); break; } } else if ($cb_stdin !== FALSE) { $a['cmds']['cmd1']['my_data_in'] = $cb_stdin; $a['cmds']['cmd1']['cb_output'] = 'rg_exec_helper_output'; } if ($cb_stdout !== FALSE) { $a['cmds']['cmd1']['my_data_out'] = $cb_stdout; $a['cmds']['cmd1']['cb_input'] = 'rg_exec_helper_data'; } if ($cb_stderr !== FALSE) { $a['cmds']['cmd1']['my_err_out'] = $cb_stderr; $a['cmds']['cmd1']['cb_input'] = 'rg_exec_helper_data'; } $r = rg_exec2($a); if ($r['ok'] != 1) { $ret['errmsg'] = $r['errmsg']; break; } $ret['code'] = $r['cmds']['cmd1']['exitcode']; $ret['data'] = $r['cmds']['cmd1']['in_buf']; $ret['stderr'] = $r['cmds']['cmd1']['err_buf']; $ret['errmsg'] = $r['cmds']['cmd1']['last_errmsg']; if ($ret['code'] == 0) $ret['ok'] = 1; break; } return $ret; } /* * Force browser to redirect to another page */ function rg_redirect($url) { rg_log("Redirecting to [$url]"); header("Location: $url"); exit(0); } /* * Force browser to redirect to another page, using a HTML header */ function rg_redirect_html($seconds, $url) { global $rg; $rg['rg_redirect_html'] = 1; $rg['rg_redirect_html_seconds'] = $seconds; $rg['rg_redirect_html_url'] = $url; } /* * Transforms strange chars to hexa */ function rg_callback_hexa($matches) { $n = pack("a*", $matches[0]); $tmp = unpack("H*", $n); return "[" . $tmp[1] . "]"; } /* * Transforms an array into a string */ function rg_array2string($a) { $what = array("/[^\pL\pN\pP\pS ]/uU"); if (!is_array($a)) return preg_replace_callback($what, "rg_callback_hexa", $a); if (empty($a)) return ""; $ret = ""; $add = ""; foreach ($a as $k => $v) { if (is_array($v)) $s = rg_array2string($v); else $s = preg_replace_callback($what, "rg_callback_hexa", $v); $ret .= $add . "$k=[$s]"; $add = " "; } return $ret; } /* * Load files from a folder using a pattern for match */ function rg_dir_load_pattern($dir, $pattern) { $ret = FALSE; if (!file_exists($dir)) { rg_util_set_error("$dir does not exists"); return $ret; } $d = @scandir($dir); if ($d === FALSE) { rg_util_set_error("cannot scan dir $dir (" . rg_php_err() . ")"); return $ret; } $ret = array(); foreach ($d as $file) { if ((strcmp($file, ".") == 0) || (strcmp($file, "..") == 0)) continue; if (preg_match('/' . $pattern . '/uD', $file) !== 1) continue; $ret[] = $file; } return $ret; } /* * Load all files from a folder */ function rg_dir_load($dir) { return rg_dir_load_pattern($dir, ".*"); } /* * Recursive dir load (used for references) */ function rg_dir_load_deep($dir) { $ret = array(); if (is_file($dir)) return array($dir); $d = rg_dir_load($dir); if ($d === FALSE) return FALSE; foreach ($d as $obj) { if (is_dir($dir . "/" . $obj)) { $c = rg_dir_load_deep($dir . "/" . $obj); foreach ($c as $obj2) $ret[] = $obj . "/" . $obj2; } else { $ret[] = $obj; } } return $ret; } /* * Copy a fs tree to another place */ function rg_copy_tree($src, $dst, $mask) { rg_prof_start("copy_tree"); rg_log_enter("copy_tree($src, $dst, mask=$mask)"); $ret = FALSE; while (1) { if (!is_dir($dst)) { $r = @mkdir($dst, $mask, TRUE); if ($r !== TRUE) { rg_log('ERROR: Cannot mkdir: ' . rg_php_err() . '.'); break; } } $d = rg_dir_load($src); if ($d === FALSE) break; $err = FALSE; foreach ($d as $obj) { if (is_dir($src . "/" . $obj)) { if (!is_dir($dst . "/" . $obj)) { $r = @mkdir($dst . "/" . $obj, $mask); if ($r !== TRUE) { rg_log('ERROR: Cannot mkdir: ' . rg_php_err() . '.'); $err = TRUE; break; } } $r = rg_copy_tree($src . "/" . $obj, $dst . "/" . $obj, $mask); if ($r !== TRUE) { $err = TRUE; break; } } else { $r = @copy($src . "/" . $obj, $dst . "/" . $obj); if ($r !== TRUE) { rg_log("ERROR: Cannot copy file (" . rg_php_err() . ")."); $err = TRUE; break; } } } if (!$err) $ret = TRUE; break; } rg_log_exit(); rg_prof_end("copy_tree"); return $ret; } /* * Recursively deletes a tree */ function rg_del_tree($dst) { rg_prof_start('del_tree'); $ret = FALSE; while (1) { if (!is_dir($dst)) { $ret = TRUE; break; } $d = rg_dir_load($dst); // TODO: here, I cannot distinguish between error and dir_not_exists if ($d === FALSE) { rg_log('DEBUG: Cannot load dir ' . $dst); break; } $err = FALSE; foreach ($d as $obj) { if (is_dir($dst . '/' . $obj)) { $r = rg_del_tree($dst . '/' . $obj); if ($r !== TRUE) { $err = TRUE; break; } } else { $r = @unlink($dst . '/' . $obj); if ($r !== TRUE) { rg_util_set_error('cannot delete file: ' . rg_php_err()); $err = TRUE; break; } } } $r = @rmdir($dst); if ($r !== TRUE) { rg_util_set_error('cannot delete dir: ' . rg_php_err()); $err = TRUE; break; } if (!$err) $ret = TRUE; break; } rg_prof_end('del_tree'); return $ret; } /* * Called by PHP in case of error */ function rg_error_handler($no, $str, $file, $line) { global $rg_php_err; if ($no == 0) return; $rg_php_err = $str; // call was prepended with '@' if (error_reporting() == 0) return; $msg = "PHP ERROR: $file:$line: $str (errno=$no)"; rg_error_core($msg); if ($no == E_ERROR) die(); $key = md5($msg); $rg_error_seen[$key] = 1; return FALSE; } /* * Shutdown function to log fatal errors */ function rg_error_shutdown() { $a = error_get_last(); if ($a === NULL) return; rg_error_handler($a['type'], $a['message'], $a['file'], $a['line']); } /* * YYYY-MM-DD -> timestamp */ function rg_date2ts($s) { rg_log("rg_date2ts s=[$s]"); if (strlen($s) != 10) return FALSE; $f = explode("-", $s); if (count($f) != 3) return FALSE; return gmmktime(0, 0, 0, $f[1], $f[2], $f[0]); } /* * YYYY-MM-DD -> timestamp (last second of the day) */ function rg_date2ts_last_second($s) { rg_log("rg_date2ts_last_second s=[$s]"); if (strlen($s) != 10) return FALSE; $f = explode("-", $s); if (count($f) != 3) return FALSE; return gmmktime(0, 0, 0, $f[1], $f[2] + 1, $f[0]) - 1; } /* * Special implode, with prefix/postfix */ function rg_implode($prefix, $a, $postfix) { if (!is_array($a)) return $a; if (empty($a)) return ""; $ret = array(); foreach ($a as $index => $data) $ret[] = $prefix . $data; return implode($postfix, $ret); } /* * Here we will cache the connections */ $rg_socket_cache = array(); /* * Receives buffers and test if @wait string is present. * @timeout - in miliseconds, NULL=forever, 0=no_wait */ function rg_socket_recv_wait($socket, $wait, $timeout) { rg_prof_start('socket_recv_wait'); if ($timeout === NULL) { $tv_sec = NULL; $tv_usec = NULL; } else { $tv_sec = $timeout / 1000; $tv_usec = ($timeout % 1000) * 1000; } $ret_buf = ''; $ret = FALSE; while (1) { $reads = array($socket); $writes = array(); $ex = array(); $r = @socket_select($reads, $writes, $ex, $tv_sec, $tv_usec); if ($r === FALSE) { rg_log('Cannot select(' . socket_strerror(socket_last_error()) . ')!'); break; } if ($r === 0) { // timeout rg_log('Timeout reading from socket!'); break; } if (!in_array($socket, $reads)) { rg_log('Select returned > 0 and my socket is not in reads'); break; } $r = @socket_recv($socket, $buf, 32 * 4096, 0); if ($r === FALSE) { rg_log('Cannot receive: ' . socket_strerror(socket_last_error())); break; } //rg_log("Received [$buf]"); $ret_buf .= $buf; $pos = strpos($buf, $wait); if ($pos !== FALSE) { $ret = $ret_buf; break; } break; } rg_prof_end('socket_recv_wait'); return $ret; } /* * Sends a full buffer * TODO: Take timeout in consideration. */ function rg_socket_send($socket, $buf) { rg_prof_start('socket_send'); $ret = FALSE; $len = strlen($buf); $off = 0; while (1) { $r = @socket_send($socket, substr($buf, $off), $len - $off, 0); if ($r === FALSE) { rg_log("Could not send (" . socket_strerror(socket_last_error()) . ")!"); break; } //rg_log("Sent $r bytes (" . substr($buf, $off, $r) . ")."); $len -= $r; $off += $r; if ($len == 0) { $ret = TRUE; break; } } rg_prof_end('socket_send'); return $ret; } /* * Connects to a socket, send @buf and returns the answer. * @timeout: NULL=forever, 0=no_wait * @tries - how many time to retry if it fails */ function rg_socket($path, $buf, $timeout, $tries, $flags) { global $rg_socket_cache; rg_prof_start('socket'); $ret = FALSE; while ($tries > 0) { if (isset($rg_socket_cache[$path])) { $socket = $rg_socket_cache[$path]; } else { rg_prof_start('socket-connect'); $socket = @socket_create(AF_UNIX, SOCK_STREAM, 0); if ($socket === FALSE) { rg_log("Could not create socket (" . socket_strerror(socket_last_error()) . ")!"); break; } while ($tries > 0) { $r = @socket_connect($socket, $path); if ($r === FALSE) { $tries--; usleep(50 * 1000); continue; } break; } if ($r === FALSE) { rg_log('Could not connect the socket [' . $path . ']' . '(' . socket_strerror(socket_last_error()) . ')!'); break; } rg_prof_end('socket-connect'); $rg_socket_cache[$path] = $socket; } $r = rg_socket_send($socket, $buf); if ($r !== TRUE) { socket_close($socket); unset($rg_socket_cache[$path]); continue; } if ($flags & RG_SOCKET_NO_WAIT) { //rg_log('We do not have to wait. Exit.'); $ret = ''; break; } $ret = rg_socket_recv_wait($socket, "\n", $timeout); break; } rg_prof_end("socket"); return $ret; } /* * Check if referer matchces current website */ function rg_valid_referer() { $ref0 = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ""; // If not provided, we can do nothing about if (empty($ref0)) return TRUE; $ref = preg_replace('|http(s)?://|', '', $ref0); $ref = preg_replace('|/.*|', '', $ref); $we = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ""; if (strcasecmp($we, $ref) == 0) return TRUE; rg_security_violation_no_exit("invalid referer for form submission" . " we=[$we] ref=[$ref] ref0=[$ref0]"); return FALSE; } /* * Returns the human formatted time based on a number of seconds * @diff - time in seconds */ function rg_human_time_interval($diff) { $ret = ($diff % 60) . 's'; $rest = intval($diff / 60); if ($rest == 0) return $ret; $ret = ($diff % 60) . 'm' . $ret; $rest = intval($diff / 60); if ($rest == 0) return $ret; $ret = ($diff % 24) . 'h' . $ret; $rest = intval($diff / 24); if ($rest == 0) return $ret; $ret = ($diff % 12) . 'm' . $ret; $rest = intval($diff / 12); if ($rest == 0) return $ret; $ret = $rest . 'y' . $ret; return $ret; } /* * Returns the age of an object */ function rg_age($ts) { $diff = time() - $ts; return rg_human_time_interval($diff); } /* * Returns the path into the temporary area */ function rg_tmp_path($file) { global $rg_state_dir; return $rg_state_dir . '/tmp/' . $file; } /* * Returns a random file name into the temporary area */ function rg_tmp_path_random($prefix) { global $rg_state_dir; return $rg_state_dir . '/tmp/' . $prefix . rg_id(10); } /* * Creates and stores content into a temporary file and returns the name */ function rg_tmp_file($file, $content) { global $rg_state_dir; $final_name = $rg_state_dir . '/tmp/' . $file; $r = @file_put_contents($final_name, $content); if ($r === FALSE) return FALSE; // TODO: log error messege? return $final_name; } /* * Function to short the certificates */ function rg_cert_short($s) { if (empty($s)) return 'n/a'; if (strlen($s) < 12) return $s; $s = str_replace('-----BEGIN CERTIFICATE-----', '', $s); $s = str_replace('-----END CERTIFICATE-----', '', $s); $s = trim($s); return substr($s, 0, 4) . '...' . substr($s, -4, 4); } /* * Remove ::ffff: prefix */ function rg_fix_ip($ip) { if (strncasecmp($ip, "::ffff:", 7) == 0) $ip = substr($ip, 7); return $ip; } /* * Escape json fields - TODO - for now do nothing */ function rg_json_escape($s) { return json_encode($s); } /* * Escapes texts inside XML tags * Example: <a>&</a> -> <a>&</a> */ function fn_xml_escape($s) { return strtr($s, array( '<' => '<', '>' => '>', '"' => '"', '\'' => ''', '&' => '&', ) ); } /* * Removes from a string all non-alpha characters */ function rg_force_alpha($s) { return preg_replace('/[^A-Za-z]/', '', $s); } /* * Removes from a string all non-alpha/num and _ characters */ function rg_force_alphanum($s) { return preg_replace('/[^A-Za-z0-9_]/', '', $s); } /* * Dummy function to be used in rg_exec callback */ function rg_echo($s) { echo $s; } /* * Function used to register login functions */ $rg_login_functions = array(); function rg_register_login_function($f) { global $rg_login_functions; $rg_login_functions[] = $f; } /* * Quote some valid UTF-8 but not printable chars */ function rg_utf8_convert_non_printable($a) { $ret = ''; $len = strlen($a); for ($i = 0; $i < $len; $i++) { $c = ord($a[$i]); if (($c <= 6) || (($c >= 0x0e) && ($c <= 0x1f)) || ($c == 0x7f)) { $ret .= chr(0x5c) . sprintf('%o', $c); continue; } switch ($c) { case 0x07: /* \a */ $ret .= chr(0x5c) . 'a'; break; case 0x08: /* \b */ $ret .= chr(0x5c) . 'b'; break; case 0x09: /* \t */ $ret .= chr(0x5c) . 't'; break; case 0x0a: /* \n */ $ret .= chr(0x5c) . 'n'; break; case 0x0b: /* \v */ $ret .= chr(0x5c) . 'v'; break; case 0x0c: /* \f */ $ret .= chr(0x5c) . 'f'; break; case 0x0d: /* \r */ $ret .= chr(0x5c) . 'r'; break; default: $ret .= $a[$i]; } } return $ret; } /* * Returns the number of bytes of the next UTF-8 char * Returns a negative number if the sequence is not correct, else, positive. * The value is the number of bytes. * Taken from: https://stackoverflow.com/questions/1473441/check-to-see-if-a-string-is-encoded-as-utf-8 */ function rg_utf8_test($a) { $len = strlen($a); $state = 0; /* 0 = unknown, 1 = valid, -1 = invalid */ $pos = 0; while ($pos < $len) { $c = ord($a[$pos]); //rg_log('c=' . $c . '(' . $a[$pos] . ') pos=' . $pos . ' state=' . $state); if ($c < 0x80) $n = 0; else if (($c & 0xE0) == 0xC0) $n = 1; else if (($c & 0xF0) == 0xE0) $n = 2; else if (($c & 0xF8) == 0xF0) $n = 3; else if (($c & 0xFC) == 0xF8) $n = 4; else if (($c & 0xFE) == 0xFC) $n = 5; else if ($state === 0) { //rg_log(' DEBUG: invalid byte, unk -> bad state'); $state = -1; $pos++; continue; } else if ($state === 1) { //rg_log(' DEBUG: invalid byte, last state good, return pos'); return $pos; } else if ($state === -1) { //rg_log(' DEBUG: already in bad state'); $pos++; continue; } //rg_log(' DEBUG: n=' . $n); if (1 + $pos + $n > $len) { //rg_log(' DEBUG: too little bytes left'); if ($state !== 1) { //rg_log(' DEBUG: state != 1; return - (1 + pos)'); return - (1 + $pos); } //rg_log(' DEBUG: state is good, return pos'); return $pos; } // Validate next chars for ($i = 0; $i < $n; $i++) { $x = ord($a[1 + $pos + $i]); if (($x & 0xC0) != 0x80) { if ($state !== 1) { //rg_log(' DEBUG: invalid body, state != 1; return - (1 + pos)'); return - (1 + $pos); } //rg_log(' DEBUG: invalid body, state == 1; return pos'); return $pos; } } if ($state === -1) { //rg_log('DEBUG: found valid bytes: return -pos'); return -$pos; } if ($state === 0) { //rg_log(' DEBUG: enter good state'); $state = 1; } $pos += 1 + $n; } return $state * $pos; } /* * Converts an string to a valid representation. * Mostly used to display strange filenames. * The string may be not be UTF-8 valid. * Example: "a\xc8a" -> 'a\310a' * Example: "\xffț" -> '\377ț' */ function rg_visible_string($a) { $ret = ''; $len = strlen($a); while ($len > 0) { //rg_log('DEBUG: a: ' . $a); $r = rg_utf8_test($a); //rg_log('DEBUG: utf8_test returned ' . $r); if ($r > 0) { $ret .= rg_utf8_convert_non_printable(substr($a, 0, $r)); $skip = $r; } else { $ret .= rg_git_quote(substr($a, 0, -$r)); $skip = -$r; } $a = substr($a, $skip); $len -= $skip; //rg_log('DEBUG: ret=' . $ret); } return $ret; } /* * Get latest bytes from a file * Note: bytes not chars */ function rg_file_get_tail($f, $bytes) { $size = @filesize($f); if ($size === FALSE) return ''; if ($size <= $bytes) return @file_get_contents($f); $r = @file_get_contents($f, FALSE, FALSE, -1, $bytes); if ($r === FALSE) return ''; $pos = strpos($r, "\n"); if ($pos === FALSE) return $r; return substr($r, $pos + 1); } /* * Unserialize JSON or php 'serialize' */ function rg_unserialize($s) { $c = substr($s, 0, 1); if ((strcmp($c, '{') == 0) || (strcmp($c, '"') == 0) || (strcmp($c, '[') == 0)) { $r = @json_decode($s, TRUE); if ($r !== NULL) return $r; $m = 'cannot decode json: ' . json_last_error_msg(); rg_internal_error($m); rg_util_set_error($m); return FALSE; } $x = substr($s, 1, 1); if (strcmp($x, ':') == 0) { $r = @unserialize($s); if ($r !== FALSE) return $r; } return $s; } /* * Serialize data */ function rg_serialize($a) { $r = @json_encode($a); if ($r === FALSE) { $m = 'error encoding json: ' . json_last_error_msg(); rg_internal_error($m); rg_util_set_error($m); return FALSE; } return $r; } /* * Compress data for web */ function rg_gzencode($c, &$orig_len, &$comp_len) { rg_prof_start('gzencode'); $orig_len = strlen($c); $ret = @gzencode($c, 6); if ($ret === FALSE) rg_fatal('cannot gzencode'); $comp_len = strlen($ret); rg_log('COMPRESSION: orig=' . $orig_len . ' comp=' . $comp_len . ' ratio=' . sprintf('%.3f', $orig_len / $comp_len)); rg_prof_end('gzencode'); return $ret; } /* * Output data to client */ function rg_web_output($c) { $acc = @$_SERVER['HTTP_ACCEPT_ENCODING']; if (stristr($acc, 'gzip')) { $c = rg_gzencode($c, $orig_len, $comp_len); header('Content-Encoding: gzip'); header('Content-Length: ' . $comp_len); } echo $c; } /* * Replaces a string with another, recursively in arrays */ function rg_str_replace($keys, $values, $a, $level) { if ($level > 100) { rg_internal_error('array too deep'); return FALSE; } if (is_string($a)) return str_replace($keys, $values, $a); if (is_array($a)) { foreach ($a as $k => $v) $a[$k] = rg_str_replace($keys, $values, $v, $level + 1); return $a; } return FALSE; } /* * Transforms a nested array into a flat one */ function rg_array_flat($prefix, $a) { $ret = array(); foreach ($a as $k => $v) { if (!is_array($v)) { $ret[$prefix . $k] = $v; continue; } $ret = array_merge($ret, rg_array_flat($prefix . $k . '::', $v)); } return $ret; } /* * Because array_splice destroys the index! */ function rg_array_top($a, $nr) { $ret = array(); $i = 0; foreach ($a as $k => $v) { if ($i++ == $nr) break; $ret[$k] = $v; } return $ret; } /* * Save a serialized array */ function rg_save($file, $a) { $r = @file_put_contents($file . '.tmp', rg_serialize($a)); if ($r === FALSE) { rg_internal_error('Cannot save file: ' . rg_php_err()); return FALSE; } $r = @rename($file . '.tmp', $file); if ($r === FALSE) { rg_internal_error('Cannot rename: ' . rg_php_err()); return FALSE; } return TRUE; } /* * Returns TRUE if we detect that the User-Agent seems a bot */ function rg_is_bot($ua) { if (empty($ua)) return FALSE; if (strstr($ua, ' SemrushBot')) return TRUE; if (strstr($ua, ' AhrefsBot')) return TRUE; if (strstr($ua, ' DotBot')) return TRUE; if (strstr($ua, ' MJ12bot')) return TRUE; if (strstr($ua, ' PetalBot')) return TRUE; if (strstr($ua, ' Googlebot')) return TRUE; if (strstr($ua, ' bingbot')) return TRUE; if (strstr($ua, ' SeznamBot')) return TRUE; if (strncmp($ua, 'CCBot', 5) == 0) return TRUE; if (strncmp($ua, 'yacybot ', 8) == 0) return TRUE; if (strcmp($ua, 'Internet-structure-research-project-bot') == 0) return TRUE; if (strstr($ua, ' Mail.RU_Bot')) return TRUE; if (strstr($ua, ' coccocbot-web')) return TRUE; if (strstr($ua, ' MojeekBot')) return TRUE; if (strstr($ua, ' DuckDuckGo')) return TRUE; if (strstr($ua, ' YandexBot')) return TRUE; if (strstr($ua, 'Applebot/')) return TRUE; if (strstr($ua, ' DNSResearchBot')) return TRUE; if (strstr($ua, ' Cliqzbot/')) return TRUE; if (strstr($ua, ' webtechbot;')) return TRUE; if (strstr($ua, ' BLEXBot/')) return TRUE; return FALSE; } /* * Makes clickable all segments of an URL */ function rg_url_segments($base, $url) { $ret = ''; $a = explode('/', $url); // ROOT $ret .= '<a href="' . $base . '">ROOT</a>'; if (empty($url)) return $ret; $p = ''; foreach ($a as $v) { $p .= '/' . rawurlencode($v); $ret .= ' / ' . '<a href="' . $base . $p . '">' . rg_xss_safe($v) . '</a>'; } return $ret; } /* * Transform a FS path into an URL escaping all path segments * Example: /a b/c/d -> /a%20b/c/d */ function rg_path2url($path) { $ret = ''; $a = explode('/', $path); $r = array(); foreach ($a as $v) $r[] = rawurlencode($v); return implode('/', $r); } /* * Check if a file is inside a directory and return 'stat' info if TRUE */ function rg_path_validate($root, $path) { $last = substr($root, -1); if (strcmp($last, '/') != 0) $root .= '/'; $ret = array(); $ret['realpath'] = @realpath($root . $path); if ($ret['realpath'] === FALSE) { rg_util_set_error('path ' . $root . $path . ' does not exists'); return FALSE; } if (strncmp($ret['realpath'], $root, strlen($root)) != 0) { rg_util_set_error('path ' . $path . ' is trying to escape root ' . $root); return FALSE; } $ret['stat'] = @stat($ret['realpath']); if ($ret['stat'] === FALSE) { rg_util_set_error('cannot stat ' . $ret['realpath'] . ': ' . rg_php_err()); return FALSE; } return $ret; } /* * Load a set of files. Used to load jobs. */ function rg_load_files($dir, $pattern, $id_field) { $ret = FALSE; while (1) { $l = rg_dir_load_pattern($dir, $pattern); if ($l === FALSE) break; $list = array(); $error = FALSE; foreach ($l as $f) { $c = @file_get_contents($dir . '/' . $f); if ($c === FALSE) { rg_util_set_error('cannot load data: ' . rg_php_err()); $error = TRUE; break; } $d = @json_decode($c, TRUE); if ($d === NULL) { rg_util_set_error('cannot decode job file ' . $f . ': ' . json_last_error_msg()); $error = TRUE; break; } $id = $d[$id_field]; $list[$id] = $d; } if ($error) break; $ret = $list; break; } return $ret; } /* * Computing the size of a directory (helper) */ function rg_dir_size_helper($dir, &$icache) { $tree = array('dirs' => array()); $ret = FALSE; while (1) { $d = @opendir($dir); if ($d === FALSE) { rg_util_set_error('cannot open dir: ' . rg_php_err()); break; } $error = FALSE; while (($f = readdir($d)) !== FALSE) { if (strcmp($f, ".") == 0) continue; if (strcmp($f, "..") == 0) continue; $s = @stat($dir . '/' . $f); if ($s === FALSE) { rg_util_set_error('cannot stat: ' . rg_php_err()); $error = TRUE; break; } if (($s['mode'] & 0040000) == 0040000) { // dir $v = rg_dir_size_helper($dir . '/' . $f, $icache); if ($v === FALSE) { $error = TRUE; break; } } else if (($s['mode'] & 0100000) == 0100000) { // regular file $k = $s['dev'] . '-' . $s['ino']; if (!isset($icache[$k])) $icache[$k] = array( 'links' => $s['nlink'], 'size' => $s['size'], 'blocks' => $s['blocks'] ); $v = $k; } $tree['dirs'][$f] = $v; } closedir($d); if (empty($tree['dirs'])) unset($tree['dirs']); if ($error === FALSE) $ret = $tree; break; } return $ret; } function rg_dir_size_helper2(&$tree, $icache) { // Step two, now we can compute the correct size $tree['blocks'] = 0; $tree['size'] = 0; if (!isset($tree['dirs'])) // empty dir return $tree; foreach ($tree['dirs'] as $k => &$o) { if (is_array($o)) { // dir //echo ' dir [' . $k . ']: ' . rg_array2string($o); $x = rg_dir_size_helper2($o, $icache); //echo ' ret: ' . rg_array2string($x) . "\n"; $tree['size'] += $x['size']; $tree['blocks'] += $x['blocks']; } else { // file $p = $icache[$o]; //echo ' file [' . $k . ']: icache: ' . rg_array2string($p) . "\n"; $tree['size'] += intval($p['size'] / $p['links']); $tree['blocks'] += intval($p['blocks'] / $p['links']); unset($tree['dirs'][$k]); } } unset($o); if (empty($tree['dirs'])) unset($tree['dirs']); return $tree; } /* * Computing the size of a directory */ function rg_dir_size($dir) { rg_prof_start('dir_size'); $ret = FALSE; while (1) { $r = file_exists($dir); if ($r === FALSE) { $ret = array('blocks' => 0, 'size' => 0); break; } // This will allow us to count correctly the size of the dir $icache = array(); $ret = rg_dir_size_helper($dir, $icache); if ($ret === FALSE) break; //rg_log_ml('dir_size: ' . print_r($ret)); //rg_log_ml('icache: ' . print_r($icache)); rg_dir_size_helper2($ret, $icache); unset($icache); break; } rg_prof_end('dir_size'); return $ret; }