diff --git a/cmd/cmd.go b/cmd/cmd.go index 49b2619..321a120 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -23,13 +23,13 @@ func Execute() { Name: "hack-browser-data", Usage: "Export passwords/cookies/history/bookmarks from browser", UsageText: "[hack-browser-data -b chrome -f json -dir results -e all]\n Get all data(password/cookie/history/bookmark) from chrome", - Version: "0.1.8", + Version: "0.1.9", Flags: []cli.Flag{ &cli.BoolFlag{Name: "verbose", Aliases: []string{"vv"}, Destination: &verbose, Value: false, Usage: "Verbose"}, &cli.StringFlag{Name: "browser", Aliases: []string{"b"}, Destination: &browser, Value: "all", Usage: "Available browsers: all|" + strings.Join(core.ListBrowser(), "|")}, &cli.StringFlag{Name: "results-dir", Aliases: []string{"dir"}, Destination: &exportDir, Value: "results", Usage: "Export dir"}, &cli.StringFlag{Name: "format", Aliases: []string{"f"}, Destination: &outputFormat, Value: "csv", Usage: "Format, csv|json"}, - &cli.StringFlag{Name: "export-data", Aliases: []string{"e"}, Destination: &exportData, Value: "all", Usage: "all|password|cookie|history|bookmark"}, + &cli.StringFlag{Name: "export-data", Aliases: []string{"e"}, Destination: &exportData, Value: "all", Usage: "all|" + strings.Join(core.ListItem(), "|")}, }, HideHelpCommand: true, HideVersion: true, @@ -40,28 +40,57 @@ func Execute() { log.InitLog("error") } // default select all browsers - browsers, err := core.PickBrowsers(browser) + browsers, err := core.PickBrowser(browser) if err != nil { log.Error(err) } - utils.MakeDir(exportDir) - for _, v := range browsers { - err := v.InitSecretKey() + err = utils.MakeDir(exportDir) + if err != nil { + log.Error(err) + } + for _, browser := range browsers { + err := browser.InitSecretKey() if err != nil { log.Error(err) } - err = v.GetProfilePath(exportData) + items, err := browser.GetAllItems(exportData) if err != nil { log.Error(err) } - v.ParseDB() - v.OutPut(exportDir, outputFormat) + name := browser.GetName() + key := browser.GetSecretKey() + for _, item := range items { + err := item.CopyItem() + if err != nil { + log.Error(err) + } + switch browser.(type) { + case *core.Chromium: + err := item.ChromeParse(key) + if err != nil { + log.Error(err) + } + case *core.Firefox: + err := item.FirefoxParse() + if err != nil { + log.Error(err) + } + } + err = item.Release() + if err != nil { + return err + } + err = item.OutPut(outputFormat, name, exportDir) + if err != nil { + log.Error(err) + } + } } return nil }, } err := app.Run(os.Args) if err != nil { - panic(err) + log.Error(err) } } diff --git a/core/browser.go b/core/browser.go index 4751134..e9f80c9 100644 --- a/core/browser.go +++ b/core/browser.go @@ -2,11 +2,11 @@ package core import ( "errors" + "strings" + "hack-browser-data/core/common" "hack-browser-data/log" "hack-browser-data/utils" - "path/filepath" - "strings" ) const ( @@ -18,35 +18,10 @@ const ( ) type Browser interface { - GetProfilePath(filename string) (err error) InitSecretKey() error - ParseDB() - OutPut(dir, format string) -} - -type chromium struct { - ProfilePath string - KeyPath string - Name string - SecretKey []byte - FileLists []FileList - Data common.BrowserData -} - -type firefox struct { - ProfilePath string - KeyPath string - Name string - FileLists []FileList - Data common.BrowserData -} - -type FileList struct { - name string - mainFile string - mainPath string - subFile string - subPath string + GetName() string + GetSecretKey() []byte + GetAllItems(itemName string) ([]common.Item, error) } const ( @@ -57,327 +32,179 @@ const ( ) var ( - errDataNotSupported = errors.New(`not supported, default is "all", choose from history|password|bookmark|cookie`) + errItemNotSupported = errors.New(`item not supported, default is "all", choose from history|password|bookmark|cookie`) errBrowserNotSupported = errors.New("browser not supported") errChromeSecretIsEmpty = errors.New("chrome secret is empty") errDbusSecretIsEmpty = errors.New("dbus secret key is empty") - chromiumParseList = map[string]FileList{ +) + +var ( + chromiumItems = map[string]struct { + mainFile string + newItem func(mainFile, subFile string) common.Item + }{ + bookmark: { + mainFile: common.ChromeBookmarkFile, + newItem: common.NewBookmarks, + }, cookie: { - name: cookie, - mainFile: common.ChromeCookies, + mainFile: common.ChromeCookieFile, + newItem: common.NewCookies, }, history: { - name: history, - mainFile: common.ChromeHistory, - }, - bookmark: { - name: bookmark, - mainFile: common.ChromeBookmarks, + mainFile: common.ChromeHistoryFile, + newItem: common.NewHistoryData, }, password: { - name: password, - mainFile: common.ChromePassword, + mainFile: common.ChromePasswordFile, + newItem: common.NewCPasswords, }, } - firefoxParseList = map[string]FileList{ + firefoxItems = map[string]struct { + mainFile string + subFile string + newItem func(mainFile, subFile string) common.Item + }{ + bookmark: { + mainFile: common.FirefoxDataFile, + newItem: common.NewBookmarks, + }, cookie: { - name: cookie, - mainFile: common.FirefoxCookie, + mainFile: common.FirefoxCookieFile, + newItem: common.NewCookies, }, history: { - name: history, - mainFile: common.FirefoxData, - }, - bookmark: { - name: bookmark, - mainFile: common.FirefoxData, + mainFile: common.FirefoxDataFile, + newItem: common.NewHistoryData, }, password: { - name: password, - mainFile: common.FirefoxKey4DB, - subFile: common.FirefoxLoginData, + mainFile: common.FirefoxKey4File, + subFile: common.FirefoxLoginFile, + newItem: common.NewFPasswords, }, } ) -func (c *chromium) GetProfilePath(filename string) (err error) { - filename = strings.ToLower(filename) - if filename == "all" { - for _, v := range chromiumParseList { - m, err := filepath.Glob(c.ProfilePath + v.mainFile) - if err != nil { - log.Error(err) - continue - } - if len(m) > 0 { - log.Debugf("%s find %s File Success", c.Name, v.name) - log.Debugf("%s file location is %s", v, m[0]) - v.mainPath = m[0] - c.FileLists = append(c.FileLists, v) - } else { - log.Errorf("%+v find %s failed", c.Name, v.name) - } - } - } else if v, ok := chromiumParseList[filename]; ok { - m, err := filepath.Glob(c.ProfilePath + v.mainFile) - if err != nil { - log.Error(err) - } - if len(m) > 0 { - log.Debugf("%s find %s File Success", c.Name, v) - log.Debugf("%s file location is %s", v, m[0]) - v.mainPath = m[0] - c.FileLists = append(c.FileLists, v) - } - } else { - return errDataNotSupported - } - return nil +type Chromium struct { + name string + profilePath string + keyPath string + secretKey []byte } -func (c *chromium) ParseDB() { - for _, v := range c.FileLists { - err := utils.CopyDB(v.mainPath, filepath.Base(v.mainPath)) - if err != nil { - log.Error(err) - } - switch v.name { - case bookmark: - if err := chromeParse(c.SecretKey, &c.Data.Bookmarks); err != nil { - log.Error(err) - } - if err := release(v.mainFile, &c.Data.Bookmarks); err != nil { - log.Error(err) - } - case history: - if err := chromeParse(c.SecretKey, &c.Data.History); err != nil { - log.Error(err) - } - if err := release(v.mainFile, &c.Data.History); err != nil { - log.Error(err) - } - case password: - if err := chromeParse(c.SecretKey, &c.Data.Logins); err != nil { - log.Error(err) - } - if err := release(v.mainFile, &c.Data.Logins); err != nil { - log.Error(err) - } - case cookie: - if err := chromeParse(c.SecretKey, &c.Data.Cookies); err != nil { - log.Error(err) - } - if err := release(v.mainFile, &c.Data.Cookies); err != nil { - log.Error(err) - } - } - } +func NewChromium(profile, key, name string) (Browser, error) { + return &Chromium{profilePath: profile, keyPath: key, name: name}, nil } -func (c *chromium) OutPut(dir, format string) { - c.Data.Sorted() - switch format { - case "json": - for _, v := range c.FileLists { - switch v.name { - case bookmark: - if err := outPutJson(c.Name, dir, &c.Data.Bookmarks); err != nil { - log.Error(err) - } - case history: - if err := outPutJson(c.Name, dir, &c.Data.History); err != nil { - log.Error(err) - } - case password: - if err := outPutJson(c.Name, dir, &c.Data.Logins); err != nil { - log.Error(err) - } - case cookie: - if err := outPutJson(c.Name, dir, &c.Data.Cookies); err != nil { - log.Error(err) - } - } - } - case "csv": - for _, v := range c.FileLists { - switch v.name { - case bookmark: - if err := outPutCsv(c.Name, dir, &c.Data.Bookmarks); err != nil { - log.Error(err) - } - case history: - if err := outPutCsv(c.Name, dir, &c.Data.History); err != nil { - log.Error(err) - } - case password: - if err := outPutCsv(c.Name, dir, &c.Data.Logins); err != nil { - log.Error(err) - } - case cookie: - if err := outPutCsv(c.Name, dir, &c.Data.Cookies); err != nil { - log.Error(err) - } - } - } - } +func (c *Chromium) GetName() string { + return c.name } -func decryptChromium(profile, key, name string) (Browser, error) { - return &chromium{ProfilePath: profile, KeyPath: key, Name: name}, nil +func (c *Chromium) GetSecretKey() []byte { + return c.secretKey } -func (f *firefox) ParseDB() { - for _, v := range f.FileLists { - err := utils.CopyDB(v.mainPath, filepath.Base(v.mainPath)) - if v.subPath != "" { - err := utils.CopyDB(v.subPath, filepath.Base(v.subPath)) +func (c *Chromium) GetAllItems(itemName string) (Items []common.Item, err error) { + itemName = strings.ToLower(itemName) + var items []common.Item + if itemName == "all" { + for item, choice := range chromiumItems { + m, err := utils.GetItemPath(c.profilePath, choice.mainFile) if err != nil { - log.Error(err) + log.Errorf("%s find %s file failed, ERR:%s", c.name, item, err) + continue } + i := choice.newItem(m, "") + log.Debugf("%s find %s File Success", c.name, item) + items = append(items, i) } + } else if item, ok := chromiumItems[itemName]; ok { + m, err := utils.GetItemPath(c.profilePath, item.mainFile) if err != nil { - log.Error(err) - } - switch v.name { - case password: - if err := firefoxParse(&f.Data.Logins); err != nil { - log.Error(err) - } - if err := release(v.mainFile, &f.Data.Logins); err != nil { - log.Error(err) - } - if err := release(v.subFile, &f.Data.Logins); err != nil { - log.Error(err) - } - case bookmark: - if err := firefoxParse(&f.Data.Bookmarks); err != nil { - log.Error(err) - } - if err := release(v.mainFile, &f.Data.Bookmarks); err != nil { - log.Error(err) - } - case history: - if err := firefoxParse(&f.Data.History); err != nil { - log.Error(err) - } - if err := release(v.mainFile, &f.Data.History); err != nil { - log.Error(err) - } - case cookie: - if err := firefoxParse(&f.Data.Cookies); err != nil { - log.Error(err) - } - if err := release(v.mainFile, &f.Data.Cookies); err != nil { - log.Error(err) - } + log.Errorf("%s find %s file failed, ERR: ", c.name, item, err) } + i := item.newItem(m, "") + items = append(items, i) + return items, nil + } else { + return nil, errItemNotSupported } + return items, nil } -func (f *firefox) GetProfilePath(filename string) (err error) { - filename = strings.ToLower(filename) - if filename == "all" { - for _, v := range firefoxParseList { - m, err := filepath.Glob(f.ProfilePath + v.mainFile) - if v.subFile != "" { - s, err := filepath.Glob(f.ProfilePath + v.subFile) +type Firefox struct { + name string + profilePath string + keyPath string +} + +func NewFirefox(profile, key, name string) (Browser, error) { + return &Firefox{profilePath: profile, keyPath: key, name: name}, nil +} + +func (f *Firefox) GetAllItems(itemName string) ([]common.Item, error) { + itemName = strings.ToLower(itemName) + var items []common.Item + if itemName == "all" { + for item, choice := range firefoxItems { + var ( + sub, main string + err error + ) + if choice.subFile != "" { + sub, err = utils.GetItemPath(f.profilePath, choice.subFile) if err != nil { - log.Error(err) + log.Errorf("%s find %s file failed, ERR:%s", f.name, item, err) continue } - if len(s) > 0 { - log.Debugf("%s find %s File Success", f.Name, v.name) - log.Debugf("%s file location is %s", v, s[0]) - v.subPath = s[0] - } } + main, err = utils.GetItemPath(f.profilePath, choice.mainFile) if err != nil { - log.Error(err) + log.Errorf("%s find %s file failed, ERR:%s", f.name, item, err) continue } - if len(m) > 0 { - log.Debugf("%s find %s File Success", f.Name, v.name) - log.Debugf("%s file location is %s", v.mainFile, m[0]) - v.mainPath = m[0] - f.FileLists = append(f.FileLists, v) - } else { - log.Errorf("%s find %s failed", f.Name, v.name) + i := choice.newItem(main, sub) + log.Debugf("%s find %s file success", f.name, item) + items = append(items, i) + } + } else if item, ok := firefoxItems[itemName]; ok { + var ( + sub, main string + err error + ) + if item.subFile != "" { + sub, err = utils.GetItemPath(f.profilePath, item.subFile) + if err != nil { + log.Errorf("%s find %s file failed, ERR:", f.name, item, err) } } - } else if v, ok := firefoxParseList[filename]; ok { - m, err := filepath.Glob(f.ProfilePath + v.mainFile) + main, err = utils.GetItemPath(f.profilePath, item.mainFile) if err != nil { - log.Error(err) - } - if len(m) > 0 { - log.Debugf("%s find %s File Success", f.Name, v) - log.Debugf("%s file location is %s", v, m[0]) - v.mainPath = m[0] - f.FileLists = append(f.FileLists, v) + log.Errorf("%s find %s file failed, ERR:%s", f.name, item, err) } + i := item.newItem(main, sub) + log.Debugf("%s find %s file success", f.name, item.mainFile) + items = append(items, i) + return items, nil } else { - return errDataNotSupported + return nil, errItemNotSupported } - return nil + return items, nil } -func (f *firefox) OutPut(dir, format string) { - f.Data.Sorted() - switch format { - case "json": - for _, v := range f.FileLists { - switch v.name { - case bookmark: - if err := outPutJson(f.Name, dir, &f.Data.Bookmarks); err != nil { - log.Error(err) - } - case history: - if err := outPutJson(f.Name, dir, &f.Data.History); err != nil { - log.Error(err) - } - case password: - if err := outPutJson(f.Name, dir, &f.Data.Logins); err != nil { - log.Error(err) - } - case cookie: - if err := outPutJson(f.Name, dir, &f.Data.Cookies); err != nil { - log.Error(err) - } - } - } - case "csv": - for _, v := range f.FileLists { - switch v.name { - case bookmark: - if err := outPutCsv(f.Name, dir, &f.Data.Bookmarks); err != nil { - log.Error(err) - } - case history: - if err := outPutCsv(f.Name, dir, &f.Data.History); err != nil { - log.Error(err) - } - case password: - if err := outPutCsv(f.Name, dir, &f.Data.Logins); err != nil { - log.Error(err) - } - case cookie: - if err := outPutCsv(f.Name, dir, &f.Data.Cookies); err != nil { - log.Error(err) - } - } - } - } +func (f *Firefox) GetName() string { + return f.name } -func (f *firefox) InitSecretKey() error { +func (f *Firefox) GetSecretKey() []byte { return nil } -func decryptFirefox(profile, key, name string) (Browser, error) { - return &firefox{ProfilePath: profile, KeyPath: key, Name: name}, nil +func (f *Firefox) InitSecretKey() error { + return nil } -func PickBrowsers(name string) ([]Browser, error) { +func PickBrowser(name string) ([]Browser, error) { var browsers []Browser name = strings.ToLower(name) if name == "all" { @@ -397,26 +224,6 @@ func PickBrowsers(name string) ([]Browser, error) { return nil, errBrowserNotSupported } -func chromeParse(key []byte, f common.Formatter) error { - return f.ChromeParse(key) -} - -func firefoxParse(f common.Formatter) error { - return f.FirefoxParse() -} - -func outPutJson(name, dir string, f common.Formatter) error { - return f.OutPutJson(name, dir) -} - -func outPutCsv(name, dir string, f common.Formatter) error { - return f.OutPutCsv(name, dir) -} - -func release(filename string, f common.Formatter) error { - return f.Release(filename) -} - func ListBrowser() []string { var l []string for k := range browserList { @@ -424,3 +231,11 @@ func ListBrowser() []string { } return l } + +func ListItem() []string { + var l []string + for k := range chromiumItems { + l = append(l, k) + } + return l +} diff --git a/core/browser_darwin.go b/core/browser_darwin.go index e61380d..63e9056 100644 --- a/core/browser_darwin.go +++ b/core/browser_darwin.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/sha1" "errors" - "hack-browser-data/log" "os/exec" "golang.org/x/crypto/pbkdf2" @@ -26,38 +25,37 @@ var ( "chrome": { ProfilePath: chromeProfilePath, Name: chromeName, - New: decryptChromium, + New: NewChromium, }, "edge": { ProfilePath: edgeProfilePath, Name: edgeName, - New: decryptChromium, + New: NewChromium, }, "firefox": { ProfilePath: fireFoxProfilePath, Name: firefoxName, - New: decryptFirefox, + New: NewFirefox, }, } ) -func (c *chromium) InitSecretKey() error { +func (c *Chromium) InitSecretKey() error { var ( cmd *exec.Cmd stdout, stderr bytes.Buffer ) //➜ security find-generic-password -wa 'Chrome' - cmd = exec.Command("security", "find-generic-password", "-wa", c.Name) + cmd = exec.Command("security", "find-generic-password", "-wa", c.name) cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() if err != nil { - log.Error(err) return err } if stderr.Len() > 0 { err = errors.New(stderr.String()) - log.Error(err) + return err } temp := stdout.Bytes() chromeSecret := temp[:len(temp)-1] @@ -67,6 +65,6 @@ func (c *chromium) InitSecretKey() error { var chromeSalt = []byte("saltysalt") // @https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/os_crypt_mac.mm;l=157 key := pbkdf2.Key(chromeSecret, chromeSalt, 1003, 16, sha1.New) - c.SecretKey = key - return err + c.secretKey = key + return nil } diff --git a/core/browser_linux.go b/core/browser_linux.go index 45393f4..a2b44e6 100644 --- a/core/browser_linux.go +++ b/core/browser_linux.go @@ -24,17 +24,17 @@ var ( "firefox": { ProfilePath: fireFoxProfilePath, Name: firefoxName, - New: decryptFirefox, + New: NewFirefox, }, "chrome": { ProfilePath: chromeProfilePath, Name: chromeName, - New: decryptChromium, + New: NewChromium, }, } ) -func (c *chromium) InitSecretKey() error { +func (c *Chromium) InitSecretKey() error { //what is d-bus @https://dbus.freedesktop.org/ var chromeSecret []byte conn, err := dbus.SessionBus() @@ -50,9 +50,7 @@ func (c *chromium) InitSecretKey() error { return err } defer func() { - if err = session.Close(); err != nil { - log.Error(err) - } + session.Close() }() collections, err := svc.GetAllCollections() if err != nil { diff --git a/core/browser_windows.go b/core/browser_windows.go index 3e5cd29..5cc756c 100644 --- a/core/browser_windows.go +++ b/core/browser_windows.go @@ -34,28 +34,28 @@ var ( ProfilePath: os.Getenv("USERPROFILE") + chromeProfilePath, KeyPath: os.Getenv("USERPROFILE") + chromeKeyPath, Name: chromeName, - New: decryptChromium, + New: NewChromium, }, "edge": { ProfilePath: os.Getenv("USERPROFILE") + edgeProfilePath, KeyPath: os.Getenv("USERPROFILE") + edgeKeyPath, Name: edgeName, - New: decryptChromium, + New: NewChromium, }, "360": { ProfilePath: os.Getenv("USERPROFILE") + speed360ProfilePath, Name: speed360Name, - New: decryptChromium, + New: NewChromium, }, "qq": { ProfilePath: os.Getenv("USERPROFILE") + qqBrowserProfilePath, Name: qqBrowserName, - New: decryptChromium, + New: NewChromium, }, "firefox": { ProfilePath: os.Getenv("USERPROFILE") + firefoxProfilePath, Name: firefoxName, - New: decryptFirefox, + New: NewFirefox, }, } ) @@ -64,11 +64,11 @@ var ( errBase64DecodeFailed = errors.New("decode base64 failed") ) -func (c *chromium) InitSecretKey() error { - if c.KeyPath == "" { +func (c *Chromium) InitSecretKey() error { + if c.keyPath == "" { return nil } - keyFile, err := utils.ReadFile(c.KeyPath) + keyFile, err := utils.ReadFile(c.keyPath) if err != nil { return err } @@ -78,7 +78,7 @@ func (c *chromium) InitSecretKey() error { if err != nil { return errBase64DecodeFailed } - c.SecretKey, err = decrypt.DPApi(pureKey[5:]) + c.secretKey, err = decrypt.DPApi(pureKey[5:]) return err } return nil diff --git a/core/common/output.go b/core/common/output.go index d5de39a..8bda64d 100644 --- a/core/common/output.go +++ b/core/common/output.go @@ -3,62 +3,63 @@ package common import ( "bytes" "encoding/json" - "errors" "fmt" "os" + "sort" - "hack-browser-data/log" "hack-browser-data/utils" "github.com/jszwec/csvutil" ) var ( - utf8Bom = []byte{239, 187, 191} - errWriteToFile = errors.New("write to file failed") + utf8Bom = []byte{239, 187, 191} + prefix = "[x]: " ) -func (b *Bookmarks) OutPutJson(browser, dir string) error { +func (b *bookmarks) outPutJson(browser, dir string) error { filename := utils.FormatFileName(dir, browser, "bookmark", "json") + sort.Slice(b.bookmarks, func(i, j int) bool { + return b.bookmarks[i].ID < b.bookmarks[j].ID + }) err := writeToJson(filename, b.bookmarks) if err != nil { - log.Error(errWriteToFile) return err } - fmt.Printf("%s Get %d bookmarks, filename is %s \n", log.Prefix, len(b.bookmarks), filename) + fmt.Printf("%s Get %d bookmarks, filename is %s \n", prefix, len(b.bookmarks), filename) return nil } -func (h *History) OutPutJson(browser, dir string) error { +func (h *historyData) outPutJson(browser, dir string) error { filename := utils.FormatFileName(dir, browser, "history", "json") + sort.Slice(h.history, func(i, j int) bool { + return h.history[i].VisitCount > h.history[j].VisitCount + }) err := writeToJson(filename, h.history) if err != nil { - log.Error(errWriteToFile) return err } - fmt.Printf("%s Get %d history, filename is %s \n", log.Prefix, len(h.history), filename) + fmt.Printf("%s Get %d history, filename is %s \n", prefix, len(h.history), filename) return nil } -func (l *Logins) OutPutJson(browser, dir string) error { +func (p *passwords) outPutJson(browser, dir string) error { filename := utils.FormatFileName(dir, browser, "password", "json") - err := writeToJson(filename, l.logins) + err := writeToJson(filename, p.logins) if err != nil { - log.Error(errWriteToFile) return err } - fmt.Printf("%s Get %d passwords, filename is %s \n", log.Prefix, len(l.logins), filename) + fmt.Printf("%s Get %d passwords, filename is %s \n", prefix, len(p.logins), filename) return nil } -func (c *Cookies) OutPutJson(browser, dir string) error { +func (c *cookies) outPutJson(browser, dir string) error { filename := utils.FormatFileName(dir, browser, "cookie", "json") err := writeToJson(filename, c.cookies) if err != nil { - log.Error(errWriteToFile) return err } - fmt.Printf("%s Get %d cookies, filename is %s \n", log.Prefix, len(c.cookies), filename) + fmt.Printf("%s Get %d cookies, filename is %s \n", prefix, len(c.cookies), filename) return nil } @@ -67,69 +68,59 @@ func writeToJson(filename string, data interface{}) error { if err != nil { return err } - defer func() { - if err := f.Close(); err != nil { - log.Error(err) - } - }() + defer f.Close() w := new(bytes.Buffer) enc := json.NewEncoder(w) enc.SetEscapeHTML(false) enc.SetIndent("", "\t") err = enc.Encode(data) if err != nil { - log.Debug(err) return err } _, err = f.Write(w.Bytes()) if err != nil { - log.Error(err) return err } return nil } -func (b *Bookmarks) OutPutCsv(browser, dir string) error { +func (b *bookmarks) outPutCsv(browser, dir string) error { filename := utils.FormatFileName(dir, browser, "bookmark", "csv") if err := writeToCsv(filename, b.bookmarks); err != nil { - log.Error(errWriteToFile) return err } - fmt.Printf("%s Get %d bookmarks, filename is %s \n", log.Prefix, len(b.bookmarks), filename) + fmt.Printf("%s Get %d bookmarks, filename is %s \n", prefix, len(b.bookmarks), filename) return nil } -func (h *History) OutPutCsv(browser, dir string) error { +func (h *historyData) outPutCsv(browser, dir string) error { filename := utils.FormatFileName(dir, browser, "history", "csv") if err := writeToCsv(filename, h.history); err != nil { - log.Error(errWriteToFile) return err } - fmt.Printf("%s Get %d history, filename is %s \n", log.Prefix, len(h.history), filename) + fmt.Printf("%s Get %d history, filename is %s \n", prefix, len(h.history), filename) return nil } -func (l *Logins) OutPutCsv(browser, dir string) error { +func (p *passwords) outPutCsv(browser, dir string) error { filename := utils.FormatFileName(dir, browser, "password", "csv") - if err := writeToCsv(filename, l.logins); err != nil { - log.Error(errWriteToFile) + if err := writeToCsv(filename, p.logins); err != nil { return err } - fmt.Printf("%s Get %d passwords, filename is %s \n", log.Prefix, len(l.logins), filename) + fmt.Printf("%s Get %d passwords, filename is %s \n", prefix, len(p.logins), filename) return nil } -func (c *Cookies) OutPutCsv(browser, dir string) error { +func (c *cookies) outPutCsv(browser, dir string) error { filename := utils.FormatFileName(dir, browser, "cookie", "csv") - var tempSlice []cookies + var tempSlice []cookie for _, v := range c.cookies { tempSlice = append(tempSlice, v...) } if err := writeToCsv(filename, tempSlice); err != nil { - log.Error(errWriteToFile) return err } - fmt.Printf("%s Get %d cookies, filename is %s \n", log.Prefix, len(c.cookies), filename) + fmt.Printf("%s Get %d cookies, filename is %s \n", prefix, len(c.cookies), filename) return nil } @@ -137,17 +128,11 @@ func writeToCsv(filename string, data interface{}) error { var d []byte f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) if err != nil { - log.Errorf("create file %s fail %s", filename, err) return err } - defer func() { - if err := f.Close(); err != nil { - log.Error(err) - } - }() + defer f.Close() _, err = f.Write(utf8Bom) if err != nil { - log.Error(err) return err } d, err = csvutil.Marshal(data) @@ -156,7 +141,6 @@ func writeToCsv(filename string, data interface{}) error { } _, err = f.Write(d) if err != nil { - log.Error(err) return err } return nil diff --git a/core/common/parse.go b/core/common/parse.go index 57d1a41..6b3042a 100644 --- a/core/common/parse.go +++ b/core/common/parse.go @@ -6,6 +6,7 @@ import ( "encoding/base64" "io/ioutil" "os" + "path/filepath" "sort" "time" @@ -17,17 +18,13 @@ import ( "github.com/tidwall/gjson" ) -const ( - ChromePassword = "Login Data" - ChromeHistory = "History" - ChromeCookies = "Cookies" - ChromeBookmarks = "Bookmarks" - FirefoxCookie = "cookies.sqlite" - FirefoxKey4DB = "key4.db" - FirefoxLoginData = "logins.json" - FirefoxData = "places.sqlite" - FirefoxKey3DB = "key3.db" -) +type Item interface { + ChromeParse(key []byte) error + FirefoxParse() error + OutPut(format, browser, dir string) error + CopyItem() error + Release() error +} var ( queryChromiumLogin = `SELECT origin_url, username_value, password_value, date_created FROM logins` @@ -41,75 +38,17 @@ var ( closeJournalMode = `PRAGMA journal_mode=off` ) -type ( - BrowserData struct { - Logins - Bookmarks - History - Cookies - } - Logins struct { - logins []loginData - } - Bookmarks struct { - bookmarks []bookmark - } - History struct { - history []history - } - Cookies struct { - cookies map[string][]cookies - } -) - -type ( - loginData struct { - UserName string - encryptPass []byte - encryptUser []byte - Password string - LoginUrl string - CreateDate time.Time - } - bookmark struct { - ID int64 - Name string - Type string - URL string - DateAdded time.Time - } - cookies struct { - Host string - Path string - KeyName string - encryptValue []byte - Value string - IsSecure bool - IsHTTPOnly bool - HasExpire bool - IsPersistent bool - CreateDate time.Time - ExpireDate time.Time - } - history struct { - Title string - Url string - VisitCount int - LastVisitTime time.Time - } -) +type bookmarks struct { + mainPath string + bookmarks []bookmark +} -const ( - bookmarkID = "id" - bookmarkAdded = "date_added" - bookmarkUrl = "url" - bookmarkName = "name" - bookmarkType = "type" - bookmarkChildren = "children" -) +func NewBookmarks(main, sub string) Item { + return &bookmarks{mainPath: main} +} -func (b *Bookmarks) ChromeParse(key []byte) error { - bookmarks, err := utils.ReadFile(ChromeBookmarks) +func (b *bookmarks) ChromeParse(key []byte) error { + bookmarks, err := utils.ReadFile(ChromeBookmarkFile) if err != nil { log.Error(err) return err @@ -125,55 +64,7 @@ func (b *Bookmarks) ChromeParse(key []byte) error { return nil } -func (l *Logins) ChromeParse(key []byte) error { - loginDB, err := sql.Open("sqlite3", ChromePassword) - if err != nil { - log.Error(err) - return err - } - defer func() { - if err := loginDB.Close(); err != nil { - log.Debug(err) - } - }() - rows, err := loginDB.Query(queryChromiumLogin) - defer func() { - if err := rows.Close(); err != nil { - log.Debug(err) - } - }() - for rows.Next() { - var ( - url, username string - pwd, password []byte - create int64 - ) - err = rows.Scan(&url, &username, &pwd, &create) - login := loginData{ - UserName: username, - encryptPass: pwd, - LoginUrl: url, - } - if key == nil { - password, err = decrypt.DPApi(pwd) - } else { - password, err = decrypt.ChromePass(key, pwd) - } - if err != nil { - log.Debugf("%s have empty password %s", login.LoginUrl, err.Error()) - } - if create > time.Now().Unix() { - login.CreateDate = utils.TimeEpochFormat(create) - } else { - login.CreateDate = utils.TimeStampFormat(create) - } - login.Password = string(password) - l.logins = append(l.logins, login) - } - return nil -} - -func getBookmarkChildren(value gjson.Result, b *Bookmarks) (children gjson.Result) { +func getBookmarkChildren(value gjson.Result, b *bookmarks) (children gjson.Result) { nodeType := value.Get(bookmarkType) bm := bookmark{ ID: value.Get(bookmarkID).Int(), @@ -194,48 +85,90 @@ func getBookmarkChildren(value gjson.Result, b *Bookmarks) (children gjson.Resul return children } -func (h *History) ChromeParse(key []byte) error { - historyDB, err := sql.Open("sqlite3", ChromeHistory) +func (b *bookmarks) FirefoxParse() error { + var ( + err error + keyDB *sql.DB + bookmarkRows *sql.Rows + tempMap map[int64]string + bookmarkUrl string + ) + keyDB, err = sql.Open("sqlite3", FirefoxDataFile) if err != nil { log.Error(err) return err } defer func() { - if err := historyDB.Close(); err != nil { + if err := keyDB.Close(); err != nil { log.Error(err) } - }() - rows, err := historyDB.Query(queryChromiumHistory) - defer func() { - if err := rows.Close(); err != nil { - log.Debug(err) + if err := bookmarkRows.Close(); err != nil { + log.Error(err) } }() - for rows.Next() { + _, err = keyDB.Exec(closeJournalMode) + if err != nil { + log.Error(err) + } + bookmarkRows, err = keyDB.Query(queryFirefoxBookMarks) + if err != nil { + log.Error(err) + } + for bookmarkRows.Next() { var ( - url, title string - visitCount int - lastVisitTime int64 + id, fk, bType, dateAdded int64 + title string ) - err := rows.Scan(&url, &title, &visitCount, &lastVisitTime) - data := history{ - Url: url, - Title: title, - VisitCount: visitCount, - LastVisitTime: utils.TimeEpochFormat(lastVisitTime), - } - if err != nil { - log.Error(err) - continue + err = bookmarkRows.Scan(&id, &fk, &bType, &dateAdded, &title) + if url, ok := tempMap[id]; ok { + bookmarkUrl = url } - h.history = append(h.history, data) + b.bookmarks = append(b.bookmarks, bookmark{ + ID: id, + Name: title, + Type: utils.BookMarkType(bType), + URL: bookmarkUrl, + DateAdded: utils.TimeStampFormat(dateAdded / 1000000), + }) } return nil } -func (c *Cookies) ChromeParse(secretKey []byte) error { - c.cookies = make(map[string][]cookies) - cookieDB, err := sql.Open("sqlite3", ChromeCookies) +func (b *bookmarks) CopyItem() error { + return utils.CopyDB(b.mainPath, filepath.Base(b.mainPath)) +} + +func (b *bookmarks) Release() error { + return os.Remove(filepath.Base(b.mainPath)) +} + +func (b *bookmarks) OutPut(format, browser, dir string) error { + sort.Slice(b.bookmarks, func(i, j int) bool { + return b.bookmarks[i].ID < b.bookmarks[j].ID + }) + switch format { + case "json": + err := b.outPutJson(browser, dir) + return err + case "csv": + err := b.outPutCsv(browser, dir) + return err + } + return nil +} + +type cookies struct { + mainPath string + cookies map[string][]cookie +} + +func NewCookies(main, sub string) Item { + return &cookies{mainPath: main} +} + +func (c *cookies) ChromeParse(secretKey []byte) error { + c.cookies = make(map[string][]cookie) + cookieDB, err := sql.Open("sqlite3", ChromeCookieFile) if err != nil { log.Debug(err) return err @@ -259,7 +192,7 @@ func (c *Cookies) ChromeParse(secretKey []byte) error { value, encryptValue []byte ) err = rows.Scan(&key, &encryptValue, &host, &path, &createDate, &expireDate, &isSecure, &isHTTPOnly, &hasExpire, &isPersistent) - cookie := cookies{ + cookie := cookie{ KeyName: key, Host: host, Path: path, @@ -284,7 +217,118 @@ func (c *Cookies) ChromeParse(secretKey []byte) error { return nil } -func (h *History) FirefoxParse() error { +func (c *cookies) FirefoxParse() error { + c.cookies = make(map[string][]cookie) + cookieDB, err := sql.Open("sqlite3", FirefoxCookieFile) + if err != nil { + log.Debug(err) + return err + } + defer func() { + if err := cookieDB.Close(); err != nil { + log.Debug(err) + } + }() + rows, err := cookieDB.Query(queryFirefoxCookie) + if err != nil { + log.Error(err) + return err + } + defer func() { + if err := rows.Close(); err != nil { + log.Debug(err) + } + }() + for rows.Next() { + var ( + name, value, host, path string + isSecure, isHttpOnly int + creationTime, expiry int64 + ) + err = rows.Scan(&name, &value, &host, &path, &creationTime, &expiry, &isSecure, &isHttpOnly) + c.cookies[host] = append(c.cookies[host], cookie{ + KeyName: name, + Host: host, + Path: path, + IsSecure: utils.IntToBool(isSecure), + IsHTTPOnly: utils.IntToBool(isHttpOnly), + CreateDate: utils.TimeStampFormat(creationTime / 1000000), + ExpireDate: utils.TimeStampFormat(expiry), + Value: value, + }) + } + return nil +} + +func (c *cookies) CopyItem() error { + return utils.CopyDB(c.mainPath, filepath.Base(c.mainPath)) +} + +func (c *cookies) Release() error { + return os.Remove(filepath.Base(c.mainPath)) +} + +func (c *cookies) OutPut(format, browser, dir string) error { + switch format { + case "json": + err := c.outPutJson(browser, dir) + return err + case "csv": + err := c.outPutCsv(browser, dir) + return err + } + return nil +} + +type historyData struct { + mainPath string + history []history +} + +func NewHistoryData(main, sub string) Item { + return &historyData{mainPath: main} +} + +func (h *historyData) ChromeParse(key []byte) error { + historyDB, err := sql.Open("sqlite3", ChromeHistoryFile) + if err != nil { + log.Error(err) + return err + } + defer func() { + if err := historyDB.Close(); err != nil { + log.Error(err) + } + }() + rows, err := historyDB.Query(queryChromiumHistory) + defer func() { + if err := rows.Close(); err != nil { + log.Debug(err) + } + }() + for rows.Next() { + var ( + url, title string + visitCount int + lastVisitTime int64 + ) + err := rows.Scan(&url, &title, &visitCount, &lastVisitTime) + data := history{ + Url: url, + Title: title, + VisitCount: visitCount, + LastVisitTime: utils.TimeEpochFormat(lastVisitTime), + } + if err != nil { + log.Error(err) + continue + } + h.history = append(h.history, data) + } + return nil +} + +func (h *historyData) FirefoxParse() error { var ( err error keyDB *sql.DB @@ -292,7 +336,7 @@ func (h *History) FirefoxParse() error { tempMap map[int64]string ) tempMap = make(map[int64]string) - keyDB, err = sql.Open("sqlite3", FirefoxData) + keyDB, err = sql.Open("sqlite3", FirefoxDataFile) if err != nil { log.Error(err) return err @@ -334,83 +378,55 @@ func (h *History) FirefoxParse() error { return nil } -func (b *Bookmarks) FirefoxParse() error { - var ( - err error - keyDB *sql.DB - bookmarkRows *sql.Rows - tempMap map[int64]string - bookmarkUrl string - ) - keyDB, err = sql.Open("sqlite3", FirefoxData) - if err != nil { - log.Error(err) +func (h *historyData) CopyItem() error { + return utils.CopyDB(h.mainPath, filepath.Base(h.mainPath)) +} + +func (h *historyData) Release() error { + return os.Remove(filepath.Base(h.mainPath)) +} + +func (h *historyData) OutPut(format, browser, dir string) error { + sort.Slice(h.history, func(i, j int) bool { + return h.history[i].VisitCount > h.history[j].VisitCount + }) + switch format { + case "json": + err := h.outPutJson(browser, dir) + return err + case "csv": + err := h.outPutCsv(browser, dir) return err - } - _, err = keyDB.Exec(closeJournalMode) - if err != nil { - log.Error(err) - } - bookmarkRows, err = keyDB.Query(queryFirefoxBookMarks) - defer func() { - if err := bookmarkRows.Close(); err != nil { - log.Error(err) - } - }() - for bookmarkRows.Next() { - var ( - id, fk, bType, dateAdded int64 - title string - ) - err = bookmarkRows.Scan(&id, &fk, &bType, &dateAdded, &title) - if url, ok := tempMap[id]; ok { - bookmarkUrl = url - } - b.bookmarks = append(b.bookmarks, bookmark{ - ID: id, - Name: title, - Type: utils.BookMarkType(bType), - URL: bookmarkUrl, - DateAdded: utils.TimeStampFormat(dateAdded / 1000000), - }) } return nil } -func (b *Bookmarks) Release(filename string) error { - return os.Remove(filename) -} - -func (c *Cookies) Release(filename string) error { - return os.Remove(filename) +type passwords struct { + mainPath string + subPath string + logins []loginData } -func (h *History) Release(filename string) error { - return os.Remove(filename) +func NewFPasswords(main, sub string) Item { + return &passwords{mainPath: main, subPath: sub} } -func (l *Logins) Release(filename string) error { - return os.Remove(filename) +func NewCPasswords(main, sub string) Item { + return &passwords{mainPath: main} } -func (c *Cookies) FirefoxParse() error { - cookie := cookies{} - c.cookies = make(map[string][]cookies) - cookieDB, err := sql.Open("sqlite3", FirefoxCookie) +func (p *passwords) ChromeParse(key []byte) error { + loginDB, err := sql.Open("sqlite3", ChromePasswordFile) if err != nil { - log.Debug(err) + log.Error(err) return err } defer func() { - if err := cookieDB.Close(); err != nil { + if err := loginDB.Close(); err != nil { log.Debug(err) } }() - rows, err := cookieDB.Query(queryFirefoxCookie) - if err != nil { - log.Error(err) - return err - } + rows, err := loginDB.Query(queryChromiumLogin) defer func() { if err := rows.Close(); err != nil { log.Debug(err) @@ -418,28 +434,39 @@ func (c *Cookies) FirefoxParse() error { }() for rows.Next() { var ( - name, value, host, path string - isSecure, isHttpOnly int - creationTime, expiry int64 + url, username string + pwd, password []byte + create int64 ) - err = rows.Scan(&name, &value, &host, &path, &creationTime, &expiry, &isSecure, &isHttpOnly) - cookie = cookies{ - KeyName: name, - Host: host, - Path: path, - IsSecure: utils.IntToBool(isSecure), - IsHTTPOnly: utils.IntToBool(isHttpOnly), - CreateDate: utils.TimeStampFormat(creationTime / 1000000), - ExpireDate: utils.TimeStampFormat(expiry), + err = rows.Scan(&url, &username, &pwd, &create) + if err != nil { + log.Error(err) } - - cookie.Value = value - c.cookies[host] = append(c.cookies[host], cookie) + login := loginData{ + UserName: username, + encryptPass: pwd, + LoginUrl: url, + } + if key == nil { + password, err = decrypt.DPApi(pwd) + } else { + password, err = decrypt.ChromePass(key, pwd) + } + if err != nil { + log.Debugf("%s have empty password %s", login.LoginUrl, err.Error()) + } + if create > time.Now().Unix() { + login.CreateDate = utils.TimeEpochFormat(create) + } else { + login.CreateDate = utils.TimeStampFormat(create) + } + login.Password = string(password) + p.logins = append(p.logins, login) } return nil } -func (l *Logins) FirefoxParse() error { +func (p *passwords) FirefoxParse() error { globalSalt, metaBytes, nssA11, nssA102, err := getDecryptKey() if err != nil { log.Error(err) @@ -479,23 +506,23 @@ func (l *Logins) FirefoxParse() error { return err } for _, v := range allLogins { - user, _ := decrypt.DecodeLogin(v.encryptUser) - pwd, _ := decrypt.DecodeLogin(v.encryptPass) - u, err := decrypt.Des3Decrypt(finallyKey, user.Iv, user.Encrypted) + userPBE, _ := decrypt.DecodeLogin(v.encryptUser) + pwdPBE, _ := decrypt.DecodeLogin(v.encryptPass) + user, err := decrypt.Des3Decrypt(finallyKey, userPBE.Iv, userPBE.Encrypted) if err != nil { log.Error(err) return err } - log.Debug("decrypt firefox success") - p, err := decrypt.Des3Decrypt(finallyKey, pwd.Iv, pwd.Encrypted) + pwd, err := decrypt.Des3Decrypt(finallyKey, pwdPBE.Iv, pwdPBE.Encrypted) if err != nil { log.Error(err) return err } - l.logins = append(l.logins, loginData{ + log.Debug("decrypt firefox success") + p.logins = append(p.logins, loginData{ LoginUrl: v.LoginUrl, - UserName: string(decrypt.PKCS5UnPadding(u)), - Password: string(decrypt.PKCS5UnPadding(p)), + UserName: string(decrypt.PKCS5UnPadding(user)), + Password: string(decrypt.PKCS5UnPadding(pwd)), CreateDate: v.CreateDate, }) @@ -505,13 +532,48 @@ func (l *Logins) FirefoxParse() error { return nil } +func (p *passwords) CopyItem() error { + err := utils.CopyDB(p.mainPath, filepath.Base(p.mainPath)) + if err != nil { + log.Error(err) + } + if p.subPath != "" { + err = utils.CopyDB(p.subPath, filepath.Base(p.subPath)) + } + return err +} + +func (p *passwords) Release() error { + err := os.Remove(filepath.Base(p.mainPath)) + if err != nil { + log.Error(err) + } + if p.subPath != "" { + err = os.Remove(filepath.Base(p.subPath)) + } + return err +} + +func (p *passwords) OutPut(format, browser, dir string) error { + sort.Sort(p) + switch format { + case "json": + err := p.outPutJson(browser, dir) + return err + case "csv": + err := p.outPutCsv(browser, dir) + return err + } + return nil +} + func getDecryptKey() (item1, item2, a11, a102 []byte, err error) { var ( keyDB *sql.DB pwdRows *sql.Rows nssRows *sql.Rows ) - keyDB, err = sql.Open("sqlite3", FirefoxKey4DB) + keyDB, err = sql.Open("sqlite3", FirefoxKey4File) if err != nil { log.Error(err) return nil, nil, nil, nil, err @@ -552,7 +614,7 @@ func getDecryptKey() (item1, item2, a11, a102 []byte, err error) { } func getLoginData() (l []loginData, err error) { - s, err := ioutil.ReadFile(FirefoxLoginData) + s, err := ioutil.ReadFile(FirefoxLoginFile) if err != nil { return nil, err } @@ -579,32 +641,72 @@ func getLoginData() (l []loginData, err error) { return } -func (b *BrowserData) Sorted() { - sort.Slice(b.bookmarks, func(i, j int) bool { - return b.bookmarks[i].ID < b.bookmarks[j].ID - }) - sort.Slice(b.history, func(i, j int) bool { - return b.history[i].VisitCount > b.history[j].VisitCount - }) - sort.Sort(b.Logins) -} +type ( + loginData struct { + UserName string + encryptPass []byte + encryptUser []byte + Password string + LoginUrl string + CreateDate time.Time + } + bookmark struct { + ID int64 + Name string + Type string + URL string + DateAdded time.Time + } + cookie struct { + Host string + Path string + KeyName string + encryptValue []byte + Value string + IsSecure bool + IsHTTPOnly bool + HasExpire bool + IsPersistent bool + CreateDate time.Time + ExpireDate time.Time + } + history struct { + Title string + Url string + VisitCount int + LastVisitTime time.Time + } +) -func (l Logins) Len() int { - return len(l.logins) -} +const ( + bookmarkID = "id" + bookmarkAdded = "date_added" + bookmarkUrl = "url" + bookmarkName = "name" + bookmarkType = "type" + bookmarkChildren = "children" +) + +const ( + ChromePasswordFile = "Login Data" + ChromeHistoryFile = "History" + ChromeCookieFile = "Cookies" + ChromeBookmarkFile = "Bookmarks" + FirefoxCookieFile = "cookies.sqlite" + FirefoxKey4File = "key4.db" + FirefoxLoginFile = "logins.json" + FirefoxDataFile = "places.sqlite" + FirefoxKey3DB = "key3.db" +) -func (l Logins) Less(i, j int) bool { - return l.logins[i].CreateDate.After(l.logins[j].CreateDate) +func (p passwords) Len() int { + return len(p.logins) } -func (l Logins) Swap(i, j int) { - l.logins[i], l.logins[j] = l.logins[j], l.logins[i] +func (p passwords) Less(i, j int) bool { + return p.logins[i].CreateDate.After(p.logins[j].CreateDate) } -type Formatter interface { - ChromeParse(key []byte) error - FirefoxParse() error - OutPutJson(browser, dir string) error - OutPutCsv(browser, dir string) error - Release(filename string) error +func (p passwords) Swap(i, j int) { + p.logins[i], p.logins[j] = p.logins[j], p.logins[i] } diff --git a/log/log.go b/log/log.go index d31b38c..6b6c916 100644 --- a/log/log.go +++ b/log/log.go @@ -7,10 +7,6 @@ import ( "os" ) -const ( - Prefix = "[x]: " -) - type Level int const ( diff --git a/utils/utils.go b/utils/utils.go index 32e1144..b05f331 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -33,6 +33,17 @@ func CopyDB(src, dst string) error { return err } +func GetItemPath(profilePath, file string) (string, error) { + p, err := filepath.Glob(profilePath + file) + if err != nil { + return "", err + } + if len(p) > 0 { + return p[0], nil + } + return "", fmt.Errorf("find %s failed", file) +} + func IntToBool(a int) bool { switch a { case 0, -1: @@ -87,8 +98,9 @@ func FormatFileName(dir, browser, filename, format string) string { return p } -func MakeDir(dirName string) { +func MakeDir(dirName string) error { if _, err := os.Stat(dirName); os.IsNotExist(err) { - err = os.Mkdir(dirName, 0700) + return os.Mkdir(dirName, 0700) } + return nil }