167 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			167 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
// 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
 | 
						|
}
 |