diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index ae01300781..0075f4ea00 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -2259,17 +2259,7 @@ func (s SqlChannelStore) SearchMore(userId string, teamId string, term string) ( } func (s SqlChannelStore) buildLIKEClause(term string, searchColumns string) (likeClause, likeTerm string) { - likeTerm = term - - // These chars must be removed from the like query. - for _, c := range ignoreLikeSearchChar { - likeTerm = strings.Replace(likeTerm, c, "", -1) - } - - // These chars must be escaped in the like query. - for _, c := range escapeLikeSearchChar { - likeTerm = strings.Replace(likeTerm, c, "*"+c, -1) - } + likeTerm = sanitizeSearchTerm(term, "*") if likeTerm == "" { return @@ -2429,6 +2419,7 @@ func (s SqlChannelStore) getSearchGroupChannelsQuery(userId, term string, isPost for idx, term := range terms { argName := fmt.Sprintf("Term%v", idx) + term = sanitizeSearchTerm(term, "\\") likeClauses = append(likeClauses, fmt.Sprintf(baseLikeClause, ":"+argName)) args[argName] = "%" + term + "%" } diff --git a/store/sqlstore/compliance_store.go b/store/sqlstore/compliance_store.go index bb32f5d3b0..3ae6ef5b47 100644 --- a/store/sqlstore/compliance_store.go +++ b/store/sqlstore/compliance_store.go @@ -90,6 +90,7 @@ func (s SqlComplianceStore) ComplianceExport(job *model.Compliance) ([]*model.Co keywordQuery = "AND (" for index, keyword := range keywords { + keyword = sanitizeSearchTerm(keyword, "\\") if index >= 1 { keywordQuery += " OR LOWER(Posts.Message) LIKE :Keyword" + strconv.Itoa(index) } else { diff --git a/store/sqlstore/emoji_store.go b/store/sqlstore/emoji_store.go index 8489454068..533f88e1f5 100644 --- a/store/sqlstore/emoji_store.go +++ b/store/sqlstore/emoji_store.go @@ -147,6 +147,8 @@ func (es SqlEmojiStore) Delete(emoji *model.Emoji, time int64) *model.AppError { func (es SqlEmojiStore) Search(name string, prefixOnly bool, limit int) ([]*model.Emoji, *model.AppError) { var emojis []*model.Emoji + name = sanitizeSearchTerm(name, "\\") + term := "" if !prefixOnly { term = "%" diff --git a/store/sqlstore/group_store.go b/store/sqlstore/group_store.go index b9f6c6ac5e..84f2139845 100644 --- a/store/sqlstore/group_store.go +++ b/store/sqlstore/group_store.go @@ -853,7 +853,7 @@ func (s *SqlGroupStore) groupsBySyncableBaseQuery(st model.GroupSyncableType, t } if len(opts.Q) > 0 { - pattern := fmt.Sprintf("%%%s%%", opts.Q) + pattern := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(opts.Q, "\\")) operatorKeyword := "ILIKE" if s.DriverName() == model.DATABASE_DRIVER_MYSQL { operatorKeyword = "LIKE" @@ -919,7 +919,7 @@ func (s *SqlGroupStore) GetGroups(page, perPage int, opts model.GroupSearchOpts) } if len(opts.Q) > 0 { - pattern := fmt.Sprintf("%%%s%%", opts.Q) + pattern := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(opts.Q, "\\")) operatorKeyword := "ILIKE" if s.DriverName() == model.DATABASE_DRIVER_MYSQL { operatorKeyword = "LIKE" diff --git a/store/sqlstore/team_store.go b/store/sqlstore/team_store.go index ec3b3770f5..0de45d0f4e 100644 --- a/store/sqlstore/team_store.go +++ b/store/sqlstore/team_store.go @@ -293,6 +293,8 @@ func (s SqlTeamStore) GetByName(name string) (*model.Team, *model.AppError) { func (s SqlTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) { var teams []*model.Team + term = sanitizeSearchTerm(term, "\\") + if _, err := s.GetReplica().Select(&teams, "SELECT * FROM Teams WHERE Name LIKE :Term OR DisplayName LIKE :Term", map[string]interface{}{"Term": term + "%"}); err != nil { return nil, model.NewAppError("SqlTeamStore.SearchAll", "store.sql_team.search_all_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError) } @@ -303,6 +305,8 @@ func (s SqlTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) { func (s SqlTeamStore) SearchOpen(term string) ([]*model.Team, *model.AppError) { var teams []*model.Team + term = sanitizeSearchTerm(term, "\\") + if _, err := s.GetReplica().Select(&teams, "SELECT * FROM Teams WHERE Type = 'O' AND AllowOpenInvite = true AND (Name LIKE :Term OR DisplayName LIKE :Term)", map[string]interface{}{"Term": term + "%"}); err != nil { return nil, model.NewAppError("SqlTeamStore.SearchOpen", "store.sql_team.search_open_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError) } @@ -313,6 +317,8 @@ func (s SqlTeamStore) SearchOpen(term string) ([]*model.Team, *model.AppError) { func (s SqlTeamStore) SearchPrivate(term string) ([]*model.Team, *model.AppError) { var teams []*model.Team + term = sanitizeSearchTerm(term, "\\") + query := `SELECT * FROM diff --git a/store/sqlstore/user_access_token_store.go b/store/sqlstore/user_access_token_store.go index 54b26d6ee0..bd5df11dc0 100644 --- a/store/sqlstore/user_access_token_store.go +++ b/store/sqlstore/user_access_token_store.go @@ -179,6 +179,7 @@ func (s SqlUserAccessTokenStore) GetByUser(userId string, offset, limit int) ([] } func (s SqlUserAccessTokenStore) Search(term string) ([]*model.UserAccessToken, *model.AppError) { + term = sanitizeSearchTerm(term, "\\") tokens := []*model.UserAccessToken{} params := map[string]interface{}{"Term": term + "%"} query := ` diff --git a/store/sqlstore/user_store.go b/store/sqlstore/user_store.go index 2f16172453..5a8fc3fe75 100644 --- a/store/sqlstore/user_store.go +++ b/store/sqlstore/user_store.go @@ -409,11 +409,13 @@ func applyRoleFilter(query sq.SelectBuilder, role string, isPostgreSQL bool) sq. return query } - roleParam := fmt.Sprintf("%%%s%%", role) if isPostgreSQL { + roleParam := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(role, "\\")) return query.Where("u.Roles LIKE LOWER(?)", roleParam) } + roleParam := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(role, "*")) + return query.Where("u.Roles LIKE ? ESCAPE '*'", roleParam) } @@ -1222,15 +1224,6 @@ func (us SqlUserStore) SearchInChannel(channelId string, term string, options *m return us.performSearch(query, term, options) } -var escapeLikeSearchChar = []string{ - "%", - "_", -} - -var ignoreLikeSearchChar = []string{ - "*", -} - var spaceFulltextSearchChar = []string{ "<", ">", @@ -1265,15 +1258,7 @@ func generateSearchQuery(query sq.SelectBuilder, terms []string, fields []string } func (us SqlUserStore) performSearch(query sq.SelectBuilder, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) { - // These chars must be removed from the like query. - for _, c := range ignoreLikeSearchChar { - term = strings.Replace(term, c, "", -1) - } - - // These chars must be escaped in the like query. - for _, c := range escapeLikeSearchChar { - term = strings.Replace(term, c, "*"+c, -1) - } + term = sanitizeSearchTerm(term, "*") searchType := USER_SEARCH_TYPE_NAMES_NO_FULL_NAME if options.AllowEmails { diff --git a/store/sqlstore/utils.go b/store/sqlstore/utils.go index a5805b86d9..531bdf8d60 100644 --- a/store/sqlstore/utils.go +++ b/store/sqlstore/utils.go @@ -8,11 +8,27 @@ import ( "database/sql" "fmt" "strconv" + "strings" "github.com/mattermost/gorp" "github.com/mattermost/mattermost-server/mlog" ) +var escapeLikeSearchChar = []string{ + "%", + "_", +} + +func sanitizeSearchTerm(term string, escapeChar string) string { + term = strings.Replace(term, escapeChar, "", -1) + + for _, c := range escapeLikeSearchChar { + term = strings.Replace(term, c, escapeChar+c, -1) + } + + return term +} + // Converts a list of strings into a list of query parameters and a named parameter map that can // be used as part of a SQL query. func MapStringsToQueryParams(list []string, paramPrefix string) (string, map[string]interface{}) { diff --git a/store/sqlstore/utils_test.go b/store/sqlstore/utils_test.go index 2adf4dc10f..f49c1b4049 100644 --- a/store/sqlstore/utils_test.go +++ b/store/sqlstore/utils_test.go @@ -2,6 +2,8 @@ package sqlstore import ( "testing" + + "github.com/stretchr/testify/require" ) func TestMapStringsToQueryParams(t *testing.T) { @@ -30,3 +32,29 @@ func TestMapStringsToQueryParams(t *testing.T) { } }) } + +func TestSanitizeSearchTerm(t *testing.T) { + term := "test" + result := sanitizeSearchTerm(term, "\\") + require.Equal(t, result, term) + + term = "%%%" + expected := "\\%\\%\\%" + result = sanitizeSearchTerm(term, "\\") + require.Equal(t, result, expected) + + term = "%\\%\\%" + expected = "\\%\\%\\%" + result = sanitizeSearchTerm(term, "\\") + require.Equal(t, result, expected) + + term = "%_test_%" + expected = "\\%\\_test\\_\\%" + result = sanitizeSearchTerm(term, "\\") + require.Equal(t, result, expected) + + term = "**test_%" + expected = "test*_*%" + result = sanitizeSearchTerm(term, "*") + require.Equal(t, result, expected) +}