This is an automated email from the ASF dual-hosted git repository.

twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new 955350e1 Reject invalid flags in the parsing of SCAN (#2245)
955350e1 is described below

commit 955350e11023e31aa990e329c2cdd3deab37d6a1
Author: Twice <[email protected]>
AuthorDate: Sat Apr 13 20:21:18 2024 +0900

    Reject invalid flags in the parsing of SCAN (#2245)
    
    Co-authored-by: lizhenglei 
<[email protected]>
    Co-authored-by: hulk <[email protected]>
---
 src/commands/cmd_server.cc          | 22 ------------
 src/commands/scan_base.h            | 67 ++++++++++++++++++-------------------
 tests/gocase/unit/scan/scan_test.go | 11 ++++++
 3 files changed, 43 insertions(+), 57 deletions(-)

diff --git a/src/commands/cmd_server.cc b/src/commands/cmd_server.cc
index d6255465..442908ff 100644
--- a/src/commands/cmd_server.cc
+++ b/src/commands/cmd_server.cc
@@ -819,28 +819,6 @@ class CommandScan : public CommandScanBase {
  public:
   CommandScan() : CommandScanBase() {}
 
-  Status Parse(const std::vector<std::string> &args) override {
-    if (args.size() % 2 != 0) {
-      return {Status::RedisParseErr, errWrongNumOfArguments};
-    }
-
-    ParseCursor(args[1]);
-    if (args.size() >= 4) {
-      Status s = ParseMatchAndCountParam(util::ToLower(args[2]), args_[3]);
-      if (!s.IsOK()) {
-        return s;
-      }
-    }
-
-    if (args.size() >= 6) {
-      Status s = ParseMatchAndCountParam(util::ToLower(args[4]), args_[5]);
-      if (!s.IsOK()) {
-        return s;
-      }
-    }
-    return Commander::Parse(args);
-  }
-
   static std::string GenerateOutput(Server *srv, const Connection *conn, const 
std::vector<std::string> &keys,
                                     const std::string &end_cursor) {
     std::vector<std::string> list;
diff --git a/src/commands/scan_base.h b/src/commands/scan_base.h
index 2e11c989..da3c81ac 100644
--- a/src/commands/scan_base.h
+++ b/src/commands/scan_base.h
@@ -21,6 +21,7 @@
 #pragma once
 
 #include "commander.h"
+#include "commands/command_parser.h"
 #include "error_constants.h"
 #include "parse_util.h"
 #include "server/server.h"
@@ -31,31 +32,40 @@ inline constexpr const char *kCursorPrefix = "_";
 
 class CommandScanBase : public Commander {
  public:
-  Status ParseMatchAndCountParam(const std::string &type, std::string value) {
-    if (type == "match") {
-      prefix_ = std::move(value);
-      if (!prefix_.empty() && prefix_[prefix_.size() - 1] == '*') {
-        prefix_ = prefix_.substr(0, prefix_.size() - 1);
-        return Status::OK();
-      }
+  Status Parse(const std::vector<std::string> &args) override {
+    CommandParser parser(args, 1);
 
-      return {Status::RedisParseErr, "only keys prefix match was supported"};
-    } else if (type == "count") {
-      auto parse_result = ParseInt<int>(value, 10);
-      if (!parse_result) {
-        return {Status::RedisParseErr, "count param should be type int"};
-      }
+    PutCursor(GET_OR_RET(parser.TakeStr()));
 
-      limit_ = *parse_result;
-      if (limit_ <= 0) {
-        return {Status::RedisParseErr, errInvalidSyntax};
+    return ParseAdditionalFlags<true>(parser);
+  }
+
+  template <bool IsScan, typename Parser>
+  Status ParseAdditionalFlags(Parser &parser) {
+    while (parser.Good()) {
+      if (parser.EatEqICase("match")) {
+        prefix_ = GET_OR_RET(parser.TakeStr());
+        if (!prefix_.empty() && prefix_.back() == '*') {
+          prefix_ = prefix_.substr(0, prefix_.size() - 1);
+        } else {
+          return {Status::RedisParseErr, "currently only key prefix matching 
is supported"};
+        }
+      } else if (parser.EatEqICase("count")) {
+        limit_ = GET_OR_RET(parser.TakeInt());
+        if (limit_ <= 0) {
+          return {Status::RedisParseErr, "limit should be a positive integer"};
+        }
+      } else if (IsScan && parser.EatEqICase("type")) {
+        return {Status::RedisParseErr, "TYPE flag is currently not supported"};
+      } else {
+        return parser.InvalidSyntax();
       }
     }
 
     return Status::OK();
   }
 
-  void ParseCursor(const std::string &param) {
+  void PutCursor(const std::string &param) {
     cursor_ = param;
     if (cursor_ == "0") {
       cursor_ = std::string();
@@ -90,26 +100,13 @@ class CommandSubkeyScanBase : public CommandScanBase {
   CommandSubkeyScanBase() : CommandScanBase() {}
 
   Status Parse(const std::vector<std::string> &args) override {
-    if (args.size() % 2 == 0) {
-      return {Status::RedisParseErr, errWrongNumOfArguments};
-    }
+    CommandParser parser(args, 1);
 
-    key_ = args[1];
-    ParseCursor(args[2]);
-    if (args.size() >= 5) {
-      Status s = ParseMatchAndCountParam(util::ToLower(args[3]), args_[4]);
-      if (!s.IsOK()) {
-        return s;
-      }
-    }
+    key_ = GET_OR_RET(parser.TakeStr());
 
-    if (args.size() >= 7) {
-      Status s = ParseMatchAndCountParam(util::ToLower(args[5]), args_[6]);
-      if (!s.IsOK()) {
-        return s;
-      }
-    }
-    return Commander::Parse(args);
+    PutCursor(GET_OR_RET(parser.TakeStr()));
+
+    return ParseAdditionalFlags<false>(parser);
   }
 
   std::string GetNextCursor(Server *srv, std::vector<std::string> &fields, 
CursorType cursor_type) const {
diff --git a/tests/gocase/unit/scan/scan_test.go 
b/tests/gocase/unit/scan/scan_test.go
index 1e6488d7..81a2ff64 100644
--- a/tests/gocase/unit/scan/scan_test.go
+++ b/tests/gocase/unit/scan/scan_test.go
@@ -290,6 +290,17 @@ func ScanTest(t *testing.T, rdb *redis.Client, ctx 
context.Context) {
                        require.Len(t, zsetKeys, test.count)
                })
        }
+
+       t.Run("SCAN reject invalid input", func(t *testing.T) {
+               util.ErrorRegexp(t, rdb.Do(ctx, "SCAN", "0", "hello").Err(), 
".*syntax error.*")
+               util.ErrorRegexp(t, rdb.Do(ctx, "SCAN", "0", "hello", 
"hi").Err(), ".*syntax error.*")
+               util.ErrorRegexp(t, rdb.Do(ctx, "SCAN", "0", "count", "1", 
"hello", "hi").Err(), ".*syntax error.*")
+               util.ErrorRegexp(t, rdb.Do(ctx, "SCAN", "0", "hello", "hi", 
"count", "1").Err(), ".*syntax error.*")
+               require.NoError(t, rdb.Do(ctx, "SCAN", "0", "count", "1", 
"match", "a*").Err())
+               require.NoError(t, rdb.Do(ctx, "SCAN", "0", "match", "a*", 
"count", "1").Err())
+               util.ErrorRegexp(t, rdb.Do(ctx, "SCAN", "0", "count", "1", 
"match", "a*", "hello").Err(), ".*syntax error.*")
+               util.ErrorRegexp(t, rdb.Do(ctx, "SCAN", "0", "count", "1", 
"match", "a*", "hello", "hi").Err(), ".*syntax error.*")
+       })
 }
 
 // SCAN of Kvrocks returns _cursor instead of cursor. Thus, redis.Client Scan 
can fail with

Reply via email to