Add startswith, endswith & added _cols
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
@@ -117,40 +117,63 @@ func removeDuplicateMaps(maps []map[string]string) []map[string]string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildSqlQuery(s *server.Server, queryText, column string, exactMatch bool) string {
|
func buildSqlQuery(s *server.Server, queryText, column string, exactMatch bool) string {
|
||||||
limit := strconv.Itoa(s.Settings.Limit)
|
// Normalize "name" -> "full_name"
|
||||||
from := getFromClause(s)
|
if strings.EqualFold(column, "name") {
|
||||||
if column == "name" {
|
|
||||||
column = "full_name"
|
column = "full_name"
|
||||||
}
|
}
|
||||||
columns := []string{column}
|
|
||||||
|
// Step 1: Determine candidate columns to search
|
||||||
|
var candidateColumns []string
|
||||||
if column == "all" || column == "" {
|
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{}
|
// Step 2: Collect all available columns across dataleaks
|
||||||
// TODO: Add columns that ends with _col aswell
|
allColumns := make([]string, 0)
|
||||||
|
seen := make(map[string]struct{})
|
||||||
for _, dataleak := range *s.Dataleaks {
|
for _, dataleak := range *s.Dataleaks {
|
||||||
for _, col := range dataleak.Columns {
|
for _, col := range dataleak.Columns {
|
||||||
if !slices.Contains(allColumns, col) {
|
if _, ok := seen[col]; !ok {
|
||||||
|
seen[col] = struct{}{}
|
||||||
allColumns = append(allColumns, col)
|
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
|
columnsFiltered = allColumns
|
||||||
} else {
|
} else {
|
||||||
for _, col := range columns {
|
for _, candidate := range candidateColumns {
|
||||||
if slices.Contains(allColumns, col) {
|
for _, available := range allColumns {
|
||||||
columnsFiltered = append(columnsFiltered, col)
|
// Exact match (case-insensitive)
|
||||||
|
if strings.EqualFold(available, candidate) {
|
||||||
|
columnsFiltered = append(columnsFiltered, available)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Match columns ending with "_<candidate>"
|
||||||
|
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 {
|
if len(columnsFiltered) == 0 {
|
||||||
return fmt.Sprintf("SELECT * FROM %s LIMIT %s", from, limit)
|
return fmt.Sprintf("SELECT * FROM %s LIMIT %s", from, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
where := getWhereClause(queryText, columnsFiltered, exactMatch)
|
where := getWhereClause(queryText, columnsFiltered, exactMatch)
|
||||||
|
|
||||||
return fmt.Sprintf("SELECT * FROM %s WHERE %s LIMIT %s", from, where, limit)
|
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
|
var orClausesForTerm []string
|
||||||
termEscaped := strings.ReplaceAll(term, "'", "''")
|
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 {
|
for _, col := range columns {
|
||||||
if exactMatch {
|
if exactMatch || (startsWith && endsWith) {
|
||||||
termEscapedILike := strings.ReplaceAll(termEscaped, "_", "\\_")
|
termEscapedILike := strings.ReplaceAll(termEscaped, "_", "\\_")
|
||||||
termEscapedILike = strings.ReplaceAll(termEscapedILike, "%", "\\%")
|
termEscapedILike = strings.ReplaceAll(termEscapedILike, "%", "\\%")
|
||||||
orClausesForTerm = append(orClausesForTerm, fmt.Sprintf("\"%s\" ILIKE '%s' ESCAPE '\\'", col, strings.ToLower(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
|
// Escape characters for ILIKE
|
||||||
termEscapedILike := strings.ReplaceAll(termEscaped, "_", "\\_")
|
termEscapedILike := strings.ReplaceAll(termEscaped, "_", "\\_")
|
||||||
termEscapedILike = strings.ReplaceAll(termEscapedILike, "%", "\\%")
|
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 ")+")")
|
andClauses = append(andClauses, "("+strings.Join(orClausesForTerm, " OR ")+")")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -35,6 +36,9 @@ type Result struct {
|
|||||||
func Search(s *server.Server, q Query, r *Result, mu *sync.RWMutex) {
|
func Search(s *server.Server, q Query, r *Result, mu *sync.RWMutex) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
cleanQueryText := strings.TrimPrefix(q.Text, "^")
|
||||||
|
cleanQueryText = strings.TrimSuffix(q.Text, "$")
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
r.Date = time.Now()
|
r.Date = time.Now()
|
||||||
r.Status = "pending"
|
r.Status = "pending"
|
||||||
@@ -65,9 +69,13 @@ func Search(s *server.Server, q Query, r *Result, mu *sync.RWMutex) {
|
|||||||
wg.Done()
|
wg.Done()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
githubResult := osint.Search(s, q.Text, q.Column)
|
githubResult := osint.Search(s, cleanQueryText, q.Column)
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
r.GithubResult = *githubResult
|
if githubResult == nil {
|
||||||
|
r.GithubResult = osint.GithubResult{}
|
||||||
|
} else {
|
||||||
|
r.GithubResult = *githubResult
|
||||||
|
}
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -34,7 +34,9 @@
|
|||||||
<span class="text-primary font-semibold">Standard Search:</span> By default,
|
<span class="text-primary font-semibold">Standard Search:</span> By default,
|
||||||
Eleakxir uses a "fuzzy" search. This means it will find results where your search
|
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
|
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 <b>AND</b>, so all terms
|
||||||
|
must be present, while multiple columns are combined with <b>OR</b>, so a match
|
||||||
|
in any column counts.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@@ -43,4 +45,13 @@
|
|||||||
is an exact match for your search term. This is useful for finding specific,
|
is an exact match for your search term. This is useful for finding specific,
|
||||||
unique values.
|
unique values.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<span class="text-primary font-semibold">Starts With or Ends With:</span>
|
||||||
|
You can refine your search by using <code>^</code> at the beginning of a term
|
||||||
|
to match only values that <b>start with</b> that term, or <code>$</code> at the
|
||||||
|
end of a term to match only values that <b>end with</b> that term. For example,
|
||||||
|
<code>^admin</code> will find entries starting with "admin", while
|
||||||
|
<code>user$</code> will find entries ending with "user".
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
})
|
})
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
result = r.data;
|
result = r.data;
|
||||||
console.log(r.data);
|
|
||||||
if (result && result.Status !== "pending") {
|
if (result && result.Status !== "pending") {
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
}
|
}
|
||||||
@@ -120,7 +119,7 @@
|
|||||||
<Stats {result} />
|
<Stats {result} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if result.LeakResult.Error !== "not enabled" }
|
{#if result.LeakResult.Error !== "not enabled"}
|
||||||
<div class="collapse collapse-arrow bg-base-100 border">
|
<div class="collapse collapse-arrow bg-base-100 border">
|
||||||
<input type="radio" name="my-accordion-2" checked={true} />
|
<input type="radio" name="my-accordion-2" checked={true} />
|
||||||
<div
|
<div
|
||||||
@@ -187,7 +186,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if result.GithubResult.Error !== "not enabled" }
|
{#if result.GithubResult.Error !== "not enabled"}
|
||||||
<div class="collapse collapse-arrow bg-base-100 border">
|
<div class="collapse collapse-arrow bg-base-100 border">
|
||||||
<input type="radio" name="my-accordion-2" />
|
<input type="radio" name="my-accordion-2" />
|
||||||
<div
|
<div
|
||||||
|
|||||||
Reference in New Issue
Block a user