From 054a080a69bbacfe7a420ac5ccbf6b8b8df0b75c Mon Sep 17 00:00:00 2001 From: Peter Sanchez Date: Mon, 2 Dec 2024 19:00:15 -0600 Subject: [PATCH] Revisiting the search work around from previous abuse. Properly escaping search input for PostgreSQL FTS now. --- admin/routes.go | 4 ++-- api/graph/schema.resolvers.go | 26 +++++++++++++------------- api/loaders/loaders.go | 4 ++-- core/routes.go | 2 +- helpers.go | 23 +++++++++++++++-------- values.go | 1 + 6 files changed, 34 insertions(+), 26 deletions(-) diff --git a/admin/routes.go b/admin/routes.go index 1899abb..292175f 100644 --- a/admin/routes.go +++ b/admin/routes.go @@ -64,10 +64,10 @@ func (s *Service) Autocomplete(c echo.Context) error { org := c.QueryParam("org") items := []Item{} if org != "" { - s := links.ParseSearch(org, true) + s := links.ParseSearch(org) opts := &database.FilterOptions{ Filter: sq.And{ - sq.Expr(`to_tsvector('simple', o.name || ' ' || o.slug) @@ websearch_to_tsquery('simple', ?)`, s), + sq.Expr(`to_tsvector('simple', o.name || ' ' || o.slug) @@ to_tsquery('simple', ?)`, s), sq.Eq{"o.is_active": true}, }, } diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 5a716a4..7038aa6 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -4292,11 +4292,11 @@ func (r *queryResolver) GetOrganizations(ctx context.Context, input *model.GetOr // OrderBy: "o.created_on ASC", //} if input.Search != nil && *input.Search != "" { - s := links.ParseSearch(*input.Search, true) + s := links.ParseSearch(*input.Search) opts.Filter = sq.And{ opts.Filter, sq.Expr(`to_tsvector('simple', o.name || ' ' || o.slug ) - @@ websearch_to_tsquery('simple', ?)`, s), + @@ to_tsquery('simple', ?)`, s), } } @@ -4735,11 +4735,11 @@ func (r *queryResolver) GetOrgLinks(ctx context.Context, input *model.GetLinkInp } if input.Search != nil && *input.Search != "" { - s := links.ParseSearch(*input.Search, true) + s := links.ParseSearch(*input.Search) linkOpts.Filter = sq.And{ linkOpts.Filter, sq.Expr(`to_tsvector('simple', ol.title || ' ' || ol.description || ' ' || ol.url) - @@ websearch_to_tsquery('simple', ?)`, s), + @@ to_tsquery('simple', ?)`, s), } } @@ -6015,11 +6015,11 @@ func (r *queryResolver) GetFeed(ctx context.Context, input *model.GetFeedInput) } if input.Search != nil && *input.Search != "" { - s := links.ParseSearch(*input.Search, true) + s := links.ParseSearch(*input.Search) linkOpts.Filter = sq.And{ linkOpts.Filter, sq.Expr(`to_tsvector('simple', ol.title || ' ' || ol.description || ' ' || ol.url) - @@ websearch_to_tsquery('simple', ?)`, s), + @@ to_tsquery('simple', ?)`, s), } } @@ -6159,11 +6159,11 @@ func (r *queryResolver) GetUsers(ctx context.Context, input *model.GetUserInput) if input.Search != nil && *input.Search != "" { // We want to search for partial match - s := links.ParseSearch(*input.Search, true) + s := links.ParseSearch(*input.Search) opts.Filter = sq.And{ opts.Filter, sq.Expr(`to_tsvector('simple', u.full_name || ' ' || u.email || ' ' || o.slug) - @@ websearch_to_tsquery('simple', ?)`, s), + @@ to_tsquery('simple', ?)`, s), } } @@ -6312,11 +6312,11 @@ func (r *queryResolver) GetAdminOrganizations(ctx context.Context, input *model. if input.Search != nil && *input.Search != "" { // We want to search for partial match - s := links.ParseSearch(*input.Search, true) + s := links.ParseSearch(*input.Search) opts.Filter = sq.And{ opts.Filter, sq.Expr(`to_tsvector('simple', o.name || ' ' || o.slug ) - @@ websearch_to_tsquery('simple', ?)`, s), + @@ to_tsquery('simple', ?)`, s), } } @@ -6593,12 +6593,12 @@ func (r *queryResolver) GetAdminDomains(ctx context.Context, input *model.GetAdm if input.Search != nil && *input.Search != "" { // NOTE: full text search (FTS) only support partial prefix search :* - // it does not support preffix based search - s := links.ParseSearch(*input.Search, true) + // it does not support prefix based search + s := links.ParseSearch(*input.Search) opts.Filter = sq.And{ opts.Filter, sq.Or{ - sq.Expr(`to_tsvector('simple', d.name) @@ websearch_to_tsquery('simple', ?)`, s), + sq.Expr(`to_tsvector('simple', d.name) @@ to_tsquery('simple', ?)`, s), sq.ILike{"lookup_name": fmt.Sprintf("%%%s%%", *input.Search)}, }, } diff --git a/api/loaders/loaders.go b/api/loaders/loaders.go index 7b44f6c..a3d99dd 100644 --- a/api/loaders/loaders.go +++ b/api/loaders/loaders.go @@ -72,11 +72,11 @@ func getPopularLinks(ctx context.Context) func(key []string) ([][]*models.BaseUR } if q != "" { - s := links.ParseSearch(q, true) + s := links.ParseSearch(q) opts.Filter = sq.And{ opts.Filter, sq.Expr(`to_tsvector('simple', b.title || ' ' || b.url) - @@ websearch_to_tsquery('simple', ?)`, s), + @@ to_tsquery('simple', ?)`, s), } } diff --git a/core/routes.go b/core/routes.go index 9eac080..9d41be5 100644 --- a/core/routes.go +++ b/core/routes.go @@ -3143,7 +3143,7 @@ func (s *Service) TagAutocomplete(c echo.Context) error { var tags []*models.Tag var err error if q != "" { - s := links.ParseSearch(q, false) + s := links.ParseSearch(q) opts := &database.FilterOptions{ Filter: sq.Expr(`to_tsvector('simple', t.name) @@ to_tsquery('simple', ?)`, s), } diff --git a/helpers.go b/helpers.go index adc879a..6849b95 100644 --- a/helpers.go +++ b/helpers.go @@ -824,15 +824,22 @@ func (t TagQuery) GetSubQuery(inputTag, inputExcludeTag *string) (string, []inte // We need to do some parsing to avoid systax error // for some string chars for non websearch queries -func ParseSearch(s string, forWeb bool) string { - if forWeb { - return s +func ParseSearch(s string) string { + re := regexp.MustCompile(`[^a-zA-Z0-9 &|!-]`) + s = re.ReplaceAllString(s, "") + + var words []string + for _, word := range strings.Split(s, " ") { + // This is used for to_tsquery searches (tag autocomplete) + word = strings.TrimSpace(word) + word = strings.Replace(word, ":", "\\:", -1) + if !strings.HasPrefix(word, "-") { + word = word + ":*" + } + words = append(words, word) } - - // This is used for to_tsquery searches (tag autocomplete) - s = strings.TrimSpace(s) - s = strings.Replace(s, ":", "\\:", -1) - return strings.Replace(s, " ", ":* & ", -1) + ":*" + s = strings.Join(words, " & ") + return s } func AddQueryElement(q template.URL, param, val string) template.URL { diff --git a/values.go b/values.go index 97a2b96..2924383 100644 --- a/values.go +++ b/values.go @@ -82,6 +82,7 @@ var InvalidSlugs = []string{ "weblog", "well-known", "api", + "faq", } // Interval used to display data in analytics engagement chart -- 2.45.2