|
| 1 | +package browsercookie |
| 2 | + |
| 3 | +import ( |
| 4 | + "net/http" |
| 5 | + "net/http/cookiejar" |
| 6 | + "os/user" |
| 7 | + "time" |
| 8 | + "net/url" |
| 9 | + "github.com/go-sqlite/sqlite3" |
| 10 | + "fmt" |
| 11 | +) |
| 12 | + |
| 13 | +/** |
| 14 | +Get cookie from chrome |
| 15 | +*/ |
| 16 | +func LoadCookieJarFromChrome(cookileUrl string) (http.CookieJar, error) { |
| 17 | + jar, _ := cookiejar.New(nil) |
| 18 | + |
| 19 | + usr, _ := user.Current() |
| 20 | + cookiesFile := fmt.Sprintf("%s/Library/Application Support/Google/Chrome/Default/Cookies", usr.HomeDir) |
| 21 | + cookies, err := ReadChromeCookies(cookiesFile, "", "", time.Time{}) |
| 22 | + if err != nil { |
| 23 | + return nil, err |
| 24 | + } |
| 25 | + |
| 26 | + cookieURL, _ := url.Parse(cookileUrl) |
| 27 | + jar.SetCookies(cookieURL, cookies) |
| 28 | + |
| 29 | + return jar, nil |
| 30 | +} |
| 31 | + |
| 32 | +func ReadChromeCookies(filename string, domainFilter string, nameFilter string, expireAfter time.Time) ([]*http.Cookie, error) { |
| 33 | + var cookies []*http.Cookie |
| 34 | + var unexpectedCols bool |
| 35 | + db, err := sqlite3.Open(filename) |
| 36 | + if err != nil { |
| 37 | + return nil, err |
| 38 | + } |
| 39 | + defer db.Close() |
| 40 | + |
| 41 | + if err := db.VisitTableRecords("cookies", func(rowId *int64, rec sqlite3.Record) error { |
| 42 | + if rowId == nil { |
| 43 | + return fmt.Errorf("unexpected nil RowID in Chrome sqlite database") |
| 44 | + } |
| 45 | + cookie := &http.Cookie{} |
| 46 | + |
| 47 | + if len(rec.Values) != 14 { |
| 48 | + unexpectedCols = true |
| 49 | + return nil |
| 50 | + } |
| 51 | + |
| 52 | + domain, ok := rec.Values[1].(string) |
| 53 | + if !ok { |
| 54 | + return fmt.Errorf("expected column 2 (host_key) to to be string; got %T", rec.Values[1]) |
| 55 | + } |
| 56 | + name, ok := rec.Values[2].(string) |
| 57 | + if !ok { |
| 58 | + return fmt.Errorf("expected column 3 (name) in cookie(domain:%s) to to be string; got %T", domain, rec.Values[2]) |
| 59 | + } |
| 60 | + value, ok := rec.Values[3].(string) |
| 61 | + if !ok { |
| 62 | + return fmt.Errorf("expected column 4 (value) in cookie(domain:%s, name:%s) to to be string; got %T", domain, name, rec.Values[3]) |
| 63 | + } |
| 64 | + path, ok := rec.Values[4].(string) |
| 65 | + if !ok { |
| 66 | + return fmt.Errorf("expected column 5 (path) in cookie(domain:%s, name:%s) to to be string; got %T", domain, name, rec.Values[4]) |
| 67 | + } |
| 68 | + var expires_utc int64 |
| 69 | + switch i := rec.Values[5].(type) { |
| 70 | + case int64: |
| 71 | + expires_utc = i |
| 72 | + case int: |
| 73 | + if i != 0 { |
| 74 | + return fmt.Errorf("expected column 6 (expires_utc) in cookie(domain:%s, name:%s) to to be int64 or int with value=0; got %T with value %v", domain, name, rec.Values[5], rec.Values[5]) |
| 75 | + } |
| 76 | + default: |
| 77 | + return fmt.Errorf("expected column 6 (expires_utc) in cookie(domain:%s, name:%s) to to be int64 or int with value=0; got %T with value %v", domain, name, rec.Values[5], rec.Values[5]) |
| 78 | + } |
| 79 | + encrypted_value, ok := rec.Values[12].([]byte) |
| 80 | + if !ok { |
| 81 | + return fmt.Errorf("expected column 13 (encrypted_value) in cookie(domain:%s, name:%s) to to be []byte; got %T", domain, name, rec.Values[12]) |
| 82 | + } |
| 83 | + |
| 84 | + var expiry time.Time |
| 85 | + if expires_utc != 0 { |
| 86 | + expiry = chromeCookieDate(expires_utc) |
| 87 | + } |
| 88 | + //creation := chromeCookieDate(*rowId) |
| 89 | + |
| 90 | + if domainFilter != "" && domain != domainFilter { |
| 91 | + return nil |
| 92 | + } |
| 93 | + |
| 94 | + if nameFilter != "" && name != nameFilter { |
| 95 | + return nil |
| 96 | + } |
| 97 | + |
| 98 | + if !expiry.IsZero() && expiry.Before(expireAfter) { |
| 99 | + return nil |
| 100 | + } |
| 101 | + |
| 102 | + cookie.Domain = domain |
| 103 | + cookie.Name = name |
| 104 | + cookie.Path = path |
| 105 | + cookie.Expires = expiry |
| 106 | + //cookie.Creation = creation |
| 107 | + cookie.Secure = rec.Values[6] == 1 |
| 108 | + cookie.HttpOnly = rec.Values[7] == 1 |
| 109 | + |
| 110 | + if len(encrypted_value) > 0 { |
| 111 | + decrypted, err := decryptValue(encrypted_value) |
| 112 | + if err != nil { |
| 113 | + return fmt.Errorf("decrypting cookie %v: %v", cookie, err) |
| 114 | + } |
| 115 | + cookie.Value = decrypted |
| 116 | + } else { |
| 117 | + cookie.Value = value |
| 118 | + } |
| 119 | + cookies = append(cookies, cookie) |
| 120 | + |
| 121 | + return nil |
| 122 | + }); err != nil { |
| 123 | + return nil, err |
| 124 | + } |
| 125 | + |
| 126 | + if cookies == nil && unexpectedCols { |
| 127 | + return nil, fmt.Errorf("no cookies found, but we skipped rows with an unexpected number of columns (not 14)") |
| 128 | + } |
| 129 | + return cookies, nil |
| 130 | +} |
| 131 | + |
| 132 | +// See https://cs.chromium.org/chromium/src/base/time/time.h?l=452&rcl=fceb9a030c182e939a436a540e6dacc70f161cb1 |
| 133 | +const windowsToUnixMicrosecondsOffset = 11644473600000000 |
| 134 | + |
| 135 | +// chromeCookieDate converts microseconds to a time.Time object, |
| 136 | +// accounting for the switch to Windows epoch (Jan 1 1601). |
| 137 | +func chromeCookieDate(timestamp_utc int64) time.Time { |
| 138 | + if timestamp_utc > windowsToUnixMicrosecondsOffset { |
| 139 | + timestamp_utc -= windowsToUnixMicrosecondsOffset |
| 140 | + } |
| 141 | + |
| 142 | + return time.Unix(timestamp_utc/1000000, (timestamp_utc%1000000)*1000) |
| 143 | +} |
| 144 | + |
0 commit comments