Files

229 lines
7.6 KiB
PHP

<?php
function git_service_native_available() {
return function_exists('inflate_init')
&& function_exists('inflate_add')
&& function_exists('inflate_get_read_len')
&& function_exists('gzcompress')
&& function_exists('hash')
&& in_array('sha1', hash_algos(), TRUE);
}
function git_service_write_repository_file($path, $contents) {
return @file_put_contents($path, $contents, LOCK_EX) === strlen($contents);
}
/* A newly-created repository starts with an unborn main branch. If the first
push creates a differently named branch, make that branch the default. */
function git_service_update_unborn_head($git_path, $updated_refs) {
$head = resolve_ref($git_path, 'HEAD');
if ($head[1] !== NULL) {
return TRUE;
}
$head_path = get_safe_file_path($git_path, '/HEAD');
$contents = $head_path === FALSE ? FALSE : @file_get_contents($head_path);
if ($contents === FALSE
|| !preg_match('~^ref:\s*(refs/heads/[^\r\n]+)\s*$~', $contents)) {
return TRUE;
}
$branch = NULL;
foreach ($updated_refs as $ref) {
$resolved = resolve_ref($git_path, $ref);
if (strpos($ref, 'refs/heads/') === 0 && $resolved[1] !== NULL) {
$branch = $ref;
break;
}
}
if ($branch === NULL) {
return TRUE;
}
$new_contents = "ref: ".$branch."\n";
return @file_put_contents($git_path.'/HEAD', $new_contents, LOCK_EX)
=== strlen($new_contents);
}
function git_service_init_bare_repository($path) {
$directories = array(
'',
'/branches',
'/hooks',
'/info',
'/objects',
'/objects/info',
'/objects/pack',
'/refs',
'/refs/heads',
'/refs/tags');
foreach ($directories as $directory) {
if (!@mkdir($path.$directory, 0777) && !is_dir($path.$directory)) {
return FALSE;
}
}
$files = array(
'/HEAD' => "ref: refs/heads/main\n",
'/config' => "[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n\tbare = true\n",
'/description' => "Unnamed repository; edit this file 'description' to name the repository.\n");
foreach ($files as $name => $contents) {
if (!git_service_write_repository_file($path.$name, $contents)) {
return FALSE;
}
}
return TRUE;
}
function git_service_create_managed_repository(
$configuration,
$value,
$owner_user_id,
$private) {
$name = normalize_managed_repository_name($value);
if ($name === FALSE) {
return array('status' => 'invalid_name');
}
$root = managed_repository_root($configuration);
if ($root === FALSE || !is_writable($root) || @scandir($root) === FALSE) {
return array('status' => 'root_unavailable');
}
$path = $root.DIRECTORY_SEPARATOR.$name;
$lock_path = $root.DIRECTORY_SEPARATOR.'.create.lock';
$lock = @fopen($lock_path, 'c+b');
if ($lock === FALSE) {
$status = file_exists($path) || is_link($path) ? 'already_exists' : 'create_failed';
return array('status' => $status, 'name' => $name);
}
if (!@flock($lock, LOCK_EX | LOCK_NB)) {
fclose($lock);
return array('status' => 'create_busy', 'name' => $name);
}
$temporary_path = NULL;
try {
if (file_exists($path) || is_link($path)) {
if (managed_repository_is_bare($path)) {
$recovery = auth_recover_repository_metadata(
$name, (int) $owner_user_id, $private);
if ($recovery['status'] === 'recovered') {
return array(
'status' => 'created',
'name' => $name,
'path' => $path,
'private' => (bool) $private);
}
if ($recovery['status'] === 'database_unavailable') {
return array('status' => 'metadata_unavailable', 'name' => $name);
}
}
return array('status' => 'already_exists', 'name' => $name);
}
try {
$suffix = bin2hex(random_bytes(16));
} catch (Exception $exception) {
return array('status' => 'create_failed', 'name' => $name);
}
$temporary_path = $root.DIRECTORY_SEPARATOR.'.create-'.$suffix.'.tmp';
if (!git_service_init_bare_repository($temporary_path)
|| !managed_repository_is_bare($temporary_path)) {
return array('status' => 'create_failed', 'name' => $name);
}
if (file_exists($path) || is_link($path)) {
return array('status' => 'already_exists', 'name' => $name);
}
$reservation = auth_reserve_repository_metadata(
$name, (int) $owner_user_id, $private);
if ($reservation['status'] === 'already_exists') {
return array('status' => 'already_exists', 'name' => $name);
}
if ($reservation['status'] !== 'reserved') {
return array('status' => 'metadata_unavailable', 'name' => $name);
}
if (!@rename($temporary_path, $path)) {
return array('status' => 'create_failed', 'name' => $name);
}
$temporary_path = NULL;
if (!auth_complete_repository_metadata(
$reservation['id'], (int) $owner_user_id)) {
return array('status' => 'metadata_unavailable', 'name' => $name);
}
return array(
'status' => 'created',
'name' => $name,
'path' => $path,
'private' => (bool) $private);
} finally {
if ($temporary_path !== NULL) {
remove_managed_repository_directory($temporary_path);
}
flock($lock, LOCK_UN);
fclose($lock);
}
}
function git_service_advertise($application, $repository, $request, $service) {
if (!git_service_native_available()) {
send_error(503, 'Service Unavailable', 'The native Git service requires PHP zlib and hash support.');
}
repository_header_nocache($repository);
header('Content-Type: application/x-'.$service.'-advertisement');
header('X-Content-Type-Options: nosniff');
echo format_packet_line('# service='.$service."\n").'0000';
if ($service === 'git-upload-pack') {
if (!git_upload_pack_advertise_native($repository)) {
error_log('Native Git advertisement failed for '.$repository['url'].'.');
}
return;
}
if ($service === 'git-receive-pack') {
if (!git_receive_pack_advertise_native($repository)) {
error_log('Native Git receive advertisement failed for '.$repository['url'].'.');
}
return;
}
send_error(403, 'Forbidden', 'Unsupported Git service.');
}
function git_service_rpc($application, $repository, $request, $service, $input) {
if (!git_service_native_available()) {
send_error(503, 'Service Unavailable', 'The native Git service requires PHP zlib and hash support.');
}
repository_header_nocache($repository);
header('Content-Type: application/x-'.$service.'-result');
header('X-Content-Type-Options: nosniff');
if ($service === 'git-upload-pack') {
if (!git_upload_pack_rpc_native($repository, $input)) {
error_log('Native Git upload-pack failed for '.$repository['url'].'.');
}
return;
}
if ($service === 'git-receive-pack') {
if (!git_receive_pack_rpc_native($repository, $input)) {
error_log('Native Git receive-pack failed for '.$repository['url'].'.');
return 1;
}
return 0;
}
send_error(403, 'Forbidden', 'Unsupported Git service.');
}