xaizek / rocketgit (License: AGPLv3+) (since 2018-12-09)
Light and fast Git hosting solution suitable to serve both as a hub or as a personal code storage with its tickets, pull requests, API and much more.
<root> / root / index.php (7cb53508f730d5009f3ffe642764622e555f7b02) (10KiB) (mode 100644) [raw]
<?php
error_reporting(E_ALL);
ini_set("track_errors", "On");
set_time_limit(30);

$peak0 = memory_get_peak_usage();

$rg = array();
$rg['start'] = microtime(TRUE);

require_once("/etc/rocketgit/config.php");
$INC = dirname(__FILE__) . "/../inc";
require_once($INC . "/init.inc.php");
require_once($INC . "/log.inc.php");
include_once($INC . "/sql.inc.php");
include_once($INC . "/struct.inc.php");
include_once($INC . "/user.inc.php");
include_once($INC . "/repo.inc.php");
include_once($INC . "/keys.inc.php");
include_once($INC . "/token.inc.php");
include_once($INC . "/prof.inc.php");
include_once($INC . "/mr.inc.php");
include_once($INC . "/bug.inc.php");
include_once($INC . "/fixes.inc.php");
include_once($INC . "/plan.inc.php");
include_once($INC . "/admin.inc.php");
include_once($INC . "/api.inc.php");
include_once($INC . "/apikeys.inc.php");
include_once($INC . "/demo.inc.php");
include_once($INC . "/ver.php");
include_once($INC . "/ldap.inc.php");
include_once($INC . "/ssh.inc.php");
include_once($INC . "/stats.inc.php");

rg_prof_start("MAIN");

rg_log_set_file($rg_log_dir . "/web.log");

ignore_user_abort(TRUE);

// database connection
rg_sql_app('rg-web-' . $rg_log_sid);
$db = rg_sql_open($rg_sql);

// Store configuration into 'rg'
if (!isset($rg_account_email_confirm))
	$rg_account_email_confirm = 1;
if (rg_var_uint('force_confirm') == 1)
	$rg_account_email_confirm = 1;
$rg['rg_account_email_confirm'] = $rg_account_email_confirm;
if (!isset($rg_account_allow_creation))
	$rg_account_allow_creation = 0;
$rg['rg_account_allow_creation'] = $rg_account_allow_creation;

// Init variables
$rg['rg_redirect_html'] = 0;
$rg['rg_theme_url'] = '/themes/' . rawurlencode($rg_theme);
$rg['login_ui'] = rg_user_empty();
$rg['target_ui'] = array("ok" => 1, "exists" => 0, "uid" => 0);
$rg['page_ui'] = array("ok" => 1, "exists" => 0, "uid" => 0);
$rg['ri'] = array('exists' => 0, 'repo_id' => 0, 'uid' => 0);
$rg['bug'] = array("bug_id" => 0);
$rg['https'] = strcmp(rg_var_str('HTTPS'), 'on') == 0 ? 1 : 0;


$sparas = rg_var_str('SCRIPT_NAME');
rg_log('DEBUG: ' . $_SERVER['REQUEST_METHOD'] . ' sparas=' . $sparas);
$rg['current_url'] = rawurlencode($sparas);
$rg['url'] = '/op';
$paras = explode("/", trim($sparas, "/"));
$_t = empty($paras) ? "" : $paras[0];
if (strcmp($_t, "op") == 0) {
	array_shift($paras);
	$_op = empty($paras) ? '' : array_shift($paras);
} else {
	$_op = '';
}

$rg['doit'] = rg_var_uint("doit");
$rg['sid_cookie_name'] = $rg['https'] == 1 ? 'sids' : 'sidu';
$rg['sid'] = rg_var_cookie_re($rg['sid_cookie_name'], '/[^A-Za-z0-9]/');
$rg['token'] = rg_var_re("token", "A-Za-z0-9");
$user = ""; $repo = ""; $organization = 0; // TODO: those are really used?

//rg_log_ml("rg: " . print_r($rg, TRUE));

$rg['ua'] = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : "";
$rg['ip'] = isset($_SERVER['REMOTE_ADDR']) ? rg_fix_ip($_SERVER['REMOTE_ADDR']) : '';
$rg['referer'] = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
$req = $_REQUEST;
if (isset($req['pass']) || isset($req['init::pass']) || isset($req['init::pass2']))
	$req['pass'] = '*****';
if (isset($req['old_pass']))
	$req['old_pass'] = '*****';
