<?php
require_once(__DIR__ . '/prof.inc.php');
require_once(__DIR__ . '/log.inc.php');
set_error_handler("rg_error_handler");
register_shutdown_function("rg_error_shutdown");
define('RG_SOCKET_NO_WAIT', 0x01);
define('RG_LOCK_BLOCK', 1 << 0);
if (!isset($rg_util_debug))
$rg_util_debug = FALSE;
if (strcmp(getenv('ROCKETGIT_UTIL_DEBUG'), '1') == 0)
$rg_util_debug = TRUE;
$rg_util_error = "";
$rg_php_err = '-';
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;
if ($v <= 9999)
return number_format($v) . "TiB";
$v /= 1024;
return number_format($v) . "PiB";
}
/*
* Transforms a kilo/mega/giga/tera 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;
if (stristr($s, 't'))
return $r * 1024 * 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');
$len2 = intval(($len + 1) / 2);
$id = rg_random_bytes($len2);
$id = bin2hex($id);
$id = substr($id, 0, $len);
rg_prof_end('id');
return $id;
}
/*
* Locks a file
*/
$_lock = array();
function rg_lock($file, $flags)
{
global $_lock;
global $rg_lock_dir;
if (!isset($rg_lock_dir))
$rg_lock_dir = "/var/lib/rocketgit/locks";
if (strncmp($file, '/', 1) == 0) // full path lock
$lock_file = $file;
else
$lock_file = $rg_lock_dir . '/' . $file;
// Double locking?
if (isset($_lock[$file])) {
rg_util_set_error('double locking error [' . $lock_file . ']');
return FALSE;
}
$f = @fopen($lock_file, "w");
if ($f === FALSE) {
rg_util_set_error('cannot open lock [' . $lock_file . ']: '
. rg_php_err());
return FALSE;
}
$x = LOCK_EX | LOCK_NB;
if ($flags & RG_LOCK_BLOCK)
$x &= ~LOCK_NB;
if (!@flock($f, $x, $wouldblock)) {
if ($wouldblock == 1)
rg_util_set_error('lock already locked [' . $lock_file . ']');
else
rg_util_set_error('cannot lock [' . $lock_file . ']: '
. rg_php_err());
fclose($f);
return FALSE;
}
fwrite($f, getmypid() . "\n");
$_lock[$file] = $f;
rg_log_debug('Lock acquired [' . $file . ']');
return TRUE;
}
function rg_lock_or_exit($file)
{
if (rg_lock($file, 0) === FALSE)
exit(0);
}
function rg_unlock($file)
{
global $_lock;
if (!isset($_lock[$file])) {
rg_internal_error('Lock not taken [' . $file . ']');
return FALSE;
}
fclose($_lock[$file]);
rg_log_debug('Lock unlocked [' . $file . ']');
unset($_lock[$file]);
}
/*
* Returns information about 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)
{
return htmlspecialchars($str,
ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, '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;
}
/*
* 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, $user, $pass)
{
// We are forced to use something if we cannot get them from cache/db
if (($hostname === FALSE) || empty($hostname))
$hostname = php_uname('n');
if (!empty($user))
$add = rawurlencode($user) . ':' . rawurlencode($pass) . '@';
else
$add = '';
// Prefer httpS
if (intval($https_allow) > 0)
return 'https://' . $add
. rg_base_url_host_port($hostname, $https_allow, 443);
return 'http://' . $add
. 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_str_list($name)
{
$a = trim(rg_var_str($name));
$a = str_replace("\n", ' ', $a);
$a = str_replace("\r", ' ', $a);
$a = str_replace("\t", ' ', $a);
$old = '';
while (strcmp($old, $a) != 0) {
$old = $a;
$a = str_replace(' ', ' ', $a);
}
return $a;
}
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;
}
/*
* Returns the members of the array, excluding arrays
*/
function rg_array_filter_arrays($a)
{
$ret = array();
foreach ($a as $k => $v) {
if (is_array($v))
continue;
$ret[$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_util_set_error('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_util_set_error('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'
* @off - offset in @s (performance reasons)
* @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_xss_safe(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
$func = $rest;
$_param = '';
} else {
$func = substr($rest, 0, $fpos);
$_param = substr($rest, $fpos + 1);
}
//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])) {
if (strncmp($param, '@@', 2) == 0) {
$param = substr($param, 2);
$param = substr($param, 0, -2);
}
$r = $rg_template_functions[$func]($param);
//rg_debug() && rg_log(' func returned [' . $r . ']');
$value = $r['value'];
if ($xss_protection && ($r['html'] !== 1))
$value = rg_xss_safe($value);
}
} 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, 'ESCAPE_SHELL_ARG:', 17) == 0) {
$rest = substr($var, 17);
$value = rg_template_tree_lookup($rest, $data, FALSE /*xss*/);
$value = escapeshellarg($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 {
// You do not want to activate next line because of spurious lookups
rg_log('WARN: 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...
* @more - ?
*/
function rg_template_table($dir, &$data, $more)
{
global $rg_theme_dir;
global $rg_theme;
rg_prof_start('template_table');
//rg_log_debug('template_table: ' . $dir
// . ' data: ' . print_r($data, TRUE));
$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'] = rg_xss_safe($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 "";
rg_log('errmsg: ' . rg_array2string($a));
$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_debug(' 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_debug(' 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_debug(' status is FALSE');
else if ($s === ZLIB_BUF_ERROR)
rg_log_debug(' status is ZLIB_BUF_ERROR');
else
rg_log_debug(' status is unknown [' . $s . ']');
return FALSE;
}
$rg_util_debug &&
rg_log_debug(' 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_avoid($a, $avoid)
{
$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 (in_array($k, $avoid)) {
$s = '-';
} else if (is_array($v)) {
$s = rg_array2string_short($v);
} else if (is_resource($v)) {
$s = 'RES';
} else if (is_object($v)) {
$s = 'OBJ';
} else if (is_string($v)) {
$s = preg_replace_callback($what, 'rg_callback_hexa', $v);
} else if (is_int($v)) {
$s = $v;
} else {
$s = '?';
}
$ret .= $add . $k . '=[' . $s . ']';
$add = ' ';
}
return $ret;
}
function rg_array2string_short($a)
{
$f = array('gpg_pub_key', 'gpg_priv_key', 'rgfs_key', 'ssh_key',
'key', 'last_output', 'client_ca_cert');
return rg_array2string_avoid($a, $f);
}
/*
* Transforms an array into a string
*/
function rg_array2string($a)
{
return rg_array2string_avoid($a, array());
}
/*
* 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 '@'
// PHP7 returned 0
$er = error_reporting();
//echo 'DEBUG: er=' . base_convert($er, 10, 16) . ' no=' . $no . ' ' . $str . "\n";
if (($er == 0) || !($er & $no))
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;
$limit = 0;
} else if ($timeout == 0) {
$tv_sec = 0;
$tv_usec = 0;
$limit = 0;
} else {
$tv_sec = intval($timeout / 1000);
$tv_usec = ($timeout % 1000) * 1000;
$limit = microtime(TRUE) + $timeout / 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;
}
if ($r === 0) {
rg_log('Cannot receive (zero)');
break;
}
//rg_log("Received [$buf]");
$ret_buf .= $buf;
$pos = strpos($buf, $wait);
if ($pos !== FALSE) {
$ret = $ret_buf;
break;
}
$t = microtime(TRUE);
if ($t > $limit)
break;
$tv_sec = intval($t - $limit);
$tv_usec = intval(($t - $limit) * 1000) % 1000;
}
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 data to socket ('
. 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;
}
rg_prof_end('socket-connect');
if ($r === FALSE) {
rg_log('Could not connect the socket [' . $path . ']'
. '(' . socket_strerror(socket_last_error()) . ')!');
break;
}
$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);
if ($ret === FALSE) {
socket_close($socket);
unset($rg_socket_cache[$path]);
break;
}
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;
// TODO: are we sure we want to strip the port?
// TODO: are we sure we want to check the referer?
$ref = preg_replace('|^http(s)?://|', '', $ref0);
$ref = preg_replace('|/.*$|', '', $ref); // remove URI
//$ref = preg_replace('|:.*$|', '', $ref); // remove port
$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 a path to a temporary directory.
* Timeout parameter will instruct the clean task when it can be removed.
* Use 0 for timeout_in_minutes to not clean that temp dir/file.
* TODO: The clean procedure is not done yet!
*/
function rg_tmp_path($name, $timeout_in_minutes)
{
global $rg_state_dir;
$p = $rg_state_dir . '/tmp/' . $name;
if ($timeout_in_minutes > 0)
@file_put_contents($p . '.meta', time() + $timeout_in_minutes * 60);
return $p;
}
/*
* 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, null, -$bytes, $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 (zlib)
*/
function rg_gzencode($c, &$orig_len, &$comp_len)
{
rg_prof_start('gzencode');
$orig_len = strlen($c);
$ret = @gzencode($c, 9);
if ($ret === FALSE)
rg_fatal('cannot gzencode');
$comp_len = strlen($ret);
rg_log('COMPRESSION: orig=' . $orig_len
. ' comp=' . $comp_len
. ' ratio=' . sprintf('%.1f', $orig_len / $comp_len));
rg_prof_end('gzencode');
return $ret;
}
/*
* Output data to client
*/
function rg_web_output($c, $disable)
{
$comp_len = 0;
if ($disable === FALSE) {
$acc = isset($_SERVER['HTTP_ACCEPT_ENCODING']) ? $_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);
}
}
if ($comp_len == 0)
$comp_len = strlen($c);
echo $c;
return $comp_len;
}
/*
* 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))
return $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)
{
if ($nr == 0)
return $a;
$ret = array();
$i = 0;
foreach ($a as $k => $v) {
if ($i++ == $nr)
break;
$ret[$k] = $v;
}
return $ret;
}
/*
* Save a file (safe version)
*/
function rg_save_plain($file, $a)
{
$r = @file_put_contents($file . '.tmp', $a);
if ($r === FALSE) {
rg_internal_error('Cannot save file [' . $file . '.tmp]: ' . rg_php_err());
return FALSE;
}
$r = @rename($file . '.tmp', $file);
if ($r === FALSE) {
@unlink($file . '.tmp');
rg_internal_error('Cannot rename: ' . rg_php_err());
return FALSE;
}
return TRUE;
}
/*
* Save a serialized array
*/
function rg_save($file, $a)
{
return rg_save_plain($file, rg_serialize($a));
}
function rg_is_abuser(string $ua)
{
if (stristr($ua, 'facebookexternalhit/')) return TRUE;
if (stristr($ua, 'gptbot/')) return TRUE;
if (stristr($ua, 'GoogleOther')) return TRUE;
return FALSE;
}
/*
* Returns TRUE if we detect that the User-Agent seems a bot
*/
function rg_is_bot($ua)
{
if (empty($ua)) return FALSE;
if (stristr($ua, 'bot/')) return TRUE;
if (stristr($ua, 'bot;')) return TRUE;
if (stristr($ua, 'crawler')) return TRUE;
if (strstr($ua, ' Adsbot/')) return TRUE;
if (strstr($ua, ' AhrefsBot')) return TRUE;
if (strstr($ua, ' aiHitBot/')) return TRUE;
if (strstr($ua, ' Amazonbot/')) return TRUE;
if (strstr($ua, 'Applebot/')) return TRUE;
if (strstr($ua, 'AWeb')) return TRUE;
if (strstr($ua, ' Babya Discoverer ')) return TRUE;
if (strstr($ua, ' Baiduspider/')) return TRUE;
if (strstr($ua, ' Barkrowler/')) return TRUE;
if (strstr($ua, 'BananaBot/')) return TRUE;
if (strstr($ua, ' bingbot')) return TRUE;
if (strstr($ua, ' BLEXBot/')) return TRUE;
if (strstr($ua, ' Bytespider')) return TRUE;
if (strstr($ua, 'CATExplorador')) return TRUE;
if (strstr($ua, 'CCBot/')) return TRUE;
if (strstr($ua, ' CensysInspect/')) return TRUE;
if (strstr($ua, 'CISPA Webcrawler ')) return TRUE;
if (strstr($ua, 'clark-crawler2/')) return TRUE;
if (strstr($ua, 'Cloud mapping experiment')) return TRUE;
if (strstr($ua, ' Cliqzbot/')) return TRUE;
if (strstr($ua, ' coccocbot-web')) return TRUE;
if (strstr($ua, ' DataForSeoBot/')) return TRUE;
if (strstr($ua, 'dcrawl/')) return TRUE;
if (strstr($ua, ' DNSResearchBot')) return TRUE;
if (strstr($ua, 'DomainStatsBot/')) return TRUE;
if (strstr($ua, ' DotBot')) return TRUE;
if (strstr($ua, ' DuckDuckGo')) return TRUE;
if (strstr($ua, 'e.ventures Investment Crawler ')) return TRUE;
if (strstr($ua, 'facebookexternalhit/')) return TRUE;
if (strstr($ua, 'Facebot')) return TRUE;
if (strstr($ua, 'GarlikCrawler/')) return TRUE;
if (strstr($ua, 'Gigabot ')) return TRUE;
if (strstr($ua, ' Googlebot')) return TRUE;
if (strstr($ua, 'googlebot')) return TRUE;
if (strstr($ua, 'Googlebot-Image/')) return TRUE;
if (strstr($ua, 'Googlebot-News')) return TRUE;
if (strstr($ua, 'Googlebot-Video/')) return TRUE;
if (strstr($ua, 'GoScraper')) return TRUE;
if (strstr($ua, 'HappyWing')) return TRUE;
if (strstr($ua, 'ichiro/')) return TRUE;
if (strstr($ua, 'IonCrawl')) return TRUE;
if (strstr($ua, 'Internet-structure-research-project-bot')) return TRUE;
if (strstr($ua, 'Lawinsiderbot/')) return TRUE;
if (strstr($ua, 'LightspeedSystemsCrawler')) return TRUE;
if (strstr($ua, ' Linguee Bot ')) return TRUE;
if (strstr($ua, 'ltx71 ')) return TRUE;
if (strstr($ua, ' MaCoCu;')) return TRUE;
if (strstr($ua, ' Mail.RU_Bot')) return TRUE;
if (strstr($ua, ' MauiBot ')) return TRUE;
if (strstr($ua, 'search.marginalia.nu')) return TRUE;
if (strstr($ua, ' MegaIndex.ru/')) return TRUE;
if (strstr($ua, 'meta-externalagent/')) return TRUE;
if (strstr($ua, ' MJ12bot')) return TRUE;
if (strstr($ua, ' MojeekBot')) return TRUE;
if (strstr($ua, 'msnbot-media/')) return TRUE;
if (strstr($ua, ' MTRobot/')) return TRUE;
if (strstr($ua, ' NetcraftSurveyAgent/')) return TRUE;
if (strstr($ua, 'NewsGator FetchLinks extension/')) return TRUE;
if (strstr($ua, 'netEstate NE Crawler ')) return TRUE;
if (strstr($ua, 'nu.marginalia.wmsa.edge-crawler')) return TRUE;
if (strstr($ua, 'OpenSearch@MPDL ')) return TRUE;
if (strstr($ua, ' PageThing ')) return TRUE;
if (strstr($ua, 'PageThing.com')) return TRUE;
if (strstr($ua, 'paloaltonetworks.com')) return TRUE;
if (strstr($ua, 'Pandalytics')) return TRUE;
if (strstr($ua, 'panscient.com')) return TRUE;
if (strstr($ua, ' PetalBot')) return TRUE;
if (strstr($ua, 'pimeyes.com crawler')) return TRUE;
if (strstr($ua, ' proximic;')) return TRUE;
if (strstr($ua, 'Re-re Studio ')) return TRUE;
if (strstr($ua, 'Robot Terminator ')) return TRUE;
if (strstr($ua, 'rpmlint/')) return TRUE;
if (strstr($ua, 'SaaSHub')) return TRUE;
if (strstr($ua, ' SemanticScholarBot')) return TRUE;
if (strstr($ua, 'Screaming Frog SEO Spider')) return TRUE;
if (strstr($ua, ' Seekport Crawler')) return TRUE;
if (strstr($ua, ' SemrushBot')) return TRUE;
if (strstr($ua, ' SEOkicks;')) return TRUE;
if (strstr($ua, 'serpstatbot/')) return TRUE;
if (strstr($ua, ' SeznamBot')) return TRUE;
if (strstr($ua, 'Sidetrade indexer bot')) return TRUE;
if (strstr($ua, 'Slackbot-LinkExpanding ')) return TRUE;
if (strstr($ua, 'Sogou web spider')) return TRUE;
if (strstr($ua, 'TelegramBot ')) return TRUE;
if (strstr($ua, 'The Knowledge AI')) return TRUE;
if (strstr($ua, 'TprAdsTxtCrawler')) return TRUE;
if (strstr($ua, 'TurnitinBot ')) return TRUE;
if (strstr($ua, 'Twitterbot/')) return TRUE;
if (strstr($ua, 'TinyTestBot')) return TRUE;
if (strstr($ua, ' VelenPublicWebCrawler/')) return TRUE;
if (strstr($ua, 'Wappalyzer')) return TRUE;
if (strstr($ua, ' webtechbot;')) return TRUE;
if (strstr($ua, 'http://webmeup-crawler.com/')) return TRUE;
if (strstr($ua, 'Xenu Link Sleuth/')) return TRUE;
if (strstr($ua, 'XYZ Spider')) return TRUE;
if (strstr($ua, 'yacybot ')) return TRUE;
if (strstr($ua, '/yacy.net/')) return TRUE;
if (strstr($ua, ' YandexBot/')) return TRUE;
if (strstr($ua, ' YandexImages/')) return TRUE;
if (strstr($ua, ' YisouSpider')) return TRUE;
if (strstr($ua, 'ZoomBot ')) return TRUE;
if (strstr($ua, 'ZoominfoBot ')) 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 (relative to @root) is inside a directory and return
* 'stat' info if TRUE.
*/
function rg_path_validate($root, $path)
{
rg_log_enter('path_validate root=' . $root . ' path=' . $path);
$ret = FALSE;
do {
$last = substr($root, -1);
if (strcmp($last, '/') != 0)
$root .= '/';
$a = array();
$a['realpath'] = @realpath($root . $path);
if ($a['realpath'] === FALSE) {
rg_util_set_error('path ' . $root . $path . ' does not exists');
break;
}
if (strncmp($a['realpath'], $root, strlen($root)) != 0) {
rg_util_set_error('path [' . $path . '] is trying to escape'
. ' root [' . $root . ']');
break;
}
$a['stat'] = @stat($a['realpath']);
if ($a['stat'] === FALSE) {
rg_util_set_error('cannot stat ' . $a['realpath']
. ': ' . rg_php_err());
break;
}
$ret = $a;
} while (0);
rg_log_exit();
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;
}
/*
* Creates all needed directories in a path to a file (NOT a dir)
*/
function rg_create_dirs($path, $mode)
{
$ret = array('ok' => 0);
do {
$d = dirname($path);
if (strcmp($d, '/') == 0) {
$ret['ok'] = 1;
break;
}
if (strcmp($d, '.') != 0) {
// Try to create the parent
$ret = rg_create_dirs($d, $mode);
if ($ret['ok'] != 1)
break;
}
$r = @stat($d);
if ($r !== FALSE) {
$ret['ok'] = 1;
break;
}
$r = @mkdir($d, $mode);
if ($r === FALSE) {
$ret['errmsg'] = rg_php_err();
break;
}
$ret['ok'] = 1;
} while (0);
return $ret;
}
$_ip = '';
function rg_ip_set($ip)
{
global $_ip;
$_ip = rg_fix_ip($ip);
}
function rg_ip()
{
global $_ip;
return $_ip;
}
$_t = @file_get_contents('/home/rocketgit/rg_debug');
$rg_debug = ($_t !== FALSE) ? intval($_t) : 0;
function rg_debug_set($level)
{
global $rg_debug;
$rg_debug |= intval($level);
rg_log('DEBUG: rg_debug set to ' . $rg_debug);
}
function rg_debug()
{
global $rg_debug;
return $rg_debug;
}
$rg_debug_html = array();
function rg_debug_html_set($k, $v)
{
global $rg_debug_html;
if (rg_debug() == 0)
return;
//rg_log_debug('rg_debug_html_set called [' . $k . '] [' . rg_array2string($v) . ']');
$rg_debug_html[$k] = $v;
}
/* Functions for template system */
function rg_debug_html($junk)
{
global $rg_debug_html;
if (rg_debug() == 0)
return array('value' => '', 'html' => 1);
$ret = array(
'html' => 1, // not really html
'value' => '<!-- --rg_debug_html-- '
. json_encode($rg_debug_html)
. ' --rg_debug_html-- -->'
);
return $ret;
}
rg_template_func('RG_DEBUG_HTML', 'rg_debug_html');
/*
* Loads a .sha256 hash file or generate one
* TODO: test if file is more recent than hash file!
*/
function rg_hash_load($hash, $f)
{
$ret = array('ok' => 0);
do {
$r = @stat($f);
if ($r === FALSE) {
$ret['errmsg'] = 'cannot stat file';
break;
}
$ret['size'] = $r['size'];
$mtime = $r['mtime'];
$r = @file_get_contents($f . '.' . $hash);
if ($r !== FALSE) {
$x = explode(' ', $r);
if (isset($x[1]) && ($mtime == $x[1])) {
$ret['hash'] = $x[0];
$ret['ok'] = 1;
break;
}
}
$r = hash_file($hash, $f);
if ($r === FALSE) {
$ret['errmsg'] = 'cannot create hash';
break;
}
$ret['hash'] = $r;
$ret['ok'] = 1;
@file_put_contents($f . '.' . $hash, $ret['hash']);
// we ignore errors here
} while (0);
return $ret;
}
/*
* XZ compress a file
*/
function rg_xz_compress($f, $extra)
{
rg_log_enter('xz_compress: f=' . $f);
$ret = array('ok' => 0);
do {
$a = array(
'cmds' => array(
'cmd1' => array(
'cmd' => 'xz ' . $extra . ' ' . escapeshellarg($f)
)
)
);
$r = rg_exec2($a);
if (($r['ok'] != 1) || ($r['cmds']['cmd1']['exitcode'] != 0)) {
$ret['errmsg'] = $r['cmds']['cmd1']['last_errmsg']
. ' [' . $r['cmds']['cmd1']['err_buf'];
break;
}
$ret['ok'] = 1;
} while (0);
rg_log_exit();
return $ret;
}
/*
* Remove dir2 from dir2 to obtain a relative path
*/
function rg_dir_cut($dir1, $dir2)
{
return substr($dir1, strlen($dir2));
}