mirror of
https://github.com/OPSnet/Gazelle.git
synced 2026-01-16 18:04:34 -05:00
store the request context in the Base class
This commit is contained in:
19
app/Base.php
19
app/Base.php
@@ -5,16 +5,25 @@ namespace Gazelle;
|
||||
abstract class Base {
|
||||
protected static DB\Mysql $db;
|
||||
protected static Cache $cache;
|
||||
protected static BaseRequestContext $requestContext;
|
||||
protected static \Twig\Environment $twig;
|
||||
|
||||
public static function initialize(Cache $cache, DB\Mysql $db, \Twig\Environment $twig): void {
|
||||
self::$db = $db;
|
||||
self::$cache = $cache;
|
||||
self::$twig = $twig;
|
||||
static::$db = $db;
|
||||
static::$cache = $cache;
|
||||
static::$twig = $twig;
|
||||
}
|
||||
|
||||
public static function setRequestContext(BaseRequestContext $c): void {
|
||||
static::$requestContext = $c;
|
||||
}
|
||||
|
||||
public function requestContext(): BaseRequestContext {
|
||||
return static::$requestContext;
|
||||
}
|
||||
|
||||
public function enumList(string $table, string $column): array {
|
||||
$columnType = (string)self::$db->scalar("
|
||||
$columnType = (string)static::$db->scalar("
|
||||
SELECT column_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = ?
|
||||
@@ -29,7 +38,7 @@ abstract class Base {
|
||||
}
|
||||
|
||||
public function enumDefault(string $table, string $column): ?string {
|
||||
$default = self::$db->scalar("
|
||||
$default = static::$db->scalar("
|
||||
SELECT column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = ?
|
||||
|
||||
80
app/BaseRequestContext.php
Normal file
80
app/BaseRequestContext.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Gazelle;
|
||||
|
||||
class BaseRequestContext {
|
||||
protected string $module; // previously known as global $Document
|
||||
protected bool $isValid;
|
||||
protected array $ua;
|
||||
|
||||
public function __construct(
|
||||
protected readonly string $scriptName,
|
||||
protected string $remoteAddr,
|
||||
string $useragent,
|
||||
) {
|
||||
$info = pathinfo($scriptName);
|
||||
if (!array_key_exists('dirname', $info)) {
|
||||
$this->module = '';
|
||||
$this->isValid = false;
|
||||
} else {
|
||||
$this->module = $info['filename'];
|
||||
$this->isValid = $info['dirname'] === '/';
|
||||
}
|
||||
$this->ua = \parse_user_agent($useragent);
|
||||
}
|
||||
|
||||
public function ua(): array {
|
||||
return $this->ua;
|
||||
}
|
||||
|
||||
public function browser(): ?string {
|
||||
return $this->ua()['Browser'];
|
||||
}
|
||||
|
||||
public function browserVersion(): ?string {
|
||||
return $this->ua()['BrowserVersion'];
|
||||
}
|
||||
|
||||
public function isValid(): bool {
|
||||
return $this->isValid;
|
||||
}
|
||||
|
||||
public function module(): string {
|
||||
return $this->module;
|
||||
}
|
||||
|
||||
public function os(): ?string {
|
||||
return $this->ua()['OperatingSystem'];
|
||||
}
|
||||
|
||||
public function osVersion(): ?string {
|
||||
return $this->ua()['OperatingSystemVersion'];
|
||||
}
|
||||
|
||||
public function remoteAddr(): string {
|
||||
return $this->remoteAddr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Because we <3 our staff
|
||||
*/
|
||||
public function anonymize(): static {
|
||||
$this->ua = [
|
||||
'Browser' => 'staff-browser',
|
||||
'BrowserVersion' => null,
|
||||
'OperatingSystem' => null,
|
||||
'OperatingSystemVersion' => null,
|
||||
];
|
||||
$this->remoteAddr = '127.0.0.1';
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Early in the startup phase, it may be desirable to
|
||||
* redirect processing to another section.
|
||||
*/
|
||||
public function setModule(string $module): static {
|
||||
$this->module = $module;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -263,17 +263,28 @@ class Mysql {
|
||||
if ($this->LinkID === false) {
|
||||
return false;
|
||||
}
|
||||
// In the event of a MySQL deadlock, we sleep allowing MySQL time to unlock, then attempt again for a maximum of 5 tries
|
||||
// In the event of a MySQL deadlock, we sleep allowing MySQL time to unlock
|
||||
// then attempt again for a maximum of 5 attempts
|
||||
$sleep = 0.5;
|
||||
for ($i = 1; $i < 6; $i++) {
|
||||
$this->QueryID = $Closure();
|
||||
if (!in_array(mysqli_errno($this->LinkID), [1213, 1205])) {
|
||||
break;
|
||||
}
|
||||
global $Debug;
|
||||
$Debug->analysis('Non-Fatal Deadlock:', $Query, 3600 * 24);
|
||||
// if we have a viewer, we have a request context, otherwise, it must be script
|
||||
global $Debug, $Viewer;
|
||||
$Debug->analysis(
|
||||
is_null($Viewer) && isset($_SERVER['argv'])
|
||||
? ($_SERVER['argv'][0] ?? 'cli')
|
||||
: $Viewer->requestContext()->module(),
|
||||
'Non-Fatal Deadlock:',
|
||||
$Query,
|
||||
86_400,
|
||||
);
|
||||
trigger_error("Database deadlock, attempt $i");
|
||||
|
||||
sleep($i * random_int(2, 5)); // Wait longer as attempts increase
|
||||
usleep((int)($sleep * 1e6));
|
||||
$sleep *= 1.75;
|
||||
}
|
||||
$QueryEndTime = microtime(true);
|
||||
// Kills admin pages, and prevents Debug->analysis when the whole set exceeds 1 MB
|
||||
|
||||
@@ -73,7 +73,10 @@ class Debug {
|
||||
}
|
||||
|
||||
if (isset($Reason[0])) {
|
||||
$this->analysis(implode(', ', $Reason));
|
||||
$this->analysis(
|
||||
$user->requestContext()->module(),
|
||||
implode(', ', $Reason)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -135,23 +138,22 @@ class Debug {
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function analysis($Message, $Report = ''): void {
|
||||
$RequestURI = empty($_SERVER['REQUEST_URI']) ? '' : substr($_SERVER['REQUEST_URI'], 1);
|
||||
public function analysis(string $module, string $message, string $report = ''): void {
|
||||
$uri = empty($_SERVER['REQUEST_URI']) ? '' : substr($_SERVER['REQUEST_URI'], 1);
|
||||
if (
|
||||
PHP_SAPI === 'cli'
|
||||
|| in_array($RequestURI, ['tools.php?action=db_sandbox'])
|
||||
|| in_array($uri, ['tools.php?action=db_sandbox'])
|
||||
) {
|
||||
// Don't spam IRC from Boris or these pages
|
||||
return;
|
||||
}
|
||||
if (empty($Report)) {
|
||||
$Report = $Message;
|
||||
if (empty($report)) {
|
||||
$report = $message;
|
||||
}
|
||||
$case = $this->saveCase($Report);
|
||||
global $Document;
|
||||
Irc::sendMessage(IRC_CHAN_STATUS, "{$Message} $Document "
|
||||
$case = $this->saveCase($report);
|
||||
Irc::sendMessage(IRC_CHAN_STATUS, "{$message} $module "
|
||||
. SITE_URL . "/tools.php?action=analysis&case=$case "
|
||||
. SITE_URL . '/' . $RequestURI
|
||||
. SITE_URL . "/{$uri}"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -192,7 +192,12 @@ class Torrent {
|
||||
) {
|
||||
$ErrMsg = "Search\Torrent constructor arguments:\n" . print_r(func_get_args(), true);
|
||||
global $Debug;
|
||||
$Debug->analysis('Bad arguments in Search\Torrent constructor', $ErrMsg, 3600 * 24);
|
||||
$Debug->analysis(
|
||||
$tgMan->requestContext()->module(),
|
||||
'Bad arguments in Search\Torrent constructor',
|
||||
$ErrMsg,
|
||||
86_400,
|
||||
);
|
||||
error('-1');
|
||||
}
|
||||
$this->Page = $searchMany ? $Page : min($Page, SPHINX_MAX_MATCHES / $PageSize);
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<?php
|
||||
|
||||
require_once(__DIR__ . '/../lib/bootstrap.php');
|
||||
|
||||
Gazelle\Base::setRequestContext(
|
||||
new Gazelle\BaseRequestContext('cli', '127.0.0.1', 'scheduler')
|
||||
);
|
||||
(new Gazelle\TaskScheduler)->run();
|
||||
|
||||
@@ -91,19 +91,23 @@ class Sphinxql extends mysqli {
|
||||
|
||||
/**
|
||||
* Print a message to privileged users and optionally halt page processing
|
||||
*
|
||||
* @param string $Msg message to display
|
||||
* @param bool $Halt halt page processing. Default is to continue processing the page
|
||||
*/
|
||||
public function error($Msg, $Halt = false) {
|
||||
public function error(string $message, bool $halt = false) {
|
||||
global $Debug, $Viewer;
|
||||
$ErrorMsg = 'SphinxQL (' . $this->Ident . '): ' . strval($Msg);
|
||||
$Debug->analysis('SphinxQL Error', $ErrorMsg, 3600 * 24);
|
||||
if ($Halt === true && (DEBUG_MODE || $Viewer->permitted('site_debug'))) {
|
||||
echo '<pre>' . display_str($ErrorMsg) . '</pre>';
|
||||
die();
|
||||
} elseif ($Halt === true) {
|
||||
error('-1');
|
||||
$error = "SphinxQL ({$this->Ident}): $message";
|
||||
$Debug->analysis(
|
||||
$Viewer->requestContext()->module(),
|
||||
'SphinxQL Error',
|
||||
$error,
|
||||
86_400,
|
||||
);
|
||||
if ($halt === true) {
|
||||
if (DEBUG_MODE || $Viewer->permitted('site_debug')) {
|
||||
echo '<pre>' . display_str($error) . '</pre>';
|
||||
die();
|
||||
} else {
|
||||
error('-1');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -337,8 +337,14 @@ class Text {
|
||||
}
|
||||
parse_str($info['query'] ?? '', $args);
|
||||
|
||||
if (isset($args['postid']) && isset($info['path']) && in_array($info['path'],
|
||||
['/artist.php', '/collages.php', '/requests.php', '/torrents.php'])) {
|
||||
if (
|
||||
isset($args['postid'])
|
||||
&& isset($info['path'])
|
||||
&& in_array(
|
||||
$info['path'],
|
||||
['/artist.php', '/collages.php', '/requests.php', '/torrents.php']
|
||||
)
|
||||
) {
|
||||
return self::bbcodeCommentUrl((int)$args['postid']);
|
||||
}
|
||||
|
||||
@@ -1207,8 +1213,8 @@ class Text {
|
||||
* that html_escape does.
|
||||
*/
|
||||
public static function parse_html(string $Html): string {
|
||||
$Document = new DOMDocument();
|
||||
$Document->loadHTML(stripslashes($Html));
|
||||
$dom = new DOMDocument();
|
||||
$dom->loadHTML(stripslashes($Html));
|
||||
|
||||
// For any manipulation that we do on the DOM tree, always go in reverse order or
|
||||
// else you end up with broken array pointers and missed elements
|
||||
@@ -1222,42 +1228,42 @@ class Text {
|
||||
}
|
||||
};
|
||||
|
||||
$Elements = $Document->getElementsByTagName('div');
|
||||
$Elements = $dom->getElementsByTagName('div');
|
||||
for ($i = $Elements->length - 1; $i >= 0; $i--) {
|
||||
/** @var \DOMElement $Element */
|
||||
$Element = $Elements->item($i);
|
||||
if (str_contains($Element->getAttribute('style'), 'text-align')) {
|
||||
$NewElement = $Document->createElement('align');
|
||||
$NewElement = $dom->createElement('align');
|
||||
$CopyNode($Element, $NewElement);
|
||||
$NewElement->setAttribute('align', str_replace('text-align: ', '', $Element->getAttribute('style')));
|
||||
$Element->parentNode->replaceChild($NewElement, $Element);
|
||||
}
|
||||
}
|
||||
|
||||
$Elements = $Document->getElementsByTagName('span');
|
||||
$Elements = $dom->getElementsByTagName('span');
|
||||
for ($i = $Elements->length - 1; $i >= 0; $i--) {
|
||||
/** @var \DOMElement $Element */
|
||||
$Element = $Elements->item($i);
|
||||
if (str_contains($Element->getAttribute('class'), 'size')) {
|
||||
$NewElement = $Document->createElement('size');
|
||||
$NewElement = $dom->createElement('size');
|
||||
$CopyNode($Element, $NewElement);
|
||||
$NewElement->setAttribute('size', str_replace('size', '', $Element->getAttribute('class')));
|
||||
$Element->parentNode->replaceChild($NewElement, $Element);
|
||||
} elseif (str_contains($Element->getAttribute('style'), 'font-style: italic')) {
|
||||
$NewElement = $Document->createElement('italic');
|
||||
$NewElement = $dom->createElement('italic');
|
||||
$CopyNode($Element, $NewElement);
|
||||
$Element->parentNode->replaceChild($NewElement, $Element);
|
||||
} elseif (str_contains($Element->getAttribute('style'), 'text-decoration: underline')) {
|
||||
$NewElement = $Document->createElement('underline');
|
||||
$NewElement = $dom->createElement('underline');
|
||||
$CopyNode($Element, $NewElement);
|
||||
$Element->parentNode->replaceChild($NewElement, $Element);
|
||||
} elseif (str_contains($Element->getAttribute('style'), 'color: ')) {
|
||||
$NewElement = $Document->createElement('color');
|
||||
$NewElement = $dom->createElement('color');
|
||||
$CopyNode($Element, $NewElement);
|
||||
$NewElement->setAttribute('color', str_replace(['color: ', ';'], '', $Element->getAttribute('style')));
|
||||
$Element->parentNode->replaceChild($NewElement, $Element);
|
||||
} elseif (preg_match("/display:[ ]*inline\-block;[ ]*padding:/", $Element->getAttribute('style')) !== false) {
|
||||
$NewElement = $Document->createElement('pad');
|
||||
$NewElement = $dom->createElement('pad');
|
||||
$CopyNode($Element, $NewElement);
|
||||
$Padding = explode(' ', trim(explode(':', (explode(';', $Element->getAttribute('style'))[1]))[1]));
|
||||
$NewElement->setAttribute('pad', implode('|', array_map(fn($x) => rtrim($x, 'px'), $Padding)));
|
||||
@@ -1265,44 +1271,44 @@ class Text {
|
||||
}
|
||||
}
|
||||
|
||||
$Elements = $Document->getElementsByTagName('ul');
|
||||
$Elements = $dom->getElementsByTagName('ul');
|
||||
for ($i = 0; $i < $Elements->length; $i++) {
|
||||
/** @var \DOMElement $Element */
|
||||
$Element = $Elements->item($i);
|
||||
$InnerElements = $Element->getElementsByTagName('li');
|
||||
for ($j = $InnerElements->length - 1; $j >= 0; $j--) {
|
||||
$Element = $InnerElements->item($j);
|
||||
$NewElement = $Document->createElement('bullet');
|
||||
$NewElement = $dom->createElement('bullet');
|
||||
$CopyNode($Element, $NewElement);
|
||||
$Element->parentNode->replaceChild($NewElement, $Element);
|
||||
}
|
||||
}
|
||||
|
||||
$Elements = $Document->getElementsByTagName('ol');
|
||||
$Elements = $dom->getElementsByTagName('ol');
|
||||
for ($i = 0; $i < $Elements->length; $i++) {
|
||||
/** @var \DOMElement $Element */
|
||||
$Element = $Elements->item($i);
|
||||
$InnerElements = $Element->getElementsByTagName('li');
|
||||
for ($j = $InnerElements->length - 1; $j >= 0; $j--) {
|
||||
$Element = $InnerElements->item($j);
|
||||
$NewElement = $Document->createElement('number');
|
||||
$NewElement = $dom->createElement('number');
|
||||
$CopyNode($Element, $NewElement);
|
||||
$Element->parentNode->replaceChild($NewElement, $Element);
|
||||
}
|
||||
}
|
||||
|
||||
$Elements = $Document->getElementsByTagName('strong');
|
||||
$Elements = $dom->getElementsByTagName('strong');
|
||||
for ($i = $Elements->length - 1; $i >= 0; $i--) {
|
||||
/** @var \DOMElement $Element */
|
||||
$Element = $Elements->item($i);
|
||||
if (in_array('important_text', explode(' ', $Element->getAttribute('class')))) {
|
||||
$NewElement = $Document->createElement('important');
|
||||
$NewElement = $dom->createElement('important');
|
||||
$CopyNode($Element, $NewElement);
|
||||
$Element->parentNode->replaceChild($NewElement, $Element);
|
||||
}
|
||||
}
|
||||
|
||||
$Elements = $Document->getElementsByTagName('a');
|
||||
$Elements = $dom->getElementsByTagName('a');
|
||||
for ($i = $Elements->length - 1; $i >= 0; $i--) {
|
||||
/** @var \DOMElement $Element */
|
||||
$Element = $Elements->item($i);
|
||||
@@ -1311,14 +1317,16 @@ class Text {
|
||||
$Element->removeAttribute('target');
|
||||
if ($Element->getAttribute('href') === $Element->nodeValue) {
|
||||
$Element->removeAttribute('href');
|
||||
} elseif ($Element->getAttribute('href') === 'javascript:void(0);'
|
||||
&& $Element->getAttribute('onclick') === 'BBCode.spoiler(this);') {
|
||||
$Spoilers = $Document->getElementsByTagName('blockquote');
|
||||
} elseif (
|
||||
$Element->getAttribute('href') === 'javascript:void(0);'
|
||||
&& $Element->getAttribute('onclick') === 'BBCode.spoiler(this);'
|
||||
) {
|
||||
$Spoilers = $dom->getElementsByTagName('blockquote');
|
||||
for ($j = $Spoilers->length - 1; $j >= 0; $j--) {
|
||||
/** @var \DOMElement $Spoiler */
|
||||
$Spoiler = $Spoilers->item($j);
|
||||
if ($Spoiler->hasAttribute('class') && $Spoiler->getAttribute('class') === 'hidden spoiler') {
|
||||
$NewElement = $Document->createElement('spoiler');
|
||||
$NewElement = $dom->createElement('spoiler');
|
||||
$CopyNode($Spoiler, $NewElement);
|
||||
$Element->parentNode->replaceChild($NewElement, $Element);
|
||||
$Spoiler->parentNode->removeChild($Spoiler);
|
||||
@@ -1326,18 +1334,18 @@ class Text {
|
||||
}
|
||||
}
|
||||
} elseif (str_starts_with($Element->getAttribute('href'), 'artist.php?artistname=')) {
|
||||
$NewElement = $Document->createElement('artist');
|
||||
$NewElement = $dom->createElement('artist');
|
||||
$CopyNode($Element, $NewElement);
|
||||
$Element->parentNode->replaceChild($NewElement, $Element);
|
||||
} elseif (str_starts_with($Element->getAttribute('href'), 'user.php?action=search&search=')) {
|
||||
$NewElement = $Document->createElement('user');
|
||||
$NewElement = $dom->createElement('user');
|
||||
$CopyNode($Element, $NewElement);
|
||||
$Element->parentNode->replaceChild($NewElement, $Element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$Str = (string)$Document->saveHTML($Document->getElementsByTagName('body')->item(0));
|
||||
$Str = (string)$dom->saveHTML($dom->getElementsByTagName('body')->item(0));
|
||||
$Str = str_replace(["<body>\n", "\n</body>", "<body>", "</body>"], "", $Str);
|
||||
$Str = str_replace(["\r\n", "\n"], "", $Str);
|
||||
$Str = preg_replace("/\<strong\>([a-zA-Z0-9 ]+)\<\/strong\>\: \<spoiler\>/", "[spoiler=\\1]", $Str);
|
||||
|
||||
@@ -14,11 +14,14 @@ class View {
|
||||
* @param array<string> $option
|
||||
*/
|
||||
public static function header(string $pageTitle, array $option = []): string {
|
||||
global $Document, $Twig, $Viewer;
|
||||
if ($pageTitle != '') {
|
||||
$pageTitle .= ' :: ';
|
||||
}
|
||||
$pageTitle .= SITE_NAME;
|
||||
global $Viewer;
|
||||
$module = is_null($Viewer)
|
||||
? 'index'
|
||||
: $Viewer->requestContext()->module();
|
||||
|
||||
$js = [
|
||||
'jquery',
|
||||
@@ -34,6 +37,7 @@ class View {
|
||||
array_push($js, ...explode(',', $option['js']));
|
||||
}
|
||||
|
||||
global $Twig;
|
||||
if (!isset($Viewer) || $pageTitle == 'Recover Password :: ' . SITE_NAME) {
|
||||
$js[] = 'storage.class';
|
||||
echo $Twig->render('index/public-header.twig', [
|
||||
@@ -63,7 +67,7 @@ class View {
|
||||
->setStaffPM(new Gazelle\Manager\StaffPM());
|
||||
|
||||
$notifier = new Gazelle\User\Notification($Viewer);
|
||||
$alertList = $notifier->setDocument($Document, $_REQUEST['action'] ?? '')->alertList();
|
||||
$alertList = $notifier->setDocument($module, $_REQUEST['action'] ?? '')->alertList();
|
||||
foreach ($alertList as $alert) {
|
||||
if (in_array($alert->display(), [Gazelle\User\Notification::DISPLAY_TRADITIONAL, Gazelle\User\Notification::DISPLAY_TRADITIONAL_PUSH])) {
|
||||
$activity->setAlert(sprintf('<a href="%s">%s</a>', $alert->notificationUrl(), $alert->title()));
|
||||
@@ -92,7 +96,7 @@ class View {
|
||||
}
|
||||
}
|
||||
|
||||
$PageID = [$Document, $_REQUEST['action'] ?? false, $_REQUEST['type'] ?? false];
|
||||
$PageID = [$module, $_REQUEST['action'] ?? false, $_REQUEST['type'] ?? false];
|
||||
$navLinks = [];
|
||||
foreach ((new Gazelle\Manager\UserNavigation())->userControlList($Viewer) as $n) {
|
||||
[$ID, $Key, $Title, $Target, $Tests, $TestUser, $Mandatory] = array_values($n);
|
||||
@@ -149,7 +153,7 @@ class View {
|
||||
'action_list' => $activity->actionList(),
|
||||
'alert_list' => $activity->alertList(),
|
||||
'bonus' => new Gazelle\User\Bonus($Viewer),
|
||||
'document' => $Document,
|
||||
'document' => $module,
|
||||
'dono_target' => $payMan->monthlyPercent(new Gazelle\Manager\Donation()),
|
||||
'nav_links' => $navLinks,
|
||||
'user' => $Viewer,
|
||||
@@ -201,8 +205,12 @@ class View {
|
||||
$launch = SITE_LAUNCH_YEAR . "-$launch";
|
||||
}
|
||||
|
||||
global $Document;
|
||||
$alertList = (new Gazelle\User\Notification($Viewer))->setDocument($Document, $_REQUEST['action'] ?? '')->alertList();
|
||||
$alertList = (new Gazelle\User\Notification($Viewer))
|
||||
->setDocument(
|
||||
$Viewer->requestContext()->module(),
|
||||
$_REQUEST['action'] ?? ''
|
||||
)
|
||||
->alertList();
|
||||
$notification = [];
|
||||
foreach ($alertList as $alert) {
|
||||
if (in_array($alert->display(), [Gazelle\User\Notification::DISPLAY_POPUP, Gazelle\User\Notification::DISPLAY_POPUP_PUSH])) {
|
||||
|
||||
98
gazelle.php
98
gazelle.php
@@ -5,27 +5,6 @@ use Gazelle\Util\Time;
|
||||
|
||||
// 1. Basic sanity checks and initialization
|
||||
|
||||
if (PHP_VERSION_ID < 80201) {
|
||||
die("Gazelle (Orpheus fork) requires at least PHP version 8.2.1");
|
||||
}
|
||||
foreach (['memcached', 'mysqli'] as $e) {
|
||||
if (!extension_loaded($e)) {
|
||||
die("$e extension not loaded");
|
||||
}
|
||||
}
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
$PathInfo = pathinfo($_SERVER['SCRIPT_NAME']);
|
||||
$Document = $PathInfo['filename'];
|
||||
|
||||
if ($PathInfo['dirname'] !== '/') { /** @phpstan-ignore-line */
|
||||
exit;
|
||||
} elseif (in_array($Document, ['announce', 'scrape']) || (isset($_REQUEST['info_hash']) && isset($_REQUEST['peer_id']))) {
|
||||
die("d14:failure reason40:Invalid .torrent, try downloading again.e");
|
||||
}
|
||||
|
||||
// 2. Start the engine
|
||||
|
||||
require_once(__DIR__ . '/lib/bootstrap.php');
|
||||
global $Cache, $Debug, $Twig;
|
||||
|
||||
@@ -37,9 +16,27 @@ if (
|
||||
) {
|
||||
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||
}
|
||||
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||
$_SERVER['HTTP_USER_AGENT'] = '[no-useragent]';
|
||||
|
||||
$context = new Gazelle\BaseRequestContext(
|
||||
$_SERVER['SCRIPT_NAME'],
|
||||
$_SERVER['REMOTE_ADDR'],
|
||||
$_SERVER['HTTP_USER_AGENT'] ?? '[no-useragent]',
|
||||
);
|
||||
if (!$context->isValid()) {
|
||||
exit;
|
||||
}
|
||||
$module = $context->module();
|
||||
if (
|
||||
in_array($module, ['announce', 'scrape'])
|
||||
|| (
|
||||
isset($_REQUEST['info_hash'])
|
||||
&& isset($_REQUEST['peer_id'])
|
||||
)
|
||||
) {
|
||||
die("d14:failure reason40:Invalid .torrent, try downloading again.e");
|
||||
}
|
||||
|
||||
// 2. Start the engine
|
||||
|
||||
// 3. Do we have a viewer?
|
||||
|
||||
@@ -50,12 +47,12 @@ $userMan = new Gazelle\Manager\User();
|
||||
Gazelle\Util\Twig::setUserMan($userMan);
|
||||
|
||||
// Authorization header only makes sense for the ajax endpoint
|
||||
if (!empty($_SERVER['HTTP_AUTHORIZATION']) && $Document === 'ajax') {
|
||||
if ($ipv4Man->isBanned($_SERVER['REMOTE_ADDR'])) {
|
||||
if (!empty($_SERVER['HTTP_AUTHORIZATION']) && $module === 'ajax') {
|
||||
if ($ipv4Man->isBanned($context->remoteAddr())) {
|
||||
header('Content-type: application/json');
|
||||
json_die('failure', 'your ip address has been banned');
|
||||
}
|
||||
[$success, $result] = $userMan->findByAuthorization($ipv4Man, $_SERVER['HTTP_AUTHORIZATION'], $_SERVER['REMOTE_ADDR']);
|
||||
[$success, $result] = $userMan->findByAuthorization($ipv4Man, $_SERVER['HTTP_AUTHORIZATION'], $context->remoteAddr());
|
||||
if ($success) {
|
||||
$Viewer = $result;
|
||||
define('AUTHED_BY_TOKEN', true);
|
||||
@@ -66,7 +63,7 @@ if (!empty($_SERVER['HTTP_AUTHORIZATION']) && $Document === 'ajax') {
|
||||
} elseif (isset($_COOKIE['session'])) {
|
||||
$forceLogout = function (): never {
|
||||
setcookie('session', '', [
|
||||
'expires' => time() - 60 * 60 * 24 * 90,
|
||||
'expires' => time() - 86_400 * 90,
|
||||
'path' => '/',
|
||||
'secure' => !DEBUG_MODE,
|
||||
'httponly' => true,
|
||||
@@ -84,7 +81,7 @@ if (!empty($_SERVER['HTTP_AUTHORIZATION']) && $Document === 'ajax') {
|
||||
if (is_null($Viewer)) {
|
||||
$forceLogout();
|
||||
}
|
||||
if ($Viewer->isDisabled() && !in_array($Document, ['index', 'login'])) {
|
||||
if ($Viewer->isDisabled() && !in_array($module, ['index', 'login'])) {
|
||||
$Viewer->logoutEverywhere();
|
||||
$forceLogout();
|
||||
}
|
||||
@@ -93,26 +90,22 @@ if (!empty($_SERVER['HTTP_AUTHORIZATION']) && $Document === 'ajax') {
|
||||
$Viewer->logout($SessionID);
|
||||
$forceLogout();
|
||||
}
|
||||
$browser = parse_user_agent($_SERVER['HTTP_USER_AGENT']);
|
||||
if ($Viewer->permitted('site_disable_ip_history')) {
|
||||
$ipaddr = '127.0.0.1';
|
||||
$browser['BrowserVersion'] = null;
|
||||
$browser['OperatingSystemVersion'] = null;
|
||||
} else {
|
||||
$ipaddr = $_SERVER['REMOTE_ADDR'];
|
||||
$context->anonymize();
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
}
|
||||
$session->refresh($SessionID, $ipaddr, $browser);
|
||||
unset($ipaddr, $browser, $session, $userId, $cookieData, $forceLogout);
|
||||
} elseif ($Document === 'torrents' && ($_REQUEST['action'] ?? '') == 'download' && isset($_REQUEST['torrent_pass'])) {
|
||||
$session->refresh($SessionID, $context->remoteAddr(), $context->ua());
|
||||
unset($browser, $session, $userId, $cookieData, $forceLogout);
|
||||
} elseif ($module === 'torrents' && ($_REQUEST['action'] ?? '') == 'download' && isset($_REQUEST['torrent_pass'])) {
|
||||
$Viewer = $userMan->findByAnnounceKey($_REQUEST['torrent_pass']);
|
||||
if (is_null($Viewer) || $Viewer->isDisabled() || $Viewer->isLocked()) {
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
exit;
|
||||
}
|
||||
} elseif (!in_array($Document, ['enable', 'index', 'login', 'recovery', 'register'])) {
|
||||
} elseif (!in_array($module, ['enable', 'index', 'login', 'recovery', 'register'])) {
|
||||
if (
|
||||
// Ocelot is allowed
|
||||
!($Document === 'tools' && ($_GET['action'] ?? '') === 'ocelot' && ($_GET['key'] ?? '') === TRACKER_SECRET)
|
||||
!($module === 'tools' && ($_GET['action'] ?? '') === 'ocelot' && ($_GET['key'] ?? '') === TRACKER_SECRET)
|
||||
) {
|
||||
// but for everything else, we need a $Viewer
|
||||
header('Location: login.php');
|
||||
@@ -126,22 +119,18 @@ if ($Viewer) {
|
||||
if ($Viewer->hasAttr('admin-error-reporting')) {
|
||||
error_reporting(E_ALL);
|
||||
}
|
||||
|
||||
// Because we <3 our staff
|
||||
if ($Viewer->permitted('site_disable_ip_history')) {
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1';
|
||||
$_SERVER['HTTP_X_REAL_IP'] = '127.0.0.1';
|
||||
$_SERVER['HTTP_USER_AGENT'] = 'staff-browser';
|
||||
$context->anonymize();
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
}
|
||||
if ($Viewer->ipaddr() != $_SERVER['REMOTE_ADDR'] && !$Viewer->permitted('site_disable_ip_history')) {
|
||||
if ($ipv4Man->isBanned($_SERVER['REMOTE_ADDR'])) {
|
||||
if ($Viewer->ipaddr() != $context->remoteAddr() && !$Viewer->permitted('site_disable_ip_history')) {
|
||||
if ($ipv4Man->isBanned($context->remoteAddr())) {
|
||||
error('Your IP address has been banned.');
|
||||
}
|
||||
$ipv4Man->register($Viewer, $_SERVER['REMOTE_ADDR']);
|
||||
$ipv4Man->register($Viewer, $context->remoteAddr());
|
||||
}
|
||||
if ($Viewer->isLocked() && !in_array($Document, ['staffpm', 'ajax', 'locked', 'logout', 'login'])) {
|
||||
$Document = 'locked';
|
||||
if ($Viewer->isLocked() && !in_array($module, ['staffpm', 'ajax', 'locked', 'logout', 'login'])) {
|
||||
$context->setModule('locked');
|
||||
}
|
||||
|
||||
// To proxify images (or not), or e.g. not render the name of a thread
|
||||
@@ -153,11 +142,12 @@ $Debug->set_flag('load page');
|
||||
if (DEBUG_MODE || ($Viewer && $Viewer->permitted('site_debug'))) {
|
||||
$Twig->addExtension(new Twig\Extension\DebugExtension());
|
||||
}
|
||||
Gazelle\Base::setRequestContext($context);
|
||||
|
||||
// for sections/tools/development/process_info.php
|
||||
$Cache->cache_value('php_' . getmypid(), [
|
||||
'start' => Time::sqlTime(),
|
||||
'document' => $Document,
|
||||
'document' => $module,
|
||||
'query' => $_SERVER['QUERY_STRING'],
|
||||
'get' => $_GET,
|
||||
'post' => array_diff_key(
|
||||
@@ -184,8 +174,8 @@ register_shutdown_function(
|
||||
header('Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0');
|
||||
header('Pragma: no-cache');
|
||||
|
||||
$file = realpath(__DIR__ . "/sections/{$Document}/index.php");
|
||||
if (!$file || !preg_match('/^[a-z][a-z0-9_]+$/', $Document)) {
|
||||
$file = realpath(__DIR__ . "/sections/{$module}/index.php");
|
||||
if (!$file || !preg_match('/^[a-z][a-z0-9_]+$/', $module)) {
|
||||
error($Viewer ? 403 : 404);
|
||||
}
|
||||
|
||||
@@ -210,5 +200,5 @@ try {
|
||||
|
||||
$Debug->set_flag('and send to user');
|
||||
if (!is_null($Viewer)) {
|
||||
$Debug->profile($Viewer, $Document);
|
||||
$Debug->profile($Viewer, $module);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,16 @@
|
||||
|
||||
/* require this file to have a fully-initialized Gazelle runtime */
|
||||
|
||||
if (PHP_VERSION_ID < 80201) {
|
||||
die("Gazelle (Orpheus fork) requires at least PHP version 8.2.1");
|
||||
}
|
||||
foreach (['memcached', 'mysqli'] as $e) {
|
||||
if (!extension_loaded($e)) {
|
||||
die("$e extension not loaded");
|
||||
}
|
||||
}
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
$now = microtime(true); // To track how long a page takes to create
|
||||
|
||||
if (!defined('SITE_NAME')) {
|
||||
|
||||
@@ -355,10 +355,10 @@ function parse_user_agent(string $useragent): array {
|
||||
* $Log If true, the user is given a link to search $Log in the site log.
|
||||
*/
|
||||
function error(int|string $Error, bool $NoHTML = false, bool $Log = false): never {
|
||||
global $Debug, $Document, $Viewer, $Twig;
|
||||
global $Debug, $Viewer, $Twig;
|
||||
require_once(__DIR__ . '/../sections/error/index.php');
|
||||
if (isset($Viewer)) {
|
||||
$Debug->profile($Viewer, $Document);
|
||||
$Debug->profile($Viewer, $Viewer->requestContext()->module());
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -39,23 +39,21 @@ if (!empty($_POST['username']) && !empty($_POST['password'])) {
|
||||
echo $Twig->render('login/weak-password.twig');
|
||||
exit;
|
||||
}
|
||||
|
||||
$browser = parse_user_agent($_SERVER['HTTP_USER_AGENT']);
|
||||
$useragent = $_SERVER['HTTP_USER_AGENT'] ?? '[no-useragent]';
|
||||
$context = new Gazelle\BaseRequestContext(
|
||||
$_SERVER['SCRIPT_NAME'],
|
||||
$_SERVER['REMOTE_ADDR'],
|
||||
$useragent,
|
||||
);
|
||||
if ($user->permitted('site_disable_ip_history')) {
|
||||
$ipaddr = '127.0.0.1';
|
||||
$browser['BrowserVersion'] = null;
|
||||
$browser['OperatingSystemVersion'] = null;
|
||||
$full_ua = 'staff-browser';
|
||||
} else {
|
||||
$ipaddr = $_SERVER['REMOTE_ADDR'];
|
||||
$full_ua = $_SERVER['HTTP_USER_AGENT'];
|
||||
$context->anonymize();
|
||||
}
|
||||
$session = new Gazelle\User\Session($user);
|
||||
$current = $session->create([
|
||||
'keep-logged' => $login->persistent() ? '1' : '0',
|
||||
'browser' => $browser,
|
||||
'ipaddr' => $ipaddr,
|
||||
'useragent' => $full_ua,
|
||||
'browser' => $context->ua(),
|
||||
'ipaddr' => $context->remoteAddr(),
|
||||
'useragent' => $useragent,
|
||||
]);
|
||||
setcookie('session', $session->cookie($current['SessionID']), [
|
||||
'expires' => (int)$login->persistent() * (time() + 60 * 60 * 24 * 90),
|
||||
|
||||
68
tests/phpunit/BaseRequestContext.php
Normal file
68
tests/phpunit/BaseRequestContext.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
require_once(__DIR__ . '/../../lib/bootstrap.php');
|
||||
require_once(__DIR__ . '/../helper.php');
|
||||
|
||||
class BaseRequestContext extends TestCase {
|
||||
protected \Gazelle\User $user;
|
||||
|
||||
public function tearDown(): void {
|
||||
if (isset($this->user)) {
|
||||
$this->user->remove();
|
||||
}
|
||||
}
|
||||
|
||||
public function testBaseRequestContext(): void {
|
||||
$context = new Gazelle\BaseRequestContext(
|
||||
'/phpunit.php',
|
||||
'224.0.0.1',
|
||||
'Lidarr/3.5.8 (windows 98)',
|
||||
);
|
||||
$this->assertTrue($context->isValid(), 'context-is-valid');
|
||||
$this->assertEquals('phpunit', $context->module(), 'context-module');
|
||||
$this->assertEquals('224.0.0.1', $context->remoteAddr(), 'context-remote-addr');
|
||||
$this->assertEquals('Lidarr', $context->browser(), 'context-browser');
|
||||
$this->assertEquals('3.5', $context->browserVersion(), 'context-version-browser');
|
||||
$this->assertEquals('98', $context->osVersion(), 'context-version-os');
|
||||
$this->assertEquals('Windows', $context->os(), 'context-os');
|
||||
|
||||
$module = randomString(4);
|
||||
$context->setModule($module);
|
||||
$this->assertEquals($module, $context->module(), 'context-override-module');
|
||||
|
||||
$context->anonymize();
|
||||
$this->assertEquals('127.0.0.1', $context->remoteAddr(), 'context-override-remoteaddr');
|
||||
$this->assertEquals('staff-browser', $context->browser(), 'context-override-browser');
|
||||
$this->assertNull($context->os(), 'context-override-os');
|
||||
}
|
||||
|
||||
public function testBadRequest(): void {
|
||||
$context = new Gazelle\BaseRequestContext('', '', '');
|
||||
$this->assertFalse($context->isValid(), 'context-not-valid');
|
||||
$this->assertNull($context->browser(), 'context-invalid-browser');
|
||||
}
|
||||
|
||||
// Any object that derives from Base has access to the request context
|
||||
public function testObject(): void {
|
||||
Gazelle\Base::setRequestContext(
|
||||
new Gazelle\BaseRequestContext(
|
||||
'/phpunit.php',
|
||||
'225.0.0.1',
|
||||
'Lidarr/5.8.13 (windows 98)',
|
||||
)
|
||||
);
|
||||
$this->assertEquals(
|
||||
'225.0.0.1',
|
||||
(new Gazelle\Manager\TGroup())->requestContext()->remoteAddr(),
|
||||
'context-manager-ip',
|
||||
);
|
||||
$this->user = Helper::makeUser('base.' . randomString(6), 'base object');
|
||||
$this->assertEquals(
|
||||
'Lidarr',
|
||||
$this->user->requestContext()->browser(),
|
||||
'context-user-browser',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -441,8 +441,7 @@ class DonorTest extends TestCase {
|
||||
]);
|
||||
global $SessionID;
|
||||
$SessionID = $current['SessionID']; // more sadness
|
||||
global $Document;
|
||||
$Document = 'index'; // utter misery
|
||||
Gazelle\Base::setRequestContext(new Gazelle\BaseRequestContext('/index.php', '127.0.0.1', ''));
|
||||
|
||||
$paginator = (new Gazelle\Util\Paginator(USERS_PER_PAGE, 1))->setTotal($manager->rewardTotal());
|
||||
$render = (Gazelle\Util\Twig::factory())->render('donation/reward-list.twig', [
|
||||
|
||||
@@ -546,10 +546,11 @@ class ForumTest extends TestCase {
|
||||
description: 'This is where it renders',
|
||||
);
|
||||
$paginator = (new Gazelle\Util\Paginator(TOPICS_PER_PAGE, 1))->setTotal(1);
|
||||
global $Document, $SessionID, $Viewer; // to render header()
|
||||
$Document = 'forum';
|
||||
Gazelle\Base::setRequestContext(new Gazelle\BaseRequestContext('/forum.php', '127.0.0.1', ''));
|
||||
global $SessionID; // to render header()
|
||||
$SessionID = 'phpunit';
|
||||
$Viewer = $admin;
|
||||
global $Viewer;
|
||||
$Viewer = $admin;
|
||||
$this->assertStringContainsString(
|
||||
"<a href=\"forums.php#$name\">$name</a>",
|
||||
(Gazelle\Util\Twig::factory())->render('forum/forum.twig', [
|
||||
|
||||
@@ -60,8 +60,8 @@ class FriendTest extends TestCase {
|
||||
'ipaddr' => '127.0.0.1',
|
||||
'useragent' => 'phpunit-browser',
|
||||
]);
|
||||
global $Document, $SessionID, $Viewer;
|
||||
$Document = 'friends';
|
||||
Gazelle\Base::setRequestContext(new Gazelle\BaseRequestContext('/friends.php', '127.0.0.1', ''));
|
||||
global $SessionID, $Viewer;
|
||||
$SessionID = $current['SessionID'];
|
||||
$Viewer = $this->friend[0]->user();
|
||||
|
||||
|
||||
@@ -131,10 +131,9 @@ class LogTest extends TestCase {
|
||||
|
||||
// FIXME: $Viewer should not be necessary
|
||||
$this->user = Helper::makeUser('sitelog.' . randomString(6), 'sitelog');
|
||||
Gazelle\Base::setRequestContext(new Gazelle\BaseRequestContext('/index.php', '127.0.0.1', ''));
|
||||
global $Viewer;
|
||||
$Viewer = $this->user;
|
||||
global $Document;
|
||||
$Document = '';
|
||||
global $SessionID;
|
||||
$SessionID = 'phpunit';
|
||||
$html = (Gazelle\Util\Twig::factory())->render('sitelog.twig', [
|
||||
|
||||
@@ -21,8 +21,7 @@ class UploadTest extends TestCase {
|
||||
|
||||
$this->assertStringContainsString($this->user->auth(), $upload->head(0), 'upload-head');
|
||||
|
||||
global $Document;
|
||||
$Document = '';
|
||||
Gazelle\Base::setRequestContext(new Gazelle\BaseRequestContext('/upload.php', '127.0.0.1', ''));
|
||||
global $SessionID;
|
||||
$SessionID = '';
|
||||
global $Viewer;
|
||||
|
||||
@@ -210,10 +210,9 @@ END;
|
||||
}
|
||||
|
||||
public function testFunction(): void {
|
||||
global $Document;
|
||||
$Document = 'index';
|
||||
Gazelle\Base::setRequestContext(new Gazelle\BaseRequestContext('/index.php', '127.0.0.1', ''));
|
||||
global $Viewer;
|
||||
$Viewer = $this->user;
|
||||
$Viewer = $this->user;
|
||||
$this->assertStringStartsWith('<!DOCTYPE html>', self::twig('{{ header("page") }}')->render(), 'twig-function-header');
|
||||
|
||||
$current = (new Gazelle\User\Session($Viewer))->create([
|
||||
|
||||
Reference in New Issue
Block a user