if (isset($req['pass1']))
	$req['pass1'] = '*****';
if (isset($req['pass2']))
	$req['pass2'] = '*****';
if (!empty($req))
	rg_log("DEBUG: _REQUEST: " . rg_array2string($req));
if (!empty($_COOKIE))
	rg_log("DEBUG: _COOKIE: " . rg_array2string($_COOKIE));
rg_log($rg['ip'] . " ver=$rocketgit_version");

// Try to detect if we cloning by http(s)
rg_log_ml('_SERVER: ' . print_r($_SERVER, TRUE));
$rg['ct'] = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '';


$rg['debug'] = rg_state_get($db, 'debug');

$r = rg_struct_ok($db);
if ($r === FALSE) {
	echo "Internal error; please try again later.";
	exit(0);
}

// Sets http(s)_allow and hostname
$hostname = rg_state_get($db, 'hostname');
$http_allow = rg_state_get($db, 'http_allow');
$https_allow = rg_state_get($db, 'https_allow');
if (isset($_SERVER['HTTP_HOST'])) {
	// We trust HTTP_HOST: somehow the user managed to get here
	$sn = $_SERVER['HTTP_HOST'];
	$_t = explode(':', $sn);
	if (isset($_t[1])) {
		$sn = $_t[0];
		$port = $_t[1];
	} else {
		$port = $rg['https'] == 1 ? 443 : 80;
	}
} else if (isset($_SERVER['SERVER_NAME'])
	&& (strcmp($_SERVER['SERVER_NAME'], '_') != 0)) {
	$sn = $_SERVER['SERVER_NAME'];
	$port = $_SERVER['SERVER_PORT'];
} else if (isset($_SERVER['SERVER_ADDR'])) {
	$sn = $_SERVER['SERVER_ADDR'];
	$port = $_SERVER['SERVER_PORT'];
} else {
	$sn = php_uname('n');
	$port = 80;
}

if ($hostname === FALSE) {
	$hostname = $sn;
} else if (empty($hostname)) {
	$hostname = $sn;
	rg_state_set($db, 'hostname', $hostname);
}

if ($rg['https'] == 1) {
	if ($https_allow === FALSE) {
		$https_allow = $port;
	} else if (strcmp($https_allow, '') == 0) {
		$https_allow = $port;
		rg_state_set($db, 'https_allow', $https_allow);
	}
} else {
	if ($http_allow === FALSE) {
		$http_allow = $port;
	} else if (strcmp($http_allow, '') == 0) {
		$http_allow = $port;
		rg_state_set($db, 'http_allow', $http_allow);
	}
}

rg_log('DEBUG: hostname=' . $hostname . ' http_allow=' . $http_allow
	. ' https_allow=' . $https_allow);
rg_base_url_build($hostname, $http_allow, $https_allow);
rg_log('DEBUG: base_url=' . rg_base_url());
$rg['hostname'] = $hostname;
$rg['hostname_port'] =
	rg_base_url_host_port($hostname, $port, $rg['https'] == 0 ? 80 : 443);
$rg['http_allow'] = $http_allow;
$rg['https_allow'] = $https_allow;
$rg['base_url'] = rg_base_url();


$skip_etag_and_compression = FALSE;

