From 6f7a7647c7716377ee4d4e5afee36dd85fc01031 Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Thu, 28 Dec 2017 05:13:19 +0000 Subject: Add search, somewhat --- main.go | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 141 insertions(+), 26 deletions(-) diff --git a/main.go b/main.go index 88cca34..6664cd5 100644 --- a/main.go +++ b/main.go @@ -13,8 +13,11 @@ import ( "errors" "fmt" "io/ioutil" + "net/url" + "os" "os/user" "path" + "regexp" "strings" "syscall" "time" @@ -82,6 +85,26 @@ func NewKeyPBKDF2(pass []byte, p *OPProfile) *Key { return NewKey(pbkdf2.Key(pass, p.Salt, p.Iterations, 64, sha512.New)) } +type ODataOverview struct { + Title string `json:"title"` + URL string `json:"url"` +} + +type OPDataSection struct { + Name string `json:"name"` + Title string `json:"title"` + Fields []OPDataField `json:"fields"` +} + +type OPDataField struct { + ID string // id + Name string // name, n + Type string // type, t + Kind string // k + Value string // value, v + Designation string // designation +} + type OPBandItem struct { Profile *OPProfile `json:"-"` UUID string `json:"uuid"` @@ -398,49 +421,141 @@ func DeriveKey(data []byte, dkey *Key) (*Key, error) { return NewKey(h.Sum(nil)), nil } -func main() { - datapath := "~/Dropbox/1Password/1Password.opvault" - if strings.HasPrefix(datapath, "~/") { - u, _ := user.Current() - datapath = u.HomeDir + datapath[1:len(datapath)] +type SearchResult struct { + Title string `json:"title"` + URL string `json:"url"` + BandItem *OPBandItem +} + +func SearchOverviews(needle string, haystack map[string]*OPBandItem) (map[string]SearchResult, error) { + test, err := regexp.Compile(fmt.Sprintf("(?iU).*%s.*", needle)) + if err != nil { + return nil, err } - fmt.Print("Enter Password: ") - password, err := terminal.ReadPassword(int(syscall.Stdin)) + m := make(map[string]SearchResult) + + // Simple linear search because the data set size is small + for k, v := range haystack { + if v.Trashed { + continue + } + + var o SearchResult + if err = json.Unmarshal(v.Overview, &o); err != nil { + fmt.Printf("[ERROR] Decoding overview for %s\n", k) + continue + } + + if test.MatchString(o.Title) || test.MatchString(o.URL) { + m[k] = SearchResult{o.Title, o.URL, v} + } + } + + return m, nil +} + +// TODO: a more sophisticated approach, perhaps +func TruncateURL(u string) string { + if u == "" { + return u + } + + p, err := url.Parse(u) if err != nil { - fmt.Println("Unable to read password") - return + if len(u) > 20 { + return fmt.Sprintf("%s...", u[:17]) + } else { + return u + } + } else { + return fmt.Sprintf("%s://%s", p.Scheme, p.Host) } - fmt.Println("") +} - p, err := LoadProfile(datapath, password) +// TODO: testing +func doSearch(p *OPProfile) { + candidates, err := SearchOverviews(os.Args[1], p.Items) if err != nil { - fmt.Println(err) + fmt.Println("Error searching") return - } + } else { + fmt.Printf("Found %d candidates\n", len(candidates)) + + outs := make([][]string, 0, len(candidates)+1) + c1len := 0 + + for k, v := range candidates { + c1l := len(v.BandItem.CategoryName) + if c1l > c1len { + c1len = c1l + } + outs = append(outs, []string{v.BandItem.CategoryName, k, v.Title, TruncateURL(v.URL)}) + } + + for _, i := range outs { + pad := "" + if len(i[0]) < (c1len + 5) { + pad = strings.Join(make([]string, c1len-(len(i[0])-1)), " ") + } + fmt.Printf("%s %s %s %s (%s)\n", i[0], pad, i[1], i[2], i[3]) + } - // Login - CD05161569D347ADB401DE06D30A0A89 - // Note - 7FF3565B434B47CF8906869BDCAD28C3 - // Password - 7059A882C5F84DDCBD2EAD9EFFAA2B58 - // Router - 6009533D5A3B483A93FC7A843C39EDED - // Server - 7BDDE92045834A5386D56576DAEDDE54 - // Credit - 6B9AFF656D264EEF8A887F79D243AE0D - // SW License - 7529DADA9453426BAF8B23A931834B2A - // Database - 102 - // Email - 111 + return + } +} - item, ok := p.Items[""] +// TODO: testing +func printItem(p *OPProfile, idx string) { + item, ok := p.Items[idx] if !ok { - fmt.Println("UUID not found in profile") + fmt.Println("Error: No such item") return } - itemD, err := item.DecryptData() + itemDetails, err := item.DecryptData() if err != nil { fmt.Println("Error decoding item data") fmt.Println(err) return } - fmt.Printf("[\"%s\", %s, %s]", item.CategoryName, item.Overview, itemD) + fmt.Printf("[\"%s\", %s, %s]", item.CategoryName, item.Overview, itemDetails) +} + +func ExpandUser(path string) (string, error) { + if strings.HasPrefix(datapath, "~/") { + if u, err := user.Current(); err != nil { + return nil, err + } else { + return fmt.Sprintf("%s%s", u.HomeDir, datapath[1:len(datapath)]), nil + } + } + return path, nil +} + +func main() { + datapath, err := ExpandUser("~/Dropbox/1Password/1Password.opvault") + if err != nil { + fmt.Printf(err) + return + } + + fmt.Print("Enter Password: ") + password, err := terminal.ReadPassword(int(syscall.Stdin)) + if err != nil { + fmt.Println("Unable to read password") + return + } + fmt.Println("") + + p, err := LoadProfile(datapath, password) + if err != nil { + fmt.Printf("ERROR: Unable to load keychain: %s", err) + return + } + + // TODO: actually handle commands, maybe cobra? + doSearch(p) + //printItem(p, "") } -- cgit v1.2.3