| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // cookieauth uses a “Netscape cookie file” to implement the GOAUTH protocol |
| // described in https://golang.org/issue/26232. |
| // It expects the location of the file as the first command-line argument. |
| // |
| // Example GOAUTH usage: |
| // export GOAUTH="cookieauth $(git config --get http.cookieFile)" |
| // |
| // See http://www.cookiecentral.com/faq/#3.5 for a description of the Netscape |
| // cookie file format. |
| package main |
| |
| import ( |
| "bufio" |
| "fmt" |
| "io" |
| "log" |
| "net/http" |
| "net/http/cookiejar" |
| "net/url" |
| "os" |
| "strconv" |
| "strings" |
| "time" |
| "unicode" |
| ) |
| |
| func main() { |
| if len(os.Args) < 2 { |
| fmt.Fprintf(os.Stderr, "usage: %s COOKIEFILE [URL]\n", os.Args[0]) |
| os.Exit(2) |
| } |
| |
| log.SetPrefix("cookieauth: ") |
| |
| f, err := os.Open(os.Args[1]) |
| if err != nil { |
| log.Fatalf("failed to read cookie file: %v\n", os.Args[1]) |
| os.Exit(1) |
| } |
| defer f.Close() |
| |
| var ( |
| targetURL *url.URL |
| targetURLs = map[string]*url.URL{} |
| ) |
| if len(os.Args) == 3 { |
| targetURL, err = url.ParseRequestURI(os.Args[2]) |
| if err != nil { |
| log.Fatalf("invalid request URI (%v): %q\n", err, os.Args[2]) |
| } |
| targetURLs[targetURL.String()] = targetURL |
| } else if len(os.Args) > 3 { |
| // Extra arguments were passed: maybe the protocol was expanded? |
| // We don't know how to interpret the request, so ignore it. |
| return |
| } |
| |
| entries, err := parseCookieFile(f.Name(), f) |
| if err != nil { |
| log.Fatalf("error reading cookie file: %v\n", f.Name()) |
| } |
| |
| jar, err := cookiejar.New(nil) |
| if err != nil { |
| log.Fatalf("failed to initialize cookie jar: %v\n", err) |
| } |
| |
| for _, e := range entries { |
| u := &url.URL{ |
| Scheme: "https", |
| Host: e.Host, |
| Path: e.Cookie.Path, |
| } |
| |
| if targetURL == nil { |
| targetURLs[u.String()] = u |
| } |
| |
| jar.SetCookies(u, []*http.Cookie{&e.Cookie}) |
| } |
| |
| for _, u := range targetURLs { |
| req := &http.Request{URL: u, Header: make(http.Header)} |
| for _, c := range jar.Cookies(req.URL) { |
| req.AddCookie(c) |
| } |
| fmt.Printf("%s\n\n", u) |
| req.Header.Write(os.Stdout) |
| fmt.Println() |
| } |
| } |
| |
| type Entry struct { |
| Host string |
| Cookie http.Cookie |
| } |
| |
| // parseCookieFile parses a Netscape cookie file as described in |
| // http://www.cookiecentral.com/faq/#3.5. |
| func parseCookieFile(name string, r io.Reader) ([]*Entry, error) { |
| var entries []*Entry |
| s := bufio.NewScanner(r) |
| line := 0 |
| for s.Scan() { |
| line++ |
| text := strings.TrimSpace(s.Text()) |
| if len(text) < 2 || (text[0] == '#' && unicode.IsSpace(rune(text[1]))) { |
| continue |
| } |
| |
| e, err := parseCookieLine(text) |
| if err != nil { |
| log.Printf("%s:%d: %v\n", name, line, err) |
| continue |
| } |
| entries = append(entries, e) |
| } |
| return entries, s.Err() |
| } |
| |
| func parseCookieLine(line string) (*Entry, error) { |
| f := strings.Fields(line) |
| if len(f) < 7 { |
| return nil, fmt.Errorf("found %d columns; want 7", len(f)) |
| } |
| |
| e := new(Entry) |
| c := &e.Cookie |
| |
| if domain := f[0]; strings.HasPrefix(domain, "#HttpOnly_") { |
| c.HttpOnly = true |
| e.Host = strings.TrimPrefix(domain[10:], ".") |
| } else { |
| e.Host = strings.TrimPrefix(domain, ".") |
| } |
| |
| isDomain, err := strconv.ParseBool(f[1]) |
| if err != nil { |
| return nil, fmt.Errorf("non-boolean domain flag: %v", err) |
| } |
| if isDomain { |
| c.Domain = e.Host |
| } |
| |
| c.Path = f[2] |
| |
| c.Secure, err = strconv.ParseBool(f[3]) |
| if err != nil { |
| return nil, fmt.Errorf("non-boolean secure flag: %v", err) |
| } |
| |
| expiration, err := strconv.ParseInt(f[4], 10, 64) |
| if err != nil { |
| return nil, fmt.Errorf("malformed expiration: %v", err) |
| } |
| c.Expires = time.Unix(expiration, 0) |
| |
| c.Name = f[5] |
| c.Value = f[6] |
| |
| return e, nil |
| } |