[tests] more strictness

This commit is contained in:
Shish 2024-01-20 19:47:26 +00:00
parent b60c3fe362
commit 8b797a9a31
28 changed files with 143 additions and 115 deletions

View file

@ -308,7 +308,7 @@ class BasePage
assert($this->file, "file should not be null with PageMode::FILE");
// https://gist.github.com/codler/3906826
$size = filesize($this->file); // File size
$size = false_throws(filesize($this->file)); // File size
$length = $size; // Content length
$start = 0; // Start byte
$end = $size - 1; // End byte
@ -420,7 +420,7 @@ class BasePage
if (!file_exists($css_cache_file)) {
$mcss = new \MicroBundler\MicroBundler();
foreach($css_files as $css) {
$mcss->addSource($css, file_get_contents($css));
$mcss->addSource($css);
}
$mcss->save($css_cache_file);
}
@ -443,7 +443,7 @@ class BasePage
if (!file_exists($js_cache_file)) {
$mcss = new \MicroBundler\MicroBundler();
foreach($js_files as $js) {
$mcss->addSource($js, file_get_contents($js));
$mcss->addSource($js);
}
$mcss->save($js_cache_file);
}
@ -471,7 +471,7 @@ class BasePage
if (!file_exists($js_cache_file)) {
$mcss = new \MicroBundler\MicroBundler();
foreach($js_files as $js) {
$mcss->addSource($js, file_get_contents($js));
$mcss->addSource($js);
}
$mcss->save($js_cache_file);
}

View file

@ -70,7 +70,7 @@ class EventTracingCache implements CacheInterface
/**
* @param string[] $keys
* @param mixed $default
* @return mixed[]
* @return iterable<mixed>
*/
public function getMultiple($keys, $default = null)
{
@ -126,10 +126,10 @@ function loadCache(?string $dsn): CacheInterface
} elseif ($url['scheme'] == "redis") {
$redis = new \Predis\Client([
'scheme' => 'tcp',
'host' => $url['host'],
'port' => $url['port'],
'username' => $url['user'],
'password' => $url['pass'],
'host' => $url['host'] ?? "127.0.0.1",
'port' => $url['port'] ?? 6379,
'username' => $url['user'] ?? null,
'password' => $url['pass'] ?? null,
], ['prefix' => 'shm:']);
$c = new \Naroga\RedisCache\Redis($redis);
}

View file

@ -129,6 +129,11 @@ class Database
}
}
/**
* @template T
* @param callable():T $callback
* @return T
*/
public function with_savepoint(callable $callback, string $name = "sp"): mixed
{
global $_tracer;

View file

@ -72,7 +72,7 @@ class MySQL extends DBEngine
public function get_version(PDO $db): string
{
return $db->query('select version()')->fetch()[0];
return false_throws($db->query('select version()'))->fetch()[0];
}
}
@ -124,14 +124,14 @@ class PostgreSQL extends DBEngine
public function get_version(PDO $db): string
{
return $db->query('select version()')->fetch()[0];
return false_throws($db->query('select version()'))->fetch()[0];
}
}
// shimmie functions for export to sqlite
function _unix_timestamp(string $date): int
{
return strtotime($date);
return false_throws(strtotime($date));
}
function _now(): string
{
@ -231,6 +231,6 @@ class SQLite extends DBEngine
public function get_version(PDO $db): string
{
return $db->query('select sqlite_version()')->fetch()[0];
return false_throws($db->query('select sqlite_version()'))->fetch()[0];
}
}

View file

