<?php require_once($INC . "/prof.inc.php"); require_once($INC . "/log.inc.php"); set_error_handler("rg_error_handler"); register_shutdown_function("rg_shutdown_error"); function rg_util_set_error($str) { global $_rg_util_error; $_rg_util_error = $str; } function rg_util_error() { global $_rg_util_error; return $_rg_util_error; } function rg_1024($v) { if ($v <= 9999) return number_format($v); $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"; } /* * Unique ID generator */ function rg_id($len) { $id = ""; rg_prof_start("urandom"); $f = @fopen("/dev/urandom", "r"); if ($f !== NULL) { $buf = @fread($f, 128); if ($buf !== NULL) $id = sha1($buf); fclose($f); } if (empty($id)) $id = sha1(mt_rand() . serialize($_REQUEST)); rg_prof_end("urandom"); return substr($id, 0, $len); } $_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])) return FALSE; $f = @fopen($rg_lock_dir . "/" . $file, "w"); if ($f === FALSE) return FALSE; if (!flock($f, LOCK_EX | LOCK_NB)) { fclose($f); return FALSE; } fwrite($f, getmypid() . "\n"); $_lock[$file] = $f; return TRUE; } function rg_lock_or_exit($file) { global $_lock; if (rg_lock($file) === FALSE) exit(0); } function rg_unlock($file) { global $_lock; fclose($_lock[$file]); } function rg_load() { return intval(file_get_contents("/proc/loadavg")); } /* * Builds URLs */ function rg_re_url($area) { if (isset($_REQUEST['rwe'])) return $area; return "/?vv=" . $area; } /* * This is used for forms */ function rg_re_post($op) { if (isset($_REQUEST['rwe'])) return $op; return "/?vv=$op"; } function rg_re_userpage($ui) { if (!isset($ui['organization'])) { rg_internal_error("rg_re_userpage called with wrong ui (no org)!"); exit(1); } $prefix = ""; if ($ui['organization'] == 0) $prefix = "/user"; $s = $prefix . "/" . $ui['username']; if (isset($_REQUEST['rwe'])) return $s; return $_SERVER['PHP_SELF'] . "?vv=$s"; } function rg_re_repopage($ui, $repo) { if (!isset($ui['organization'])) { rg_internal_error("rg_re_repopage called with wrong ui (no org)!"); exit(1); } $s = rg_re_userpage($ui) . "/" . $repo; if (isset($_REQUEST['rwe'])) return $s; return $_SERVER['PHP_SELF'] . "?vv=$s"; } function rg_re_bugpage($ui, $ri, $bug_id) { if (!isset($ui['organization'])) { rg_internal_error("rg_re_repopage called with wrong ui (no org)!"); exit(1); } $s = rg_re_userpage($ui) . "/" . $ri['name'] . "/bug/" . $bug_id; if (isset($_REQUEST['rwe'])) return $s; return $_SERVER['PHP_SELF'] . "?vv=$s"; } function rg_re_repo_ssh($rr) { global $rg_ssh_host; global $rg_ssh_port; if ($rg_ssh_port == 22) $port = ""; else $port = ":" . $rg_ssh_port; return "ssh://rocketgit@" . $rg_ssh_host . $port . $rr['prefix'] . "/" . $rr['user'] . "/" . $rr['repo']; } function rg_re_repo_git($rr) { global $rg_git_port; if ($rg_git_port == 9418) $port = ""; else $port = ":" . $rg_git_port; return "git://" . $_SERVER['SERVER_NAME'] . $port . $rr['prefix'] . "/" . $rr['user'] . "/" . $rr['repo']; } function rg_var_str($name) { $ret = ""; if (isset($_COOKIE[$name])) $ret = $_COOKIE[$name]; else if (isset($_POST[$name])) $ret = $_POST[$name]; else if (isset($_GET[$name])) $ret = $_GET[$name]; if (is_string($ret)) return htmlspecialchars($ret, ENT_QUOTES); if (is_array($ret)) { $ret2 = array(); foreach ($ret as $k => $v) $ret2[$k] = htmlspecialchars($v, ENT_QUOTES); return $ret; } return ""; } function rg_var_int($name) { return sprintf("%d", rg_var_str($name)); } function rg_var_uint($name) { return sprintf("%u", rg_var_str($name)); } function rg_var_re($name, $re) { $a = rg_var_str($name); return preg_replace($re, "", $a); } /* * Enforce chars in a name. It is used for user and repo. */ function rg_chars_allow($name, $allowed_regexp) { if (preg_match($allowed_regexp, $name) === 0) return FALSE; return TRUE; } /* * Deletes a folder and the files inside it */ function rg_rmdir($dir) { $scan = glob($dir . "/*"); if ($scan === FALSE) { rg_util_set_error("invalid pattern [$dir/*]"); return FALSE; } if (count($scan) > 0) { foreach ($scan as $junk => $path) { if (!@unlink($path)) { rg_util_set_error("cannot remove [$path] ($php_errormsg)"); return FALSE; } } } if (!@rmdir($dir)) { rg_util_set_error("cannot remove main dir ($php_errormsg)"); return FALSE; } return TRUE; } /* * Generates a menu */ function rg_menu($a, $rg_ui, $op, $subop) { rg_log("menu: op=$op subop=$subop"); $menu = "<div class=\"menu\">\n"; $menu .= "\t<ul>\n"; $menu2 = ""; foreach ($a as $_id => $_info) { // we ignore fake menus like 'home' if (!isset($_info['text'])) continue; if (isset($_info['needs_admin']) && ($rg_ui['is_admin'] == 0)) continue; if (isset($_info['uid0']) && ($rg_ui['uid'] > 0)) continue; if (!isset($_info['uid0']) && ($rg_ui['uid'] == 0)) continue; $_text = $_info['text']; $_url = rg_re_url($_info['op']); //$menu .= "<!-- op=" . $_info['op'] . " url=$_url." . " -->\n"; rg_log("\tDEBUG: compare with [" . $_info['op'] . "]"); $add = ""; if (strcmp($_info['op'], "/op/" . $op) == 0) $add = " class=\"selected\"";; $menu .= "\t\t<li><a href=\"" . $_url . "\"" . $add . ">" . $_text . "</a></li>\n"; if (!isset($_info['sub']) || (count($_info['sub']) == 0)) continue; $menu2 .= "\t<div class=\"menu\">\n"; $menu2 .= "\t<ul>\n"; foreach ($_info['sub'] as $junk => $_info2) { $_text2 = $_info2['text']; $_url2 = $_url . "/" . $_info2['op']; $add = ""; rg_log("\tDEBUG: compare with [" . $_info2['op'] . "]"); if (strcmp($_info2['op'], $subop) == 0) $add = " class=\"selected\"";; $menu2 .= "\t\t<li><a href=\"" . $_url2 . "\"" . $add . ">" . $_text2 . "</a></li>\n"; } $menu2 .= "\t</ul>\n"; $menu2 .= "\t</div>\n"; } $menu .= "\t</ul>\n"; $menu .= "\t</div>\n"; $ret = array(); $ret[0] = $menu; if (!empty($menu2)) $ret[1] = $menu2; return $ret; } /* * Provides a link to a image, taking in consideration the theme * Used by rg_prepare_image. */ function rg_image_callback($matches) { global $rg_scripts; global $rg_theme; $n = $matches[1]; $url = "/themes/" . $rg_theme . "/" . $n; $xfile = $rg_scripts . "/root" . $url; if (!is_file($xfile)) $url = "/themes/default/" . $n; return $url; } /* * Prepare an image taking in consideration the them. It will use rg_img as * callback. */ function rg_prepare_image($line) { return preg_replace_callback('/@@IMG:(.*)@@/uU', "rg_image_callback", $line); } /* * Helper for rg_replace_conditionals. * It works at line level. * @master_block (TRUE / FALSE) is the condition for parent block. */ function rg_replace_conditionals_block($block, &$stack) { //rg_log("rg_replace_conditionals_block: block=[$block]" // . " stack=" . rg_array2string($stack)); if (!is_string($block)) { rg_internal_error("Block is not a string!"); return FALSE; } // Nesting error // TODO: rg_internal_error? if (empty($stack)) return FALSE; $cond = array_pop($stack); $stack[] = $cond; //rg_log("cond is " . ($cond ? "TRUE" : "FALSE")); // First, try to match a start of 'if' $match1 = "@@if\s*\((.*?)\)\s*?{{"; $match2 = "{{"; $match3 = "}}"; $search = $match1 . '|' . $match2 . '|' . $match3; $r = preg_match('/^(.*?)(' . $search . ')(.*)/su', $block, $matches); if ($r === FALSE) return FALSE; if ($r === 1) { rg_log("matches: " . rg_array2string($matches)); $ret = ""; if ($cond) $ret = $matches[1]; $rest = $matches[4]; if (strcmp($matches[2], "}}") == 0) { // We pop from stack only at }} and not at {{ //rg_log("}}: Pop the stack!"); array_pop($stack); } else if (strcmp($matches[2], "{{") == 0) { //rg_log("{{"); } else { rg_log("cond=" . $matches[3]); if (empty($matches[3])) { $new_cond = FALSE; } else { $r = preg_match('/^(.+?)(\s*(==|!=)?\s*(.*?))?$/su', $matches[3], $matches2); if ($r === FALSE) { rg_internal_error("Invalid condition!"); return FALSE; } rg_log("matches2: " . rg_array2string($matches2)); $left = trim($matches2[1]); $op = trim($matches2[3]); $right = trim($matches2[4]); rg_log("if left=[$left] op=[$op] right=[$right]"); if (empty($op)) { $new_cond = empty($left) ? FALSE : TRUE; } else if (strcmp($op, "==") == 0) { $new_cond = strcmp($left, $right) == 0 ? TRUE : FALSE; } else if (strcmp($op, "!=") == 0) { $new_cond = strcmp($left, $right) == 0 ? FALSE : TRUE; } else { rg_internal_error("Invalid operation!"); return FALSE; } } $not_new_cond = $new_cond ? FALSE : TRUE; // We have to respect the outer block condition // We have to push in reverse order. Remeber, first in, last out $stack[] = $not_new_cond && $cond; $stack[] = $new_cond && $cond; } $tmp = rg_replace_conditionals_block($rest, $stack); if ($tmp === FALSE) return FALSE; //rg_log("returning [" . $ret . $tmp . "]"); return $ret . $tmp; } if ($cond === FALSE) $block = ""; //rg_log("returning [$block]"); return $block; } /* * Replace conditionals * @@if(X == Y){{A}}{{B}} - if X == Y it will return A, else B. * Do note that there is no ending @@. * Because of complexity, I choosed to have a restriction: the 'if' is on * a single line or '@@if(...){{', '}}{{' and '}}' are on separate lines. * TODO: Also, we must have both branches (both true and false), for now. * We support nested ifs. */ function rg_replace_conditionals($block) { rg_log("rg_replace_conditionals"); $ret = array(); $stack = array(); $stack[] = TRUE; $ret = rg_replace_conditionals_block($block, $stack); if (empty($stack) || ($stack[0] !== TRUE)) { rg_internal_error("Template nesting error!"); return FALSE; } return $ret; } function rg_prepare_replace(&$data, &$what, &$values) { if (!empty($data)) { if (!is_array($data)) rg_internal_error("invalid type passed"); foreach ($data as $k => $v) { if (strncmp($k, "HTML:", 5) == 0) { $k = substr($k, 5); } else { $v = htmlspecialchars($v); } $what[$k] = "/@@" . $k . "@@/uU"; $values[$k] = $v; } } $what['DUMP'] = "/@@DUMP@@/uU"; $values['DUMP'] = htmlspecialchars(print_r($data, TRUE)); // we replace @@unknown@@ with empty //$what['FINAL'] = "/@@.*@@/U"; //$values['FINAL'] = ""; //rg_log_ml("DEBUG: what: " . print_r($what, TRUE)); //rg_log_ml("DEBUG: values: " . print_r($values, TRUE)); } /* * Loads a file if exists, else return "" */ function rg_file_get_contents($f) { if (!file_exists($f)) return ""; return @file_get_contents($f); } /* * Builds a html output based on a template with header, footer and line */ function rg_template_table($dir, $data, $more) { global $rg_theme; global $rg_scripts; $xdir = $rg_scripts . "/root/themes/" . $rg_theme . "/" . $dir; if (!is_dir($xdir)) $xdir = $rg_scripts . "/root/themes/default/" . $dir; $m_what = array(); $m_values = array(); rg_prepare_replace($more, $m_what, $m_values); if (!is_array($data) || (count($data) == 0)) { $no_data = rg_file_get_contents($xdir . "/nodata.html"); $_tmp = preg_replace($m_what, $m_values, $no_data); return rg_replace_conditionals($_tmp); } $head = rg_file_get_contents($xdir . "/header.html"); $line = rg_file_get_contents($xdir . "/line.html"); $foot = rg_file_get_contents($xdir . "/footer.html"); $between = rg_file_get_contents($xdir . "/between.html"); $head = preg_replace($m_what, $m_values, $head); $foot = preg_replace($m_what, $m_values, $foot); $between = preg_replace($m_what, $m_values, $between); $body = ""; $first = 1; foreach ($data as $index => $info) { $what = $m_what; $values = $m_values; rg_prepare_replace($info, $what, $values); $line = rg_prepare_image($line); if ($first == 1) { $first = 0; } else { $body .= $between; } $body .= preg_replace($what, $values, $line); } return rg_replace_conditionals($head . $body . $foot); } function rg_template($file, $data) { global $rg_scripts; global $rg_theme; $xfile = $rg_scripts . "/root/themes/" . $rg_theme . "/" . $file; if (!is_file($xfile)) $xfile = $rg_scripts . "/root/themes/default/" . $file; $body = rg_file_get_contents($xfile); if (empty($body)) return ""; $what = array(); $values = array(); rg_prepare_replace($data, $what, $values); $body = rg_prepare_image($body); $ret = preg_replace($what, $values, $body); return rg_replace_conditionals($ret); } /* * 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 . " " . htmlspecialchars($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()); } /* * Execute $cmd and returns the output as a string, binary safe */ function rg_exec($cmd) { rg_prof_start("exec($cmd)"); rg_log("Executing [$cmd]..."); $ret = array(); $ret['ok'] = 0; $ret['errmsg'] = ""; $desc = array( 0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w") ); $a = proc_open($cmd, $desc, $pipes); if ($a === FALSE) { $ret['errmsg'] = "cannot call proc_open"; rg_prof_end("exec($cmd)"); return $ret; } $errmsg = trim(stream_get_contents($pipes[2])); $ret['data'] = stream_get_contents($pipes[1]); for ($i = 0; $i < 3; $i++) fclose($pipes[$i]); $ret['code'] = proc_close($a); if ($ret['code'] == 0) $ret['ok'] = 1; else $ret['errmsg'] = "code " . $ret['code'] . ": " . $errmsg; rg_prof_end("exec($cmd)"); return $ret; } /* * Force browser to redirect to another page */ function rg_redirect($url) { header("Location: $url"); exit(0); } /* * 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 in a string */ function rg_array2string($a) { if (!is_array($a)) rg_internal_error("not an array!"); if (empty($a)) return ""; $what = array("/[^\pL\pN\pP\pS ]/uU"); $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 */ function rg_dir_load($dir) { global $php_errormsg; $ret = array(); if (!file_exists($dir)) { rg_log("$dir does not exists!"); return $ret; } $d = @scandir($dir); if ($d === FALSE) { rg_log("Cannot scan dir $dir ($php_errormsg)."); return $ret; } foreach ($d as $file) { if ((strcmp($file, ".") == 0) || (strcmp($file, "..") == 0)) continue; $ret[] = $file; } return $ret; } /* * 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); 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) { global $php_errormsg; if (!is_dir($dst)) { $r = @mkdir($dst, $mask); if ($r !== TRUE) { rg_log("ERROR: Cannot mkdir [$dst] ($php_errormsg)."); return FALSE; } } $d = rg_dir_load($src); 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 [$dst/$obj]" . " ($php_errormsg)."); return FALSE; } } $r = rg_copy_tree($src . "/" . $obj, $dst . "/" . $obj, $mask); if ($r !== TRUE) return FALSE; } else { $r = @copy($src . "/" . $obj, $dst . "/" . $obj); if ($r !== TRUE) { rg_log("ERROR: Cannot copy file ($php_errormsg)."); return FALSE; } } } return TRUE; } /* * Called by PHP in case of error */ function rg_error_handler($no, $str, $file, $line) { if ($no == 0) return; $str = str_replace("\n", "\\n", $str); rg_log("PHP ERROR: $file:$line: $str (errno=$no)"); if ($no == E_ERROR) die(); return FALSE; } /* * shutdown function to call fatal errors */ function rg_shutdown_error() { $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; } ?>