[update] remove extension - unzipping new code over the top of old code is fundamentally unsafe

This commit is contained in:
Shish 2024-02-10 23:15:08 +00:00
parent cc9de6b4b2
commit 79087c51a5
5 changed files with 88 additions and 285 deletions

View file

@ -294,162 +294,135 @@ class Pools extends Extension
if ($event->page_matches("pool/edit")) {
$pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($this->have_permission($user, $pool)) {
$result = $database->execute("SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order ASC", ["pid" => $pool_id]);
$images = [];
while ($row = $result->fetch()) {
$images[] = Image::by_id((int) $row["image_id"]);
}
$this->theme->edit_pool($page, $pool, $images);
} else {
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
$result = $database->execute("SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order ASC", ["pid" => $pool_id]);
$images = [];
while ($row = $result->fetch()) {
$images[] = Image::by_id((int) $row["image_id"]);
}
$this->theme->edit_pool($page, $pool, $images);
}
if ($event->page_matches("pool/order")) {
$pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($event->get_POST("order_view")) {
if ($this->have_permission($user, $pool)) {
$result = $database->execute(
"SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order ASC",
["pid" => $pool_id]
);
$images = [];
$result = $database->execute(
"SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order ASC",
["pid" => $pool_id]
);
$images = [];
while ($row = $result->fetch()) {
$image = $database->get_row(
"
while ($row = $result->fetch()) {
$image = $database->get_row(
"
SELECT * FROM images AS i
INNER JOIN pool_images AS p ON i.id = p.image_id
WHERE pool_id=:pid AND i.id=:iid",
["pid" => $pool_id, "iid" => (int) $row['image_id']]
);
$images[] = ($image ? new Image($image) : null);
}
$this->theme->edit_order($page, $pool, $images);
} else {
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
["pid" => $pool_id, "iid" => (int) $row['image_id']]
);
$images[] = ($image ? new Image($image) : null);
}
$this->theme->edit_order($page, $pool, $images);
} else {
if ($this->have_permission($user, $pool)) {
foreach ($event->POST as $key => $value) {
if (str_starts_with($key, "order_")) {
$imageID = (int) substr($key, 6);
$database->execute(
"
foreach ($event->POST as $key => $value) {
if (str_starts_with($key, "order_")) {
$imageID = (int) substr($key, 6);
$database->execute(
"
UPDATE pool_images
SET image_order = :ord
WHERE pool_id = :pid AND image_id = :iid",
["ord" => $value, "pid" => int_escape($event->req_POST('pool_id')), "iid" => $imageID]
);
}
["ord" => $value, "pid" => int_escape($event->req_POST('pool_id')), "iid" => $imageID]
);
}
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
} else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
}
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
}
}
if ($event->page_matches("pool/reverse")) {
$pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($this->have_permission($user, $pool)) {
$database->with_savepoint(function () use ($pool_id) {
global $database;
$result = $database->execute(
"SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order DESC",
["pid" => $pool_id]
);
$image_order = 1;
while ($row = $result->fetch()) {
$database->execute(
"
$database->with_savepoint(function () use ($pool_id) {
global $database;
$result = $database->execute(
"SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order DESC",
["pid" => $pool_id]
);
$image_order = 1;
while ($row = $result->fetch()) {
$database->execute(
"
UPDATE pool_images
SET image_order=:ord
WHERE pool_id = :pid AND image_id = :iid",
["ord" => $image_order, "pid" => $pool_id, "iid" => (int) $row['image_id']]
);
$image_order = $image_order + 1;
}
});
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
} else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
}
["ord" => $image_order, "pid" => $pool_id, "iid" => (int) $row['image_id']]
);
$image_order = $image_order + 1;
}
});
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
}
if ($event->page_matches("pool/import")) {
$pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($this->have_permission($user, $pool)) {
$images = Search::find_images(
limit: $config->get_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000),
tags: Tag::explode($event->req_POST("pool_tag"))
);
$this->theme->pool_result($page, $images, $pool);
} else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
}
$images = Search::find_images(
limit: $config->get_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000),
tags: Tag::explode($event->req_POST("pool_tag"))
);
$this->theme->pool_result($page, $images, $pool);
}
if ($event->page_matches("pool/add_posts")) {
$pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($this->have_permission($user, $pool)) {
$image_ids = array_map('intval', $event->req_POST_array('check'));
send_event(new PoolAddPostsEvent($pool_id, $image_ids));
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
} else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
}
$image_ids = array_map('intval', $event->req_POST_array('check'));
send_event(new PoolAddPostsEvent($pool_id, $image_ids));
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
}
if ($event->page_matches("pool/remove_posts")) {
$pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($this->have_permission($user, $pool)) {
$images = "";
foreach ($event->req_POST_array('check') as $imageID) {
$database->execute(
"DELETE FROM pool_images WHERE pool_id = :pid AND image_id = :iid",
["pid" => $pool_id, "iid" => $imageID]
);
$images .= " " . $imageID;
}
$count = (int) $database->get_one(
"SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid",
["pid" => $pool_id]
$images = "";
foreach ($event->req_POST_array('check') as $imageID) {
$database->execute(
"DELETE FROM pool_images WHERE pool_id = :pid AND image_id = :iid",
["pid" => $pool_id, "iid" => $imageID]
);
$this->add_history($pool_id, 0, $images, $count);
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
} else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
$images .= " " . $imageID;
}
$count = (int) $database->get_one(
"SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid",
["pid" => $pool_id]
);
$this->add_history($pool_id, 0, $images, $count);
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
}
if ($event->page_matches("pool/edit_description")) {
$pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($this->have_permission($user, $pool)) {
$database->execute(
"UPDATE pools SET description=:dsc,lastupdated=CURRENT_TIMESTAMP WHERE id=:pid",
["dsc" => $event->req_POST('description'), "pid" => $pool_id]
);
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
} else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
}
$database->execute(
"UPDATE pools SET description=:dsc,lastupdated=CURRENT_TIMESTAMP WHERE id=:pid",
["dsc" => $event->req_POST('description'), "pid" => $pool_id]
);
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
}
if ($event->page_matches("pool/nuke")) {
// Completely remove the given pool.
@ -462,7 +435,7 @@ class Pools extends Extension
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/list"));
} else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
throw new PermissionDeniedException("You do not have permission to access this page");
}
}
}
@ -646,6 +619,13 @@ class Pools extends Extension
);
}
private function assert_permission(User $user, Pool $pool): void
{
if (!$this->have_permission($user, $pool)) {
throw new PermissionDeniedException("You do not have permission to access this pool");
}
}
private function list_pools(Page $page, int $pageNumber, ?string $search): void
{
global $config, $database;
@ -758,9 +738,7 @@ class Pools extends Extension
global $database, $user;
$pool = $this->get_single_pool($event->pool_id);
if (!$this->have_permission($user, $pool)) {
return;
}
$this->assert_permission($user, $pool);
$images = [];
foreach ($event->posts as $post_id) {

View file

@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class UpdateInfo extends ExtensionInfo
{
public const KEY = "update";
public string $key = self::KEY;
public string $name = "Update";
public string $url = "http://www.codeanimu.net";
public array $authors = ["DakuTree" => "dakutree@codeanimu.net"];
public string $license = self::LICENSE_GPLV2;
public string $description = "Shimmie updater!";
public array $dependencies = [AdminPageInfo::KEY];
public ExtensionCategory $category = ExtensionCategory::ADMIN;
}

View file

@ -1,120 +0,0 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class Update extends Extension
{
/** @var UpdateTheme */
protected Themelet $theme;
public function onInitExt(InitExtEvent $event): void
{
global $config;
$config->set_default_string("update_guserrepo", "shish/shimmie2");
$config->set_default_string("commit_hash", "unknown");
$config->set_default_string("update_time", "01/01/1970");
}
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$sb = $event->panel->create_new_block("Update");
$sb->add_text_option("update_guserrepo", "User/Repo: ");
}
public function onAdminBuilding(AdminBuildingEvent $event): void
{
global $config;
if ($config->get_string(UploadConfig::TRANSLOAD_ENGINE) !== "none") {
$this->theme->display_admin_block();
}
}
public function onPageRequest(PageRequestEvent $event): void
{
global $user, $page;
$sha = $event->get_GET('sha');
// FIXME: use POST
if ($event->page_matches("update/download", permission: Permissions::EDIT_FILES)) {
assert(!is_null($sha));
$ok = $this->download_shimmie($sha);
$page->set_mode(PageMode::REDIRECT);
if ($ok) {
$page->set_redirect(make_link("update/update", "sha=".$sha));
} else {
$page->set_redirect(make_link("admin"));
} //TODO: Show error?
}
if ($event->page_matches("update/update", permission: Permissions::EDIT_FILES)) {
assert(!is_null($sha));
$ok = $this->update_shimmie($sha);
$page->set_mode(PageMode::REDIRECT);
if ($ok) {
$page->set_redirect(make_link("admin"));
} //TODO: Show success?
else {
$page->set_redirect(make_link("admin"));
} //TODO: Show error?
}
}
private function download_shimmie(string $commitSHA): bool
{
global $config;
$g_userrepo = $config->get_string('update_guserrepo');
$url = "https://codeload.github.com/".$g_userrepo."/zip/".$commitSHA;
$filename = "./data/update_{$commitSHA}.zip";
log_info("update", "Attempting to download Shimmie commit: ".$commitSHA);
$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;
}
private function update_shimmie(string $commitSHA): bool
{
global $config;
log_info("update", "Download succeeded. Attempting to update Shimmie.");
$ok = false;
/** TODO: Backup all folders (except /data, /images, /thumbs) before attempting this?
Either that or point to https://github.com/shish/shimmie2/blob/master/README.txt -> Upgrade from 2.3.X **/
$zip = new \ZipArchive();
if ($zip->open("./data/update_$commitSHA.zip") === true) {
for ($i = 1; $i < $zip->numFiles; $i++) {
$filename = false_throws($zip->getNameIndex($i));
if (substr($filename, -1) !== "/") {
copy("zip://".dirname(dirname(__DIR__)).'/'."./data/update_$commitSHA.zip"."#".$filename, substr($filename, 50));
}
}
$ok = true; //TODO: Do proper checking to see if everything copied properly
} else {
log_warning("update", "Update failed to open ZIP.");
}
$zip->close();
unlink("./data/update_$commitSHA.zip");
if ($ok) {
$config->set_string("commit_hash", $commitSHA);
$config->set_string("update_time", date('d-m-Y'));
log_info("update", "Update succeeded?");
}
return $ok;
}
}

View file

@ -1,16 +0,0 @@
/*jshint bitwise:true, curly:true, forin:false, noarg:true, noempty:true, nonew:true, undef:true, strict:false, browser:true, jquery:true */
document.addEventListener('DOMContentLoaded', () => {
if($('#updatecheck').length !== 0){
$.getJSON('https://api.github.com/repos/shish/shimmie2/commits', function(data){
var c = data[0];
$('#updatecheck').html('<a href="'+ c.html_url+'">'+ c.sha+'</a>' + " ("+ c.commit.message+")");
var params = $.param({sha: c.sha, date: c.commit.committer.date});
$('#updatelink').attr('href', function(i, val){ return val + "?" + params; });
$('#updatelink').text("Update");
}).fail(function(){
$('#updatecheck').text("Loading failed. (Github down?)");
});
}
});

View file

@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class UpdateTheme extends Themelet
{
public function display_admin_block(): void
{
global $page, $config;
$html = "".
"<b>Current Commit</b>: ".$config->get_string('commit_hash')." | (".$config->get_string('update_time').")".
"<br><b>Latest Commit</b>: <span id='updatecheck'>Loading...</span>".
"<br><a href='" . make_link('update/download') . "' id='updatelink'></a>";
//TODO: Show warning before use.
$page->add_block(new Block("Software Update", $html, "main", 75));
}
}