misuzu/public/_github-callback.php

147 lines
4.4 KiB
PHP

<?php
namespace Misuzu;
use Misuzu\DB;
use Misuzu\Changelog\ChangelogChange;
$misuzuBypassLockdown = true;
require_once '../misuzu.php';
function ghcb_strip_prefix(string $line): string {
$findColon = mb_strpos($line, ':');
return trim($findColon === false || $findColon >= 10 ? $line : mb_substr($line, $findColon + 1));
}
function ghcb_changelog_action(string &$line): int {
$original = trim($line);
$line = ghcb_strip_prefix($line);
$firstSix = mb_strtolower(mb_substr($original, 0, 6));
if($firstSix === 'revert'
|| $firstSix === 'restor')
return ChangelogChange::ACTION_REVERT;
if($firstSix === 'import')
return ChangelogChange::ACTION_IMPORT;
$firstThree = mb_strtolower(mb_substr($original, 0, 3));
if($firstThree === 'add'
|| $firstSix === 'create')
return ChangelogChange::ACTION_ADD;
if($firstThree === 'fix')
return ChangelogChange::ACTION_FIX;
$firstFour = mb_strtolower(mb_substr($original, 0, 4));
$firstEight = mb_strtolower(mb_substr($original, 0, 8));
if($firstSix === 'delete'
|| $firstSix === 'remove'
|| $firstFour === 'nuke'
|| $firstEight === 'dropkick')
return ChangelogChange::ACTION_REMOVE;
return ChangelogChange::ACTION_UPDATE;
}
header('Content-Type: text/plain; charset=utf-8');
if($_SERVER['REQUEST_METHOD'] !== 'POST')
die('no');
$config = MSZ_ROOT . '/config/github.ini';
if(!is_file($config))
die('config missing');
$config = parse_ini_file(MSZ_ROOT . '/config/github.ini', true);
if(empty($config['tokens']['token']))
die('config invalid');
$isGitea = isset($_SERVER['HTTP_X_GITEA_DELIVERY']) && isset($_SERVER['HTTP_X_GITEA_EVENT']);
$rawData = file_get_contents('php://input');
$sigParts = $isGitea
? ['sha256', $_SERVER['HTTP_X_GITEA_SIGNATURE']]
: explode('=', $_SERVER['HTTP_X_HUB_SIGNATURE'] ?? '', 2);
if(empty($sigParts[1]))
die('invalid signature');
$repoAuthenticated = false;
foreach($config['tokens']['token'] as $repoName => $repoToken) {
if(hash_equals(hash_hmac($sigParts[0], $rawData, $repoToken), $sigParts[1])) {
$repoAuthenticated = true;
break;
}
}
if(!$repoAuthenticated)
die('signature check failed');
$data = json_decode($_SERVER['CONTENT_TYPE'] === 'application/x-www-form-urlencoded'
? $_POST['payload']
: $rawData);
if(empty($data))
die('body is corrupt');
if(empty($data->repository->full_name))
die('body is corrupt');
if($data->repository->full_name !== $repoName)
die('invalid repository token');
if($_SERVER['HTTP_X_GITHUB_EVENT'] !== 'push')
die('only push event is supported');
$commitCount = count($data->commits);
if($commitCount < 1)
die('no commits received');
$repoInfo = $config['repo:' . $repoName] ?? [];
$repoMaster = 'refs/heads/master';
if(!empty($repoInfo['master']))
$repoMaster = $repoInfo['master'];
if($data->ref !== $repoMaster)
die('only the master branch is tracked');
// the actual changelog api sucks ass
$changeCreate = DB::prepare('INSERT INTO `msz_changelog_changes` (`change_log`, `change_text`, `change_action`, `user_id`, `change_created`) VALUES (:log, :text, :action, :user, FROM_UNIXTIME(:created))');
$changeTag = DB::prepare('REPLACE INTO `msz_changelog_change_tags` VALUES (:change_id, :tag_id)');
$tags = $repoInfo['tags'] ?? [];
$addresses = $config['addresses'] ?? [];
foreach($data->commits as $commit) {
$message = trim($commit->message);
if(mb_strpos($message, $repoName) !== false
|| mb_substr($message, 0, 2) === '//'
|| mb_strpos(mb_strtolower($message), 'merge pull request') !== false)
continue;
$index = mb_strpos($message, "\n");
$line = $index === false ? $message : mb_substr($message, 0, $index);
$body = trim($index === false ? '' : mb_substr($message, $index + 1));
$changeCreate->bind('user', $addresses[$commit->author->email] ?? null);
$changeCreate->bind('action', ghcb_changelog_action($line));
$changeCreate->bind('log', $line);
$changeCreate->bind('text', empty($body) ? null : $body);
$changeCreate->bind('created', max(1, strtotime($commit->timestamp)));
$changeId = $changeCreate->executeGetId();
if(!empty($tags) && !empty($changeId)) {
$changeTag->bind('change_id', $changeId);
foreach($tags as $tag) {
$changeTag->bind('tag_id', $tag);
$changeTag->execute();
}
}
unset($changeId, $tag);
}