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> / tests / http.inc.php (e5656321a7223397cd8fbfbf18f21c53b1f3b0c9) (9,489B) (mode 100644) [raw]
<?php

if (!isset($test_ua))
	$test_ua = "curl";

/*
 * Clean all cookies
 */
function clean_cookies()
{
	global $_testns;

	$path = __DIR__ . '/jars';
	if (!file_exists($path))
		return;

	rg_log('Cleaning cookies...');
	$cookie_jar = $path . '/' . $_testns;
	@unlink($cookie_jar);
}

/*
 * This is called at the begining of all tests
 */
function prepare_http()
{
	clean_cookies();
}

/*
 * Data is an array
 */
function do_req($url, &$data, &$headers)
{
	global $test_ua, $test_referer;
	global $cookie_jar;
	static $http_handles = array();
	global $http_client;
	global $_testns;
	global $rg_log_sid;

	if (!isset($http_client))
		$http_client = $_testns;

	$path = __DIR__ . '/jars';
	if (!file_exists($path))
		mkdir($path);
	$cookie_jar = $path . '/' . $http_client;

	if (is_null($data))
		$data = array();

	if (!is_array($headers)) {
		rg_log("Headers is not an array, reset it.");
		$headers = array();
	}

	// to easy identify requests in the logs
	if (!strstr($url, '?'))
		$url .= '?tid=' . rg_id(10);
	else
		$url .= '&tid=' . rg_id(10);
	$url .= '&_testns=' . $_testns;
	$url .= '&rg_log_sid=' . $rg_log_sid;

	rg_log_ml('do_req url: ' . $url . "\n"
		. 'data=' . print_r($data, TRUE) . "\n"
		. 'headers=' . print_r($headers, TRUE));

	$c = FALSE;
	if (isset($http_handles[$http_client]))
		$c = $http_handles[$http_client];
	if ($c === FALSE) {
		$c = curl_init();
		$http_handles[$http_client] = $c;
	}

	curl_setopt($c, CURLOPT_URL, $url);
	if (!empty($data)) {
		curl_setopt($c, CURLOPT_POST, 1);
		curl_setopt($c, CURLOPT_POSTFIELDS, $data);
	} else {
		curl_setopt($c, CURLOPT_POST, 0);
	}
	curl_setopt($c, CURLOPT_RETURNTRANSFER, TRUE);
	curl_setopt($c, CURLOPT_FOLLOWLOCATION, 1);
	curl_setopt($c, CURLOPT_HEADER, 1);
	curl_setopt($c, CURLOPT_HTTPHEADER, $headers);
	curl_setopt($c, CURLOPT_USERAGENT, $test_ua);
	curl_setopt($c, CURLOPT_REFERER, $test_referer);
	curl_setopt($c, CURLOPT_CERTINFO, TRUE);
	curl_setopt($c, CURLOPT_VERBOSE, TRUE);
	curl_setopt($c, CURLOPT_ENCODING , 'gzip');
	curl_setopt($c, CURLOPT_COOKIEJAR, $cookie_jar);
	curl_setopt($c, CURLOPT_COOKIEFILE, $cookie_jar);

	// HTTP/2
	if (curl_version()['features'] & CURL_VERSION_HTTP2)
		curl_setopt($c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);

	$err = @fopen('php://temp', 'w');
	if ($err !== FALSE) {
		curl_setopt($c, CURLOPT_STDERR, $err);
	} else {
		rg_log('Cannot open stderr redirection!');
	}

	$r = curl_exec($c);

	if ($err !== FALSE) {
		rewind($err);
		$xerr = @fread($err, 16 * 4096);
		fclose($err);
		rg_log_ml($xerr);
	}

	if ($r === FALSE) {
		rg_log_ml("Cannot load (url=$url), data: "
			. print_r($data, TRUE));
		rg_log("curl error: " . curl_error($c));
		exit(1);
	}

	$ret = array();
	$ret['ci'] = @curl_getinfo($c);
	if ($ret['ci'] === FALSE) {
		rg_log('Cannot call curl_getinfo!');
		exit(1);
	}

	//rg_log_ml('DEBUG: ci: ' . print_r($ret['ci'], TRUE));

	if ($ret['ci']['http_code'] == 500)
		return $ret;

	$header_size = $ret['ci']['header_size'];
	//rg_log('DEBUG: r (len=' . strlen($r) . '): ' . $r);
	//rg_log('DEBUG: header_size: ' . $header_size);
	$ret['header'] = substr($r, 0, $header_size);
	$ret['body'] = substr($r, $header_size);

	if ($ret['ci']['http_code'] != 200)
		return $ret;

	if (stristr($ret['header'], 'Content-Type: text/html')) {
		// Check for XSS
		if (stristr($ret['body'], '<xss>')) {
			file_put_contents('http_xss.out', $ret['body']);
			rg_log("Found <xss> token! Check http_xss.out. Not good!");
			exit(1);
		}
	}

	// TODO: should we compress the file downloads?
	if (stristr($ret['header'], 'Content-Disposition: attachment'))
		return $ret;

	if (!stristr($ret['header'], 'Content-Encoding: gzip')) {
		rg_log_ml('headers: ' . print_r($ret['header'], TRUE));
		rg_log('Content is not compressed!');
		exit(1);
	}

	if (!stristr($ret['header'], "\n" . 'ETag: ')) {
		rg_log_ml('headers: ' . print_r($ret['header'], TRUE));
		rg_log('ETag is not present!');
		exit(1);
	}

	// Check with tidy
	if (!stristr($ret['header'], 'Content-Type: ')) {
		// do nothing
	} else if (stristr($ret['header'], 'Content-Type: application/octet-stream')) {
		// do nothing
	} else if (stristr($ret['header'], 'Content-Type: text/html')) {
		// some fixes
		$ret['body'] = str_replace('autocomplete="off"', '', $ret['body']);
		$ret['body'] = str_replace('<xss>', '|xss|', $ret['body']);
		file_put_contents("http.tidy.in", $ret['body']);
		$cmd = "tidy -errors -utf8 -file http.tidy.out http.tidy.in";
		$r = rg_exec($cmd, '', FALSE, FALSE, FALSE);
		if ($r['ok'] != 1) {
			rg_log_ml('body: ' . $ret['body']);
			rg_log_ml('tidy error: ' . $r['stderr']);
			rg_log_ml(file_get_contents('http.tidy.out'));
			exit(1);
		}
	} else if (stristr($ret['header'], 'Content-Type: application/json')) {
		$ret['json'] = @json_decode($ret['body'], TRUE);
		if ($ret['json'] === NULL) {
			rg_log('body: ' . $ret['body']);
			rg_log('Cannot decode JSON: ' . json_last_error_msg() . '!');
			exit(1);
		}
		rg_log_ml('Decoded JSON: ' . print_r($ret['json'], TRUE));
	} else {
		rg_log('I do not know how to deal with this content-type!');
		exit(1);
	}

	// Check if a '@@' is present
	if (strstr($ret['body'], '@@')) {
		$t = explode('@@', $ret['body']);
		$t = explode('@@', $t[1]);
		if (!strstr($t[0], ' ')) {
			rg_log_ml('body: ' . $ret['body']);
			rg_log("We have unresolved variables: [" . $t[0] . "]!");
			exit(1);
		}
	}

	// Find cookies
	$ret['cookies'] = array();
	$x = preg_match_all('/Set-Cookie: (.*?)=(.*?)[;]/',
		$ret['header'], $matches, PREG_SET_ORDER);
	if ($x !== FALSE) {
		foreach ($matches as $junk => $info) {
			$k = $info[1];
			$v = $info[2];
			$ret['cookies'][$k] = $v;
		}
	}
	//rg_log_ml('ret[cookies]: ' . print_r($ret['cookies'], TRUE));

	$ret['sid'] = '';
	if (isset($ret['cookies']['sidu']))
		$ret['sid'] = $ret['cookies']['sidu'];
	if (isset($ret['cookies']['sids']))
		$ret['sid'] = $ret['cookies']['sids'];

	$ret['tokens'] = array();
	$x = preg_match_all('/ name="token" value="([a-zA-Z0-9_:]*)"/',
		$ret['body'], $matches);
	//rg_log_ml('DEBUG: tokens matches: ' . print_r($matches, TRUE));
	if (($x === FALSE) || (!isset($matches[1]))) {
		//rg_log("CHECK: no token found");
	} else {
		foreach ($matches[1] as $m) {
			$t = explode(':', $m);
			if (!isset($t[1])) {
				rg_log_ml('body: ' . print_r($ret['body'], TRUE));
				rg_log_ml('matches: ' . print_r($matches[1], TRUE));
				rg_log('Invalid debug token (no prefix): ' . $m);
				exit(1);
			}
			$ret['tokens'][$t[1]] = $t[0];
		}
	}
	rg_log('DEBUG ret[tokens]: ' . rg_array2string($ret['tokens']) . '.');

	// Collect '<input>' tags
	$ret['inputs'] = array();
	$x = preg_match_all('/<input .* name="(.*?)" .*value="(.*?)"/uD',
		$ret['body'], $matches);
	//rg_log_ml('DEBUG: inputs matches: ' . print_r($matches, TRUE));
	if (($x === FALSE) || (!isset($matches[1]))) {
		//rg_log("CHECK: no token found");
	} else {
		foreach ($matches[1] as $i => $d)
			$ret['inputs'][$d] = $matches[2][$i];
	}
	//rg_log_ml('DEBUG ret[inputs]: ' .print_r($ret['inputs'], TRUE));

	// find logout token
	$x = preg_match('/logout\?token=([a-zA-Z0-9:]*)"/', $ret['body'], $matches);
	//rg_log_ml('DEBUG: matches[logout]: ' . print_r($matches, TRUE));
	if (($x === FALSE) || (!isset($matches[1]))) {
		$ret['tokens']['logout'] = '';
	} else {
		$t = explode(':', $matches[1]);
		$ret['tokens']['logout'] = $t[0];
	}

	$x = preg_match_all('/ class="secret_token">([A-Z0-9]*)</', $ret['body'], $matches);
	if (($x !== FALSE) && (isset($matches[1])) && isset($matches[1][0])) {
		$ret['totp_secret'] = $matches[1][0];
		rg_log('DEBUG ret[totp_secret]=' . $ret['totp_secret']);
	}

	@rename('http-last.out', 'http-prev.out');
	file_put_contents('http-last.out', $ret['body']);

	return $ret;
}

