diff --git a/app/common/dao/base.go b/app/common/dao/base.go index ab5c989..d003693 100644 --- a/app/common/dao/base.go +++ b/app/common/dao/base.go @@ -16,15 +16,21 @@ func getDB(db ...*gorm.DB) *gorm.DB { return global.GetDB() } +var sortCol = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) + func ezSort(a string) string { - var sortCol = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) - s := strings.Fields(a) - if len(s) != 2 || !sortCol.MatchString(s[0]) { - return "" + items := strings.Split(a, ",") + parts := make([]string, 0, len(items)) + for _, item := range items { + s := strings.Fields(item) + if len(s) != 2 || !sortCol.MatchString(s[0]) { + return "" + } + dir := strings.ToUpper(s[1]) + if dir != "ASC" && dir != "DESC" { + return "" + } + parts = append(parts, "`"+s[0]+"` "+dir) } - dir := strings.ToUpper(s[1]) - if dir != "ASC" && dir != "DESC" { - return "" - } - return "`" + s[0] + "` " + dir + return strings.Join(parts, ",") } diff --git a/app/common/dao/base_test.go b/app/common/dao/base_test.go new file mode 100644 index 0000000..c6e2b71 --- /dev/null +++ b/app/common/dao/base_test.go @@ -0,0 +1,73 @@ +package dao + +import "testing" + +func TestEzSort(t *testing.T) { + tests := []struct { + name string + in string + want string + }{ + {name: "empty", in: "", want: ""}, + {name: "spaces", in: " ", want: ""}, + {name: "tabs", in: "\t\t", want: ""}, + {name: "comma only", in: ",", want: ""}, + {name: "direction only", in: "desc", want: ""}, + {name: "column only", in: "pinned", want: ""}, + + {name: "one column desc", in: "pinned desc", want: "`pinned` DESC"}, + {name: "one column asc", in: "weight asc", want: "`weight` ASC"}, + {name: "direction uppercase", in: "pinned DESC", want: "`pinned` DESC"}, + {name: "direction mixed case", in: "pinned DeSc", want: "`pinned` DESC"}, + {name: "column case kept", in: "Pinned desc", want: "`Pinned` DESC"}, + {name: "underscore column", in: "_pinned desc", want: "`_pinned` DESC"}, + {name: "digit in column", in: "weight2 desc", want: "`weight2` DESC"}, + {name: "extra inner spaces", in: "pinned desc", want: "`pinned` DESC"}, + {name: "leading and trailing spaces", in: " pinned desc ", want: "`pinned` DESC"}, + {name: "tab between column and direction", in: "pinned\tdesc", want: "`pinned` DESC"}, + + {name: "two columns", in: "pinned desc, weight desc", want: "`pinned` DESC,`weight` DESC"}, + {name: "mixed directions", in: "pinned asc, weight desc", want: "`pinned` ASC,`weight` DESC"}, + {name: "no space after comma", in: "pinned desc,weight desc", want: "`pinned` DESC,`weight` DESC"}, + {name: "spaces around comma", in: " pinned desc , weight ASC ", want: "`pinned` DESC,`weight` ASC"}, + {name: "three columns", in: "a desc, b asc, c_2 DESC", want: "`a` DESC,`b` ASC,`c_2` DESC"}, + + {name: "trailing comma", in: "pinned desc,", want: ""}, + {name: "leading comma", in: ",pinned desc", want: ""}, + {name: "double comma", in: "pinned desc,,weight desc", want: ""}, + {name: "second missing direction", in: "pinned desc, weight", want: ""}, + {name: "first missing direction", in: "pinned, weight desc", want: ""}, + {name: "extra token", in: "pinned desc extra", want: ""}, + {name: "extra token on second", in: "pinned desc, weight desc extra", want: ""}, + {name: "direction glued to column", in: "pinned_desc", want: ""}, + {name: "column starts with digit", in: "1pinned desc", want: ""}, + {name: "dot qualifier", in: "yt_page.id desc", want: ""}, + {name: "hyphen", in: "pinned-at desc", want: ""}, + {name: "unicode column", in: "置顶 desc", want: ""}, + {name: "fullwidth comma", in: "pinned desc,weight desc", want: ""}, + {name: "star", in: "* desc", want: ""}, + {name: "function call", in: "count(*) desc", want: ""}, + {name: "parentheses", in: "id) desc", want: ""}, + {name: "single quote", in: "id' desc", want: ""}, + {name: "double quote", in: "id\" desc", want: ""}, + {name: "backslash", in: "id\\ desc", want: ""}, + {name: "null byte", in: "id\x00 desc", want: ""}, + {name: "semicolon", in: "id desc; drop table yt_page", want: ""}, + {name: "comment", in: "id desc--", want: ""}, + {name: "hash comment", in: "id desc#", want: ""}, + {name: "comma expression", in: "id DESC,extractvalue(1,concat(0x7e,user()))", want: ""}, + {name: "backtick breakout", in: "id`,extractvalue(1,concat(0x7e,user())) DESC", want: ""}, + {name: "space before breakout", in: "id desc, extractvalue(1,concat(0x7e,user()))", want: ""}, + {name: "newline payload", in: "id desc\nextractvalue()", want: ""}, + {name: "invalid direction", in: "pinned drop", want: ""}, + {name: "direction with junk", in: "pinned desc,", want: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ezSort(tt.in); got != tt.want { + t.Fatalf("ezSort(%q) = %q, want %q", tt.in, got, tt.want) + } + }) + } +}