Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SearchTermParseEvent::matches(), because preg_match is a footgun #1288

Merged
merged 1 commit into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions ext/approval/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,23 +123,18 @@ public function onUserBlockBuilding(UserBlockBuildingEvent $event): void
}
}

public const SEARCH_REGEXP = "/^approved:(yes|no)/";
public const SEARCH_REGEXP = "/^approved:(yes|no)/i";
public function onSearchTermParse(SearchTermParseEvent $event): void
{
global $user, $config;

if ($config->get_bool(ApprovalConfig::IMAGES)) {
$matches = [];

if (is_null($event->term) && $this->no_approval_query($event->context)) {
$event->add_querylet(new Querylet("approved = :true", ["true" => true]));
}

if (is_null($event->term)) {
return;
}
if (preg_match(self::SEARCH_REGEXP, strtolower($event->term), $matches)) {
if ($user->can(Permissions::APPROVE_IMAGE) && $matches[1] == "no") {
if ($matches = $event->matches(self::SEARCH_REGEXP)) {
if ($user->can(Permissions::APPROVE_IMAGE) && strtolower($matches[1]) == "no") {
$event->add_querylet(new Querylet("approved != :true", ["true" => true]));
} else {
$event->add_querylet(new Querylet("approved = :true", ["true" => true]));
Expand Down
7 changes: 1 addition & 6 deletions ext/artists/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,7 @@ public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event): void

public function onSearchTermParse(SearchTermParseEvent $event): void
{
if (is_null($event->term)) {
return;
}

$matches = [];
if (preg_match("/^(author|artist)[=|:](.*)$/i", $event->term, $matches)) {
if ($matches = $event->matches("/^(author|artist)[=|:](.*)$/i")) {
$char = $matches[2];
$event->add_querylet(new Querylet("author = :author_char", ["author_char" => $char]));
}
Expand Down
11 changes: 3 additions & 8 deletions ext/comment/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -377,19 +377,14 @@ public function onSetupBuilding(SetupBuildingEvent $event): void

public function onSearchTermParse(SearchTermParseEvent $event): void
{
if (is_null($event->term)) {
return;
}

$matches = [];
if (preg_match("/^comments([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) {
if ($matches = $event->matches("/^comments([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i")) {
$cmp = ltrim($matches[1], ":") ?: "=";
$comments = $matches[2];
$event->add_querylet(new Querylet("images.id IN (SELECT DISTINCT image_id FROM comments GROUP BY image_id HAVING count(image_id) $cmp $comments)"));
} elseif (preg_match("/^commented_by[=|:](.*)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^commented_by[=|:](.*)$/i")) {
$user_id = User::name_to_id($matches[1]);
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM comments WHERE owner_id = $user_id)"));
} elseif (preg_match("/^commented_by_userno[=|:]([0-9]+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^commented_by_userno[=|:]([0-9]+)$/i")) {
$user_id = int_escape($matches[1]);
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM comments WHERE owner_id = $user_id)"));
}
Expand Down
13 changes: 4 additions & 9 deletions ext/favorites/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,22 +132,17 @@ public function onUserBlockBuilding(UserBlockBuildingEvent $event): void

public function onSearchTermParse(SearchTermParseEvent $event): void
{
if (is_null($event->term)) {
return;
}

$matches = [];
if (preg_match("/^favorites([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) {
if ($matches = $event->matches("/^favorites([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i")) {
$cmp = ltrim($matches[1], ":") ?: "=";
$favorites = $matches[2];
$event->add_querylet(new Querylet("images.id IN (SELECT id FROM images WHERE favorites $cmp $favorites)"));
} elseif (preg_match("/^favorited_by[=|:](.*)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^favorited_by[=|:](.*)$/i")) {
$user_id = User::name_to_id($matches[1]);
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM user_favorites WHERE user_id = $user_id)"));
} elseif (preg_match("/^favorited_by_userno[=|:](\d+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^favorited_by_userno[=|:](\d+)$/i")) {
$user_id = int_escape($matches[1]);
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM user_favorites WHERE user_id = $user_id)"));
} elseif (preg_match("/^order[=|:](favorites)(?:_(desc|asc))?$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^order[=|:](favorites)(?:_(desc|asc))?$/i")) {
$default_order_for_column = "DESC";
$sort = isset($matches[2]) ? strtoupper($matches[2]) : $default_order_for_column;
$event->order = "images.favorites $sort";
Expand Down
15 changes: 15 additions & 0 deletions ext/index/events.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ public function add_tag_condition(TagCondition $c): void
{
$this->tag_conditions[] = $c;
}

/**
* @return array<string>|null
*/
public function matches(string $regex): ?array
{
$matches = [];
if (is_null($this->term)) {
return null;
}
if (\Safe\preg_match($regex, $this->term, $matches) === 1) {
return $matches;
}
return null;
}
}

class SearchTermParseException extends InvalidInput
Expand Down
28 changes: 11 additions & 17 deletions ext/index/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,48 +194,43 @@ public function onSearchTermParse(SearchTermParseEvent $event): void
{
global $database;

if (is_null($event->term)) {
return;
}

$matches = [];
if (preg_match("/^filesize([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+[kmg]?b?)$/i", $event->term, $matches)) {
if ($matches = $event->matches("/^filesize([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+[kmg]?b?)$/i")) {
$cmp = ltrim($matches[1], ":") ?: "=";
$val = parse_shorthand_int($matches[2]);
$event->add_querylet(new Querylet("images.filesize $cmp :val{$event->id}", ["val{$event->id}" => $val]));
} elseif (preg_match("/^id=([\d,]+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^id=([\d,]+)$/i")) {
$val = array_map(fn ($x) => int_escape($x), explode(",", $matches[1]));
$set = implode(",", $val);
$event->add_querylet(new Querylet("images.id IN ($set)"));
} elseif (preg_match("/^id([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^id([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i")) {
$cmp = ltrim($matches[1], ":") ?: "=";
$val = int_escape($matches[2]);
$event->add_querylet(new Querylet("images.id $cmp :val{$event->id}", ["val{$event->id}" => $val]));
} elseif (preg_match("/^(hash|md5)[=|:]([0-9a-fA-F]*)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^(hash|md5)[=|:]([0-9a-fA-F]*)$/i")) {
$hash = strtolower($matches[2]);
$event->add_querylet(new Querylet('images.hash = :hash', ["hash" => $hash]));
} elseif (preg_match("/^(phash)[=|:]([0-9a-fA-F]*)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^(phash)[=|:]([0-9a-fA-F]*)$/i")) {
$phash = strtolower($matches[2]);
$event->add_querylet(new Querylet('images.phash = :phash', ["phash" => $phash]));
} elseif (preg_match("/^(filename|name)[=|:](.+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^(filename|name)[=|:](.+)$/i")) {
$filename = strtolower($matches[2]);
$event->add_querylet(new Querylet("lower(images.filename) LIKE :filename{$event->id}", ["filename{$event->id}" => "%$filename%"]));
} elseif (preg_match("/^posted([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])([0-9-]*)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^posted([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])([0-9-]*)$/i")) {
// TODO Make this able to search = without needing a time component.
$cmp = ltrim($matches[1], ":") ?: "=";
$val = $matches[2];
$event->add_querylet(new Querylet("images.posted $cmp :posted{$event->id}", ["posted{$event->id}" => $val]));
} elseif (preg_match("/^order[=|:](id|width|height|length|filesize|filename)[_]?(desc|asc)?$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^order[=|:](id|width|height|length|filesize|filename)[_]?(desc|asc)?$/i")) {
$ord = strtolower($matches[1]);
$default_order_for_column = preg_match("/^(id|filename)$/", $matches[1]) ? "ASC" : "DESC";
$sort = isset($matches[2]) ? strtoupper($matches[2]) : $default_order_for_column;
$event->order = "images.$ord $sort";
} elseif (preg_match("/^order[=|:]random[_]([0-9]{1,4})$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^order[=|:]random[_]([0-9]{1,4})$/i")) {
// requires a seed to avoid duplicates
// since the tag can't be changed during the parseevent, we instead generate the seed during submit using js
$seed = (int)$matches[1];
$event->order = $database->seeded_random($seed, "images.id");
} elseif (preg_match("/^order[=|:]dailyshuffle$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^order[=|:]dailyshuffle$/i")) {
// will use today's date as seed, thus allowing for a dynamic randomized list without outside intervention.
// This way the list will change every day, giving a more dynamic feel to the imageboard.
// recommended to change homepage to "post/list/order:dailyshuffle/1"
Expand All @@ -244,8 +239,7 @@ public function onSearchTermParse(SearchTermParseEvent $event): void
}

// If we've reached this far, and nobody else has done anything with this term, then treat it as a tag
if ($event->order === null && $event->img_conditions == [] && $event->tag_conditions == []) {
assert(is_string($event->term));
if (!is_null($event->term) && $event->order === null && $event->img_conditions == [] && $event->tag_conditions == []) {
$event->add_tag_condition(new TagCondition($event->term, !$event->negative));
}
}
Expand Down
17 changes: 6 additions & 11 deletions ext/media/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -239,33 +239,28 @@ public function onMediaResize(MediaResizeEvent $event): void

public function onSearchTermParse(SearchTermParseEvent $event): void
{
if (is_null($event->term)) {
return;
}

$matches = [];
if (preg_match("/^content[=|:]((video)|(audio)|(image)|(unknown))$/i", $event->term, $matches)) {
if ($matches = $event->matches("/^content[=|:]((video)|(audio)|(image)|(unknown))$/i")) {
$field = $matches[1];
if ($field === "unknown") {
$event->add_querylet(new Querylet("video IS NULL OR audio IS NULL OR image IS NULL"));
} else {
$event->add_querylet(new Querylet("$field = :true", ["true" => true]));
}
} elseif (preg_match("/^ratio([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+):(\d+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^ratio([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+):(\d+)$/i")) {
$cmp = preg_replace_ex('/^:/', '=', $matches[1]);
$args = ["width{$event->id}" => int_escape($matches[2]), "height{$event->id}" => int_escape($matches[3])];
$event->add_querylet(new Querylet("width / :width{$event->id} $cmp height / :height{$event->id}", $args));
} elseif (preg_match("/^size([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)x(\d+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^size([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)x(\d+)$/i")) {
$cmp = ltrim($matches[1], ":") ?: "=";
$args = ["width{$event->id}" => int_escape($matches[2]), "height{$event->id}" => int_escape($matches[3])];
$event->add_querylet(new Querylet("width $cmp :width{$event->id} AND height $cmp :height{$event->id}", $args));
} elseif (preg_match("/^width([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^width([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i")) {
$cmp = ltrim($matches[1], ":") ?: "=";
$event->add_querylet(new Querylet("width $cmp :width{$event->id}", ["width{$event->id}" => int_escape($matches[2])]));
} elseif (preg_match("/^height([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^height([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i")) {
$cmp = ltrim($matches[1], ":") ?: "=";
$event->add_querylet(new Querylet("height $cmp :height{$event->id}", ["height{$event->id}" => int_escape($matches[2])]));
} elseif (preg_match("/^length([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(.+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^length([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(.+)$/i")) {
$value = parse_to_milliseconds($matches[2]);
$cmp = ltrim($matches[1], ":") ?: "=";
$event->add_querylet(new Querylet("length $cmp :length{$event->id}", ["length{$event->id}" => $value]));
Expand Down
10 changes: 2 additions & 8 deletions ext/mime/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,10 @@ public function onHelpPageBuilding(HelpPageBuildingEvent $event): void

public function onSearchTermParse(SearchTermParseEvent $event): void
{
if (is_null($event->term)) {
return;
}

$matches = [];
// check for tags first as tag based searches are more common.
if (preg_match("/^ext[=|:]([a-zA-Z0-9]+)$/i", $event->term, $matches)) {
if ($matches = $event->matches("/^ext[=|:]([a-zA-Z0-9]+)$/i")) {
$ext = strtolower($matches[1]);
$event->add_querylet(new Querylet('images.ext = :ext', ["ext" => $ext]));
} elseif (preg_match("/^mime[=|:](.+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^mime[=|:](.+)$/i")) {
$mime = strtolower($matches[1]);
$event->add_querylet(new Querylet("images.mime = :mime", ["mime" => $mime]));
}
Expand Down
13 changes: 4 additions & 9 deletions ext/notes/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -201,22 +201,17 @@ public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event):
*/
public function onSearchTermParse(SearchTermParseEvent $event): void
{
if (is_null($event->term)) {
return;
}

$matches = [];
if (preg_match("/^note[=|:](.*)$/i", $event->term, $matches)) {
if ($matches = $event->matches("/^note[=|:](.*)$/i")) {
$notes = int_escape($matches[1]);
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM notes WHERE note = $notes)"));
} elseif (preg_match("/^notes([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)%/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^notes([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)%/i")) {
$cmp = ltrim($matches[1], ":") ?: "=";
$notes = $matches[2];
$event->add_querylet(new Querylet("images.id IN (SELECT id FROM images WHERE notes $cmp $notes)"));
} elseif (preg_match("/^notes_by[=|:](.*)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^notes_by[=|:](.*)$/i")) {
$user_id = User::name_to_id($matches[1]);
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM notes WHERE user_id = $user_id)"));
} elseif (preg_match("/^(notes_by_userno|notes_by_user_id)[=|:](\d+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^(notes_by_userno|notes_by_user_id)[=|:](\d+)$/i")) {
$user_id = int_escape($matches[2]);
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM notes WHERE user_id = $user_id)"));
}
Expand Down
22 changes: 8 additions & 14 deletions ext/numeric_score/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -315,40 +315,35 @@ public function onHelpPageBuilding(HelpPageBuildingEvent $event): void

public function onSearchTermParse(SearchTermParseEvent $event): void
{
if (is_null($event->term)) {
return;
}

$matches = [];
if (preg_match("/^score([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(-?\d+)$/i", $event->term, $matches)) {
if ($matches = $event->matches("/^score([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(-?\d+)$/i")) {
$cmp = ltrim($matches[1], ":") ?: "=";
$score = $matches[2];
$event->add_querylet(new Querylet("numeric_score $cmp $score"));
} elseif (preg_match("/^upvoted_by[=|:](.*)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^upvoted_by[=|:](.*)$/i")) {
$duser = User::by_name($matches[1]);
$event->add_querylet(new Querylet(
"images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=1)",
["ns_user_id" => $duser->id]
));
} elseif (preg_match("/^downvoted_by[=|:](.*)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^downvoted_by[=|:](.*)$/i")) {
$duser = User::by_name($matches[1]);
$event->add_querylet(new Querylet(
"images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=-1)",
["ns_user_id" => $duser->id]
));
} elseif (preg_match("/^upvoted_by_id[=|:](\d+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^upvoted_by_id[=|:](\d+)$/i")) {
$iid = int_escape($matches[1]);
$event->add_querylet(new Querylet(
"images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=1)",
["ns_user_id" => $iid]
));
} elseif (preg_match("/^downvoted_by_id[=|:](\d+)$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^downvoted_by_id[=|:](\d+)$/i")) {
$iid = int_escape($matches[1]);
$event->add_querylet(new Querylet(
"images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=-1)",
["ns_user_id" => $iid]
));
} elseif (preg_match("/^order[=|:](?:numeric_)?(score)(?:_(desc|asc))?$/i", $event->term, $matches)) {
} elseif ($matches = $event->matches("/^order[=|:](?:numeric_)?(score)(?:_(desc|asc))?$/i")) {
$default_order_for_column = "DESC";
$sort = isset($matches[2]) ? strtoupper($matches[2]) : $default_order_for_column;
$event->order = "images.numeric_score $sort";
Expand All @@ -364,10 +359,9 @@ public function onTagTermCheck(TagTermCheckEvent $event): void

public function onTagTermParse(TagTermParseEvent $event): void
{
$matches = [];
global $user;

if (preg_match("/^vote[=|:](up|down|remove)$/", $event->term, $matches)) {
global $user;
if ($matches = $event->matches("/^vote[=|:](up|down|remove)$/")) {
$score = ($matches[1] == "up" ? 1 : ($matches[1] == "down" ? -1 : 0));
if ($user->can(Permissions::CREATE_VOTE)) {
send_event(new NumericScoreSetEvent($event->image_id, $user, $score));
Expand Down
Loading