// 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 }