github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/cmd/auth/cookieauth/cookieauth.go (about)

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // cookieauth uses a “Netscape cookie file” to implement the GOAUTH protocol
     6  // described in https://golang.org/issue/26232.
     7  // It expects the location of the file as the first command-line argument.
     8  //
     9  // Example GOAUTH usage:
    10  //
    11  //	export GOAUTH="cookieauth $(git config --get http.cookieFile)"
    12  //
    13  // See http://www.cookiecentral.com/faq/#3.5 for a description of the Netscape
    14  // cookie file format.
    15  package main
    16  
    17  import (
    18  	"bufio"
    19  	"fmt"
    20  	"io"
    21  	"log"
    22  	"net/http"
    23  	"net/http/cookiejar"
    24  	"net/url"
    25  	"os"
    26  	"strconv"
    27  	"strings"
    28  	"time"
    29  	"unicode"
    30  )
    31  
    32  func main() {
    33  	if len(os.Args) < 2 {
    34  		fmt.Fprintf(os.Stderr, "usage: %s COOKIEFILE [URL]\n", os.Args[0])
    35  		os.Exit(2)
    36  	}
    37  
    38  	log.SetPrefix("cookieauth: ")
    39  
    40  	f, err := os.Open(os.Args[1])
    41  	if err != nil {
    42  		log.Fatalf("failed to read cookie file: %v\n", os.Args[1])
    43  	}
    44  	defer f.Close()
    45  
    46  	var (
    47  		targetURL  *url.URL
    48  		targetURLs = map[string]*url.URL{}
    49  	)
    50  	if len(os.Args) == 3 {
    51  		targetURL, err = url.ParseRequestURI(os.Args[2])
    52  		if err != nil {
    53  			log.Fatalf("invalid request URI (%v): %q\n", err, os.Args[2])
    54  		}
    55  		targetURLs[targetURL.String()] = targetURL
    56  	} else if len(os.Args) > 3 {
    57  		// Extra arguments were passed: maybe the protocol was expanded?
    58  		// We don't know how to interpret the request, so ignore it.
    59  		return
    60  	}
    61  
    62  	entries, err := parseCookieFile(f.Name(), f)
    63  	if err != nil {
    64  		log.Fatalf("error reading cookie file: %v\n", f.Name())
    65  	}
    66  
    67  	jar, err := cookiejar.New(nil)
    68  	if err != nil {
    69  		log.Fatalf("failed to initialize cookie jar: %v\n", err)
    70  	}
    71  
    72  	for _, e := range entries {
    73  		u := &url.URL{
    74  			Scheme: "https",
    75  			Host:   e.Host,
    76  			Path:   e.Cookie.Path,
    77  		}
    78  
    79  		if targetURL == nil {
    80  			targetURLs[u.String()] = u
    81  		}
    82  
    83  		jar.SetCookies(u, []*http.Cookie{&e.Cookie})
    84  	}
    85  
    86  	for _, u := range targetURLs {
    87  		req := &http.Request{URL: u, Header: make(http.Header)}
    88  		for _, c := range jar.Cookies(req.URL) {
    89  			req.AddCookie(c)
    90  		}
    91  		fmt.Printf("%s\n\n", u)
    92  		req.Header.Write(os.Stdout)
    93  		fmt.Println()
    94  	}
    95  }
    96  
    97  type Entry struct {
    98  	Host   string
    99  	Cookie http.Cookie
   100  }
   101  
   102  // parseCookieFile parses a Netscape cookie file as described in
   103  // http://www.cookiecentral.com/faq/#3.5.
   104  func parseCookieFile(name string, r io.Reader) ([]*Entry, error) {
   105  	var entries []*Entry
   106  	s := bufio.NewScanner(r)
   107  	line := 0
   108  	for s.Scan() {
   109  		line++
   110  		text := strings.TrimSpace(s.Text())
   111  		if len(text) < 2 || (text[0] == '#' && unicode.IsSpace(rune(text[1]))) {
   112  			continue
   113  		}
   114  
   115  		e, err := parseCookieLine(text)
   116  		if err != nil {
   117  			log.Printf("%s:%d: %v\n", name, line, err)
   118  			continue
   119  		}
   120  		entries = append(entries, e)
   121  	}
   122  	return entries, s.Err()
   123  }
   124  
   125  func parseCookieLine(line string) (*Entry, error) {
   126  	f := strings.Fields(line)
   127  	if len(f) < 7 {
   128  		return nil, fmt.Errorf("found %d columns; want 7", len(f))
   129  	}
   130  
   131  	e := new(Entry)
   132  	c := &e.Cookie
   133  
   134  	if domain := f[0]; strings.HasPrefix(domain, "#HttpOnly_") {
   135  		c.HttpOnly = true
   136  		e.Host = strings.TrimPrefix(domain[10:], ".")
   137  	} else {
   138  		e.Host = strings.TrimPrefix(domain, ".")
   139  	}
   140  
   141  	isDomain, err := strconv.ParseBool(f[1])
   142  	if err != nil {
   143  		return nil, fmt.Errorf("non-boolean domain flag: %v", err)
   144  	}
   145  	if isDomain {
   146  		c.Domain = e.Host
   147  	}
   148  
   149  	c.Path = f[2]
   150  
   151  	c.Secure, err = strconv.ParseBool(f[3])
   152  	if err != nil {
   153  		return nil, fmt.Errorf("non-boolean secure flag: %v", err)
   154  	}
   155  
   156  	expiration, err := strconv.ParseInt(f[4], 10, 64)
   157  	if err != nil {
   158  		return nil, fmt.Errorf("malformed expiration: %v", err)
   159  	}
   160  	c.Expires = time.Unix(expiration, 0)
   161  
   162  	c.Name = f[5]
   163  	c.Value = f[6]
   164  
   165  	return e, nil
   166  }