Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
barnardb committed Apr 20, 2019
0 parents commit de1bb10
Show file tree
Hide file tree
Showing 9 changed files with 357 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/cookies
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019 Ben Barnard

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
Cookies
=======

Extracts cookies from Chrome's cookie database,
outputting them in a format appropriate for use in the HTTP `Cookie` header.
This is useful in some scripting situations.

This core cookie reading code is provided by the [zellyn/kooky] cookie extraction library
(currently using a [fork] that merges [zellyn/kooky #5] to support newer Firefox databases).
This project wraps that library with some code to abstract browser differences away,
filter for cookies that match a URL, and provide a command-line interface.

[zellyn/kooky]: https://github.com/zellyn/kooky
[barnardb/kooky]: https://github.com/barnardb/kooky
[zellyn/kookie #5]: https://github.com/zellyn/kooky/pull/5


Status
------

The code is working on my MacOS 10.14.4 Mojave system,
though with an issue related to reading Safari cookies (see [zellyn/kooky #7]).
Paths to cookie files are currently MacOS-specific, but could easily be made OS-dependent.
Pull requests are welcome.

[zellyn/kooky #7]: https://github.com/zellyn/kooky/issues/7


Usage
-----

```bash
cookies www.example.com
```
might yield
```
some.random.value=1234;JSESSIONID=0123456789ABCDEF0123456789ABCDEF;another_cookie:example-cookie-value
```

### cURL example

```bash
curl --cookie "$(cookies http://www.example.com)" http://www.example.com
```

might produce an HTTP request like this:

```http
GET / HTTP/1.1
Host: www.example.com
User-Agent: curl/7.54.0
Accept: */*
Cookie: some.random.value=1234;JSESSIONID=0123456789ABCDEF0123456789ABCDEF;another_cookie:example-cookie-value
```

### HTTPie example

```bash
http http://www.example.com Cookie:"$(cookies http://www.example.com)"
```

might produce an HTTP request like this:

```http
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cookie: some.random.value=1234;JSESSIONID=0123456789ABCDEF0123456789ABCDEF;another_cookie:example-cookie-value
Host: www.example.com
User-Agent: HTTPie/1.0.2
```

58 changes: 58 additions & 0 deletions find.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

import (
"log"
"github.com/barnardb/kooky"
"net/url"
"strings"
"time"
)

func findCookies(url *url.URL, browsers []string, logger *log.Logger) (cookies []*kooky.Cookie) {
logger.Printf("Looking for cookies for URL %s", url)

for _, browser := range browsers {
loader, err := getCookieLoader(browser)
if err != nil {
logger.Printf("Error getting cookie loader for %s: %s", browser, err)
continue
}

logger.Printf("Loading cookies from %s", browser)
cookies, err := loader(url.Host)
if err != nil {
logger.Printf("Error loading cookies from %s: %s", browser, err)
continue
}
cookies = filterCookies(cookies, url, logger)

if len(cookies) > 0 {
return cookies
}
}

return []*kooky.Cookie{}
}

func filterCookies(cookies []*kooky.Cookie, url *url.URL, logger *log.Logger) []*kooky.Cookie {
logger.Printf("Filtering %d cookies", len(cookies))
filtered := []*kooky.Cookie{}
now := time.Now()
logger.Printf("Current time is %v", now)
for _, cookie := range cookies {
if cookie.Domain != url.Host {
logger.Printf("Rejecting cookie for non-matching domain: %v", cookie)
} else if url.Scheme != "https" && cookie.Secure {
logger.Printf("Rejecting secure cookie for non-HTTPS URL: %v", cookie)
} else if !cookie.Expires.IsZero() && cookie.Expires.Before(now) {
logger.Printf("Rejecting expired cookie: %v", cookie)
} else if url.Path != "" && !strings.HasPrefix(url.Path, cookie.Path) {
logger.Printf("Rejecting cookie due to unmatched path: %v", cookie)
} else {
logger.Printf("Accepting: %v", cookie)
filtered = append(filtered, cookie)
}
}
logger.Printf("Accepted %d of %d cookies", len(filtered), len(cookies))
return filtered
}
20 changes: 20 additions & 0 deletions format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"fmt"
"github.com/barnardb/kooky"
"io"
)

func formatCookies(w io.Writer, cookies []*kooky.Cookie) {
for i, cookie := range cookies {
if i > 0 {
fmt.Fprint(w, ";")
}
formatCookie(w, cookie)
}
}

func formatCookie(w io.Writer, cookie *kooky.Cookie) {
fmt.Fprintf(w, "%s=%s", cookie.Name, cookie.Value)
}
13 changes: 13 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/barnardb/cookies

require (
github.com/barnardb/kooky v0.0.0-20190420082209-c58c0ed851b3
github.com/go-sqlite/sqlite3 v0.0.0-20180313105335-53dd8e640ee7 // indirect
github.com/gonuts/binary v0.2.0 // indirect
github.com/keybase/go-keychain v0.0.0-20190410171543-b4561aacec0d // indirect
github.com/spf13/pflag v1.0.3
github.com/stretchr/objx v0.2.0 // indirect
github.com/zellyn/kooky v0.0.0-20190327021746-75a44d2f7546
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480 // indirect
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a // indirect
)
31 changes: 31 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
github.com/barnardb/kooky v0.0.0-20190327021746-75a44d2f7546 h1:KNZGSCT/9dDNy9jZq7YkhMjom+4EYybwDBlbSECAeew=
github.com/barnardb/kooky v0.0.0-20190327021746-75a44d2f7546/go.mod h1:m1tY/UUilgNwFhzjvfKUo+vzze/TvSpRAvgk1RJGnEw=
github.com/barnardb/kooky v0.0.0-20190420082209-c58c0ed851b3 h1:hBAjBbzIjOuBG5OHUdMVjKACfeo/3zKQg53COUNs84g=
github.com/barnardb/kooky v0.0.0-20190420082209-c58c0ed851b3/go.mod h1:m1tY/UUilgNwFhzjvfKUo+vzze/TvSpRAvgk1RJGnEw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-sqlite/sqlite3 v0.0.0-20180313105335-53dd8e640ee7 h1:ow5vK9Q/DSKkxbEIJHBST6g+buBDwdaDIyk1dGGwpQo=
github.com/go-sqlite/sqlite3 v0.0.0-20180313105335-53dd8e640ee7/go.mod h1:JxSQ+SvsjFb+p8Y+bn+GhTkiMfKVGBD0fq43ms2xw04=
github.com/gonuts/binary v0.2.0 h1:caITwMWAoQWlL0RNvv2lTU/AHqAJlVuu6nZmNgfbKW4=
github.com/gonuts/binary v0.2.0/go.mod h1:kM+CtBrCGDSKdv8WXTuCUsw+loiy8f/QEI8YCCC0M/E=
github.com/keybase/go-keychain v0.0.0-20190301032907-79b9ae49810a h1:iYS7ucfad91wT/M5HEEsNz+wDuS1fvUY/WeEwA/qqqY=
github.com/keybase/go-keychain v0.0.0-20190301032907-79b9ae49810a/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc=
github.com/keybase/go-keychain v0.0.0-20190410171543-b4561aacec0d h1:cQAx5D1MbRNnLbeqFqv0uwYn+TvVZwIl/LZAGmZNvUY=
github.com/keybase/go-keychain v0.0.0-20190410171543-b4561aacec0d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/zellyn/kooky v0.0.0-20190208185654-f09b994866ee h1:IslNbJBJ0cbkzi7jQE9l2juz+wJOv232WjbxI5SA86Y=
github.com/zellyn/kooky v0.0.0-20190208185654-f09b994866ee/go.mod h1:45MAFn3o6erpJrzlrSmsJBaUNWhz5YyM9lxj/nyXDjM=
github.com/zellyn/kooky v0.0.0-20190327021746-75a44d2f7546/go.mod h1:45MAFn3o6erpJrzlrSmsJBaUNWhz5YyM9lxj/nyXDjM=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25 h1:jsG6UpNLt9iAsb0S2AGW28DveNzzgmbXR+ENoPjUeIU=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480 h1:O5YqonU5IWby+w98jVUG9h7zlCWCcH4RHyPVReBmhzk=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
57 changes: 57 additions & 0 deletions load.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package main

import (
"fmt"
"github.com/barnardb/kooky"
"os/user"
"strings"
"time"
)

type cookieLoader func(string) ([]*kooky.Cookie, error)

func pathFromHome(pathRootedAtHome string) (path string, err error) {
usr, err := user.Current()
if err != nil {
return
}
return (usr.HomeDir + pathRootedAtHome), nil
}

func loadChromeCookies(domain string) ([]*kooky.Cookie, error) {
cookiesFile, err := pathFromHome("/Library/Application Support/Google/Chrome/Default/Cookies")
if err != nil {
return nil, err
}
return kooky.ReadChromeCookies(cookiesFile, domain, "", time.Time{})
}

func loadFirefoxCookies() ([]*kooky.Cookie, error) {
cookiesFile, err := pathFromHome("/Library/Application Support/Firefox/Profiles/cp9hpajj.default/cookies.sqlite")
if err != nil {
return nil, err
}
return kooky.ReadFirefoxCookies(cookiesFile)
}

func loadSafariCookies(domain string) ([]*kooky.Cookie, error) {
cookiesFile, err := pathFromHome("/Library/Cookies/Cookies.binarycookies")
if err != nil {
return nil, err
}
return kooky.ReadSafariCookies(cookiesFile, domain, "", time.Time{})
}

func getCookieLoader(name string) (loader cookieLoader, err error) {
normalized := strings.ToLower(name); switch normalized {
case "chrome":
loader = loadChromeCookies
case "firefox":
loader = func(_ string) ([]*kooky.Cookie, error) { return loadFirefoxCookies() }
case "safari":
loader = loadSafariCookies
default:
err = fmt.Errorf("No cookie loader matching %s", normalized)
}
return
}
83 changes: 83 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package main

import (
"fmt"
"io/ioutil"
"log"
"net/url"
"os"
)
import flag "github.com/spf13/pflag"

type options struct {
browsers []string
url *url.URL
failWhenNotFound bool
verbose bool
}

func main() {
options := parseCommandLine()
logger := buildLogger(options.verbose)

cookies := findCookies(options.url, options.browsers, logger)
if len(cookies) == 0 {
if options.failWhenNotFound {
os.Exit(1)
}
return
}

formatCookies(os.Stdout, cookies)
fmt.Print("\n")
}

func parseCommandLine() (options options) {
flagSet := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)

fatalError := func(error ...interface{}) {
fmt.Fprintln(os.Stderr, error...)
flagSet.Usage()
os.Exit(2)
}

flagSet.Usage = func() {
fmt.Fprintf(os.Stderr, "usage: %s [options…] <URL>\n\nThe following options are available:\n", os.Args[0])
flagSet.PrintDefaults()
}

flagSet.StringArrayVarP(&options.browsers, "browser", "b", []string{"chrome", "firefox", "safari"}, "browser to try extracting a cookie from, can be repeated to try multiple browsers")
flagSet.BoolVarP(&options.failWhenNotFound, "permitNotFound", "p", true, "Don't fail with exit status 1 when cookies aren't found")
flagSet.BoolVarP(&options.verbose, "verbose", "v", false, "enables logging to stderr")

err := flagSet.Parse(os.Args[1:])
if err != nil {
if err == flag.ErrHelp {
os.Exit(0)
}
fatalError(err)
}

if flagSet.NArg() != 1 {
fatalError("error: expected 1 argument but got %d\n", flag.NArg())
}

options.url, err = url.Parse(flagSet.Arg(0))
if err != nil {
fatalError("error parsing URL:", err)
} else if options.url.Scheme != "http" && options.url.Scheme != "https" {
fatalError(fmt.Sprintf("URL scheme must be http or https, but got \"%s\"", options.url.Scheme))
} else if options.url.Host == "" {
fatalError("URL host must be non-empty")
}

return
}

func buildLogger(verbose bool) *log.Logger {
w := ioutil.Discard
if verbose {
w = os.Stderr
}
return log.New(w, "", 0)
}

0 comments on commit de1bb10

Please sign in to comment.