@ -43,9 +43,13 @@ abstract class Extension
$normal = "Shimmie2\\{$base}Theme";
if (class_exists($custom)) {
return new $custom();
$c = new $custom();
assert(is_a($c, Themelet::class));
return $c;
} elseif (class_exists($normal)) {
return new $normal();
$n = new $normal();
assert(is_a($n, Themelet::class));
return $n;
} else {
return new Themelet();
}
@ -266,6 +270,7 @@ abstract class ExtensionInfo
{
foreach (get_subclasses_of(ExtensionInfo::class) as $class) {
$extension_info = new $class();
assert(is_a($extension_info, ExtensionInfo::class));
if (array_key_exists($extension_info->key, self::$all_info_by_key)) {
throw new SCoreException("Extension Info $class with key $extension_info->key has already been loaded");
}
@ -317,7 +322,7 @@ abstract class DataHandlerExtension extends Extension
throw new UploadException("Invalid or corrupted file");
}
$existing = Image::by_hash(md5_file($event->tmpname));
$existing = Image::by_hash(false_throws(md5_file($event->tmpname)));
if (!is_null($existing)) {
if ($config->get_string(ImageConfig::UPLOAD_COLLISION_HANDLER) == ImageConfig::COLLISION_MERGE) {
// Right now tags are the only thing that get merged, so
@ -338,8 +343,8 @@ abstract class DataHandlerExtension extends Extension
assert(is_readable($filename));
$image = new Image();
$image->tmp_file = $filename;
$image->filesize = filesize($filename);
$image->hash = md5_file($filename);
$image->filesize = false_throws(filesize($filename));
$image->hash = false_throws(md5_file($filename));
$image->filename = (($pos = strpos($event->metadata['filename'], '?')) !== false) ? substr($event->metadata['filename'], 0, $pos) : $event->metadata['filename'];
$image->set_mime(MimeType::get_for_file($filename, get_file_ext($event->metadata["filename"]) ?? null));
if (empty($image->get_mime())) {
@ -424,6 +429,7 @@ abstract class DataHandlerExtension extends Extension
$arr = [];
foreach (get_subclasses_of(DataHandlerExtension::class) as $handler) {
$handler = (new $handler());
assert(is_a($handler, DataHandlerExtension::class));
$arr = array_merge($arr, $handler->SUPPORTED_MIME);
}

View file

@ -79,9 +79,13 @@ function full_copy(string $source, string $target): void
if (is_dir($source)) {
@mkdir($target);
$d = dir($source);
$d = false_throws(dir($source));
while (false !== ($entry = $d->read())) {
while (true) {
$entry = $d->read();
if ($entry === false) {
break;
}
if ($entry == '.' || $entry == '..') {
continue;
}
@ -155,12 +159,16 @@ function flush_output(): void
function stream_file(string $file, int $start, int $end): void
{
$fp = fopen($file, 'r');
if(!$fp) {
throw new \Exception("Failed to open $file");
}
try {
fseek($fp, $start);
$buffer = 1024 * 1024;
while (!feof($fp) && ($p = ftell($fp)) <= $end) {
if ($p + $buffer > $end) {
$buffer = $end - $p + 1;
assert($buffer >= 0);
}
echo fread($fp, $buffer);
flush_output();
@ -180,13 +188,13 @@ function stream_file(string $file, int $start, int $end): void
# http://www.php.net/manual/en/function.http-parse-headers.php#112917
if (!function_exists('http_parse_headers')) {
/**
* @return string[]
* @return array<string, string|string[]>
*/
function http_parse_headers(string $raw_headers): array
{
$headers = []; // $headers = [];
$headers = [];
foreach (explode("\n", $raw_headers) as $i => $h) {
foreach (explode("\n", $raw_headers) as $h) {
$h = explode(':', $h, 2);
if (isset($h[1])) {
@ -441,7 +449,7 @@ function page_number(string $input, ?int $max = null): int
} else {
$pageNumber = $input - 1;
}
return $pageNumber;
return (int)$pageNumber;
}
function is_numberish(string $s): bool
@ -449,6 +457,14 @@ function is_numberish(string $s): bool
return is_numeric($s);
}
/**
* Because apparently phpstan thinks that if $i is an int, type(-$i) == int|float
*/
function negative_int(int $i): int
{
return -$i;
}
function clamp(int $val, ?int $min = null, ?int $max = null): int
{
if (!is_null($min) && $val < $min) {
@ -611,11 +627,24 @@ function parse_to_milliseconds(string $input): int
*/
function autodate(string $date, bool $html = true): string
{
$cpu = date('c', strtotime($date));
$hum = date('F j, Y; H:i', strtotime($date));
$cpu = date('c', false_throws(strtotime($date)));
$hum = date('F j, Y; H:i', false_throws(strtotime($date)));
return ($html ? "<time datetime='$cpu'>$hum</time>" : $hum);
}
/**
* @template T
* @param T|false $x
* @return T
*/
function false_throws(mixed $x): mixed
{
if($x === false) {
throw new \Exception("Unexpected false");
}
return $x;
}
/**
* Check if a given string is a valid date-time. ( Format: yyyy-mm-dd hh:mm:ss )
*/
@ -710,7 +739,7 @@ function validate_input(array $inputs): array
} elseif (in_array('bool', $flags)) {
$outputs[$key] = bool_escape($value);
} elseif (in_array('date', $flags)) {
$outputs[$key] = date("Y-m-d H:i:s", strtotime(trim($value)));
$outputs[$key] = date("Y-m-d H:i:s", false_throws(strtotime(trim($value))));
} elseif (in_array('string', $flags)) {
if (in_array('trim', $flags)) {
$value = trim($value);

View file

@ -84,6 +84,7 @@ function modify_current_url(array $changes): string
*/
function modify_url(string $url, array $changes): string
{
/** @var array<string, mixed> */
$parts = parse_url($url);
$params = [];

View file

@ -89,6 +89,7 @@ class UserClass
$_all_false = [];
foreach ((new \ReflectionClass(Permissions::class))->getConstants() as $k => $v) {
assert(is_string($v));
$_all_false[$v] = false;
}
new UserClass("base", null, $_all_false);

View file

@ -194,7 +194,7 @@ function get_session_ip(Config $config): string
{
$mask = $config->get_string("session_hash_mask", "255.255.0.0");
$addr = get_real_ip();
$addr = inet_ntop(inet_pton($addr) & inet_pton($mask));
$addr = false_throws(inet_ntop(false_throws(inet_pton($addr)) & false_throws(inet_pton($mask))));
return $addr;
}
@ -285,17 +285,21 @@ function load_balance_url(string $tmpl, string $hash, int $n = 0): string
return $tmpl;
}
class FetchException extends \Exception
{
}
/**
* @return null|array<string, mixed>
* @return array<string, string|string[]>
*/
function fetch_url(string $url, string $mfile): ?array
function fetch_url(string $url, string $mfile): array
{
global $config;
if ($config->get_string(UploadConfig::TRANSLOAD_ENGINE) === "curl" && function_exists("curl_init")) {
$ch = curl_init($url);
assert($ch !== false);
$fp = fopen($mfile, "w");
$fp = false_throws(fopen($mfile, "w"));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
@ -306,37 +310,37 @@ function fetch_url(string $url, string $mfile): ?array
$response = curl_exec($ch);
if ($response === false) {
return null;
throw new FetchException("cURL failed: ".curl_error($ch));
}
if ($response === true) { // we use CURLOPT_RETURNTRANSFER, so this should never happen
throw new FetchException("cURL failed successfully??");
}
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = http_parse_headers(implode("\n", preg_split('/\R/', rtrim(substr($response, 0, $header_size)))));
$header_text = trim(substr($response, 0, $header_size));
$headers = http_parse_headers(implode("\n", false_throws(preg_split('/\R/', $header_text))));
$body = substr($response, $header_size);
curl_close($ch);
fwrite($fp, $body);
fclose($fp);
return $headers;
}
if ($config->get_string(UploadConfig::TRANSLOAD_ENGINE) === "wget") {
} elseif ($config->get_string(UploadConfig::TRANSLOAD_ENGINE) === "wget") {
$s_url = escapeshellarg($url);
$s_mfile = escapeshellarg($mfile);
system("wget --no-check-certificate $s_url --output-document=$s_mfile");
return file_exists($mfile) ? ["ok" => "true"] : null;
}
if ($config->get_string(UploadConfig::TRANSLOAD_ENGINE) === "fopen") {
if(!file_exists($mfile)) {
throw new FetchException("wget failed");
}
$headers = [];
} elseif ($config->get_string(UploadConfig::TRANSLOAD_ENGINE) === "fopen") {
$fp_in = @fopen($url, "r");
$fp_out = fopen($mfile, "w");
if (!$fp_in || !$fp_out) {
return null;
throw new FetchException("fopen failed");
}
$length = 0;
while (!feof($fp_in) && $length <= $config->get_int(UploadConfig::SIZE)) {
$data = fread($fp_in, 8192);
$data = false_throws(fread($fp_in, 8192));
$length += strlen($data);
fwrite($fp_out, $data);
}
@ -344,11 +348,16 @@ function fetch_url(string $url, string $mfile): ?array
fclose($fp_out);
$headers = http_parse_headers(implode("\n", $http_response_header));
return $headers;
} else {
throw new FetchException("No transload engine configured");
}
return null;
if (filesize($mfile) == 0) {
@unlink($mfile);
throw new FetchException("No data found in $url -- perhaps the site has hotlink protection?");
}
return $headers;
}
/**
@ -412,29 +421,17 @@ function get_dir_contents(string $dir): array
return [];
}
return array_diff(
scandir(
$dir
),
false_throws(scandir($dir)),
['..', '.']
);
}
function remove_empty_dirs(string $dir): bool
{
assert(!empty($dir));
$result = true;
if (!is_dir($dir)) {
return false;
}
$items = array_diff(
scandir(
$dir
),
['..', '.']
);
$items = get_dir_contents($dir);
;
foreach ($items as $item) {
$path = join_path($dir, $item);
if (is_dir($path)) {
@ -454,22 +451,10 @@ function remove_empty_dirs(string $dir): bool
*/
function get_files_recursively(string $dir): array
{
assert(!empty($dir));
if (!is_dir($dir)) {
return [];
}
$things = array_diff(
scandir(
$dir
),
['..', '.']
);
$things = get_dir_contents($dir);
$output = [];
foreach ($things as $thing) {
$path = join_path($dir, $thing);
if (is_file($path)) {
@ -659,8 +644,10 @@ function _fatal_error(\Exception $e): void
foreach ($t as $n => $f) {
$c = $f['class'] ?? '';
$t = $f['type'] ?? '';
$i = $f['file'] ?? 'unknown file';
$l = $f['line'] ?? -1;
$a = implode(", ", array_map("Shimmie2\stringer", $f['args'] ?? []));
print("$n: {$f['file']}({$f['line']}): {$c}{$t}{$f['function']}({$a})\n");
print("$n: {$i}({$l}): {$c}{$t}{$f['function']}({$a})\n");
}
print("Message: $message\n");

View file

@ -105,7 +105,7 @@ class AliasEditor extends Extension
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
if (count($_FILES) > 0) {
$tmp = $_FILES['alias_file']['tmp_name'];
$contents = file_get_contents($tmp);
$contents = false_throws(file_get_contents($tmp));
$this->add_alias_csv($contents);
log_info("alias_editor", "Imported aliases from file", "Imported aliases"); # FIXME: how many?
$page->set_mode(PageMode::REDIRECT);

View file

@ -111,7 +111,7 @@ class AutoTagger extends Extension
if ($user->can(Permissions::MANAGE_AUTO_TAG)) {
if (count($_FILES) > 0) {
$tmp = $_FILES['auto_tag_file']['tmp_name'];
$contents = file_get_contents($tmp);
$contents = false_throws(file_get_contents($tmp));
$count = $this->add_auto_tag_csv($contents);
log_info(AutoTaggerInfo::KEY, "Imported $count auto-tag definitions from file from file", "Imported $count auto-tag definitions");
$page->set_mode(PageMode::REDIRECT);

View file

@ -28,7 +28,7 @@ class BrowserSearch extends Extension
$search_title = $config->get_string(SetupConfig::TITLE);
$search_form_url = search_link(['{searchTerms}']);
$suggenton_url = make_link('browser_search/')."{searchTerms}";
$icon_b64 = base64_encode(file_get_contents("ext/static_files/static/favicon.ico"));
$icon_b64 = base64_encode(false_throws(file_get_contents("ext/static_files/static/favicon.ico")));
// Now for the XML
$xml = "

View file

@ -48,7 +48,7 @@ class BulkDownload extends Extension
if ($user->can(Permissions::BULK_DOWNLOAD) &&
($event->action == BulkDownload::DOWNLOAD_ACTION_NAME)) {
$download_filename = $user->name . '-' . date('YmdHis') . '.zip';
$zip_filename = tempnam(sys_get_temp_dir(), "shimmie_bulk_download");
$zip_filename = false_throws(tempnam(sys_get_temp_dir(), "shimmie_bulk_download"));
$zip = new \ZipArchive();
$size_total = 0;
$max_size = $config->get_int(BulkDownloadConfig::SIZE_LIMIT);
@ -61,7 +61,6 @@ class BulkDownload extends Extension
throw new BulkDownloadException("Bulk download limited to ".human_filesize($max_size));
}
$filename = urldecode($image->get_nice_image_name());
$filename = str_replace(":", ";", $filename);
$zip->addFile($img_loc, $filename);

View file

@ -313,10 +313,11 @@ class DanbooruApi extends Extension
$source = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source'];
$file = tempnam(sys_get_temp_dir(), "shimmie_transload");
assert($file !== false);
$ok = fetch_url($source, $file);
if (!$ok) {
try {
fetch_url($source, $file);
} catch(FetchException $e) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: fopen read error");
$page->add_http_header("X-Danbooru-Errors: $e");
return;
}
$filename = basename($source);

View file

@ -28,7 +28,7 @@ class Home extends Extension
$counters = [];
$counters["None"] = "none";
$counters["Text-only"] = "text-only";
foreach (glob("ext/home/counters/*") as $counter_dirname) {
foreach (false_throws(glob("ext/home/counters/*")) as $counter_dirname) {
$name = str_replace("ext/home/counters/", "", $counter_dirname);
$counters[ucfirst($name)] = $name;
}

View file

@ -352,8 +352,10 @@ class OuroborosAPI extends Extension
// Transload from source
$meta['file'] = tempnam(sys_get_temp_dir(), 'shimmie_transload_' . $config->get_string(UploadConfig::TRANSLOAD_ENGINE));
$meta['filename'] = basename($post->file_url);
if (!fetch_url($post->file_url, $meta['file'])) {
$this->sendResponse(500, 'Transloading failed');
try {
fetch_url($post->file_url, $meta['file']);
} catch (FetchException $e) {
$this->sendResponse(500, "Transloading failed: $e");
return;
}
$meta['hash'] = md5_file($meta['file']);

View file

@ -109,7 +109,7 @@ class RSSImages extends Extension
$tags = html_escape($image->get_tag_list());
$thumb_url = $image->get_thumb_link();
$image_url = $image->get_image_link();
$posted = date(DATE_RSS, strtotime($image->posted));
$posted = date(DATE_RSS, false_throws(strtotime($image->posted)));
$content = html_escape(
"<div>" .
"<p>" . $this->theme->build_thumb_html($image) . "</p>" .

View file

@ -348,7 +348,7 @@ class Setup extends Extension
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$themes = [];
foreach (glob("themes/*") as $theme_dirname) {
foreach (false_throws(glob("themes/*")) as $theme_dirname) {
$name = str_replace("themes/", "", $theme_dirname);
$human = str_replace("_", " ", $name);
$human = ucwords($human);

View file

@ -211,7 +211,7 @@ class TranscodeVideo extends Extension
}
$original_file = warehouse_path(Image::IMAGE_DIR, $image->hash);
$tmp_filename = tempnam(sys_get_temp_dir(), "shimmie_transcode_video");
$tmp_filename = false_throws(tempnam(sys_get_temp_dir(), "shimmie_transcode_video"));
$tmp_filename = $this->transcode_video($original_file, $image->video_codec, $target_mime, $tmp_filename);
send_event(new ImageReplaceEvent($image, $tmp_filename));
return true;

View file

@ -69,18 +69,14 @@ class Update extends Extension
$filename = "./data/update_{$commitSHA}.zip";
log_info("update", "Attempting to download Shimmie commit: ".$commitSHA);
if ($headers = fetch_url($url, $filename)) {
if (($headers['Content-Type'] !== MimeType::ZIP) || ((int) $headers['Content-Length'] !== filesize($filename))) {
unlink("./data/update_{$commitSHA}.zip");
log_warning("update", "Download failed: not zip / not same size as remote file.");
return false;
}
return true;
$headers = fetch_url($url, $filename);
if (($headers['Content-Type'] !== MimeType::ZIP) || ((int) $headers['Content-Length'] !== filesize($filename))) {
unlink("./data/update_{$commitSHA}.zip");
log_warning("update", "Download failed: not zip / not same size as remote file.");
return false;
}
log_warning("update", "Download failed to download.");
return false;
return true;
}
private function update_shimmie(): bool

View file

@ -385,13 +385,10 @@ class Upload extends Extension
try {
// Fetch file
$headers = fetch_url($url, $tmp_filename);
if (is_null($headers)) {
log_warning("core-util", "Failed to fetch $url");
throw new UploadException("Error reading from $url");
}
if (filesize($tmp_filename) == 0) {
throw new UploadException("No data found in $url -- perhaps the site has hotlink protection?");
try {
$headers = fetch_url($url, $tmp_filename);
} catch (FetchException $e) {
throw new UploadException("Error reading from $url: $e");
}
// Parse metadata

View file

@ -8,7 +8,7 @@ use MicroHTML\HTMLElement;
class UserBlockBuildingEvent extends Event
{
/** @var array<int, array{name: string, link: string}> */
/** @var array<int, array{name: string|HTMLElement, link: string}> */
public array $parts = [];
public function add_link(string|HTMLElement $name, string $link, int $position = 50): void

View file

@ -45,7 +45,7 @@ class UserPageTheme extends Themelet
}
/**
* @param array<int, array{name: string, link: string}> $parts
* @param array<int, array{name: string|HTMLElement, link: string}> $parts
*/
public function display_user_links(Page $page, User $user, array $parts): void
{
@ -53,7 +53,7 @@ class UserPageTheme extends Themelet
}
/**
* @param array<array{link: string, name: string}> $parts
* @param array<array{link: string, name: string|HTMLElement}> $parts
*/
public function display_user_block(Page $page, User $user, array $parts): void
{

View file

@ -5,7 +5,9 @@ declare(strict_types=1);
namespace Shimmie2;
define("UNITTEST", true);
define("EXTRA_EXTS", str_replace("ext/", "", implode(',', glob('ext/*'))));
$_all_exts = glob('ext/*');
assert($_all_exts !== false);
define("EXTRA_EXTS", str_replace("ext/", "", implode(',', $_all_exts)));
define("DATABASE_DSN", null);
define("DATABASE_TIMEOUT", 10000);

View file

@ -1,6 +1,6 @@
parameters:
errorFormat: raw
level: 5
level: 6
paths:
- ../core
- ../ext

View file

@ -59,7 +59,8 @@ class CustomCommentListTheme extends CommentListTheme
if ($comment_limit > 0 && $comment_count > $comment_limit) {
//$hidden = $comment_count - $comment_limit;
$comment_html .= "<p>showing $comment_limit of $comment_count comments</p>";
$comments = array_slice($comments, -$comment_limit);
$comments = array_slice($comments, negative_int($comment_limit));
}
foreach ($comments as $comment) {
$comment_html .= $this->comment_to_html($comment);

View file

@ -59,7 +59,7 @@ class CustomCommentListTheme extends CommentListTheme
if ($comment_limit > 0 && $comment_count > $comment_limit) {
//$hidden = $comment_count - $comment_limit;
$comment_html .= "<p>showing $comment_limit of $comment_count comments</p>";
$comments = array_slice($comments, -$comment_limit);
$comments = array_slice($comments, negative_int($comment_limit));
}
foreach ($comments as $comment) {
$comment_html .= $this->comment_to_html($comment);

View file

@ -88,6 +88,7 @@ EOD;
$footer_html = $this->footer_html();
$header_inc = file_get_contents("themes/rule34v2/header.inc");
assert($header_inc !== false);
$header_inc = str_replace('$QUERY', $query, $header_inc);
return <<<EOD
<table id="header" width="100%">