kooky

package module
v0.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2025 License: MIT Imports: 11 Imported by: 13

README

kooky

PkgGoDev Go Report Card Lines of code No Maintenance Intended PRs Welcome MIT license

Reaching into browser-specific, vaguely documented, possibly concurrently modified cookie stores to pilfer cookies is a bad idea. Since you've arrived here, you're almost certainly going to do it anyway. Me too. And if we're going to do the Wrong Thing, at least let's try to Do it Right.

Package kooky contains routines to reach into cookie stores for Chrome, Firefox, Safari, ... and retrieve the cookies.

It aspires to be pure Go (I spent quite a while making go-sqlite/sqlite3 work for it).

It also aspires to work for all major browsers, on all three major platforms.

Status

Basic functionality works on Windows, MacOS and Linux. Some functions might not yet be implemented on some platforms. The API is currently not expected to be at all stable.

PRs more than welcome.

Example usage

package main

import (
	"fmt"

	"github.com/browserutils/kooky"
	_ "github.com/browserutils/kooky/browser/all" // register cookie store finders!
)

func main() {
	// uses registered finders to find cookie store files in default locations
	// applies the passed filters "Valid", "DomainHasSuffix()" and "Name()" in order to the cookies
	cookiesSeq := kooky.TraverseCookies(context.TODO(), kooky.Valid, kooky.DomainHasSuffix(`google.com`), kooky.Name(`NID`)).OnlyCookies()

	for cookie := range cookiesSeq {
		fmt.Println(cookie.Domain, cookie.Name, cookie.Value)
	}
 }
Chrome on macOS
package main

import (
	"fmt"
	"log"
	"os"

	"github.com/browserutils/kooky/browser/chrome"
)

func main() {
	dir, _ := os.UserConfigDir() // "/<USER>/Library/Application Support/"
	cookiesFile := dir + "/Google/Chrome/Default/Cookies"
	cookiesSeq := chrome.TraverseCookies(cookiesFile).OnlyCookies()
	for cookie := range cookiesSeq {
		fmt.Println(cookie)
	}
}
Safari
package main

import (
	"fmt"
	"log"
	"os"

	"github.com/browserutils/kooky/browser/safari"
)

func main() {
	dir, _ := os.UserHomeDir()
	cookiesFile := dir + "/Library/Containers/com.apple.Safari/Data/Library/Cookies/Cookies.binarycookies"
	cookiesSeq := safari.TraverseCookies(cookiesFile).OnlyCookies()
	for cookie := range cookiesSeq {
		fmt.Println(cookie)
	}
}

Thanks/references

Documentation

Overview

Package kooky contains routines to reach into cookie stores for various browsers and retrieve the cookies.

Example (ChromeSimpleMacOS)
package main

import (
	"fmt"
	"os"

	"github.com/browserutils/kooky/browser/chrome"
)

// on macOS:
var cookieStorePath = "/Google/Chrome/Default/Cookies"

func main() {
	// construct file path for the sqlite database containing the cookies
	dir, _ := os.UserConfigDir() // on macOS: "/<USER>/Library/Application Support/"
	cookieStoreFile := dir + cookieStorePath

	// read the cookies from the file
	// decryption is handled automatically
	for cookie := range chrome.TraverseCookies(cookieStoreFile).OnlyCookies() {
		fmt.Println(cookie)
	}
}
Example (CookieJar)
package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"strings"

	"github.com/browserutils/kooky"
	_ "github.com/browserutils/kooky/browser/firefox"
)

func main() {
	ctx := context.TODO()
	stores := kooky.FindAllCookieStores(ctx)
	var s kooky.CookieStore
	for _, store := range stores {
		if store.Browser() != `firefox` || !store.IsDefaultProfile() {
			continue
		}
		s = store
		break
	}
	// jar := s
	// only store cookies relevant for the target website in the cookie jar
	jar, _ := s.SubJar(ctx, kooky.FilterFunc(func(c *kooky.Cookie) bool {
		return kooky.Domain(`github.com`).Filter(c) || kooky.Domain(`.github.com`).Filter(c)
	}))

	u, _ := url.Parse(`https://github.com/settings/profile`)

	cookies := kooky.FilterCookies(ctx, jar.Cookies(u), kooky.Name(`logged_in`)).Collect(ctx)
	if len(cookies) == 0 {
		log.Fatal(`not logged in`)
	}

	client := http.Client{Jar: jar}
	resp, _ := client.Get(u.String())
	body, _ := io.ReadAll(resp.Body)
	if !strings.Contains(string(body), `id="user_profile_name"`) {
		fmt.Print("not ")
	}
	fmt.Println("logged in")

}
Output:

