replace the veto system with exceptions

This commit is contained in:
Shish 2009-01-04 06:01:59 -08:00
parent 76f79668b5
commit 1c8094cabf
20 changed files with 98 additions and 103 deletions

View File

@ -30,15 +30,13 @@ porn
else if($word[0] == '/') { else if($word[0] == '/') {
// lines that start with slash are regex // lines that start with slash are regex
if(preg_match($word, $comment)) { if(preg_match($word, $comment)) {
$event->veto("Comment contains banned terms"); throw new CommentPostingException("Comment contains banned terms");
break;
} }
} }
else { else {
// other words are literal // other words are literal
if(strpos($comment, $word) !== false) { if(strpos($comment, $word) !== false) {
$event->veto("Comment contains banned terms"); throw new CommentPostingException("Comment contains banned terms");
break;
} }
} }
} }

View File

@ -35,10 +35,12 @@ class BulkAdd implements Extension {
$metadata['extension'] = $pathinfo['extension']; $metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = $tags; $metadata['tags'] = $tags;
$metadata['source'] = null; $metadata['source'] = null;
try {
$event = new DataUploadEvent($user, $tmpname, $metadata); $event = new DataUploadEvent($user, $tmpname, $metadata);
send_event($event); send_event($event);
if($event->vetoed) { }
return $event->veto_reason; catch(Exception $ex) {
return $ex->getMessage();
} }
} }
} }

View File

@ -223,15 +223,10 @@ class DanbooruApi implements Extension
$metadata['tags'] = $posttags; $metadata['tags'] = $posttags;
$metadata['source'] = $source; $metadata['source'] = $source;
try {
$nevent = new DataUploadEvent($user, $file, $metadata); $nevent = new DataUploadEvent($user, $file, $metadata);
send_event($nevent); send_event($nevent);
// Did something screw up? // If it went ok, grab the id for the newly uploaded image and pass it in the header
if($event->vetoed) {
header("HTTP/1.0 409 Conflict");
header("X-Danbooru-Errors: $event->veto_reason");
return;
} else
{ // If it went ok, grab the id for the newly uploaded image and pass it in the header
$newimg = Image::by_hash($config, $database, $hash); $newimg = Image::by_hash($config, $database, $hash);
$newid = make_link("post/view/" . $newimg->id); $newid = make_link("post/view/" . $newimg->id);
// Did we POST or GET this call? // Did we POST or GET this call?
@ -242,6 +237,12 @@ class DanbooruApi implements Extension
else else
header("Location: $newid"); header("Location: $newid");
} }
catch(UploadException $ex) {
// Did something screw up?
header("HTTP/1.0 409 Conflict");
header("X-Danbooru-Errors: ". $ex->getMessage());
return;
}
} else } else
{ {
header("HTTP/1.0 409 Conflict"); header("HTTP/1.0 409 Conflict");

View File

@ -47,10 +47,12 @@ class ArchiveFileHandler implements Extension {
$metadata['extension'] = $pathinfo['extension']; $metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = $tags; $metadata['tags'] = $tags;
$metadata['source'] = null; $metadata['source'] = null;
try {
$event = new DataUploadEvent($user, $tmpname, $metadata); $event = new DataUploadEvent($user, $tmpname, $metadata);
send_event($event); send_event($event);
if($event->vetoed) { }
return $event->veto_reason; catch(UploadException $ex) {
return $ex->getMessage();
} }
} }
} }

View File

@ -18,9 +18,9 @@ class FlashFileHandler implements Extension {
send_event(new ThumbnailGenerationEvent($event->hash, $event->type)); send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
$image = $this->create_image_from_data("images/$ha/$hash", $event->metadata); $image = $this->create_image_from_data("images/$ha/$hash", $event->metadata);
if(is_null($image)) { if(is_null($image)) {
$event->veto("Flash Handler failed to create image object from data. ". throw new UploadException(
"Flash Handler failed to create image object from data. ".
"Note: compressed flash files are currently unsupported"); "Note: compressed flash files are currently unsupported");
return;
} }
send_event(new ImageAdditionEvent($event->user, $image)); send_event(new ImageAdditionEvent($event->user, $image));
} }

View File

