diff options
-rw-r--r-- | jstests/core/regex.js | 19 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser.cpp | 17 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser_test.cpp | 61 |
3 files changed, 95 insertions, 2 deletions
diff --git a/jstests/core/regex.js b/jstests/core/regex.js index e891ddf77fa..1c6a9d6a3bb 100644 --- a/jstests/core/regex.js +++ b/jstests/core/regex.js @@ -61,4 +61,23 @@ assert.throws(function() { t.find({key: {$regex: 'abcd\0xyz'}}).explain(); }); + + // + // Confirm $options and mode specified in $regex are not allowed to be specified together. + // + t.drop(); + assert.commandWorked(t.insert({x: ["abc"]})); + + let regexFirst = assert.throws(() => t.find({x: {$regex: /ab/i, $options: 's'}}).itcount()); + assert.commandFailedWithCode(regexFirst, 51075); + + let optsFirst = assert.throws(() => t.find({x: {$options: 's', $regex: /ab/i}}).itcount()); + assert.commandFailedWithCode(optsFirst, 51074); + + t.drop(); + assert.commandWorked(t.save({x: ["abc"]})); + + assert.eq(1, t.count({x: {$regex: /ABC/i}})); + assert.eq(1, t.count({x: {$regex: /ABC/, $options: 'i'}})); + assert.eq(1, t.count({x: {$options: 'i', $regex: /ABC/}})); })(); diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp index 13e75b92859..185952e97e6 100644 --- a/src/mongo/db/matcher/expression_parser.cpp +++ b/src/mongo/db/matcher/expression_parser.cpp @@ -549,15 +549,28 @@ StatusWithMatchExpression parseRegexDocument(StringData name, const BSONObj& doc regex = e.valueStringData(); } else if (e.type() == BSONType::RegEx) { regex = e.regex(); - regexOptions = e.regexFlags(); + if (!StringData{e.regexFlags()}.empty()) { + if (!regexOptions.empty()) { + return {Status(ErrorCodes::Error(51074), + "options set in both $regex and $options")}; + } + regexOptions = e.regexFlags(); + } } else { return {Status(ErrorCodes::BadValue, "$regex has to be a string")}; } break; case PathAcceptingKeyword::OPTIONS: - if (e.type() != BSONType::String) + if (e.type() != BSONType::String) { return {Status(ErrorCodes::BadValue, "$options has to be a string")}; + } + + if (!regexOptions.empty()) { + return {Status(ErrorCodes::Error(51075), + "options set in both $regex and $options")}; + } + regexOptions = e.valueStringData(); break; default: diff --git a/src/mongo/db/matcher/expression_parser_test.cpp b/src/mongo/db/matcher/expression_parser_test.cpp index 9ac6125f684..d0ee2f1dea7 100644 --- a/src/mongo/db/matcher/expression_parser_test.cpp +++ b/src/mongo/db/matcher/expression_parser_test.cpp @@ -327,6 +327,67 @@ TEST(MatchExpressionParserTest, WhereParsesSuccessfullyWhenAllowed) { .getStatus()); } +TEST(MatchExpressionParserTest, RegexParsesSuccessfullyWithoutOptions) { + auto query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", ""))); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexParsesSuccessfullyWithOptionsInline) { + auto query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "i"))); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexParsesSuccessfullyWithoutOptionsInlineAndEmptyOptionsStr) { + auto query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options" + << "")); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexDoesNotParseSuccessfullyWithOptionsInlineAndEmptyOptionsStr) { + auto query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "i") << "$options" + << "")); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_NOT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexParsesSuccessfullyWithOptionsNotInline) { + auto query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options" + << "i")); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexDoesNotParseSuccessfullyWithMultipleOptions) { + auto query = BSON("a" << BSON("$options" + << "s" + << "$regex" + << BSONRegEx("/myRegex/", "i"))); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_NOT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexParsesSuccessfullyWithOptionsFirst) { + auto query = BSON("a" << BSON("$options" + << "s" + << "$regex" + << BSONRegEx("/myRegex/", ""))); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexParsesSuccessfullyWithOptionsFirstEmptyOptions) { + auto query = BSON("a" << BSON("$options" + << "" + << "$regex" + << BSONRegEx("/myRegex/", ""))); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + + TEST(MatchExpressionParserTest, NearSphereFailsToParseWhenDisallowed) { auto query = fromjson("{a: {$nearSphere: {$geometry: {type: 'Point', coordinates: [0, 0]}}}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); |