/*
 * Helper function that will do the login
 */
function test_login($url, $rg_ui)
{
	global $test_ua;

	// First we need to load the form so we can get the token
	$data = array();
	$r = do_req($url . "/op/login", $data, $headers);
	if ($r === FALSE) {
		rg_log('Cannot load login form!');
		return FALSE;
	}
	if (!isset($r['tokens']['login'])) {
		rg_log_ml('r: ' . print_r($r, TRUE));
		rg_log('Login token not returned!');
		return FALSE;
	}
	$good_token = $r['tokens']['login'];

	// Now, post login form
	rg_log("Do the real login post request");
	$data = array(
		"doit" => 1,
		"token" => $good_token,
		"user" => $rg_ui['username'],
		"pass" => $rg_ui['pass'],
		"lock_ip" => 1
	);
	if (isset($rg_ui['t']))
		$data['t'] = $rg_ui['t'];
	$headers = array();
	$r = do_req($url . "/op/login", $data, $headers);
	if ($r === FALSE) {
		rg_log_ml("Cannot login: " . print_r($r, TRUE));
		return FALSE;
	}

	if (strstr($r['body'], "invalid user")) {
		rg_log_ml(print_r($r, TRUE));
		rg_log("Login invalid. Check above!");
		return FALSE;
	}

	return $r;
}

/*
 * Restore password aaaa for user catab
 */
function test_restore($db)
{
	$salt = 'd0a41957b835fbf7bfe63b750db15108cc048259';
	$pass = 'aaaa';
	$pass = rg_user_pass($salt, $pass);
	$sql = "UPDATE users SET salt = '$salt'"
		. ", pass = '$pass'"
		. ", session_time = 3600"
		. " WHERE username = 'catab'";
	$res = rg_sql_query($db, $sql);
	if ($res == FALSE) {
		rg_log("Cannot update (" . rg_sql_error() . ")!");
		exit(1);
	}
	rg_sql_free_result($res);

	rg_cache_unset('user::4::info', RG_SOCKET_NO_WAIT);
}

/*
 * Set user agent
 */
function test_set_ua($s)
{
	global $test_ua;

	$test_ua = $s;
}

/*
 * Set referer
 */
function test_set_referer($s)
{
	global $test_referer;

	$test_referer = $s;
}

?>
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