logged in

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ExportCookies added in v0.2.1

func ExportCookies[S CookieSeq | []*Cookie | []*http.Cookie](ctx context.Context, w io.Writer, cookies S)

ExportCookies() export "cookies" in the Netscape format.

curl, wget, ... use this format.

Example
package main

import (
	"context"
	"net/http"
	"os"

	"github.com/browserutils/kooky"
)

var cookieFile = `cookies.txt`

func main() {
	var cookies = []*kooky.Cookie{{Cookie: http.Cookie{Domain: `.test.com`, Name: `test`, Value: `dGVzdA==`}}}

	file, err := os.OpenFile(cookieFile, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		// TODO: handle error
		return
	}
	defer file.Close()

	kooky.ExportCookies(context.TODO(), file, cookies)
}

func FilterCookie added in v0.2.1

func FilterCookie[T Cookie | http.Cookie](ctx context.Context, cookie *T, filters ...Filter) bool

FilterCookie() tells if a "cookie" passes all "filters".

func RegisterFinder added in v0.2.1

func RegisterFinder(browser string, finder CookieStoreFinder)

RegisterFinder() registers CookieStoreFinder enabling automatic finding of cookie stores with FindAllCookieStores() and ReadCookies().

RegisterFinder() is called by init() in the browser subdirectories.

Types

type BrowserInfo added in v0.2.3

type BrowserInfo interface {
	Browser() string
	Profile() string
	IsDefaultProfile() bool
	FilePath() string
}
type Cookie struct {
	http.Cookie
	Creation  time.Time
	Container string
	Browser   BrowserInfo
}

Cookie is an http.Cookie augmented with information obtained through the scraping process.

func (*Cookie) MarshalJSON added in v0.2.3

func (c *Cookie) MarshalJSON() ([]byte, error)

type CookieSeq added in v0.2.3

type CookieSeq iter.Seq2[*Cookie, error]

for-rangeable cookie retriever

func FilterCookies added in v0.2.1

func FilterCookies[S CookieSeq | ~[]*Cookie | ~[]*http.Cookie](ctx context.Context, cookies S, filters ...Filter) CookieSeq
Example
package main

import (
	"context"

	"github.com/browserutils/kooky"
	_ "github.com/browserutils/kooky/browser/all" // register cookiestore finders
)

var cookieName = `NID`

func main() {
	ctx := context.TODO()

	cookies := kooky.AllCookies(). // automatic read
					Seq().
					Filter(
			ctx,
			kooky.Valid,                    // remove expired cookies
			kooky.DomainContains(`google`), // cookie domain has to contain "google"
			kooky.Name(cookieName),         // cookie name is "NID"
			kooky.Debug,                    // print cookies after applying previous filter
		).
		Collect(ctx) // iterate and collect in a slice

	_ = cookies // do something
}

func MergeCookieSeqs added in v0.2.3

func MergeCookieSeqs(seqs ...CookieSeq) CookieSeq

func TraverseCookies added in v0.2.3

func TraverseCookies(ctx context.Context, filters ...Filter) CookieSeq

func (CookieSeq) Chan added in v0.2.3

func (s CookieSeq) Chan(ctx context.Context) <-chan *Cookie

func (CookieSeq) Collect added in v0.2.3

func (s CookieSeq) Collect(ctx context.Context) Cookies

Collect() is the same as ReadAllCookies but ignores the error

func (CookieSeq) Export added in v0.2.3

func (s CookieSeq) Export(ctx context.Context, w io.Writer)

func (CookieSeq) Filter added in v0.2.3

func (s CookieSeq) Filter(ctx context.Context, filters ...Filter) CookieSeq

func (CookieSeq) FirstMatch added in v0.2.3

func (s CookieSeq) FirstMatch(ctx context.Context, filters ...Filter) *Cookie

func (CookieSeq) Merge added in v0.2.3

func (s CookieSeq) Merge(seqs ...CookieSeq) CookieSeq

func (CookieSeq) OnlyCookies added in v0.2.3

func (s CookieSeq) OnlyCookies() CookieSeq