@ -18,16 +18,9 @@ class IcoFileHandler implements Extension {
send_event(new ThumbnailGenerationEvent($event->hash, $event->type)); send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
$image = $this->create_image_from_data("images/$ha/$hash", $event->metadata); $image = $this->create_image_from_data("images/$ha/$hash", $event->metadata);
if(is_null($image)) { if(is_null($image)) {
$event->veto("Handler failed to create image object from data"); throw new UploadException("Icon handler failed to create image object from data");
return;
}
$iae = new ImageAdditionEvent($event->user, $image);
send_event($iae);
if($iae->vetoed) {
$event->veto($iae->veto_reason);
return;
} }
send_event(new ImageAdditionEvent($event->user, $image));
} }
if(($event instanceof ThumbnailGenerationEvent) && $this->supported_ext($event->type)) { if(($event instanceof ThumbnailGenerationEvent) && $this->supported_ext($event->type)) {

View File

@ -18,8 +18,7 @@ class MP3FileHandler implements Extension {
send_event(new ThumbnailGenerationEvent($event->hash, $event->type)); send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
$image = $this->create_image_from_data("images/$ha/$hash", $event->metadata); $image = $this->create_image_from_data("images/$ha/$hash", $event->metadata);
if(is_null($image)) { if(is_null($image)) {
$event->veto("MP3 Handler failed to create image object from data"); throw new UploadException("MP3 handler failed to create image object from data");
return;
} }
send_event(new ImageAdditionEvent($event->user, $image)); send_event(new ImageAdditionEvent($event->user, $image));
} }

View File

@ -18,8 +18,7 @@ class SVGFileHandler implements Extension {
send_event(new ThumbnailGenerationEvent($event->hash, $event->type)); send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
$image = $this->create_image_from_data("images/$ha/$hash", $event->metadata); $image = $this->create_image_from_data("images/$ha/$hash", $event->metadata);
if(is_null($image)) { if(is_null($image)) {
$event->veto("SVG Handler failed to create image object from data"); throw new UploadException("SVG handler failed to create image object from data");
return;
} }
send_event(new ImageAdditionEvent($event->user, $image)); send_event(new ImageAdditionEvent($event->user, $image));
} }

View File

@ -48,7 +48,7 @@ class ImageBan implements Extension {
$row = $database->db->GetRow("SELECT * FROM image_bans WHERE hash = ?", $event->hash); $row = $database->db->GetRow("SELECT * FROM image_bans WHERE hash = ?", $event->hash);
if($row) { if($row) {
$event->veto("Image ".html_escape($row["hash"])." has been banned, reason: ".format_text($row["reason"])); throw new UploadException("Image ".html_escape($row["hash"])." has been banned, reason: ".format_text($row["reason"]));
} }
} }

View File

@ -18,10 +18,10 @@ class ResolutionLimit implements Extension {
$image = $event->image; $image = $event->image;
if($min_w > 0 && $image->width < $min_w) $event->veto("Image too small"); if($min_w > 0 && $image->width < $min_w) throw new UploadException("Image too small");
if($min_h > 0 && $image->height < $min_h) $event->veto("Image too small"); if($min_h > 0 && $image->height < $min_h) throw new UploadException("Image too small");
if($max_w > 0 && $image->width > $min_w) $event->veto("Image too large"); if($max_w > 0 && $image->width > $min_w) throw new UploadExceptiono("Image too large");
if($max_h > 0 && $image->height > $min_h) $event->veto("Image too large"); if($max_h > 0 && $image->height > $min_h) throw new UploadException("Image too large");
if(count($ratios) > 0) { if(count($ratios) > 0) {
$ok = false; $ok = false;
@ -36,7 +36,9 @@ class ResolutionLimit implements Extension {
} }
} }
if(!$ok) { if(!$ok) {
$event->veto("Image needs to be in one of these ratios: ".html_escape($config->get_string("upload_ratios", ""))); throw new UploadException(
"Image needs to be in one of these ratios: ".
html_escape($config->get_string("upload_ratios", "")));
} }
} }
} }

View File

@ -5,17 +5,10 @@
*/ */
abstract class Event { abstract class Event {
var $context; var $context;
var $vetoed = false;
var $veto_reason;
public function __construct(RequestContext $context) { public function __construct(RequestContext $context) {
$this->context = $context; $this->context = $context;
} }
public function veto($reason="") {
$this->vetoed = true;
$this->veto_reason = $reason;
}
} }

View File

@ -485,7 +485,7 @@ function move_upload_to_archive($event) {
$hash = $event->hash; $hash = $event->hash;
$ha = substr($hash, 0, 2); $ha = substr($hash, 0, 2);
if(!@copy($event->tmpname, "images/$ha/$hash")) { if(!@copy($event->tmpname, "images/$ha/$hash")) {
$event->veto("Failed to copy file from uploads ({$event->tmpname}) to archive (images/$ha/$hash)"); throw new UploadException("Failed to copy file from uploads ({$event->tmpname}) to archive (images/$ha/$hash)");
return false; return false;
} }
return true; return true;

View File

@ -380,7 +380,6 @@ function send_event(Event $event) {
ksort($my_event_listeners); ksort($my_event_listeners);
foreach($my_event_listeners as $listener) { foreach($my_event_listeners as $listener) {
$listener->receive_event($event); $listener->receive_event($event);
if($event->vetoed) break;
} }
$_event_count++; $_event_count++;
} }

