From 394a07ff3e283f94c7ead44e8bd02d44c223314b Mon Sep 17 00:00:00 2001 From: pk910 Date: Fri, 15 Feb 2013 02:43:25 +0100 Subject: [PATCH] continued :) --- htdocs/lib/CommitLoader.class.php | 36 ++++++++++ htdocs/lib/ContentProvider.class.php | 5 +- htdocs/lib/GitCommand.class.php | 68 ++++++++++++------- htdocs/lib/PHPGitWeb.class.php | 17 ++++- htdocs/lib/ProjectLoader.class.php | 2 + htdocs/lib/Tools.class.php | 7 ++ htdocs/lib/Validation.class.php | 63 +++++++++++++++++ htdocs/pages/commit.class.php | 97 +++++++++++++++++++++++++++ htdocs/pages/projects.class.php | 2 +- htdocs/pages/shortlog.class.php | 37 ++++++++-- htdocs/pages/summary.class.php | 5 +- htdocs/templates/default/commit.tpl | 87 ++++++++++++++++++++++++ htdocs/templates/default/main.tpl | 17 ++++- htdocs/templates/default/projects.tpl | 5 ++ htdocs/templates/default/shortlog.tpl | 24 ++++++- 15 files changed, 435 insertions(+), 37 deletions(-) create mode 100644 htdocs/lib/CommitLoader.class.php create mode 100644 htdocs/lib/Validation.class.php create mode 100644 htdocs/pages/commit.class.php create mode 100644 htdocs/templates/default/commit.tpl diff --git a/htdocs/lib/CommitLoader.class.php b/htdocs/lib/CommitLoader.class.php new file mode 100644 index 0000000..d6e822c --- /dev/null +++ b/htdocs/lib/CommitLoader.class.php @@ -0,0 +1,36 @@ +. + */ + +class CommitLoader { + private $project; + + public function __construct($project) { + $this->project = $project; + } + + public function load($id) { + if(!Validation::validate_hash($id)) { + trigger_error("Invalid hash parameter", E_USER_ERROR); + return false; + } + $commit = GitCommand::get_commit($this->project['path'], $id); + return $commit; + } + + +} diff --git a/htdocs/lib/ContentProvider.class.php b/htdocs/lib/ContentProvider.class.php index cd88b91..a32a37f 100644 --- a/htdocs/lib/ContentProvider.class.php +++ b/htdocs/lib/ContentProvider.class.php @@ -41,6 +41,8 @@ class ContentProvider { return; if(!array_key_exists(strtolower($name), $this->content)) $this->content[strtolower($name)] = array(); + if(!is_array($this->content[strtolower($name)])) + $this->content[strtolower($name)] = array($this->content[strtolower($name)]); $this->content[strtolower($name)][] = $value; } @@ -76,9 +78,6 @@ class ContentProvider { case "year": $rep = date("Y"); break; - case "title": - $rep = GitConfig::GITWEB_TITLE; - break; case "rendertime": $rep = "%rendertime%"; //gets replaced later break; diff --git a/htdocs/lib/GitCommand.class.php b/htdocs/lib/GitCommand.class.php index 4e20281..609d0ad 100644 --- a/htdocs/lib/GitCommand.class.php +++ b/htdocs/lib/GitCommand.class.php @@ -20,7 +20,7 @@ class GitCommand { private static $git = null; private static $counter = 0; - private static function command_header($git_path = null) { + private static function command_header() { if(!self::$git) { //find git executable if(GitConfig::GIT_EXEC) @@ -33,36 +33,41 @@ class GitCommand { } } $command = self::$git; - if($git_path) - $command .= " --git-dir=".$git_path; + return $command; } - private static function execute($command) { + private static function git_execute($params, $git_path = null) { $result = array(); + + if($git_path) { + $args = array("--git-dir=".$git_path); + $args = array_merge($args, $params); + } else + $args = $params; + + $command = self::command_header(); + foreach($args as $arg) { + $command .= ' '.escapeshellarg($arg); + } + exec($command, $result); self::$counter++; return implode("\n", $result); } public static function core_version() { - $command = self::command_header(); - if(!$command) - return null; - $command .= " --version"; - $version = self::execute($command); + $args = array("--version"); + $version = self::git_execute($args); preg_match("/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/i", $version, $result); return $result[0]; } public static function last_activity($git_path, $ref = null) { - $command = self::command_header($git_path); - if(!$command) - return null; - $command .= " for-each-ref --format=%\(committer\) --sort=-committerdate --count=1"; + $args = array("for-each-ref", "--format=%(committer)", "--sort=-committerdate", "--count=1"); if($ref) - $command .= " ".$ref; - $age = self::execute($command); + $args[] = $ref; + $age = self::git_execute($args, $git_path); preg_match("/[0-9]{9,}/i", $age, $result); return $result[0]; } @@ -71,6 +76,9 @@ class GitCommand { $commit = array(); $rev_lines = explode("\n", str_replace("\r", "", $commit_data)); $commit['id'] = $rev_lines[0]; + $commit['parent'] = array(); + if(!preg_match("/^[a-f0-9]{40}$/i", $commit['id'])) + return null; foreach($rev_lines as $rev_line) { if(substr($rev_line, 0, 4) == " ") { if(array_key_exists('text', $commit)) @@ -85,15 +93,17 @@ class GitCommand { else if($opt[0] == "parent") $commit['parent'][] = $opt[1]; else if($opt[0] == "author") { - preg_match('/(.*) <([^>]*)> ([0-9]*) [+-0-9]*/i', $opt[1], $matches); + preg_match('/(.*) <([^>]*)> ([0-9]*) ([+\-0-9]{5})/i', $opt[1], $matches); $commit['author'] = $matches[1]; $commit['author_mail'] = $matches[2]; $commit['author_time'] = $matches[3]; + $commit['author_timezone'] = $matches[4]; } else if($opt[0] == "committer") { - preg_match('/(.*) <([^>]*)> ([0-9]*) [+-0-9]*/i', $opt[1], $matches); + preg_match('/(.*) <([^>]*)> ([0-9]*) ([+\-0-9]{5})/i', $opt[1], $matches); $commit['committer'] = $matches[1]; $commit['committer_mail'] = $matches[2]; $commit['committer_time'] = $matches[3]; + $commit['committer_timezone'] = $matches[4]; } } } @@ -101,13 +111,10 @@ class GitCommand { } public static function get_commits($git_path, $head, $maxcount, $skip, $file = null) { - $command = self::command_header($git_path); - if(!$command) - return null; - $command .= " rev-list --header --max-count=".$maxcount." --skip=".$skip." ".($head ? $head : "--all")." --"; + $args = array("rev-list", "--header", "--max-count=".$maxcount, "--skip=".$skip, ($head ? $head : "--all"), "--"); if($file) - $command .= " ".$file; - $commit_list = self::execute($command); + $args[] = $file; + $commit_list = self::git_execute($args, $git_path); $commits = array(); foreach(explode("\000", $commit_list) as $commit) { if($commit) @@ -116,4 +123,19 @@ class GitCommand { return $commits; } + public static function get_commit($git_path, $commit_id) { + $args = array("rev-list", "--header", "--max-count=1", $commit_id, "--"); + $commit_data = self::git_execute($args, $git_path); + $commit = self::parse_commit($commit_data); + return $commit; + } + + public static function get_hash($git_path, $ref) { + $args = array("rev-parse", "--verify", "-q", $ref); + $result = self::git_execute($args, $git_path); + if(prag_match("#([a-f0-9]{40})#i", $result, $match)) + return $match[1]; + return null; + } + } diff --git a/htdocs/lib/PHPGitWeb.class.php b/htdocs/lib/PHPGitWeb.class.php index 8d16d7b..5789d1f 100644 --- a/htdocs/lib/PHPGitWeb.class.php +++ b/htdocs/lib/PHPGitWeb.class.php @@ -19,8 +19,10 @@ define("PHPGITWEB_VERSION", "0.0.1"); require_once("lib/ContentProvider.class.php"); require_once("lib/ProjectLoader.class.php"); +require_once("lib/CommitLoader.class.php"); require_once("lib/GitCommand.class.php"); require_once("lib/Tools.class.php"); +require_once("lib/Validation.class.php"); require_once("lib/graph.class.php"); class PHPGitWeb { @@ -35,6 +37,7 @@ class PHPGitWeb { $this->page->set('git_version', GitCommand::core_version()); $this->project_loader = new ProjectLoader(); + $this->page->append('title', GitConfig::GITWEB_TITLE); } public function get_project_loader() { @@ -45,11 +48,12 @@ class PHPGitWeb { $this->project = $this->project_loader->getProject($project); if(!$this->project) - $this->page->append('content', new ContentProvider('main', 'project_error')); + $this->page->append('content', new ContentProvider('main', 'err404_project')); else { ContentProvider::overall_set('project', $this->project['name']); ContentProvider::overall_set('project_head', "HEAD"); $this->append_header_nav($this->project['name'], '?p='.$this->project['name'].'&a=summary', true); + $this->append_title(" - ".$this->project['name'], false); $this->project_header = new ContentProvider('main', 'project_header'); $this->project_header->set('sub_nav', ""); $this->page->append('content', $this->project_header); @@ -91,6 +95,17 @@ class PHPGitWeb { $this->page->append('header_nav', $name); } + public function append_title($name, $prepend_slash = true) { + if($prepend_slash) + $this->page->append('title', '/'); + $this->page->append('title', $name); + } + + public function append_sub_nav($html) { + if($this->project_header) + $this->project_header->append('sub_nav', $html); + } + public function load_extension($extension) { switch($extension) { case "graph": diff --git a/htdocs/lib/ProjectLoader.class.php b/htdocs/lib/ProjectLoader.class.php index a5ebd74..20ee218 100644 --- a/htdocs/lib/ProjectLoader.class.php +++ b/htdocs/lib/ProjectLoader.class.php @@ -47,6 +47,8 @@ class ProjectLoader { $project['name'] = $name; $dir_seperator = (substr(GitConfig::PROJECT_ROOT, -1) == '/' ? '' : '/'); + if(!Validation::validate_path($name)) + return NULL; if(is_dir(GitConfig::PROJECT_ROOT.$dir_seperator.$name)) $project['path'] = GitConfig::PROJECT_ROOT.$dir_seperator.$name; else if(is_dir(GitConfig::PROJECT_ROOT.$dir_seperator.$name.".git")) diff --git a/htdocs/lib/Tools.class.php b/htdocs/lib/Tools.class.php index d6f9b64..d7f9075 100644 --- a/htdocs/lib/Tools.class.php +++ b/htdocs/lib/Tools.class.php @@ -83,6 +83,13 @@ class Tools { return $ctext; } + public static function parseTimeZone($timezone) { + if(!preg_match("/^([+-])([0-9]{2})([0-9]{2})$/i", $timezone, $treffer)) + return 0; + $offset = ($treffer[1] == '-' ? -1 : 1) * (($treffer[2] * 3600) + ($treffer[3] * 60)); + return $offset; + } + } ?> \ No newline at end of file diff --git a/htdocs/lib/Validation.class.php b/htdocs/lib/Validation.class.php new file mode 100644 index 0000000..4db14d9 --- /dev/null +++ b/htdocs/lib/Validation.class.php @@ -0,0 +1,63 @@ +. + */ + +class Validation { + + public static function validate_path($path) { + /* Path validation #1 + * no '.' or '..' as elements of path, i.e. no '.' nor '..' + * at the beginning, at the end, and between slashes. + * also this catches doubled slashes + */ + if(preg_match('#(^|/)(|\.|\.\.)(/|$)#', $path)) + return false; + + /* Path validation #2 + * no null characters + */ + if(preg_match('#\0#', $path)) + return false; + + return true; + } + + public static function validate_hash($hash) { + /* Hash validation #1 + * regular hashes [a-f0-9] are always ok + */ + if(preg_match('#^[a-f0-9]{1,40}$#i', $hash)) + return true; + + /* Hash validation #2 + * must be a valid path + */ + if(!self::validate_path($hash)) + return false; + + /* Hash validation #3 + * restrictions on ref name according to git-check-ref-format + */ + if(preg_match('#(\.|\.\.|[\000-\040\177 ~^:?*\[\]]|/$)#', $hash)) + return false; + + return true; + } + +} + +?> \ No newline at end of file diff --git a/htdocs/pages/commit.class.php b/htdocs/pages/commit.class.php new file mode 100644 index 0000000..17f5678 --- /dev/null +++ b/htdocs/pages/commit.class.php @@ -0,0 +1,97 @@ +. + */ + +require_once('pages/shortlog.class.php'); + +class page_commit { + private $page, $phpgitweb; + private $commitid; + + public function main($phpgitweb, $project) { + $this->phpgitweb = $phpgitweb; + if(!$project) + return new ContentProvider('main', 'err400'); + $project['refs'] = $phpgitweb->get_project_loader()->getProjectRefs($project); + $phpgitweb->append_header_nav("commit", null, true); + $phpgitweb->append_title("commit"); + + $commit_loader = new CommitLoader($project); + + if(array_key_exists('h', $_GET)) + $commitid = $_GET['h']; + else + $commitid = "HEAD"; + + $commit = $commit_loader->load($commitid); + if(!$commit) + return new ContentProvider('main', 'err404_object'); + + ContentProvider::overall_set('project_head', $commit['id']); + + + $this->page = new ContentProvider('commit', 'main'); + $this->page->set('hash', $commit['id']); + $this->page->set('author', htmlentities($commit['author'])); + $this->page->set('author_mail', htmlentities($commit['author_mail'])); + $this->page->set('author_date', gmdate('r', $commit['author_time'])); + $author_local_time = $commit['author_time'] + Tools::parseTimeZone($commit['author_timezone']); + if(gmdate('H', $author_local_time) < 6) + $this->page->set('author_local_date', ''.gmdate('H:i', $author_local_time).''); + else + $this->page->set('author_local_date', gmdate('H:i', $author_local_time)); + $this->page->set('author_timezone', $commit['author_timezone']); + $this->page->set('committer', htmlentities($commit['committer'])); + $this->page->set('committer_mail', htmlentities($commit['committer_mail'])); + $this->page->set('committer_date', gmdate('r', $commit['committer_time'])); + $committer_local_time = $commit['committer_time'] + Tools::parseTimeZone($commit['committer_timezone']); + if(gmdate('H', $committer_local_time) < 6) + $this->page->set('committer_local_date', ''.gmdate('H:i', $committer_local_time).''); + else + $this->page->set('committer_local_date', gmdate('H:i', $committer_local_time)); + $this->page->set('committer_timezone', $commit['committer_timezone']); + $this->page->set('message', htmlentities(Tools::chop_text($commit['text'], 50, 5))); + $this->page->set('full_message', htmlentities($commit['text'])); + $this->page->set('tree_hash', $commit['tree']); + foreach($commit['parent'] as $parent) { + $this->page->append('parents', new ContentProvider('commit', 'parent', array('hash' => $parent, 'head' => $commit['id']))); + } + + $refs = new ContentProvider('commit', 'commit_refs'); + $found = false; + foreach($project['refs'] as $ref => $rhash) { + if(strtolower($rhash) == strtolower($commit['id'])) { + $refexp = explode('/', $ref, 3); + $reftype = $refexp[1]; + if($reftype == 'heads') + $reftype = 'head'; + else if($reftype == 'remotes') + $reftype = 'remote'; + else if($reftype == 'tags') + $reftype = 'tag'; + $refs->append('refs', new ContentProvider('commit', 'commit_ref_'.$reftype, array("name" => $refexp[2], "ref_link" => $ref))); + $found = true; + } + } + $this->page->set('refs', ($found ? $refs : "")); + + return $this->page; + } + +} + +?> \ No newline at end of file diff --git a/htdocs/pages/projects.class.php b/htdocs/pages/projects.class.php index ade8eb6..be0766b 100644 --- a/htdocs/pages/projects.class.php +++ b/htdocs/pages/projects.class.php @@ -99,7 +99,7 @@ class page_projects { $age = Tools::age_calculate($project['last_change']); - $entry->set('ageclass', $age['age_class']); + $entry->set('age_class', $age['age_class']); $entry->set('age', $age['age_str']); return $entry; } diff --git a/htdocs/pages/shortlog.class.php b/htdocs/pages/shortlog.class.php index 34cfa80..38c8db5 100644 --- a/htdocs/pages/shortlog.class.php +++ b/htdocs/pages/shortlog.class.php @@ -22,6 +22,7 @@ class shortlog { private $project; private $graph_data; private $first_commit; + private $have_more = false; public function generate_shortlog($project, $head, $max, $skip, $file = null, $pages = true, $next_page = 0) { $this->project = $project; @@ -66,6 +67,7 @@ class shortlog { if($commit_counter < $skip) continue; if($commit_counter > $max+$skip) { + $this->have_more = true; if($pages) { $content->append('entries', new ContentProvider('shortlog', 'shortlog_page', array("page" => $next_page))); } else @@ -81,6 +83,10 @@ class shortlog { return $this->first_commit; } + public function get_have_more() { + return $this->have_more; + } + private function shortlog_entry($class, $commit) { $entry = new ContentProvider('shortlog', 'shortlog_entry'); $entry->set('class', $class); @@ -101,17 +107,17 @@ class shortlog { if(GitConfig::GITGRAPH_ENABLE) $entry->set('graph_data', $this->graph_data->get_graph($commit['id'])); - $entry->set('refs', $this->shortlog_commit_refs($commit['id'])); + $entry->set('refs', $this->shortlog_commit_refs($this->project, $commit['id'])); return $entry; } - private function shortlog_commit_refs($hash) { - if(!is_array($this->project['refs'])) + public function shortlog_commit_refs($project, $hash) { + if(!is_array($project['refs'])) return ""; $refs = new ContentProvider('shortlog', 'shortlog_refs'); $found = false; - foreach($this->project['refs'] as $ref => $rhash) { + foreach($project['refs'] as $ref => $rhash) { if(strtolower($rhash) == strtolower($hash)) { $refexp = explode('/', $ref, 3); $reftype = $refexp[1]; @@ -138,24 +144,45 @@ class page_shortlog { $this->phpgitweb = $phpgitweb; $this->project = $project; if(!$this->project) - return; + return new ContentProvider('main', 'err400'); $project['refs'] = $phpgitweb->get_project_loader()->getProjectRefs($project); $phpgitweb->append_header_nav("shortlog", null, true); + $phpgitweb->append_title("shortlog"); $this->page = new ContentProvider('shortlog', 'main'); //pages if(array_key_exists('pg', $_GET)) { + $pg = $_GET['pg']; + if($pg < 0) + $pg = 0; $skip = $_GET['pg'] * 100; $next_page = $_GET['pg'] + 1; } else { + $pg = 0; $skip = 0; $next_page = 1; } + $subnav = new ContentProvider('shortlog', 'shortlog_subnav'); + $phpgitweb->append_sub_nav($subnav); + + if($pg) { + $subnav->set('first', new ContentProvider('shortlog', 'shortlog_subnav_first_link')); + $subnav->set('prev', new ContentProvider('shortlog', 'shortlog_subnav_prev_link', array('page' => ($pg - 1)))); + } else { + $subnav->set('first', new ContentProvider('shortlog', 'shortlog_subnav_first')); + $subnav->set('prev', new ContentProvider('shortlog', 'shortlog_subnav_prev')); + } + $shortlog = new shortlog(); $this->page->set('shortlog', $shortlog->generate_shortlog($project, null, 100, $skip, null, true, $next_page)); + if($shortlog->get_have_more()) + $subnav->set('next', new ContentProvider('shortlog', 'shortlog_subnav_next_link', array('page' => ($pg + 1)))); + else + $subnav->set('next', new ContentProvider('shortlog', 'shortlog_subnav_next')); + return $this->page; } diff --git a/htdocs/pages/summary.class.php b/htdocs/pages/summary.class.php index 360ae01..0e3eae6 100644 --- a/htdocs/pages/summary.class.php +++ b/htdocs/pages/summary.class.php @@ -24,9 +24,10 @@ class page_summary { public function main($phpgitweb, $project) { $this->phpgitweb = $phpgitweb; if(!$project) - return; + return new ContentProvider('main', 'err400'); $project['refs'] = $phpgitweb->get_project_loader()->getProjectRefs($project); $phpgitweb->append_header_nav("summary", null, true); + $phpgitweb->append_title("summary"); $this->page = new ContentProvider('summary', 'main'); @@ -49,7 +50,7 @@ class page_summary { $this->page->set('shortlog', $shortlog->generate_shortlog($project, null, 16, 0, null, false)); $first_commit = $shortlog->get_first_commit(); - $this->page->set('last_change', date('r', $first_commit['committer_time'])); + $this->page->set('last_change', gmdate('r', $first_commit['committer_time'])); return $this->page; } diff --git a/htdocs/templates/default/commit.tpl b/htdocs/templates/default/commit.tpl new file mode 100644 index 0000000..cb2fc4e --- /dev/null +++ b/htdocs/templates/default/commit.tpl @@ -0,0 +1,87 @@ +# [main] +
+%message% %refs% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +%parents% +
author + %author% + <%author_mail%> +
%author_date% (%author_local_date% %author_timezone%)
committer + %committer% + <%committer_mail%> +
%committer_date% (%committer_local_date% %committer_timezone%)
commit%hash%
tree%tree_hash%
+
+
+%full_message%
+
+ +
+
+ +%tree% +
+ + +# [parent] + + parent + %hash% + + commit | + diff + + + +# [commit_refs] + %refs% + +# [commit_ref_head] +%name% + +# [commit_ref_remote] +%name% + +# [commit_ref_tag] +%name% + +# [tree] + + src/mod-helpserv.c + + + diff | + blob | + history + diff --git a/htdocs/templates/default/main.tpl b/htdocs/templates/default/main.tpl index 52d883d..d3908a5 100644 --- a/htdocs/templates/default/main.tpl +++ b/htdocs/templates/default/main.tpl @@ -36,12 +36,27 @@ Rendertime: %rendertime% sec -# [project_error] +# [err400]
+

+404 - Project needed +

+
+ +# [err404_project] +
+

404 - Project not found

+# [err404_object] +
+

+404 - Unknown commit object +

+
+ # [project_header]