From 3cb63ec240278059a172a1a3e5cb775a0580160d Mon Sep 17 00:00:00 2001 From: Hadi <112569860+anotherhadi@users.noreply.github.com> Date: Thu, 25 Sep 2025 17:22:35 +0200 Subject: [PATCH] Add startswith, endswith & added _cols Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com> --- back/search/dataleak/dataleak.go | 70 +++++++++++++++---- back/search/search.go | 12 +++- .../index/search/howToSearch.svelte | 13 +++- front/src/routes/search/[id]/index.svelte | 5 +- 4 files changed, 79 insertions(+), 21 deletions(-) diff --git a/back/search/dataleak/dataleak.go b/back/search/dataleak/dataleak.go index d48212c..c1469d8 100644 --- a/back/search/dataleak/dataleak.go +++ b/back/search/dataleak/dataleak.go @@ -117,40 +117,63 @@ func removeDuplicateMaps(maps []map[string]string) []map[string]string { } func buildSqlQuery(s *server.Server, queryText, column string, exactMatch bool) string { - limit := strconv.Itoa(s.Settings.Limit) - from := getFromClause(s) - if column == "name" { + // Normalize "name" -> "full_name" + if strings.EqualFold(column, "name") { column = "full_name" } - columns := []string{column} + + // Step 1: Determine candidate columns to search + var candidateColumns []string if column == "all" || column == "" { - columns = s.Settings.BaseColumns + // Use base columns if "all" or empty + candidateColumns = s.Settings.BaseColumns + } else { + // Otherwise, only search the given column + candidateColumns = []string{column} } - columnsFiltered := []string{} - allColumns := []string{} - // TODO: Add columns that ends with _col aswell + + // Step 2: Collect all available columns across dataleaks + allColumns := make([]string, 0) + seen := make(map[string]struct{}) for _, dataleak := range *s.Dataleaks { for _, col := range dataleak.Columns { - if !slices.Contains(allColumns, col) { + if _, ok := seen[col]; !ok { + seen[col] = struct{}{} allColumns = append(allColumns, col) } } } - if column == "full_text" { + + // Step 3: Resolve which columns should actually be used in the WHERE clause + var columnsFiltered []string + if strings.EqualFold(column, "full_text") { + // "full_text" means search across all columns columnsFiltered = allColumns } else { - for _, col := range columns { - if slices.Contains(allColumns, col) { - columnsFiltered = append(columnsFiltered, col) + for _, candidate := range candidateColumns { + for _, available := range allColumns { + // Exact match (case-insensitive) + if strings.EqualFold(available, candidate) { + columnsFiltered = append(columnsFiltered, available) + continue + } + // Match columns ending with "_" + if strings.HasSuffix(strings.ToLower(available), "_"+strings.ToLower(candidate)) { + columnsFiltered = append(columnsFiltered, available) + } } } } + limit := strconv.Itoa(s.Settings.Limit) + from := getFromClause(s) + if len(columnsFiltered) == 0 { return fmt.Sprintf("SELECT * FROM %s LIMIT %s", from, limit) } where := getWhereClause(queryText, columnsFiltered, exactMatch) + return fmt.Sprintf("SELECT * FROM %s WHERE %s LIMIT %s", from, where, limit) } @@ -162,8 +185,19 @@ func getWhereClause(queryText string, columns []string, exactMatch bool) string var orClausesForTerm []string termEscaped := strings.ReplaceAll(term, "'", "''") + startsWith := false + endsWith := false + if strings.HasPrefix(termEscaped, "^") { + startsWith = true + termEscaped = strings.TrimPrefix(termEscaped, "^") + } + if strings.HasSuffix(termEscaped, "$") { + endsWith = true + termEscaped = strings.TrimSuffix(termEscaped, "$") + } + for _, col := range columns { - if exactMatch { + if exactMatch || (startsWith && endsWith) { termEscapedILike := strings.ReplaceAll(termEscaped, "_", "\\_") termEscapedILike = strings.ReplaceAll(termEscapedILike, "%", "\\%") orClausesForTerm = append(orClausesForTerm, fmt.Sprintf("\"%s\" ILIKE '%s' ESCAPE '\\'", col, strings.ToLower(termEscapedILike))) @@ -171,7 +205,13 @@ func getWhereClause(queryText string, columns []string, exactMatch bool) string // Escape characters for ILIKE termEscapedILike := strings.ReplaceAll(termEscaped, "_", "\\_") termEscapedILike = strings.ReplaceAll(termEscapedILike, "%", "\\%") - orClausesForTerm = append(orClausesForTerm, fmt.Sprintf("\"%s\" ILIKE '%%%s%%' ESCAPE '\\'", col, strings.ToLower(termEscapedILike))) + if startsWith { + orClausesForTerm = append(orClausesForTerm, fmt.Sprintf("\"%s\" ILIKE '%s%%' ESCAPE '\\'", col, strings.ToLower(termEscapedILike))) + } else if endsWith { + orClausesForTerm = append(orClausesForTerm, fmt.Sprintf("\"%s\" ILIKE '%%%s' ESCAPE '\\'", col, strings.ToLower(termEscapedILike))) + } else { + orClausesForTerm = append(orClausesForTerm, fmt.Sprintf("\"%s\" ILIKE '%%%s%%' ESCAPE '\\'", col, strings.ToLower(termEscapedILike))) + } } } andClauses = append(andClauses, "("+strings.Join(orClausesForTerm, " OR ")+")") diff --git a/back/search/search.go b/back/search/search.go index 31605cb..a03e146 100644 --- a/back/search/search.go +++ b/back/search/search.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "strings" "sync" "time" @@ -35,6 +36,9 @@ type Result struct { func Search(s *server.Server, q Query, r *Result, mu *sync.RWMutex) { var wg sync.WaitGroup + cleanQueryText := strings.TrimPrefix(q.Text, "^") + cleanQueryText = strings.TrimSuffix(q.Text, "$") + mu.Lock() r.Date = time.Now() r.Status = "pending" @@ -65,9 +69,13 @@ func Search(s *server.Server, q Query, r *Result, mu *sync.RWMutex) { wg.Done() return } - githubResult := osint.Search(s, q.Text, q.Column) + githubResult := osint.Search(s, cleanQueryText, q.Column) mu.Lock() - r.GithubResult = *githubResult + if githubResult == nil { + r.GithubResult = osint.GithubResult{} + } else { + r.GithubResult = *githubResult + } mu.Unlock() wg.Done() }() diff --git a/front/src/lib/components/index/search/howToSearch.svelte b/front/src/lib/components/index/search/howToSearch.svelte index 5c77e35..8852191 100644 --- a/front/src/lib/components/index/search/howToSearch.svelte +++ b/front/src/lib/components/index/search/howToSearch.svelte @@ -34,7 +34,9 @@ Standard Search: By default, Eleakxir uses a "fuzzy" search. This means it will find results where your search terms are part of a larger string. For example, searching for 1234 would find - john.doe@1234.com. + john.doe@1234.com. Multiple terms are combined with AND, so all terms + must be present, while multiple columns are combined with OR, so a match + in any column counts.

@@ -43,4 +45,13 @@ is an exact match for your search term. This is useful for finding specific, unique values.

+ +

+ Starts With or Ends With: + You can refine your search by using ^ at the beginning of a term + to match only values that start with that term, or $ at the + end of a term to match only values that end with that term. For example, + ^admin will find entries starting with "admin", while + user$ will find entries ending with "user". +

diff --git a/front/src/routes/search/[id]/index.svelte b/front/src/routes/search/[id]/index.svelte index 11dc40c..96b4c5e 100644 --- a/front/src/routes/search/[id]/index.svelte +++ b/front/src/routes/search/[id]/index.svelte @@ -41,7 +41,6 @@ }) .then((r) => { result = r.data; - console.log(r.data); if (result && result.Status !== "pending") { clearInterval(intervalId); } @@ -120,7 +119,7 @@ - {#if result.LeakResult.Error !== "not enabled" } + {#if result.LeakResult.Error !== "not enabled"}
{/if} - {#if result.GithubResult.Error !== "not enabled" } + {#if result.GithubResult.Error !== "not enabled"}