-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit de1bb10
Showing
9 changed files
with
357 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/cookies |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |