diff --git a/go.mod b/go.mod index 87ed7a6..db3904a 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/spf13/cobra v1.5.0 github.com/spf13/pflag v1.0.5 github.com/tg123/go-htpasswd v1.2.0 + golang.org/x/text v0.3.7 modernc.org/sqlite v1.18.1 ) @@ -46,7 +47,6 @@ require ( golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect - golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.12 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect lukechampine.com/uint128 v1.2.0 // indirect diff --git a/storage/database.go b/storage/database.go index ffe15a8..78f2a45 100644 --- a/storage/database.go +++ b/storage/database.go @@ -307,81 +307,8 @@ func Search(search string) ([]data.Summary, error) { panic(err) } - q := sqlf.From("mailbox"). - Select(`ID, Data, read, - json_extract(Data, '$.To') as ToJSON, - json_extract(Data, '$.From') as FromJSON, - json_extract(Data, '$.Subject') as Subject, - json_extract(Data, '$.Attachments') as Attachments - `). - OrderBy("Sort DESC"). - Limit(200) - - for _, w := range args { - if cleanString(w) == "" { - continue - } - - exclude := false - if strings.HasPrefix(w, "-") || strings.HasPrefix(w, "!") { - exclude = true - w = w[1:] - } - - if strings.HasPrefix(w, "to:") { - w = cleanString(w[3:]) - if w != "" { - if exclude { - q.Where("ToJSON NOT LIKE ?", "%"+escPercentChar(w)+"%") - } else { - q.Where("ToJSON LIKE ?", "%"+escPercentChar(w)+"%") - } - } - } else if strings.HasPrefix(w, "from:") { - w = cleanString(w[5:]) - if w != "" { - if exclude { - q.Where("FromJSON NOT LIKE ?", "%"+escPercentChar(w)+"%") - } else { - q.Where("FromJSON LIKE ?", "%"+escPercentChar(w)+"%") - } - } - } else if strings.HasPrefix(w, "subject:") { - w = cleanString(w[8:]) - if w != "" { - if exclude { - q.Where("Subject NOT LIKE ?", "%"+escPercentChar(w)+"%") - } else { - q.Where("Subject LIKE ?", "%"+escPercentChar(w)+"%") - } - } - } else if w == "is:read" { - if exclude { - q.Where("Read = 0") - } else { - q.Where("Read = 1") - } - } else if w == "is:unread" { - if exclude { - q.Where("Read = 1") - } else { - q.Where("Read = 0") - } - } else if w == "has:attachment" || w == "has:attachments" { - if exclude { - q.Where("Attachments = 0") - } else { - q.Where("Attachments > 0") - } - } else { - // search text - if exclude { - q.Where("search NOT LIKE ?", "%"+cleanString(escPercentChar(w))+"%") - } else { - q.Where("search LIKE ?", "%"+cleanString(escPercentChar(w))+"%") - } - } - } + // generate the SQL based on arguments + q := searchParser(args) if err := q.QueryAndClose(nil, db, func(row *sql.Rows) { var id string diff --git a/storage/search.go b/storage/search.go new file mode 100644 index 0000000..91fbfa9 --- /dev/null +++ b/storage/search.go @@ -0,0 +1,95 @@ +package storage + +import ( + "regexp" + "strings" + + "github.com/leporo/sqlf" +) + +// SearchParser returns the SQL syntax for the database search based on the search arguments +func searchParser(args []string) *sqlf.Stmt { + q := sqlf.From("mailbox"). + Select(`ID, Data, read, + json_extract(Data, '$.To') as ToJSON, + json_extract(Data, '$.From') as FromJSON, + json_extract(Data, '$.Subject') as Subject, + json_extract(Data, '$.Attachments') as Attachments + `). + OrderBy("Sort DESC"). + Limit(200) + + for _, w := range args { + if cleanString(w) == "" { + continue + } + + exclude := false + // search terms starting with a `-` or `!` imply an exclude + if len(w) > 1 && (strings.HasPrefix(w, "-") || strings.HasPrefix(w, "!")) { + exclude = true + w = w[1:] + } + + re := regexp.MustCompile(`[a-zA-Z0-9]+`) + if !re.MatchString(w) { + continue + } + + if strings.HasPrefix(w, "to:") { + w = cleanString(w[3:]) + if w != "" { + if exclude { + q.Where("ToJSON NOT LIKE ?", "%"+escPercentChar(w)+"%") + } else { + q.Where("ToJSON LIKE ?", "%"+escPercentChar(w)+"%") + } + } + } else if strings.HasPrefix(w, "from:") { + w = cleanString(w[5:]) + if w != "" { + if exclude { + q.Where("FromJSON NOT LIKE ?", "%"+escPercentChar(w)+"%") + } else { + q.Where("FromJSON LIKE ?", "%"+escPercentChar(w)+"%") + } + } + } else if strings.HasPrefix(w, "subject:") { + w = cleanString(w[8:]) + if w != "" { + if exclude { + q.Where("Subject NOT LIKE ?", "%"+escPercentChar(w)+"%") + } else { + q.Where("Subject LIKE ?", "%"+escPercentChar(w)+"%") + } + } + } else if w == "is:read" { + if exclude { + q.Where("Read = 0") + } else { + q.Where("Read = 1") + } + } else if w == "is:unread" { + if exclude { + q.Where("Read = 1") + } else { + q.Where("Read = 0") + } + } else if w == "has:attachment" || w == "has:attachments" { + if exclude { + q.Where("Attachments = 0") + } else { + q.Where("Attachments > 0") + } + } else { + // search text + if exclude { + q.Where("search NOT LIKE ?", "%"+cleanString(escPercentChar(w))+"%") + } else { + q.Where("search LIKE ?", "%"+cleanString(escPercentChar(w))+"%") + } + } + } + + return q +}