diff --git a/modules/search/search.extender.inc b/modules/search/search.extender.inc index b7af4d0..e4589ae 100644 --- a/modules/search/search.extender.inc +++ b/modules/search/search.extender.inc @@ -123,6 +123,16 @@ class SearchQuery extends SelectQueryExtender { protected $multiply = array(); /** + * Whether or not search expressions were ignored. + * + * The maximum number of AND/OR combinations exceeded can be configured to + * avoid Denial-of-Service attacks. Expressions beyond the limit are ignored. + * + * @var boolean + */ + protected $expressionsIgnored = FALSE; + + /** * Sets up the search query expression. * * @param $query @@ -183,7 +193,17 @@ class SearchQuery extends SelectQueryExtender { // Classify tokens. $or = FALSE; $warning = ''; + $limit_combinations = variable_get('search_and_or_limit', 7); + // The first search expression does not count as AND. + $and_count = -1; + $or_count = 0; foreach ($keywords as $match) { + if ($or_count && $and_count + $or_count >= $limit_combinations) { + // Ignore all further search expressions to prevent Denial-of-Service + // attacks using a high number of AND/OR combinations. + $this->expressionsIgnored = TRUE; + break; + } $phrase = FALSE; // Strip off phrase quotes. if ($match[2]{0} == '"') { @@ -212,6 +230,7 @@ class SearchQuery extends SelectQueryExtender { } $this->keys['positive'][] = $last; $or = TRUE; + $or_count++; continue; } // AND operator: implied, so just ignore it. @@ -231,6 +250,7 @@ class SearchQuery extends SelectQueryExtender { } else { $this->keys['positive'] = array_merge($this->keys['positive'], $words); + $and_count++; } } $or = FALSE; @@ -323,6 +343,9 @@ class SearchQuery extends SelectQueryExtender { form_set_error('keys', format_plural(variable_get('minimum_word_size', 3), 'You must include at least one positive keyword with 1 character or more.', 'You must include at least one positive keyword with @count characters or more.')); return FALSE; } + if ($this->expressionsIgnored) { + drupal_set_message(t('Your search used too many AND/OR expressions. Only the first @count terms were included in this search.', array('@count' => variable_get('search_and_or_limit', 7))), 'warning'); + } $this->executedFirstPass = TRUE; if (!empty($this->words)) { diff --git a/modules/search/search.test b/modules/search/search.test index 3ea089c..3989c17 100644 --- a/modules/search/search.test +++ b/modules/search/search.test @@ -290,6 +290,20 @@ class SearchPageText extends DrupalWebTestCase { $this->drupalGet('search/node/' . $arg); $input = $this->xpath("//input[@id='edit-keys' and @value='{$arg}']"); $this->assertFalse(empty($input), 'Search keys with a / are correctly set as the default value in the search box.'); + + // Test a search input exceeding the limit of AND/OR combinations to test + // the Denial-of-Service protection. + $limit = variable_get('search_and_or_limit', 7); + $keys = array(); + for ($i = 0; $i < $limit + 1; $i++) { + $keys[] = $this->randomName(3); + if ($i % 2 == 0) { + $keys[] = 'OR'; + } + } + $edit['keys'] = implode(' ', $keys); + $this->drupalPost('search/node', $edit, t('Search')); + $this->assertRaw(t('Your search used too many AND/OR expressions. Only the first @count terms were included in this search.', array('@count' => $limit))); } }