sequence of non-nil cookies and nil errors

func (CookieSeq) ReadAllCookies added in v0.2.3

func (s CookieSeq) ReadAllCookies(ctx context.Context) (Cookies, error)

type CookieStore added in v0.2.1

type CookieStore interface {
	http.CookieJar
	BrowserInfo
	SubJar(context.Context, ...Filter) (http.CookieJar, error)
	TraverseCookies(...Filter) CookieSeq
	Close() error
}

CookieStore represents a file, directory, etc containing cookies.

Call CookieStore.Close() after using any of its methods.

func FindAllCookieStores added in v0.2.1

func FindAllCookieStores(ctx context.Context) []CookieStore

FindAllCookieStores() tries to find cookie stores at default locations.

FindAllCookieStores() requires registered CookieStoreFinders.

Register cookie store finders for all browsers like this:

import _ "github.com/browserutils/kooky/browser/all"

Or only a specific browser:

import _ "github.com/browserutils/kooky/browser/chrome"
Example
package main

import (
	"context"
	"fmt"

	"github.com/browserutils/kooky"
	_ "github.com/browserutils/kooky/browser/all"
)

func main() {
	ctx := context.TODO()
	cookieStores := kooky.FindAllCookieStores(ctx)

	for _, store := range cookieStores {
		// CookieStore keeps files/databases open for repeated reads
		// close those when no longer needed
		defer store.Close()

		var filters = []kooky.Filter{
			kooky.Valid, // remove expired cookies
		}

		for cookie := range store.TraverseCookies(filters...).OnlyCookies() {
			fmt.Printf(
				"%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
				store.Browser(),
				store.Profile(),
				store.FilePath(),
				cookie.Domain,
				cookie.Name,
				cookie.Value,
				cookie.Expires.Format(`2006.01.02 15:04:05`),
			)
		}
	}
}

type CookieStoreFinder added in v0.2.1

type CookieStoreFinder interface {
	FindCookieStores() CookieStoreSeq
}

CookieStoreFinder tries to find cookie stores at default locations.

type CookieStoreSeq added in v0.2.3

type CookieStoreSeq iter.Seq2[CookieStore, error]

func TraverseCookieStores added in v0.2.3

func TraverseCookieStores(ctx context.Context) CookieStoreSeq

func (CookieStoreSeq) AllCookieStores added in v0.2.3

func (s CookieStoreSeq) AllCookieStores(ctx context.Context) []CookieStore

func (CookieStoreSeq) OnlyCookieStores added in v0.2.3

func (s CookieStoreSeq) OnlyCookieStores() CookieStoreSeq

sequence of non-nil cookie stores and nil errors

func (CookieStoreSeq) TraverseCookies added in v0.2.3

func (s CookieStoreSeq) TraverseCookies(ctx context.Context, filters ...Filter) CookieSeq

type Cookies added in v0.2.3

type Cookies []*Cookie

func AllCookies added in v0.2.3

func AllCookies(filters ...Filter) Cookies

func ReadCookies added in v0.2.1

func ReadCookies(ctx context.Context, filters ...Filter) (Cookies, error)

Cookie retrieving functions in this package like TraverseCookies(), ReadCookies(), AllCookies() use registered cookiestore finders to read cookies. Erronous reads are skipped.

Register cookie store finders for all browsers like this:

import _ "github.com/browserutils/kooky/browser/all"

Or only a specific browser:

import _ "github.com/browserutils/kooky/browser/chrome"
Example (All)
package main

import (
	"fmt"

	"github.com/browserutils/kooky"
	_ "github.com/browserutils/kooky/browser/all" // This registers all cookiestore finders!
	// _ "github.com/browserutils/kooky/browser/chrome" // load only the chrome cookiestore finder
)

func main() {
	// try to find cookie stores in default locations and
	// read the cookies from them.
	// decryption is handled automatically.
	cookies := kooky.AllCookies()

	for _, cookie := range cookies {
		fmt.Println(cookie)
	}
}

var _ struct{} // ignore this - for correct working of the documentation tool

func (Cookies) Seq added in v0.2.3

func (c Cookies) Seq() CookieSeq

type Filter added in v0.2.1

type Filter interface{ Filter(*Cookie) bool }

Filter is used for filtering cookies in ReadCookies() functions. Filter order might be changed for performance reasons (omission of value decryption of filtered out cookies, etc).