View File

@ -10,6 +10,8 @@ class AddAliasEvent extends Event {
} }
} }
class AddAliasException extends SCoreException {}
class AliasEditor implements Extension { class AliasEditor implements Extension {
var $theme; var $theme;
@ -20,15 +22,15 @@ class AliasEditor implements Extension {
if($event->get_arg(0) == "add") { if($event->get_arg(0) == "add") {
if($event->user->is_admin()) { if($event->user->is_admin()) {
if(isset($_POST['oldtag']) && isset($_POST['newtag'])) { if(isset($_POST['oldtag']) && isset($_POST['newtag'])) {
try {
$aae = new AddAliasEvent($_POST['oldtag'], $_POST['newtag']); $aae = new AddAliasEvent($_POST['oldtag'], $_POST['newtag']);
send_event($aae); send_event($aae);
if($aae->vetoed) {
$this->theme->display_error($event->page, "Error adding alias", $aae->veto_reason);
}
else {
$event->page->set_mode("redirect"); $event->page->set_mode("redirect");
$event->page->set_redirect(make_link("alias/list")); $event->page->set_redirect(make_link("alias/list"));
} }
catch(AddAliasException $ex) {
$this->theme->display_error($event->page, "Error adding alias", $ex->getMessage());
}
} }
} }
} }
@ -80,7 +82,7 @@ class AliasEditor implements Extension {
global $database; global $database;
$pair = array($event->oldtag, $event->newtag); $pair = array($event->oldtag, $event->newtag);
if($database->db->GetRow("SELECT * FROM aliases WHERE oldtag=? AND lower(newtag)=lower(?)", $pair)) { if($database->db->GetRow("SELECT * FROM aliases WHERE oldtag=? AND lower(newtag)=lower(?)", $pair)) {
$event->veto("That alias already exists"); throw new AddAliasException("That alias already exists");
} }
else { else {
$database->Execute("INSERT INTO aliases(oldtag, newtag) VALUES(?, ?)", $pair); $database->Execute("INSERT INTO aliases(oldtag, newtag) VALUES(?, ?)", $pair);

View File

@ -35,6 +35,7 @@ class CommentDeletionEvent extends Event {
} }
} }
// }}} // }}}
class CommentPostingException extends SCoreException {}
class Comment { // {{{ class Comment { // {{{
public function Comment($row) { public function Comment($row) {
@ -68,15 +69,15 @@ class CommentList implements Extension {
if(($event instanceof PageRequestEvent) && $event->page_matches("comment")) { if(($event instanceof PageRequestEvent) && $event->page_matches("comment")) {
if($event->get_arg(0) == "add") { if($event->get_arg(0) == "add") {
try {
$cpe = new CommentPostingEvent($_POST['image_id'], $event->user, $_POST['comment']); $cpe = new CommentPostingEvent($_POST['image_id'], $event->user, $_POST['comment']);
send_event($cpe); send_event($cpe);
if($cpe->vetoed) {
$this->theme->display_error($event->page, "Comment Blocked", $cpe->veto_reason);
}
else {
$event->page->set_mode("redirect"); $event->page->set_mode("redirect");
$event->page->set_redirect(make_link("post/view/".int_escape($_POST['image_id']))); $event->page->set_redirect(make_link("post/view/".int_escape($_POST['image_id'])));
} }
catch(CommentPostingException $ex) {
$this->theme->display_error($event->page, "Comment Blocked", $ex->getMessage());
}
} }
else if($event->get_arg(0) == "delete") { else if($event->get_arg(0) == "delete") {
if($event->user->is_admin()) { if($event->user->is_admin()) {
@ -346,37 +347,39 @@ class CommentList implements Extension {
// basic sanity checks // basic sanity checks
if(!$config->get_bool('comment_anon') && $user->is_anonymous()) { if(!$config->get_bool('comment_anon') && $user->is_anonymous()) {
$event->veto("Anonymous posting has been disabled"); throw new CommentPostingException("Anonymous posting has been disabled");
} }
else if(is_null(Image::by_id($config, $database, $image_id))) { else if(is_null(Image::by_id($config, $database, $image_id))) {
$event->veto("The image does not exist"); throw new CommentPostingException("The image does not exist");
} }
else if(trim($comment) == "") { else if(trim($comment) == "") {
$event->veto("Comments need text..."); throw new CommentPostingException("Comments need text...");
} }
else if(strlen($comment) > 9000) { else if(strlen($comment) > 9000) {
$event->veto("Comment too long~"); throw new CommentPostingException("Comment too long~");
} }
// advanced sanity checks // advanced sanity checks
else if(strlen($comment)/strlen(gzcompress($comment)) > 10) { else if(strlen($comment)/strlen(gzcompress($comment)) > 10) {
$event->veto("Comment too repetitive~"); throw new CommentPostingException("Comment too repetitive~");
} }
else if($user->is_anonymous() && !$this->hash_match()) { else if($user->is_anonymous() && !$this->hash_match()) {
$event->veto("Comment submission form is out of date; refresh the comment form to show you aren't a spammer~"); throw new CommentPostingException(
"Comment submission form is out of date; refresh the ".
"comment form to show you aren't a spammer~");
} }
// database-querying checks // database-querying checks
else if($this->is_comment_limit_hit()) { else if($this->is_comment_limit_hit()) {
$event->veto("You've posted several comments recently; wait a minute and try again..."); throw new CommentPostingException("You've posted several comments recently; wait a minute and try again...");
} }
else if($this->is_dupe($image_id, $comment)) { else if($this->is_dupe($image_id, $comment)) {
$event->veto("Someone already made that comment on that image -- try and be more original?"); throw new CommentPostingException("Someone already made that comment on that image -- try and be more original?");
} }
// rate-limited external service checks last // rate-limited external service checks last
else if($user->is_anonymous() && $this->is_spam($comment)) { else if($user->is_anonymous() && $this->is_spam($comment)) {
$event->veto("Akismet thinks that your comment is spam. Try rewriting the comment, or logging in."); throw new CommentPostingException("Akismet thinks that your comment is spam. Try rewriting the comment, or logging in.");
} }
// all checks passed // all checks passed

View File

@ -18,16 +18,11 @@ class PixelFileHandler implements Extension {
send_event(new ThumbnailGenerationEvent($event->hash, $event->type)); send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
$image = $this->create_image_from_data("images/$ha/$hash", $event->metadata); $image = $this->create_image_from_data("images/$ha/$hash", $event->metadata);
if(is_null($image)) { if(is_null($image)) {
$event->veto("Pixel Handler failed to create image object from data"); throw new UploadException("Pixel Handler failed to create image object from data");
return;
} }
$iae = new ImageAdditionEvent($event->user, $image); $iae = new ImageAdditionEvent($event->user, $image);
send_event($iae); send_event($iae); // this might raise an exception, but all we'd do is re-throw it...
if($iae->vetoed) {
$event->veto($iae->veto_reason);
return;
}
} }
if(($event instanceof ThumbnailGenerationEvent) && $this->supported_ext($event->type)) { if(($event instanceof ThumbnailGenerationEvent) && $this->supported_ext($event->type)) {

View File

@ -107,7 +107,7 @@ class ImageIO implements Extension {
if($event instanceof ImageAdditionEvent) { if($event instanceof ImageAdditionEvent) {
$error = $this->add_image($event->image); $error = $this->add_image($event->image);
if(!empty($error)) $event->veto($error); if(!empty($error)) throw new UploadException($error);
} }
if($event instanceof ImageDeletionEvent) { if($event instanceof ImageDeletionEvent) {

View File

@ -59,7 +59,6 @@ class Index implements Extension {
else { else {
$event->page->set_mode("redirect"); $event->page->set_mode("redirect");
$event->page->set_redirect(make_link("post/list/$search/1")); $event->page->set_redirect(make_link("post/list/$search/1"));
//$event->veto();
} }
return; return;
} }

View File

@ -24,6 +24,8 @@ class DataUploadEvent extends Event {
} }
} }
class UploadException extends SCoreException {}
class Upload implements Extension { class Upload implements Extension {
var $theme; var $theme;
// event handling {{{ // event handling {{{
@ -113,12 +115,12 @@ class Upload implements Extension {
if($event instanceof DataUploadEvent) { if($event instanceof DataUploadEvent) {
global $config; global $config;
if($is_full) { if($is_full) {
$event->veto("Upload failed; disk nearly full"); throw new UploadException("Upload failed; disk nearly full");
} }
if(filesize($event->tmpname) > $config->get_int('upload_size')) { if(filesize($event->tmpname) > $config->get_int('upload_size')) {
$size = to_shorthand_int(filesize($event->tmpname)); $size = to_shorthand_int(filesize($event->tmpname));
$limit = to_shorthand_int($config->get_int('upload_size')); $limit = to_shorthand_int($config->get_int('upload_size'));
$event->veto("File too large ($size &gt; $limit)"); throw new UploadException("File too large ($size &gt; $limit)");
} }
} }
} }
@ -146,10 +148,12 @@ class Upload implements Extension {
$metadata['tags'] = $tags; $metadata['tags'] = $tags;
$metadata['source'] = $source; $metadata['source'] = $source;
$event = new DataUploadEvent($user, $file['tmp_name'], $metadata); $event = new DataUploadEvent($user, $file['tmp_name'], $metadata);
try {
send_event($event); send_event($event);
if($event->vetoed) { }
catch(UploadException $ex) {
$this->theme->display_upload_error($page, "Error with ".html_escape($file['name']), $this->theme->display_upload_error($page, "Error with ".html_escape($file['name']),
$event->veto_reason); $ex->getMessage());
$ok = false; $ok = false;
} }
} }
@ -224,10 +228,12 @@ class Upload implements Extension {
$metadata['tags'] = $tags; $metadata['tags'] = $tags;
$metadata['source'] = $source; $metadata['source'] = $source;
$event = new DataUploadEvent($user, $tmp_filename, $metadata); $event = new DataUploadEvent($user, $tmp_filename, $metadata);
try {
send_event($event); send_event($event);
if($event->vetoed) { }
catch(UploadException $ex) {
$this->theme->display_upload_error($page, "Error with ".html_escape($url), $this->theme->display_upload_error($page, "Error with ".html_escape($url),
$event->veto_reason); $ex->getMessage());
$ok = false; $ok = false;
} }
} }
@ -238,5 +244,5 @@ class Upload implements Extension {
} }
// }}} // }}}
} }
add_event_listener(new Upload(), 40); // early, so it can veto the DataUploadEvent before any data handlers see it add_event_listener(new Upload(), 40); // early, so it can stop the DataUploadEvent before any data handlers see it
?> ?>

View File

@ -36,6 +36,8 @@ class UserCreationEvent extends Event {
} }
} }
class UserCreationException extends SCoreException {}
class UserPage implements Extension { class UserPage implements Extension {
var $theme; var $theme;
@ -81,16 +83,16 @@ class UserPage implements Extension {
$this->theme->display_error($event->page, "Password Mismatch", "Passwords don't match"); $this->theme->display_error($event->page, "Password Mismatch", "Passwords don't match");
} }
else { else {
try {
$uce = new UserCreationEvent($_POST['name'], $_POST['pass1'], $_POST['email']); $uce = new UserCreationEvent($_POST['name'], $_POST['pass1'], $_POST['email']);
send_event($uce); send_event($uce);
if($uce->vetoed) {
$this->theme->display_error($event->page, "User Creation Error", $uce->veto_reason);
}
else {
$this->set_login_cookie($uce->username, $uce->password); $this->set_login_cookie($uce->username, $uce->password);
$event->page->set_mode("redirect"); $event->page->set_mode("redirect");
$event->page->set_redirect(make_link("user")); $event->page->set_redirect(make_link("user"));
} }
catch(UserCreationException $ex) {
$this->theme->display_error($event->page, "User Creation Error", $ex->getMessage());
}
} }
} }
else if($event->get_arg(0) == "set_more") { else if($event->get_arg(0) == "set_more") {
@ -210,16 +212,16 @@ class UserPage implements Extension {
global $database; global $database;
if(strlen($name) < 1) { if(strlen($name) < 1) {
$event->veto("Username must be at least 1 character"); throw new UserCreationException("Username must be at least 1 character");
} }
else if(!preg_match('/^[a-zA-Z0-9-_]+$/', $name)) { else if(!preg_match('/^[a-zA-Z0-9-_]+$/', $name)) {
$event->veto("Username contains invalid characters. Allowed characters are letters, numbers, dash, and underscore"); throw new UserCreationException(
"Username contains invalid characters. Allowed characters are ".
"letters, numbers, dash, and underscore");
} }
else if($database->db->GetRow("SELECT * FROM users WHERE name = ?", array($name))) { else if($database->db->GetRow("SELECT * FROM users WHERE name = ?", array($name))) {
$event->veto("That username is already taken"); throw new UserCreationException("That username is already taken");
} }
return (!$event->vetoed);
} }
private function create_user($event) { private function create_user($event) {