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