A cookie passes the Filter if Filter.Filter returns true.

Example (Regex)
package main

import (
	"context"
	"fmt"
	"net/http"
	"regexp"

	"github.com/browserutils/kooky"
)

// example regex matching base64 strings
var reBase64 = regexp.MustCompile(`^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$`)

func main() {
	var cookies = []*kooky.Cookie{{Cookie: http.Cookie{Name: `test`, Value: `dGVzdA==`}}}

	ctx := context.Background()
	cookies = kooky.FilterCookies(
		ctx,
		cookies,
		ValueRegexMatch(reBase64), // filter cookies with the regex filter
		// kooky.Debug,            // print cookies after applying the regex filter
	).Collect(ctx)

	for _, cookie := range cookies {
		fmt.Println(cookie.Value)
		break // only first element
	}

}

func ValueRegexMatch(re *regexp.Regexp) kooky.Filter {
	return kooky.FilterFunc(func(cookie *kooky.Cookie) bool {
		return cookie != nil && re != nil && re.Match([]byte(cookie.Value))
	})
}
Output:

dGVzdA==
var Debug Filter = FilterFunc(func(cookie *Cookie) bool {

	fmt.Printf("%+#v\n", cookie)
	return true
})

Debug prints the cookie.

Position Debug after the filter you want to test.

var Expired Filter = FilterFunc(func(cookie *Cookie) bool {
	return cookie != nil && cookie.Expires.Before(time.Now())
})
var HTTPOnly Filter = FilterFunc(func(cookie *Cookie) bool {
	return cookie != nil && cookie.HttpOnly
})
var Secure Filter = FilterFunc(func(cookie *Cookie) bool {
	return cookie != nil && cookie.Secure
})
var Valid Filter = ValueFilterFunc(func(cookie *Cookie) bool {
	return cookie != nil && cookie.Expires.After(time.Now()) && cookie.Cookie.Valid() == nil
})

func CreationAfter added in v0.2.1

func CreationAfter(u time.Time) Filter

func CreationBefore added in v0.2.1

func CreationBefore(u time.Time) Filter

func Domain added in v0.2.1

func Domain(domain string) Filter

func DomainContains added in v0.2.1

func DomainContains(substr string) Filter

func DomainHasPrefix added in v0.2.1

func DomainHasPrefix(prefix string) Filter

func DomainHasSuffix added in v0.2.1

func DomainHasSuffix(suffix string) Filter

func ExpiresAfter added in v0.2.1

func ExpiresAfter(u time.Time) Filter

func ExpiresBefore added in v0.2.1

func ExpiresBefore(u time.Time) Filter

func Name added in v0.2.1

func Name(name string) Filter

func NameContains added in v0.2.1

func NameContains(substr string) Filter

func NameHasPrefix added in v0.2.1

func NameHasPrefix(prefix string) Filter

func NameHasSuffix added in v0.2.1

func NameHasSuffix(suffix string) Filter

func Path added in v0.2.1

func Path(path string) Filter

func PathContains added in v0.2.1

func PathContains(substr string) Filter

func PathDepth added in v0.2.1

func PathDepth(depth int) Filter

func PathHasPrefix added in v0.2.1

func PathHasPrefix(prefix string) Filter

func PathHasSuffix added in v0.2.1

func PathHasSuffix(suffix string) Filter

func Value added in v0.2.1

func Value(value string) Filter

func ValueContains added in v0.2.1

func ValueContains(substr string) Filter

func ValueHasPrefix added in v0.2.1

func ValueHasPrefix(prefix string) Filter

func ValueHasSuffix added in v0.2.1

func ValueHasSuffix(suffix string) Filter

func ValueLen added in v0.2.1

func ValueLen(length int) Filter

type FilterFunc added in v0.2.1

type FilterFunc func(*Cookie) bool

func (FilterFunc) Filter added in v0.2.1

func (f FilterFunc) Filter(c *Cookie) bool

type ValueFilterFunc added in v0.2.2

type ValueFilterFunc func(*Cookie) bool

func (ValueFilterFunc) Filter added in v0.2.2

func (f ValueFilterFunc) Filter(c *Cookie) bool

Directories

Path Synopsis
browser
all
browsh
Browsh Browser
Browsh Browser
ie
w3m
cmd
kooky command
internal
ie
wsl

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL