├── .gitignore ├── LICENSE ├── README.md ├── build.sh ├── find.go ├── format.go ├── go.mod ├── go.sum ├── logger.go ├── main.go └── prepare-release.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /cookies 2 | /*.gz 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ben Barnard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Cookies 2 | ======= 3 | 4 | Extracts cookies from the user's Chrome, Chromium, Firefox or Safari cookie database. 5 | A single cookie value can be retrieved, or all cookies applicable to given URL 6 | can be retrieved and output in a format appropriate for use in the HTTP `Cookie` header. 7 | Both of these usages are useful for scripting purposes. 8 | 9 | The core cookie-reading code is provided by the [zellyn/kooky] cookie extraction library. 10 | This `cookies` tool provides a command-line interface to that library that 11 | allows you to select which browser cookie databases to use and filter for all 12 | or a particular cookie that are relevant for a given URL. 13 | 14 | [zellyn/kooky]: https://github.com/zellyn/kooky 15 | 16 | 17 | - [Installing](#installing) 18 | - [Usage](#usage) 19 | - [cURL Example](#curl-example) 20 | - [HTTPie Example](#httpie-example) 21 | - [Status](#status) 22 | - [Building](#building) 23 | - [Releasing](#releasing) 24 | 25 | 26 | Installing 27 | ---------- 28 | 29 | On MacOS with Homebrew: 30 | 31 | ```bash 32 | brew install barnardb/cookies/cookies 33 | ``` 34 | 35 | Alternatively, or on other platforms, follow the instructions below for [building](#building) an executable. 36 | 37 | 38 | Usage 39 | ----- 40 | 41 | As explained by `cookies --help`: 42 | ```text 43 | usage: ./cookies [options…] [] 44 | 45 | The following options are available: 46 | -a, --accept-missing don't fail with exit status 1 when cookies aren't found 47 | -b, --browser stringArray browser to try extracting a cookie from, can be repeated to try multiple browsers (default [chrome,chromium,firefox,safari]) 48 | -v, --verbose[=level] enables logging to stderr; specify it twice or provide level 2 to get per-cookie details (`-vv` or `--verbose=2`) 49 | --version prints version information and exits 50 | 51 | cookies version 0.5.1 (https://github.com/barnardb/cookies) 52 | ``` 53 | 54 | To get all cookies relevant to a URL in the format expected by the `Cookie` header, 55 | provide the URL as an argument. E.g., running 56 | ```bash 57 | cookies http://www.example.com 58 | ``` 59 | might yield 60 | ``` 61 | some.random.value=1234;JSESSIONID=0123456789ABCDEF0123456789ABCDEF;another_cookie:example-cookie-value 62 | ``` 63 | 64 | Or you can get just the value of a particular cookie by providing both a URL and a cookie name. 65 | E.g. running 66 | ```bash 67 | cookies http://www.example.com JSESSIONID 68 | ``` 69 | might yield 70 | ``` 71 | 0123456789ABCDEF0123456789ABCDEF 72 | ``` 73 | 74 | ### cURL example 75 | 76 | ```bash 77 | curl --cookie "$(cookies http://www.example.com)" http://www.example.com 78 | ``` 79 | 80 | might produce an HTTP request like this: 81 | 82 | ```http 83 | GET / HTTP/1.1 84 | Host: www.example.com 85 | User-Agent: curl/7.54.0 86 | Accept: */* 87 | Cookie: some.random.value=1234;JSESSIONID=0123456789ABCDEF0123456789ABCDEF;another_cookie:example-cookie-value 88 | ``` 89 | 90 | ### HTTPie example 91 | 92 | ```bash 93 | http http://www.example.com Cookie:"$(cookies http://www.example.com)" 94 | ``` 95 | 96 | might produce an HTTP request like this: 97 | 98 | ```http 99 | GET / HTTP/1.1 100 | Accept: */* 101 | Accept-Encoding: gzip, deflate 102 | Connection: keep-alive 103 | Cookie: some.random.value=1234;JSESSIONID=0123456789ABCDEF0123456789ABCDEF;another_cookie:example-cookie-value 104 | Host: www.example.com 105 | User-Agent: HTTPie/1.0.2 106 | ``` 107 | 108 | 109 | Status 110 | ------ 111 | 112 | I use this tool for day-to-day tasks on multiple MacOS systems. 113 | (Note that there is a cookie database permission issue you will have to deal with if you want to read Safari cookies: [zellyn/kooky #7].) 114 | 115 | [zellyn/kooky #7]: https://github.com/zellyn/kooky/issues/7 116 | 117 | As the library is essentially a wrapper around [zellyn/kooky] and the library 118 | supports other platforms as well, this tool should also work on other platforms. 119 | 120 | Pull requests are welcome. 121 | 122 | 123 | Building 124 | -------- 125 | 126 | Requires [Go](https://golang.org/). 127 | 128 | To build the code, check out the repository and run: 129 | ```bash 130 | go build 131 | ``` 132 | 133 | This produces a `cookies` executable. 134 | 135 | 136 | Releasing 137 | --------- 138 | 139 | Releases are prepared by running: 140 | ```bash 141 | ./prepare-release.sh "${version}" 142 | ``` 143 | `${version}` should be a semantic version number in the "0.0.0" format. 144 | This tags the release (e.g. as "v0.0.0") and creates a draft release in GitHub, 145 | which can be given release notes and published. 146 | 147 | Once the new release is published to GitHub, the homebrew formula in 148 | [barnardb/homebrew-cookies] should be updated following the instructions in 149 | that repo's README. 150 | 151 | [barnardb/homebrew-cookies]: https://github.com/barnardb/homebrew-cookies 152 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | [ $# -eq 1 ] && grep -qE '^\d+\.\d+\.\d+$' <<<"$1" || { 5 | echo "usage: $0 " >&2 6 | exit 2 7 | } 8 | 9 | version="$1" 10 | 11 | go build -ldflags="-X 'main.version=${version}'" 12 | -------------------------------------------------------------------------------- /find.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/url" 5 | "strings" 6 | "time" 7 | 8 | "github.com/zellyn/kooky" 9 | _ "github.com/zellyn/kooky/allbrowsers" 10 | ) 11 | 12 | func contains(values []string, value string) bool { 13 | for _, v := range values { 14 | if v == value { 15 | return true 16 | } 17 | } 18 | return false 19 | } 20 | 21 | func findCookies(url *url.URL, name string, browsers []string, logger *Logger) (results []*kooky.Cookie) { 22 | logger.Printf("Looking for browsers %v", browsers) 23 | logger.Printf("Looking for cookies for URL %s", url) 24 | filter := currentlyAppliesToURLAndName(url, name, logger.RequireVerbosity(2)) 25 | 26 | cookies := make(chan *kooky.Cookie) 27 | go func() { 28 | kooky.ConcurrentlyVisitFinders(func(name string, finder kooky.CookieStoreFinder) { 29 | if !contains(browsers, name) { 30 | return 31 | } 32 | logger.Printf("Looking for %s cookie stores", name) 33 | kooky.ConcurrentlyVisitStores(finder, func(store kooky.CookieStore) { 34 | logger.Printf("Loading cookies from %v", store) 35 | err := store.VisitCookies(func(cookie *kooky.Cookie, initializeValue kooky.CookieValueInitializer) error { 36 | if !filter(cookie) { 37 | return nil 38 | } 39 | err := initializeValue(cookie) 40 | if err == nil { 41 | cookies <- cookie 42 | } 43 | return err 44 | }) 45 | if err != nil { 46 | logger.Printf("Error loading cookies from %v: %s", store, err) 47 | } else { 48 | logger.Printf("Done loading cookies from %v", store) 49 | } 50 | }) 51 | logger.Printf("Done loading from %s cookie stores", name) 52 | }) 53 | close(cookies) 54 | }() 55 | for cookie := range cookies { 56 | results = append(results, cookie) 57 | } 58 | logger.Printf("Found %d matching cookie(s)", len(results)) 59 | return 60 | } 61 | 62 | func currentlyAppliesToURLAndName(url *url.URL, name string, logger *Logger) kooky.Filter { 63 | currentTime := time.Now() 64 | logger.Printf("Current time is %v", currentTime) 65 | return appliesToURLAndNameAtTime(url, name, currentTime, logger) 66 | } 67 | 68 | func appliesToURLAndNameAtTime(url *url.URL, name string, time time.Time, logger *Logger) kooky.Filter { 69 | urlIsNotSecure := url.Scheme != "https" 70 | return func(cookie *kooky.Cookie) bool { 71 | if !hostMatchesDomain(url.Host, cookie.Domain) { 72 | logger.Printf("Rejecting cookie for non-matching domain: %v", cookie) 73 | } else if urlIsNotSecure && cookie.Secure { 74 | logger.Printf("Rejecting secure cookie for non-HTTPS URL: %v", cookie) 75 | } else if !(cookie.Expires.IsZero() || time.Before(cookie.Expires)) { 76 | logger.Printf("Rejecting expired cookie: %v", cookie) 77 | } else if url.Path != "" && !strings.HasPrefix(url.Path, cookie.Path) { 78 | logger.Printf("Rejecting cookie due to unmatched path: %v", cookie) 79 | } else if name != "" && cookie.Name != name { 80 | logger.Printf("Rejecting cookie due to unmatched name: %v", cookie) 81 | } else { 82 | logger.Printf("Accepting: %v", cookie) 83 | return true 84 | } 85 | return false 86 | } 87 | } 88 | 89 | func hostMatchesDomain(host string, domain string) bool { 90 | return host == domain || 91 | (strings.HasPrefix(domain, ".") && (strings.HasSuffix(host, domain) || host == domain[1:])) 92 | } 93 | -------------------------------------------------------------------------------- /format.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/zellyn/kooky" 6 | "io" 7 | ) 8 | 9 | func formatCookies(w io.Writer, cookies []*kooky.Cookie) { 10 | for i, cookie := range cookies { 11 | if i > 0 { 12 | fmt.Fprint(w, ";") 13 | } 14 | formatCookie(w, cookie) 15 | } 16 | } 17 | 18 | func formatCookie(w io.Writer, cookie *kooky.Cookie) { 19 | fmt.Fprintf(w, "%s=%s", cookie.Name, cookie.Value) 20 | } 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/barnardb/cookies 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/spf13/pflag v1.0.5 7 | github.com/zellyn/kooky v0.0.0-20201108220156-bec09c12c339 8 | ) 9 | 10 | // We are currently using the head of . 11 | // I decided to use my fork while waiting for https://github.com/zellyn/kooky/pull/43 12 | // to be merged, but then decided to do some work to allow the kind of 13 | // parallelism and defered decrypting I wanted to have. The result is a 14 | // speed-up taking a typical use case of mine from ~1200ms to ~100ms. 15 | // I intend to try to contribute these changes back upstream. 16 | replace github.com/zellyn/kooky => github.com/barnardb/kooky v0.0.0-20210125015714-b0182bf77e67 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/barnardb/kooky v0.0.0-20210125015714-b0182bf77e67 h1:sBislMisEMpXDgZwsfKDe+iIqZD5wjBaGCTSwrs+0cI= 2 | github.com/barnardb/kooky v0.0.0-20210125015714-b0182bf77e67/go.mod h1:QM4+3a3KkwFXtuTz+w41LBKU+MW7KecHBn0bNalcPKA= 3 | github.com/bobesa/go-domain-util v0.0.0-20190911083921-4033b5f7dd89 h1:2pkAuIM8OF1fy4ToFpMnI4oE+VeUNRbGrpSLKshK0oQ= 4 | github.com/bobesa/go-domain-util v0.0.0-20190911083921-4033b5f7dd89/go.mod h1:/09nEjna1UMoasyyQDhOrIn8hi2v2kiJglPWed1idck= 5 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 6 | github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/go-ini/ini v1.62.0 h1:7VJT/ZXjzqSrvtraFp4ONq80hTcRQth1c9ZnQ3uNQvU= 10 | github.com/go-ini/ini v1.62.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 11 | github.com/go-sqlite/sqlite3 v0.0.0-20180313105335-53dd8e640ee7 h1:ow5vK9Q/DSKkxbEIJHBST6g+buBDwdaDIyk1dGGwpQo= 12 | github.com/go-sqlite/sqlite3 v0.0.0-20180313105335-53dd8e640ee7/go.mod h1:JxSQ+SvsjFb+p8Y+bn+GhTkiMfKVGBD0fq43ms2xw04= 13 | github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= 14 | github.com/gonuts/binary v0.2.0 h1:caITwMWAoQWlL0RNvv2lTU/AHqAJlVuu6nZmNgfbKW4= 15 | github.com/gonuts/binary v0.2.0/go.mod h1:kM+CtBrCGDSKdv8WXTuCUsw+loiy8f/QEI8YCCC0M/E= 16 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 17 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 18 | github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d h1:gVjhBCfVGl32RIBooOANzfw+0UqX8HU+yPlMv8vypcg= 19 | github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d/go.mod h1:W6EbaYmb4RldPn0N3gvVHjY1wmU59kbymhW9NATWhwY= 20 | github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE= 21 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 22 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 23 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 24 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 25 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 26 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 27 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 28 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 29 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 30 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 31 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 32 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 33 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 34 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 35 | github.com/zalando/go-keyring v0.1.0/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0= 36 | github.com/zellyn/kooky v0.0.0-20201108220156-bec09c12c339 h1:girtxC8UoQF7GzkSkC3BAggcsH6Zl6b9nqsn5iLEM/U= 37 | github.com/zellyn/kooky v0.0.0-20201108220156-bec09c12c339/go.mod h1:QM4+3a3KkwFXtuTz+w41LBKU+MW7KecHBn0bNalcPKA= 38 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 39 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 40 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 41 | golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= 42 | golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 43 | golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 44 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 45 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 46 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw= 47 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 48 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 49 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 51 | golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 52 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 53 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 54 | golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= 55 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 56 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 57 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 58 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 59 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 60 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 61 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 62 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 63 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | ) 8 | 9 | type Logger struct { 10 | writer io.Writer 11 | verbosity int 12 | } 13 | 14 | func LoggerWithVerbosity(verbosity int) *Logger { 15 | if verbosity > 0 { 16 | return &Logger{os.Stderr, verbosity} 17 | } 18 | return nil 19 | } 20 | 21 | func (l *Logger) RequireVerbosity(verbosity int) *Logger { 22 | if l != nil && l.verbosity >= verbosity { 23 | return l 24 | } 25 | return nil 26 | } 27 | 28 | func (l *Logger) Printf(format string, a ...interface{}) { 29 | if l == nil { 30 | return 31 | } 32 | fmt.Fprintf(l.writer, string(append([]byte(format), 0x0a)), a...) 33 | } 34 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/url" 7 | "os" 8 | "regexp" 9 | 10 | flag "github.com/spf13/pflag" 11 | "github.com/zellyn/kooky" 12 | ) 13 | 14 | // Overridden by the linker at build time. 15 | var version string = "development" 16 | 17 | type options struct { 18 | browsers []string 19 | url *url.URL 20 | name string 21 | acceptMissing bool 22 | verbosity int 23 | } 24 | 25 | func main() { 26 | options := parseCommandLine() 27 | logger := LoggerWithVerbosity(options.verbosity) 28 | 29 | cookies := findCookies(options.url, options.name, options.browsers, logger) 30 | if len(cookies) == 0 { 31 | if !options.acceptMissing { 32 | os.Exit(1) 33 | } 34 | return 35 | } 36 | 37 | if len(options.name) == 0 { 38 | formatCookies(os.Stdout, cookies) 39 | } else { 40 | writeStrongestValue(os.Stdout, cookies) 41 | } 42 | fmt.Print("\n") 43 | } 44 | 45 | func parseCommandLine() (options options) { 46 | flagSet := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) 47 | 48 | usage := func(output io.Writer) { 49 | fmt.Fprintf(output, "usage: %s [options…] []\n\nThe following options are available:\n", os.Args[0]) 50 | fmt.Fprint(output, regexp.MustCompile(`--verbose level `).ReplaceAllString(flagSet.FlagUsages(), `--verbose[=level]`)) 51 | fmt.Fprintf(output, "\ncookies version %s (https://github.com/barnardb/cookies)\n", version) 52 | } 53 | 54 | fatalError := func(error ...interface{}) { 55 | fmt.Fprintln(os.Stderr, error...) 56 | usage(os.Stderr) 57 | os.Exit(2) 58 | } 59 | 60 | flagSet.Usage = func() { usage(os.Stdout) } 61 | 62 | flagSet.BoolVarP(&options.acceptMissing, "accept-missing", "a", false, "don't fail with exit status 1 when cookies aren't found") 63 | flagSet.StringArrayVarP(&options.browsers, "browser", "b", []string{"chrome", "chromium", "firefox", "safari"}, "browser to try extracting a cookie from, can be repeated to try multiple browsers") 64 | flagSet.CountVarP(&options.verbosity, "verbose", "v", "enables logging to stderr; specify it twice or provide `level` 2 to get per-cookie details (`-vv` or `--verbose=2`)") 65 | 66 | versionFlag := flagSet.Bool("version", false, "prints version information and exits") 67 | 68 | err := flagSet.Parse(os.Args[1:]) 69 | if err != nil { 70 | if err == flag.ErrHelp { 71 | os.Exit(0) 72 | } 73 | fatalError(err) 74 | } 75 | 76 | if *versionFlag { 77 | fmt.Println(version) 78 | os.Exit(0) 79 | } 80 | 81 | if flagSet.NArg() != 1 && flagSet.NArg() != 2 { 82 | fatalError("error: expected 1 or 2 arguments but got", flag.NArg()) 83 | } 84 | 85 | options.url, err = url.Parse(flagSet.Arg(0)) 86 | if err != nil { 87 | fatalError("error parsing URL:", err) 88 | } else if options.url.Scheme != "http" && options.url.Scheme != "https" { 89 | fatalError(fmt.Sprintf("URL scheme must be http or https, but got \"%s\"", options.url.Scheme)) 90 | } else if options.url.Host == "" { 91 | fatalError("URL host must be non-empty") 92 | } 93 | 94 | if flagSet.NArg() > 1 { 95 | options.name = flagSet.Arg(1) 96 | } 97 | 98 | return 99 | } 100 | 101 | func writeStrongestValue(w io.Writer, cookies []*kooky.Cookie) { 102 | strongest := cookies[0] 103 | for _, cookie := range cookies[1:] { 104 | if len(cookie.Domain) > len(strongest.Domain) || (cookie.Domain == strongest.Domain && 105 | (len(cookie.Path) > len(strongest.Path) || (cookie.Path == strongest.Path && 106 | cookie.Creation.After(strongest.Creation)))) { 107 | strongest = cookie 108 | } 109 | } 110 | fmt.Print(strongest.Value) 111 | } 112 | -------------------------------------------------------------------------------- /prepare-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | cd "$(dirname "$0")" 5 | 6 | [ $# -eq 1 ] && grep -qE '^\d+\.\d+\.\d+$' <<<"$1" || { 7 | echo "usage: $0 " >&2 8 | exit 2 9 | } 10 | 11 | set -x 12 | 13 | version="$1" 14 | tag="v${version}" 15 | 16 | export GOOS=darwin 17 | export GOARCH=amd64 18 | ./build.sh "${version}" 19 | archive="cookies_${tag}_${GOOS}_${GOARCH}.gz" 20 | gzip --stdout cookies >"${archive}" 21 | gh release create --draft --generate-notes "${tag}" "${archive}" 22 | --------------------------------------------------------------------------------