diff --git a/browser/chromium/chromium.go b/browser/chromium/chromium.go index b8dc7f0..75d1d7a 100644 --- a/browser/chromium/chromium.go +++ b/browser/chromium/chromium.go @@ -2,6 +2,7 @@ package chromium import ( "io/fs" + "os" "path/filepath" "strings" @@ -86,7 +87,7 @@ func (c *Chromium) copyItemToLocal() error { err = fileutil.CopyDir(path, filename, "lock") } if i == item.ChromiumExtension { - err = fileutil.CopyDirHasSuffix(path, filename, "manifest.json") + err = os.WriteFile(filename, []byte(fileutil.ParentDir(path)), 0o600) } default: err = fileutil.CopyFile(path, filename) diff --git a/browsingdata/extension/extension.go b/browsingdata/extension/extension.go index 1773d0e..ade2902 100644 --- a/browsingdata/extension/extension.go +++ b/browsingdata/extension/extension.go @@ -1,51 +1,105 @@ package extension import ( + "errors" "os" + "path/filepath" + "strings" "github.com/tidwall/gjson" + "golang.org/x/text/language" "github.com/moond4rk/hackbrowserdata/item" - "github.com/moond4rk/hackbrowserdata/log" "github.com/moond4rk/hackbrowserdata/utils/fileutil" ) type ChromiumExtension []*extension type extension struct { + ID string + URL string + Enabled bool Name string Description string Version string HomepageURL string } -const ( - manifest = "manifest.json" -) - func (c *ChromiumExtension) Parse(_ []byte) error { - files, err := fileutil.FilesInFolder(item.TempChromiumExtension, manifest) + profilePath, err := fileutil.ReadFile(item.TempChromiumExtension) if err != nil { return err } - defer os.RemoveAll(item.TempChromiumExtension) - for _, f := range files { - content, err := fileutil.ReadFile(f) - if err != nil { - log.Error("Failed to read content: %s", err) - continue + _ = os.Remove(item.TempChromiumExtension) + + var extensions *gjson.Result + if result, err := readChromiumExts(filepath.Join(profilePath, "Secure Preferences")); err == nil { + extensions = result + } else if result, err := readChromiumExts(filepath.Join(profilePath, "Preferences")); err == nil { + extensions = result + } else { + return err + } + + extensions.ForEach(func(id, ext gjson.Result) bool { + location := ext.Get("location") + if !location.Exists() { + return true } - b := gjson.Parse(content) + switch location.Int() { + case 5, 10: // https://source.chromium.org/chromium/chromium/src/+/main:extensions/common/mojom/manifest.mojom + return true + } + + // https://source.chromium.org/chromium/chromium/src/+/main:extensions/browser/disable_reason.h + enabled := !ext.Get("disable_reasons").Exists() + + b := ext.Get("manifest") + if !b.Exists() { + *c = append(*c, &extension{ + ID: id.String(), + Enabled: enabled, + Name: ext.Get("path").String(), + }) + return true + } + *c = append(*c, &extension{ + ID: id.String(), + URL: getChromiumExtURL(id.String(), b.Get("update_url").String()), + Enabled: enabled, Name: b.Get("name").String(), Description: b.Get("description").String(), Version: b.Get("version").String(), HomepageURL: b.Get("homepage_url").String(), }) - } + return true + }) + return nil } +func readChromiumExts(filename string) (*gjson.Result, error) { + prefs, err := fileutil.ReadFile(filename) + if err != nil { + return nil, err + } + extensions := gjson.Parse(prefs).Get("extensions.settings") + if extensions.Exists() { + return &extensions, nil + } + return nil, errors.New("extensions not found") +} + +func getChromiumExtURL(id, updateURL string) string { + if strings.HasSuffix(updateURL, "clients2.google.com/service/update2/crx") { + return "https://chrome.google.com/webstore/detail/" + id + } else if strings.HasSuffix(updateURL, "edge.microsoft.com/extensionwebstorebase/v1/crx") { + return "https://microsoftedge.microsoft.com/addons/detail/" + id + } + return "" +} + func (c *ChromiumExtension) Name() string { return "extension" } @@ -56,15 +110,37 @@ func (c *ChromiumExtension) Len() int { type FirefoxExtension []*extension +var lang = language.Und + func (f *FirefoxExtension) Parse(_ []byte) error { s, err := fileutil.ReadFile(item.TempFirefoxExtension) if err != nil { return err } - defer os.Remove(item.TempFirefoxExtension) + _ = os.Remove(item.TempFirefoxExtension) j := gjson.Parse(s) for _, v := range j.Get("addons").Array() { + // https://searchfox.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIDatabase.jsm#157 + if v.Get("location").String() != "app-profile" { + continue + } + + if lang != language.Und { + locale := findFirefoxLocale(v.Get("locales").Array(), lang) + *f = append(*f, &extension{ + ID: v.Get("id").String(), + Enabled: v.Get("active").Bool(), + Name: locale.Get("name").String(), + Description: locale.Get("description").String(), + Version: v.Get("version").String(), + HomepageURL: locale.Get("homepageURL").String(), + }) + continue + } + *f = append(*f, &extension{ + ID: v.Get("id").String(), + Enabled: v.Get("active").Bool(), Name: v.Get("defaultLocale.name").String(), Description: v.Get("defaultLocale.description").String(), Version: v.Get("version").String(), @@ -74,6 +150,23 @@ func (f *FirefoxExtension) Parse(_ []byte) error { return nil } +func findFirefoxLocale(locales []gjson.Result, targetLang language.Tag) gjson.Result { + tags := make([]language.Tag, 0, len(locales)) + indices := make([]int, 0, len(locales)) + for i, locale := range locales { + for _, tagStr := range locale.Get("locales").Array() { + tag, _ := language.Parse(tagStr.String()) + if tag == language.Und { + continue + } + tags = append(tags, tag) + indices = append(indices, i) + } + } + _, tagIndex, _ := language.NewMatcher(tags).Match(targetLang) + return locales[indices[tagIndex]] +} + func (f *FirefoxExtension) Name() string { return "extension" } diff --git a/utils/fileutil/filetutil.go b/utils/fileutil/filetutil.go index f126b66..54bb99b 100644 --- a/utils/fileutil/filetutil.go +++ b/utils/fileutil/filetutil.go @@ -3,7 +3,6 @@ package fileutil import ( "archive/zip" "bytes" - "errors" "fmt" "os" "path" @@ -37,21 +36,6 @@ func IsDirExists(folder string) bool { return info.IsDir() } -// FilesInFolder returns the filepath contains in the provided folder -func FilesInFolder(dir, filename string) ([]string, error) { - if !IsDirExists(dir) { - return nil, errors.New(dir + "folder does not exist") - } - var files []string - err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { - if !f.IsDir() && strings.HasSuffix(path, filename) { - files = append(files, path) - } - return err - }) - return files, err -} - // ReadFile reads the file from the provided path func ReadFile(filename string) (string, error) { s, err := os.ReadFile(filename) @@ -67,33 +51,6 @@ func CopyDir(src, dst, skip string) error { return cp.Copy(src, dst, s) } -// CopyDirHasSuffix copies the directory from the source to the destination -// contain is the file if you want to copy, and rename copied filename with dir/index_filename -func CopyDirHasSuffix(src, dst, suffix string) error { - var files []string - err := filepath.Walk(src, func(path string, f os.FileInfo, err error) error { - if !f.IsDir() && strings.HasSuffix(strings.ToLower(f.Name()), suffix) { - files = append(files, path) - } - return err - }) - if err != nil { - return err - } - if err := os.MkdirAll(dst, 0o700); err != nil { - return err - } - for index, file := range files { - // p = dir/index_file - p := fmt.Sprintf("%s/%d_%s", dst, index, BaseDir(file)) - err = CopyFile(file, p) - if err != nil { - return err - } - } - return nil -} - // CopyFile copies the file from the source to the destination func CopyFile(src, dst string) error { s, err := os.ReadFile(src)