// Is it a http push/fetch?
$r = rg_user_http_git($db, $rg, $paras);
if ($r === TRUE) {
	rg_stats_conns_set('type', 'git-over-http');
	$skip_etag_and_compression = TRUE;
} else if (strcmp($_op, 'api') == 0) { // API dispatch
	rg_stats_conns_set('type', 'api-over-http');

	// https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
	header('Access-Control-Allow-Origin: *');
	header('Access-Control-Allow-Headers: Authorization');
	header('Content-Type: application/json;charset=UTF-8');

	$_auth = isset($_SERVER['HTTP_AUTHORIZATION']) ?
		$_SERVER['HTTP_AUTHORIZATION'] : '';
	$_t = explode(' ', $_auth, 2);
	$_user = trim($_t[0]);
	if (isset($_t[1]))
		$_key = trim($_t[1]);
	else
		$_key = '';
	rg_log('api user=[' . $_user . '] key=[' . $_key . ']');

	while (1) {
		$key_id = FALSE;

		$rg['login_ui'] = rg_user_info($db, 0, $_user, '');
		if ($rg['login_ui']['ok'] != 1) {
			$e = array('error' => rg_user_error());
			break;
		}

		if ($rg['login_ui']['exists'] != 1) {
			$e = array('error' => 'invalid user or apikey');
			break;
		}

		$key_id = rg_ak_valid($db, $rg['login_ui']['uid'], $_key);
		if ($key_id === FALSE) {
			$e = array('error' => 'invalid user or apikey');
			break;
		}

		$post = @file_get_contents('php://input');
		rg_log('post: ' . $post);

		$json = @json_decode($post, TRUE);
		if ($json === NULL) {
			rg_log('error parsing json ('
				. json_last_error_msg() . '): [' . $post . ']');
			$e = array('error' => json_last_error_msg());
			break;
		}

		$e = rg_api($db, $rg, $json);
		break;
	}
	$_c = json_encode($e, JSON_PRETTY_PRINT) . "\n";
} else { // normal web request
	rg_stats_conns_set('type', 'http');

	rg_user_login_by_sid($db, $rg);
	rg_log("After login_by_sid, login_ui=" . rg_array2string($rg['login_ui']));
	// If user provided an old/expired sid, we generate a new one, pre-login
	if (($rg['login_ui']['uid'] == 0) && (strncmp($rg['sid'], "X", 1) != 0))
		$rg['sid'] = "";
	if (empty($rg['sid'])) {
		$rg['sid'] = rg_user_set_session_cookie($db, 0 /*uid*/, 600,
			FALSE /*lock_ip*/, $rg['https'], $rg['hostname']);
		rg_log("User has no sid, generate one [" . $rg['sid'] . "]");
	}

	$body = "";

	// Some variables from the database
	$rg['first_install_text'] = "?";

	$r = rg_state_get($db, "first_install");
	if ($r === FALSE) {
		// Probably we cannot connect to database/cache
		$body .= rg_template('admin/db_error.html', TRUE /*xss*/);
	} else if ($r === '') {
		$body .= rg_init($db, $rg);
	} else {
		$rg['first_install_text'] = gmdate("Y-m-d", $r);

		rg_log("Dispatching to [$_op]");
		include($INC . "/dispatch/dispatch.php");
	}

	if ($rg['login_ui']['uid'] > 0) {
		$rg['logout_token'] = rg_token_get($db, $rg, 'logout');
	} else {
		$rg['logout_token'] = '';
	}


	// Redirect if http is not allowed and the user is not logged in
	if (($rg['https'] == 0)
		&& ($rg['login_ui']['uid'] == 0)
		&& (strcmp($http_allow, '0') == 0))
		rg_redirect(rg_base_url() . $sparas);


	$rg['HTML:rg_body'] = $body;
	$_c = rg_template('index.html', $rg, TRUE /*xss*/);

	rg_stats_conns_set('uid', $rg['page_ui']['uid']);
	if ($rg['ri']['exists'] > 0)
		rg_stats_conns_set('repo_id', $rg['ri']['repo_id']);
}

if ($skip_etag_and_compression == FALSE) {
	// TODO: really?! What about API?
	header('Cache-Control: private, no-cache');

	// Caching
	$proto = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : '';
	if ((strcmp($proto, 'HTTP/1.1') == 0) || (strcmp($proto, 'HTTP/2.0') == 0)) {
		while (1) {
			$we_have = sha1($_c);
			if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
				$client_have = $_SERVER['HTTP_IF_NONE_MATCH'];

				if (strcmp($client_have, $we_have) == 0) {
					rg_log('CACHE: Client has the latest version; no need to resend');
					header($proto . ' 304 Not modified');
					break;
				}
			}

			header('ETag: ' . $we_have);
			rg_web_output($_c);
			break;
		}
	} else {
		rg_log('WARN: Un-handled protocol [' . $proto . ']');
		rg_web_output($_c);
	}
}

rg_prof_end('MAIN');
rg_prof_log();

fastcgi_finish_request();
// Now, answer was delivered to the client, we can do some boring tasks.

while (strcmp($_op, 'api') == 0) {
	if ($key_id === FALSE)
		break;

	rg_ak_update_use($db, $rg['login_ui']['uid'], $key_id,
		$rg['ip'], $post);
	break;
}

$list = array('start', 'ip', 'ua', 'referer');
foreach ($list as $k)
	rg_stats_conns_set($k, $rg[$k]);
//TODO: lines below override the good ones in case of api calls
//rg_stats_conns_set('uid', $rg['ri']['uid']);
//rg_stats_conns_set('repo_id', $rg['ri']['repo_id']);
rg_stats_conns_set('url', $sparas);
rg_stats_conns_insert($db);

Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/rocketgit

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/rocketgit

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master