<commit_before>package realtime

import (
	"fmt"
	"testing"
	"time"
)

func TestStockRealTime(*testing.T) {
	r := &StockRealTime{
		No: "2618",
		//Date:      time.Now(),
		Date:     time.Date(2015, 4, 1, 0, 0, 0, 0, time.Local),
		Exchange: "tse",
	}

	r.URL()
	v, _ := r.Get()
	fmt.Println(v.BestAskPrice)
	fmt.Println(v.BestBidPrice)
	fmt.Println(v.BestAskVolume)
	fmt.Println(v.BestBidVolume)
	fmt.Println(v)
	fmt.Println("UnixMapData", r.UnixMapData)
}

func TestStockRealTimeOTC(*testing.T) {
	r := &StockRealTime{
		No: "8446",
		//Date:      time.Now(),
		Date:     time.Date(2015, 4, 1, 0, 0, 0, 0, time.Local),
		Exchange: "otc",
	}

	r.URL()
	v, _ := r.Get()
	fmt.Println(v.BestAskPrice)
	fmt.Println(v.BestBidPrice)
	fmt.Println(v.BestAskVolume)
	fmt.Println(v.BestBidVolume)
	fmt.Println(v)
	fmt.Println("UnixMapData", r.UnixMapData)
}

func BenchmarkGet(b *testing.B) {
	r := &StockRealTime{
		No: "2618",
		//Date:      time.Now(),
		Date:     time.Date(2015, 4, 1, 0, 0, 0, 0, time.Local),
		Exchange: "tse",
	}

	for i := 0; i <= b.N; i++ {
		r.Get()
	}
}

func ExampleStockRealTime() {
	r := StockRealTime{
		No:       "2618",
		Date:     time.Date(2014, 12, 26, 0, 0, 0, 0, time.Local),
		Exchange: "tse",
	}

	data, _ := r.Get()
	fmt.Printf("%v", data)
}
<commit_msg>Add some example note.<commit_after>package realtime

import (
	"fmt"
	"testing"
	"time"
)

func TestStockRealTime(*testing.T) {
	r := &StockRealTime{
		No: "2618",
		//Date:      time.Now(),
		Date:     time.Date(2015, 4, 1, 0, 0, 0, 0, time.Local),
		Exchange: "tse",
	}

	r.URL()
	v, _ := r.Get()
	fmt.Println(v.BestAskPrice)
	fmt.Println(v.BestBidPrice)
	fmt.Println(v.BestAskVolume)
	fmt.Println(v.BestBidVolume)
	fmt.Println(v)
	fmt.Println("UnixMapData", r.UnixMapData)
}

func TestStockRealTimeOTC(*testing.T) {
	r := &StockRealTime{
		No: "8446",
		//Date:      time.Now(),
		Date:     time.Date(2015, 4, 1, 0, 0, 0, 0, time.Local),
		Exchange: "otc",
	}

	r.URL()
	v, _ := r.Get()
	fmt.Println(v.BestAskPrice)
	fmt.Println(v.BestBidPrice)
	fmt.Println(v.BestAskVolume)
	fmt.Println(v.BestBidVolume)
	fmt.Println(v)
	fmt.Println("UnixMapData", r.UnixMapData)
}

func BenchmarkGet(b *testing.B) {
	r := &StockRealTime{
		No: "2618",
		//Date:      time.Now(),
		Date:     time.Date(2015, 4, 1, 0, 0, 0, 0, time.Local),
		Exchange: "tse",
	}

	for i := 0; i <= b.N; i++ {
		r.Get()
	}
}

// 擷取 長榮航(2618) 上市即時盤股價資訊
func ExampleStockRealTime_Get_twse() {
	r := StockRealTime{
		No:       "2618",
		Date:     time.Date(2015, 4, 1, 0, 0, 0, 0, time.Local),
		Exchange: "tse",
	}

	data, _ := r.Get()
	fmt.Printf("%v", data)
}

// 擷取 華研(8446) 上櫃即時盤股價資訊
func ExampleStockRealTime_Get_otc() {
	r := StockRealTime{
		No:       "8446",
		Date:     time.Date(2015, 4, 1, 0, 0, 0, 0, time.Local),
		Exchange: "otc",
	}

	data, _ := r.Get()
	fmt.Printf("%v", data)
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"testing"

	"google.golang.org/grpc"

	"github.com/dgraph-io/dgraph/client"
	"github.com/dgraph-io/dgraph/query/graph"
	bolt "github.com/johnnadratowski/golang-neo4j-bolt-driver"
)

func BenchmarkDgraphQuery(b *testing.B) {
	queries := []struct {
		name  string
		query string
	}{
		{
			"Simple", `{
					me(id: m.06pj8) {
						type.object.name.en
						film.director.film  {
							film.film.genre {
								type.object.name.en
							}
							type.object.name.en
							film.film.initial_release_date
						}
					}
				}`,
		},
		{
			"GetStarted1", `{
				director(allof("type.object.name.en", "steven spielberg")) {
					type.object.name.en
					film.director.film (order: film.film.initial_release_date) {
						type.object.name.en
						film.film.initial_release_date
					}
				}
			}`,
		},
		{
			"GetStarted2", `{
				director(allof("type.object.name.en", "steven spielberg")) {
						type.object.name.en
						film.director.film (order: film.film.initial_release_date) @filter(geq("film.film.initial_release_date", "1984-08")) {
							type.object.name.en
							film.film.initial_release_date
						}
					}
				}`,
		}, {
			"GetStarted3", `{
					director(allof("type.object.name.en", "steven spielberg")) {
						type.object.name.en
						film.director.film (order: film.film.initial_release_date) @filter(geq("film.film.initial_release_date", "1990") && leq("film.film.initial_release_date", "2000")) {
							type.object.name.en
							film.film.initial_release_date
						}
					}
				}`,
		},
	}

	for _, q := range queries {
		b.Run(q.name, func(b *testing.B) {

			conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
			if err != nil {
				b.Fatal("DialTCPConnection")
			}
			defer conn.Close()
			c := graph.NewDgraphClient(conn)
			b.ResetTimer()

			for i := 0; i < b.N; i++ {
				_, err := c.Run(context.Background(), &graph.Request{Query: q.query})
				if err != nil {
					b.Fatalf("Error in getting response from server, %s", err)
				}
			}
		})
	}

	for _, q := range queries {
		b.Run(fmt.Sprintf("%v-parallel", q.name), func(b *testing.B) {

			b.RunParallel(func(pb *testing.PB) {
				conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
				if err != nil {
					b.Fatal("DialTCPConnection")
				}
				defer conn.Close()
				c := graph.NewDgraphClient(conn)
				b.ResetTimer()

				for pb.Next() {
					_, err = c.Run(context.Background(), &graph.Request{Query: q.query})
					if err != nil {
						b.Fatal("Error in query", err)
					}
				}
			})
		})
	}
}

func BenchmarkNeoQuery(b *testing.B) {
	queries := []struct {
		name  string
		query string
	}{
		{"Simple", `MATCH (d: Director) - [r:FILMS] -> (f:Film) - [r2:GENRE] -> (g:Genre) WHERE d.directorId="m.06pj8" RETURN d, f, g`},
		{"GetStarted1", `MATCH (d: Director) - [r:FILMS] -> (f:Film) WHERE d.name CONTAINS "Steven Spielberg" WITH d,f ORDER BY f.release_date ASC RETURN d, f`},
		{"GetStarted2", `MATCH (d: Director) - [r:FILMS] -> (f:Film) WHERE d.name CONTAINS "Steven Spielberg" AND f.release_date >= "1984-08" WITH d,f ORDER BY f.release_date ASC RETURN d, f`},
		{"GetStarted3", `MATCH (d: Director) - [r:FILMS] -> (f:Film) WHERE d.name CONTAINS "Steven Spielberg" AND f.release_date >= "1984" AND f.release_date <= "2000" WITH d,f ORDER BY f.release_date ASC RETURN d, f`},
	}

	for _, q := range queries {
		b.Run(q.name, func(b *testing.B) {
			driver := bolt.NewDriver()
			conn, err := driver.OpenNeo("bolt://localhost:7687")
			if err != nil {
				b.Fatal(err)
			}
			defer conn.Close()
			b.ResetTimer()

			for i := 0; i < b.N; i++ {
				_, _, _, err := conn.QueryNeoAll(q.query, nil)
				if err != nil {
					b.Fatal(err)
				}
			}
		})
	}

	for _, q := range queries {
		b.Run(fmt.Sprintf("%v-parallel", q.name), func(b *testing.B) {
			b.RunParallel(func(pb *testing.PB) {
				conn, err := driver.OpenNeo("bolt://localhost:7687")
				if err != nil {
					b.Fatal(err)
				}
				defer conn.Close()
				b.ResetTimer()

				for pb.Next() {
					_, _, _, err = conn.QueryNeoAll(q.query, nil)
					if err != nil {
						b.Fatal(err)
					}
				}
			})
		})
	}
}

func BenchmarkDgraphSimpleQueryAndMutation(b *testing.B) {
	conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
	if err != nil {
		b.Fatal("DialTCPConnection")
	}
	defer conn.Close()

	c := graph.NewDgraphClient(conn)
	req := client.Req{}
	req.SetQuery(`	{
					me(id: m.06pj8) {
						type.object.name.en
						film.director.film  {
						film.film.genre {
							type.object.name.en
						}
						type.object.name.en
						film.film.initial_release_date
						}
					}
				}
    `)
	req.AddMutation(graph.NQuad{
		Subject:     "m.0322yj",
		Predicate:   "type.object.name.en",
		ObjectValue: &graph.Value{&graph.Value_StrVal{"Terminal"}},
	}, client.SET)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err := c.Run(context.Background(), req.Request())
		if err != nil {
			b.Fatal("Error in query", err)
		}
	}
}

func BenchmarkNeo4jSimpleQueryAndMutation(b *testing.B) {
	query := `MATCH (d: Director) - [r:FILMS] -> (f:Film) - [r2:GENRE] -> (g:Genre) WHERE d.directorId="m.06pj8" RETURN d, f, g`
	mutation := `MATCH (n:Director { directorId: {id} }) SET n.name = {name}`
	params := map[string]interface{}{"id": "m.0322yj", "name": "Terminal"}
	driver := bolt.NewDriver()
	conn, err := driver.OpenNeo("bolt://localhost:7687")
	if err != nil {
		b.Fatal(err)
	}
	defer conn.Close()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _, _, err = conn.QueryNeoAll(query, nil)
		if err != nil {
			b.Fatal(err)
		}
		_, err = conn.ExecNeo(mutation, params)
		if err != nil {
			b.Fatal(err)
		}
	}
}

func BenchmarkDgraphSimpleQueryAndMutationParallel(b *testing.B) {
	conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
	if err != nil {
		b.Fatal("DialTCPConnection")
	}
	defer conn.Close()
	c := graph.NewDgraphClient(conn)
	req := client.Req{}
	req.SetQuery(`	{
					me(id: m.06pj8) {
						type.object.name.en
						film.director.film  {
						film.film.genre {
							type.object.name.en
						}
						type.object.name.en
						film.film.initial_release_date
						}
					}
				}
    `)
	req.AddMutation(graph.NQuad{
		Subject:     "m.0322yj",
		Predicate:   "type.object.name.en",
		ObjectValue: &graph.Value{&graph.Value_StrVal{"Terminal"}},
	}, client.SET)

	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			_, err := c.Run(context.Background(), req.Request())
			if err != nil {
				b.Fatal("Error in query", err)
			}
		}
	})
}

func BenchmarkNeo4jSimpleQueryAndMutationParallel(b *testing.B) {
	driver := bolt.NewDriver()
	query := `MATCH (d: Director) - [r:FILMS] -> (f:Film) - [r2:GENRE] -> (g:Genre) WHERE d.directorId="m.06pj8" RETURN d, f, g`
	mutation := `MATCH (n:Director { directorId: {id} }) SET n.name = {name}`
	params := map[string]interface{}{"id": "m.0322yj", "name": "Terminal"}
	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			conn, err := driver.OpenNeo("bolt://localhost:7687")
			if err != nil {
				b.Fatal(err)
			}
			defer conn.Close()

			_, _, _, err = conn.QueryNeoAll(query, nil)
			if err != nil {
				b.Fatal(err)
			}
			_, err = conn.ExecNeo(mutation, params)
			if err != nil {
				b.Fatal(err)
			}
		}
	})
}
<commit_msg>Simplify QueryAndMutation<commit_after>package main

import (
	"context"
	"fmt"
	"testing"

	"google.golang.org/grpc"

	"github.com/dgraph-io/dgraph/client"
	"github.com/dgraph-io/dgraph/query/graph"
	bolt "github.com/johnnadratowski/golang-neo4j-bolt-driver"
)

func BenchmarkDgraphQuery(b *testing.B) {
	queries := []struct {
		name  string
		query string
	}{
		{
			"Simple", `{
					me(id: m.06pj8) {
						type.object.name.en
						film.director.film  {
							film.film.genre {
								type.object.name.en
							}
							type.object.name.en
							film.film.initial_release_date
						}
					}
				}`,
		},
		{
			"GetStarted1", `{
				director(allof("type.object.name.en", "steven spielberg")) {
					type.object.name.en
					film.director.film (order: film.film.initial_release_date) {
						type.object.name.en
						film.film.initial_release_date
					}
				}
			}`,
		},
		{
			"GetStarted2", `{
				director(allof("type.object.name.en", "steven spielberg")) {
						type.object.name.en
						film.director.film (order: film.film.initial_release_date) @filter(geq("film.film.initial_release_date", "1984-08")) {
							type.object.name.en
							film.film.initial_release_date
						}
					}
				}`,
		}, {
			"GetStarted3", `{
					director(allof("type.object.name.en", "steven spielberg")) {
						type.object.name.en
						film.director.film (order: film.film.initial_release_date) @filter(geq("film.film.initial_release_date", "1990") && leq("film.film.initial_release_date", "2000")) {
							type.object.name.en
							film.film.initial_release_date
						}
					}
				}`,
		},
	}

	for _, q := range queries {
		b.Run(q.name, func(b *testing.B) {

			conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
			if err != nil {
				b.Fatal("DialTCPConnection")
			}
			defer conn.Close()
			c := graph.NewDgraphClient(conn)
			b.ResetTimer()

			for i := 0; i < b.N; i++ {
				_, err := c.Run(context.Background(), &graph.Request{Query: q.query})
				if err != nil {
					b.Fatalf("Error in getting response from server, %s", err)
				}
			}
		})
	}

	for _, q := range queries {
		b.Run(fmt.Sprintf("%v-parallel", q.name), func(b *testing.B) {

			b.RunParallel(func(pb *testing.PB) {
				conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
				if err != nil {
					b.Fatal("DialTCPConnection")
				}
				c := graph.NewDgraphClient(conn)
				b.ResetTimer()
				for pb.Next() {
					_, err = c.Run(context.Background(), &graph.Request{Query: q.query})
					if err != nil {
						b.Fatal("Error in query", err)
					}
				}
			})
		})
	}
}

func BenchmarkNeoQuery(b *testing.B) {
	queries := []struct {
		name  string
		query string
	}{
		{"Simple", `MATCH (d: Director) - [r:FILMS] -> (f:Film) - [r2:GENRE] -> (g:Genre) WHERE d.directorId="m.06pj8" RETURN d, f, g`},
		{"GetStarted1", `MATCH (d: Director) - [r:FILMS] -> (f:Film) WHERE d.name CONTAINS "Steven Spielberg" WITH d,f ORDER BY f.release_date ASC RETURN d, f`},
		{"GetStarted2", `MATCH (d: Director) - [r:FILMS] -> (f:Film) WHERE d.name CONTAINS "Steven Spielberg" AND f.release_date >= "1984-08" WITH d,f ORDER BY f.release_date ASC RETURN d, f`},
		{"GetStarted3", `MATCH (d: Director) - [r:FILMS] -> (f:Film) WHERE d.name CONTAINS "Steven Spielberg" AND f.release_date >= "1984" AND f.release_date <= "2000" WITH d,f ORDER BY f.release_date ASC RETURN d, f`},
	}

	driver := bolt.NewDriver()
	for _, q := range queries {
		b.Run(q.name, func(b *testing.B) {
			conn, err := driver.OpenNeo("bolt://localhost:7687")
			if err != nil {
				b.Fatal(err)
			}
			defer conn.Close()
			b.ResetTimer()

			for i := 0; i < b.N; i++ {
				_, _, _, err := conn.QueryNeoAll(q.query, nil)
				if err != nil {
					b.Fatal(err)
				}
			}
		})
	}

	for _, q := range queries {
		b.Run(fmt.Sprintf("%v-parallel", q.name), func(b *testing.B) {
			b.RunParallel(func(pb *testing.PB) {
				conn, err := driver.OpenNeo("bolt://localhost:7687")
				if err != nil {
					b.Fatal(err)
				}
				defer conn.Close()
				b.ResetTimer()

				for pb.Next() {
					_, _, _, err = conn.QueryNeoAll(q.query, nil)
					if err != nil {
						b.Fatal(err)
					}
				}
			})
		})
	}
}

func BenchmarkDgraphQueryAndMutation(b *testing.B) {
	req := client.Req{}
	req.SetQuery(`	{
					me(id: m.06pj8) {
						type.object.name.en
						film.director.film  {
						film.film.genre {
							type.object.name.en
						}
						type.object.name.en
						film.film.initial_release_date
						}
					}
				}
    `)
	req.AddMutation(graph.NQuad{
		Subject:     "m.0322yj",
		Predicate:   "type.object.name.en",
		ObjectValue: &graph.Value{&graph.Value_StrVal{"Terminal"}},
	}, client.SET)

	b.Run("QueryAndMutation", func(b *testing.B) {
		conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
		if err != nil {
			b.Fatal("DialTCPConnection")
		}
		defer conn.Close()
		c := graph.NewDgraphClient(conn)
		b.ResetTimer()

		for i := 0; i < b.N; i++ {
			_, err := c.Run(context.Background(), req.Request())
			if err != nil {
				b.Fatalf("Error in getting response from server, %s", err)
			}
		}
	})

	b.Run("QueryAndMutation-parallel", func(b *testing.B) {
		b.RunParallel(func(pb *testing.PB) {
			conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
			if err != nil {
				b.Fatal("DialTCPConnection")
			}
			c := graph.NewDgraphClient(conn)
			b.ResetTimer()

			for pb.Next() {
				_, err = c.Run(context.Background(), req.Request())
				if err != nil {
					b.Fatal("Error in query", err)
				}
			}
		})
	})
}

func BenchmarkNeo4jQueryAndMutation(b *testing.B) {
	query := `MATCH (d: Director) - [r:FILMS] -> (f:Film) - [r2:GENRE] -> (g:Genre) WHERE d.directorId="m.06pj8" RETURN d, f, g`
	mutation := `MATCH (n:Director { directorId: {id} }) SET n.name = {name}`
	params := map[string]interface{}{"id": "m.0322yj", "name": "Terminal"}
	driver := bolt.NewDriver()

	b.Run("QueryAndMutation", func(b *testing.B) {
		conn, err := driver.OpenNeo("bolt://localhost:7687")
		if err != nil {
			b.Fatal(err)
		}
		defer conn.Close()
		b.ResetTimer()

		for i := 0; i < b.N; i++ {
			_, _, _, err = conn.QueryNeoAll(query, nil)
			if err != nil {
				b.Fatal(err)
			}
			_, err = conn.ExecNeo(mutation, params)
			if err != nil {
				b.Fatal(err)
			}
		}
	})

	b.Run("QueryAndMutation-parallel", func(b *testing.B) {
		b.RunParallel(func(pb *testing.PB) {
			conn, err := driver.OpenNeo("bolt://localhost:7687")
			if err != nil {
				b.Fatal(err)
			}
			defer conn.Close()
			b.ResetTimer()

			for pb.Next() {
				_, _, _, err = conn.QueryNeoAll(query, nil)
				if err != nil {
					b.Fatal(err)
				}
				_, err = conn.ExecNeo(mutation, params)
				if err != nil {
					b.Fatal(err)
				}
			}
		})
	})
}
<|endoftext|>


<commit_before>package query

import (
	"fmt"
	"strings"
	"unicode"
)

// TokenType represents a Token's type.
type TokenType int8

const (
	// Unknown represents an unknown Token type.
	Unknown TokenType = iota
	// Subquery represents a subquery in this query string
	Subquery
	// Select represents the SELECT clause.
	Select
	// From represents the FROM clause.
	From
	// Where represents the WHERE clause.
	Where
	// Or represents the OR keyword for conditional disjunction.
	Or
	// And represents the AND keyword for conditonal conjunction.
	And
	// Not represents the NOT keyword for conditional negation.
	Not
	// In represents the IN keyword for list-based comparisons.
	In
	// Is represents the IS keyword for file type comparisons.
	Is
	// Like represents the LIKE keyword for string comparisons.
	Like
	// RLike represents the RLIKE keyword for string regexp comparisons.
	RLike
	// Identifier represents the value for each Query.
	Identifier
	// OpenParen represents an open parenthesis.
	OpenParen
	// CloseParen represents a closed parenthesis.
	CloseParen
	// Comma represents a comma.
	Comma
	// Minus represents the `-` operator for directory exclusion.
	Minus
	// Equals represents the `=` comparator for string/numeric comparisons.
	Equals
	// NotEquals represents the `<>` comparator for string/numeric comparisons.
	NotEquals
	// GreaterThanEquals represents the `>=` comparator for numeric comparisons.
	GreaterThanEquals
	// GreaterThan represents the `>` comparator for numeric comparisons.
	GreaterThan
	// LessThanEquals represents the `<=` comparator for numeric comparisons.
	LessThanEquals
	// LessThan represents the `<` comparator for numeric comparisons.
	LessThan
)

func (t TokenType) String() string {
	switch t {
	case Subquery:
		return "subquery"
	case Select:
		return "select"
	case From:
		return "from"
	case Where:
		return "where"
	case Or:
		return "or"
	case And:
		return "and"
	case Not:
		return "not"
	case In:
		return "in"
	case Is:
		return "is"
	case Like:
		return "like"
	case RLike:
		return "RLike"
	case Identifier:
		return "identifier"
	case OpenParen:
		return "open-parentheses"
	case CloseParen:
		return "close-parentheses"
	case Comma:
		return "comma"
	case Minus:
		return "minus"
	case Equals:
		return "equal"
	case NotEquals:
		return "not-equal"
	case GreaterThanEquals:
		return "greater-than-or-equal"
	case GreaterThan:
		return "greater-than"
	case LessThanEquals:
		return "less-than-or-equal"
	case LessThan:
		return "less-than"
	default:
		return "unknown"
	}
}

// Token represents a single token.
type Token struct {
	Type     TokenType
	Raw      string
	Previous *Token
}

func (t Token) String() string {
	return fmt.Sprintf("{type: %s, raw: \"%s\"}", t.Type.String(), t.Raw)
}

// Tokenizer represents a token worker.
type Tokenizer struct {
	input    []rune
	previous *Token
}

// NewTokenizer initializes a new Tokenizer.
func NewTokenizer(input string) *Tokenizer {
	return &Tokenizer{input: []rune(input), previous: nil}
}

// All parses all tokens for this Tokenizer.
func (t *Tokenizer) All() []Token {
	tokens := []Token{}
	for tok := t.Next(); tok != nil; tok = t.Next() {
		tokens = append(tokens, *tok)
	}
	return tokens
}

// Next finds and returns the next Token in the input string.
func (t *Tokenizer) Next() *Token {
	for unicode.IsSpace(t.current()) {
		t.input = t.input[1:]
	}

	current := t.current()
	if current == -1 {
		return nil
	}

	switch current {
	case '(':
		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: OpenParen, Raw: "("})

	case ')':
		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: CloseParen, Raw: ")"})

	case ',':
		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: Comma, Raw: ","})

	case '-':
		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: Minus, Raw: "-"})

	case '=':
		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: Equals, Raw: "="})

	case '>':
		if t.getRuneAtIndex(1) == '=' {
			t.input = t.input[2:]
			return t.setNextToken(&Token{Type: GreaterThanEquals, Raw: ">="})
		}

		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: GreaterThan, Raw: ">"})

	case '<':
		if t.getRuneAtIndex(1) == '=' {
			t.input = t.input[2:]
			return t.setNextToken(&Token{Type: LessThanEquals, Raw: ">="})
		}

		if t.getRuneAtIndex(1) == '>' {
			t.input = t.input[2:]
			return t.setNextToken(&Token{Type: NotEquals, Raw: "<>"})
		}

		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: LessThan, Raw: "<"})
	}

	if !t.currentIs(-1, ',', '\'', '"', '`', '(', ')') {
		word := t.readWord()
		tok := &Token{Raw: word}

		switch strings.ToUpper(word) {
		case "SELECT":
			tok.Type = Select
		case "FROM":
			tok.Type = From
		case "WHERE":
			tok.Type = Where
		case "OR":
			tok.Type = Or
		case "AND":
			tok.Type = And
		case "NOT":
			tok.Type = Not
		case "IN":
			tok.Type = In
		case "IS":
			tok.Type = Is
		case "LIKE":
			tok.Type = Like
		case "RLIKE":
			tok.Type = RLike
		default:
			tok.Type = Identifier
		}

		// If the previous token was a `(`, and the one before was IN, then
		// this must be a subquery. Keep reading until we reach a `)`.
		if t.previous != nil && t.previous.Type == OpenParen &&
			t.previous.Previous != nil && t.previous.Previous.Type == In {
			tok.Type = Subquery
			tok.Raw = t.readQuery(word)
		}

		return t.setNextToken(tok)
	}

	tok := &Token{Type: Unknown, Raw: string(current)}

	// If the current rune is a single/double quote or backtick, we want to keep
	// reading until we reach the closing symbol.
	if t.currentIs('\'', '"', '`') {
		t.input = t.input[1:]
		tok.Raw = t.readUntil(t.readWord(), current)
		tok.Type = Identifier
	}

	t.input = t.input[1:]
	return t.setNextToken(tok)
}

// Return the rune at the ith index of the input.
func (t *Tokenizer) getRuneAtIndex(i int) rune {
	if len(t.input) == i {
		return -1
	}

	return t.input[i]
}

// Return the rune at the 0th index of the input.
func (t *Tokenizer) current() rune {
	return t.getRuneAtIndex(0)
}

// Returns true iff the input's current rune (at index 0) is in rs.
func (t *Tokenizer) currentIs(rs ...rune) bool {
	for _, r := range rs {
		if r == t.current() {
			return true
		}
	}
	return false
}

// Update the previous Token for both the Tokenizer and the supplied Token.
func (t *Tokenizer) setNextToken(token *Token) *Token {
	token.Previous, t.previous = t.previous, token
	return token
}

// Read a single word from the input. Returns when the next rune is any
// of: -1, " ", comma, single/double quote, backtick, or parenthesis.
func (t *Tokenizer) readWord() string {
	word := []rune{}

	for {
		if unicode.IsSpace(t.current()) ||
			t.currentIs(-1, ',', '\'', '"', '`', '(', ')') {
			return string(word)
		}

		word = append(word, t.current())
		t.input = t.input[1:]
	}
}

// Read a full string until we reaching a closing parentheses. Maintains a
// count of opening parens to ensure we don't return early.
func (t *Tokenizer) readQuery(start string) string {
	query := fmt.Sprintf("%s ", start)

	var count = 1
	for count > 0 {
		for unicode.IsSpace(t.current()) {
			t.input = t.input[1:]
		}

		word := fmt.Sprintf("%s ", t.readWord())

		if t.current() == '(' {
			count++
			word = "("
		} else if t.current() == ')' {
			count--
			word = ")"
		}

		if t.current() == -1 || count <= 0 {
			break
		}

		query += word
		t.input = t.input[1:]
	}

	return query
}

// Read the input starting at start, until reaching a rune in runes.
func (t *Tokenizer) readUntil(start string, runes ...rune) string {
	word := start
	for !t.currentIs(runes...) {
		for unicode.IsSpace(t.current()) {
			t.input = t.input[1:]
		}
		word = fmt.Sprintf("%s %s", word, t.readWord())
	}
	return word
}
<commit_msg>Fix bug with missing ending identifier in parsed subqueries<commit_after>package query

import (
	"fmt"
	"strings"
	"unicode"
)

// TokenType represents a Token's type.
type TokenType int8

const (
	// Unknown represents an unknown Token type.
	Unknown TokenType = iota
	// Subquery represents a subquery in this query string
	Subquery
	// Select represents the SELECT clause.
	Select
	// From represents the FROM clause.
	From
	// Where represents the WHERE clause.
	Where
	// Or represents the OR keyword for conditional disjunction.
	Or
	// And represents the AND keyword for conditonal conjunction.
	And
	// Not represents the NOT keyword for conditional negation.
	Not
	// In represents the IN keyword for list-based comparisons.
	In
	// Is represents the IS keyword for file type comparisons.
	Is
	// Like represents the LIKE keyword for string comparisons.
	Like
	// RLike represents the RLIKE keyword for string regexp comparisons.
	RLike
	// Identifier represents the value for each Query.
	Identifier
	// OpenParen represents an open parenthesis.
	OpenParen
	// CloseParen represents a closed parenthesis.
	CloseParen
	// Comma represents a comma.
	Comma
	// Minus represents the `-` operator for directory exclusion.
	Minus
	// Equals represents the `=` comparator for string/numeric comparisons.
	Equals
	// NotEquals represents the `<>` comparator for string/numeric comparisons.
	NotEquals
	// GreaterThanEquals represents the `>=` comparator for numeric comparisons.
	GreaterThanEquals
	// GreaterThan represents the `>` comparator for numeric comparisons.
	GreaterThan
	// LessThanEquals represents the `<=` comparator for numeric comparisons.
	LessThanEquals
	// LessThan represents the `<` comparator for numeric comparisons.
	LessThan
)

func (t TokenType) String() string {
	switch t {
	case Subquery:
		return "subquery"
	case Select:
		return "select"
	case From:
		return "from"
	case Where:
		return "where"
	case Or:
		return "or"
	case And:
		return "and"
	case Not:
		return "not"
	case In:
		return "in"
	case Is:
		return "is"
	case Like:
		return "like"
	case RLike:
		return "RLike"
	case Identifier:
		return "identifier"
	case OpenParen:
		return "open-parentheses"
	case CloseParen:
		return "close-parentheses"
	case Comma:
		return "comma"
	case Minus:
		return "minus"
	case Equals:
		return "equal"
	case NotEquals:
		return "not-equal"
	case GreaterThanEquals:
		return "greater-than-or-equal"
	case GreaterThan:
		return "greater-than"
	case LessThanEquals:
		return "less-than-or-equal"
	case LessThan:
		return "less-than"
	default:
		return "unknown"
	}
}

// Token represents a single token.
type Token struct {
	Type     TokenType
	Raw      string
	Previous *Token
}

func (t Token) String() string {
	return fmt.Sprintf("{type: %s, raw: \"%s\"}", t.Type.String(), t.Raw)
}

// Tokenizer represents a token worker.
type Tokenizer struct {
	input    []rune
	previous *Token
}

// NewTokenizer initializes a new Tokenizer.
func NewTokenizer(input string) *Tokenizer {
	return &Tokenizer{input: []rune(input), previous: nil}
}

// All parses all tokens for this Tokenizer.
func (t *Tokenizer) All() []Token {
	tokens := []Token{}
	for tok := t.Next(); tok != nil; tok = t.Next() {
		tokens = append(tokens, *tok)
	}
	return tokens
}

// Next finds and returns the next Token in the input string.
func (t *Tokenizer) Next() *Token {
	for unicode.IsSpace(t.current()) {
		t.input = t.input[1:]
	}

	current := t.current()
	if current == -1 {
		return nil
	}

	switch current {
	case '(':
		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: OpenParen, Raw: "("})

	case ')':
		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: CloseParen, Raw: ")"})

	case ',':
		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: Comma, Raw: ","})

	case '-':
		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: Minus, Raw: "-"})

	case '=':
		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: Equals, Raw: "="})

	case '>':
		if t.getRuneAtIndex(1) == '=' {
			t.input = t.input[2:]
			return t.setNextToken(&Token{Type: GreaterThanEquals, Raw: ">="})
		}

		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: GreaterThan, Raw: ">"})

	case '<':
		if t.getRuneAtIndex(1) == '=' {
			t.input = t.input[2:]
			return t.setNextToken(&Token{Type: LessThanEquals, Raw: ">="})
		}

		if t.getRuneAtIndex(1) == '>' {
			t.input = t.input[2:]
			return t.setNextToken(&Token{Type: NotEquals, Raw: "<>"})
		}

		t.input = t.input[1:]
		return t.setNextToken(&Token{Type: LessThan, Raw: "<"})
	}

	if !t.currentIs(-1, ',', '\'', '"', '`', '(', ')') {
		word := t.readWord()
		tok := &Token{Raw: word}

		switch strings.ToUpper(word) {
		case "SELECT":
			tok.Type = Select
		case "FROM":
			tok.Type = From
		case "WHERE":
			tok.Type = Where
		case "OR":
			tok.Type = Or
		case "AND":
			tok.Type = And
		case "NOT":
			tok.Type = Not
		case "IN":
			tok.Type = In
		case "IS":
			tok.Type = Is
		case "LIKE":
			tok.Type = Like
		case "RLIKE":
			tok.Type = RLike
		default:
			tok.Type = Identifier
		}

		// If the previous token was a `(`, and the one before was IN, then
		// this must be a subquery. Keep reading until we reach a `)`.
		if t.previous != nil && t.previous.Type == OpenParen &&
			t.previous.Previous != nil && t.previous.Previous.Type == In {
			tok.Type = Subquery
			tok.Raw = word + t.readQuery()
		}

		return t.setNextToken(tok)
	}

	tok := &Token{Type: Unknown, Raw: string(current)}

	// If the current rune is a single/double quote or backtick, we want to keep
	// reading until we reach the closing symbol.
	if t.currentIs('\'', '"', '`') {
		t.input = t.input[1:]
		tok.Raw = t.readWord() + t.readUntil(current)
		tok.Type = Identifier
	}

	t.input = t.input[1:]
	return t.setNextToken(tok)
}

// Return the rune at the ith index of the input.
func (t *Tokenizer) getRuneAtIndex(i int) rune {
	if len(t.input) == i {
		return -1
	}

	return t.input[i]
}

// Return the rune at the 0th index of the input.
func (t *Tokenizer) current() rune {
	return t.getRuneAtIndex(0)
}

// Returns true iff the input's current rune (at index 0) is in rs.
func (t *Tokenizer) currentIs(rs ...rune) bool {
	for _, r := range rs {
		if r == t.current() {
			return true
		}
	}
	return false
}

// Update the previous Token for both the Tokenizer and the supplied Token.
func (t *Tokenizer) setNextToken(token *Token) *Token {
	token.Previous, t.previous = t.previous, token
	return token
}

// Read a single word from the input. Returns when the next rune is any
// of: -1, " ", comma, single/double quote, backtick, or parenthesis.
func (t *Tokenizer) readWord() string {
	word := []rune{}

	for {
		if unicode.IsSpace(t.current()) ||
			t.currentIs(-1, ',', '\'', '"', '`', '(', ')') {
			return string(word)
		}

		word = append(word, t.current())
		t.input = t.input[1:]
	}
}

// Read a full string until we reaching a closing parentheses. Maintains a
// count of opening parens to ensure we don't return early.
func (t *Tokenizer) readQuery() string {
	var query string

	var count = 1
	for count > 0 {
		for unicode.IsSpace(t.current()) {
			t.input = t.input[1:]
		}

		word := fmt.Sprintf(" %s", t.readWord())

		if t.current() == -1 {
			break
		}

		if t.current() == '(' {
			count++
			word = "("
		} else if t.current() == ')' {
			count--
			if count <= 0 {
				query += word
				break
			}
			word = ")"
		}

		query += word
		t.input = t.input[1:]
	}

	return query
}

// Read the input starting at start, until reaching a rune in runes.
func (t *Tokenizer) readUntil(runes ...rune) string {
	var word string
	for !t.currentIs(runes...) {
		for unicode.IsSpace(t.current()) {
			t.input = t.input[1:]
		}
		word = fmt.Sprintf("%s %s", word, t.readWord())
	}
	return word
}
<|endoftext|>


<commit_before>package httpbackend

import (
	"log"
	"net/http"

	"github.com/khades/servbot/models"
	"github.com/khades/servbot/repos"
)

func mod(next sessionAndChannelHandlerFunc) sessionAndChannelHandlerFunc {
	return func(w http.ResponseWriter, r *http.Request, session *models.HTTPSession, channelID *string, channelName *string) {
		channelInfo, error := repos.GetChannelInfo(channelID)
		log.Println(channelInfo)
		if error != nil {
			log.Println(error)
			writeJSONError(w, "That channel is not defined", http.StatusForbidden)
			return
		}
		log.Println("FIXING GETTING SESSION")
		log.Println(*session)
		if channelInfo.GetIfUserIsMod(&session.UserID) == true {
			next(w, r, session, channelID, channelName)
		} else {
			writeJSONError(w, "You're not moderator", http.StatusForbidden)
			return
		}
	}
}

func withMod(next sessionAndChannelHandlerFunc) http.HandlerFunc {
	return withSessionAndChannel(mod(next))
}
<commit_msg>Fix: excessive 403 when no session on mod<commit_after>package httpbackend

import (
	"log"
	"net/http"

	"github.com/khades/servbot/models"
	"github.com/khades/servbot/repos"
)

func mod(next sessionAndChannelHandlerFunc) sessionAndChannelHandlerFunc {
	return func(w http.ResponseWriter, r *http.Request, session *models.HTTPSession, channelID *string, channelName *string) {
		channelInfo, error := repos.GetChannelInfo(channelID)
		log.Println(channelInfo)
		if error != nil {
			log.Println(error)
			writeJSONError(w, "That channel is not defined", http.StatusForbidden)
			return
		}
		log.Println("FIXING GETTING SESSION")
		log.Println(*session)
		if channelInfo.GetIfUserIsMod(&session.UserID) == true {
			next(w, r, session, channelID, channelName)
		} else {
			writeJSONError(w, "You're not moderator", http.StatusForbidden)
			return
		}
	}
}

func withMod(next sessionAndChannelHandlerFunc) http.HandlerFunc {
	return withSessionAndChannel(auth(mod(next)))
}
<|endoftext|>


<commit_before>package postgis

import (
	"database/sql"
	"fmt"
	"os"
	"strings"
	"sync"
)

// disableDefaultSsl adds sslmode=disable to params
// when sslmode param and PGSSLMODE environment are both not set.
//
// Reason: PG will renegotiate encryption after 512MB by default, but
// Go's TLS does not suport renegotiation. Disable SSL to work around that.
// See: https://code.google.com/p/go/issues/detail?id=5742
// and ssl_renegotiation_limit on:
// http://www.postgresql.org/docs/9.1/static/runtime-config-connection.html

func disableDefaultSsl(params string) string {
	parts := strings.Fields(params)
	for _, p := range parts {
		if strings.HasPrefix(p, "sslmode=") {
			return params
		}
	}

	for _, v := range os.Environ() {
		if strings.HasPrefix(v, "PGSSLMODE=") {
			return params
		}
	}

	return params + " sslmode=disable"
}

func stripPrefixFromConnectionParams(params string) (string, string) {
	parts := strings.Fields(params)
	var prefix string
	for i, p := range parts {
		if strings.HasPrefix(p, "prefix=") {
			prefix = strings.Replace(p, "prefix=", "", 1)
			parts = append(parts[:i], parts[i+1:]...)
			params = strings.Join(parts, " ")
			break
		}
	}
	if prefix == "NONE" {
		return params, ""
	}

	if prefix == "" {
		// default
		prefix = "osm_"
	}
	if prefix[len(prefix)-1] != '_' {
		// always separated by _
		prefix = prefix + "_"
	}
	return params, prefix
}

func tableExists(tx *sql.Tx, schema, table string) (bool, error) {
	var exists bool
	sql := fmt.Sprintf(`SELECT EXISTS(SELECT * FROM information_schema.tables WHERE table_name='%s' AND table_schema='%s')`,
		table, schema)
	row := tx.QueryRow(sql)
	err := row.Scan(&exists)
	if err != nil {
		return false, err
	}
	return exists, nil
}

func dropTableIfExists(tx *sql.Tx, schema, table string) error {
	exists, err := tableExists(tx, schema, table)
	if err != nil {
		return err
	}
	if !exists {
		return nil
	}
	sqlStmt := fmt.Sprintf("SELECT DropGeometryTable('%s', '%s');",
		schema, table)
	row := tx.QueryRow(sqlStmt)
	var void interface{}
	err = row.Scan(&void)
	if err != nil {
		return &SQLError{sqlStmt, err}
	}
	return nil
}

// rollbackIfTx rollsback transaction if tx is not nil.
func rollbackIfTx(tx **sql.Tx) {
	if *tx != nil {
		if err := tx.Rollback(); err != nil {
			log.Fatal("rollback failed", err)
		}
	}
}

// workerPool runs functions in n (worker) parallel goroutines.
// wait will return the first error or nil when all functions
// returned succesfull.
type workerPool struct {
	in  chan func() error
	out chan error
	wg  *sync.WaitGroup
}

func newWorkerPool(worker, tasks int) *workerPool {
	p := &workerPool{
		make(chan func() error, tasks),
		make(chan error, tasks),
		&sync.WaitGroup{},
	}
	for i := 0; i < worker; i++ {
		p.wg.Add(1)
		go p.workerLoop()
	}
	return p
}

func (p *workerPool) workerLoop() {
	for f := range p.in {
		p.out <- f()
	}
	p.wg.Done()
}

func (p *workerPool) wait() error {
	close(p.in)
	done := make(chan bool)
	go func() {
		p.wg.Wait()
		done <- true
	}()

	for {
		select {
		case err := <-p.out:
			if err != nil {
				return err
			}
		case <-done:
			return nil
		}
	}
}
<commit_msg>small fix<commit_after>package postgis

import (
	"database/sql"
	"fmt"
	"os"
	"strings"
	"sync"
)

// disableDefaultSsl adds sslmode=disable to params
// when sslmode param and PGSSLMODE environment are both not set.
//
// Reason: PG will renegotiate encryption after 512MB by default, but
// Go's TLS does not suport renegotiation. Disable SSL to work around that.
// See: https://code.google.com/p/go/issues/detail?id=5742
// and ssl_renegotiation_limit on:
// http://www.postgresql.org/docs/9.1/static/runtime-config-connection.html

func disableDefaultSsl(params string) string {
	parts := strings.Fields(params)
	for _, p := range parts {
		if strings.HasPrefix(p, "sslmode=") {
			return params
		}
	}

	for _, v := range os.Environ() {
		if strings.HasPrefix(v, "PGSSLMODE=") {
			return params
		}
	}

	return params + " sslmode=disable"
}

func stripPrefixFromConnectionParams(params string) (string, string) {
	parts := strings.Fields(params)
	var prefix string
	for i, p := range parts {
		if strings.HasPrefix(p, "prefix=") {
			prefix = strings.Replace(p, "prefix=", "", 1)
			parts = append(parts[:i], parts[i+1:]...)
			params = strings.Join(parts, " ")
			break
		}
	}
	if prefix == "NONE" {
		return params, ""
	}

	if prefix == "" {
		// default
		prefix = "osm_"
	}
	if prefix[len(prefix)-1] != '_' {
		// always separated by _
		prefix = prefix + "_"
	}
	return params, prefix
}

func tableExists(tx *sql.Tx, schema, table string) (bool, error) {
	var exists bool
	sql := fmt.Sprintf(`SELECT EXISTS(SELECT * FROM information_schema.tables WHERE table_name='%s' AND table_schema='%s')`,
		table, schema)
	row := tx.QueryRow(sql)
	err := row.Scan(&exists)
	if err != nil {
		return false, err
	}
	return exists, nil
}

func dropTableIfExists(tx *sql.Tx, schema, table string) error {
	exists, err := tableExists(tx, schema, table)
	if err != nil {
		return err
	}
	if !exists {
		return nil
	}
	sqlStmt := fmt.Sprintf("SELECT DropGeometryTable('%s', '%s');",
		schema, table)
	row := tx.QueryRow(sqlStmt)
	var void interface{}
	err = row.Scan(&void)
	if err != nil {
		return &SQLError{sqlStmt, err}
	}
	return nil
}

// rollbackIfTx rollsback transaction if tx is not nil.
func rollbackIfTx(tx **sql.Tx) {
	if *tx != nil {
		if err := (*tx).Rollback(); err != nil {
			log.Fatal("rollback failed", err)
		}
	}
}

// workerPool runs functions in n (worker) parallel goroutines.
// wait will return the first error or nil when all functions
// returned succesfull.
type workerPool struct {
	in  chan func() error
	out chan error
	wg  *sync.WaitGroup
}

func newWorkerPool(worker, tasks int) *workerPool {
	p := &workerPool{
		make(chan func() error, tasks),
		make(chan error, tasks),
		&sync.WaitGroup{},
	}
	for i := 0; i < worker; i++ {
		p.wg.Add(1)
		go p.workerLoop()
	}
	return p
}

func (p *workerPool) workerLoop() {
	for f := range p.in {
		p.out <- f()
	}
	p.wg.Done()
}

func (p *workerPool) wait() error {
	close(p.in)
	done := make(chan bool)
	go func() {
		p.wg.Wait()
		done <- true
	}()

	for {
		select {
		case err := <-p.out:
			if err != nil {
				return err
			}
		case <-done:
			return nil
		}
	}
}
<|endoftext|>


<commit_before>package httpclient

import (
	"bytes"
	"crypto/rand"
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
	"time"

	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"

	"github.com/rightscale/rsc/log"
	"github.com/rightscale/rsc/recording"
)

const (
	// NoDump is the default value for DumpFormat.
	NoDump Format = 1 << iota
	// Debug formats the dumps in human readable format, the use of this flag is exclusive with
	// JSON.
	Debug
	// JSON formats the dumps in JSON, the use of this flag is exclusive with Debug.
	JSON
	// Verbose enables the dumps for all requests and auth headers.
	Verbose
	// Record causes the dumps to be written to the recorder file descriptor (used by tests).
	Record
)

const (
	noRedirectError = "no redirect"
)

var (
	// DumpFormat dictates how HTTP requests and responses are logged: NoDump prevents logging
	// altogether, Debug generates logs in human readable format and JSON in JSON format.
	// Verbose causes all headers to be logged - including sensitive ones.
	DumpFormat Format

	// Insecure dictates whether HTTP (true) or HTTPS (false) should be used to connect to the
	// API endpoints.
	Insecure bool

	// NoCertCheck dictates whether the SSL handshakes should bypass X509 certificate
	// validation (true) or not (false).
	NoCertCheck bool

	// ResponseHeaderTimeout if non-zero, specifies the amount of
	// time to wait in seconds for a server's response headers after fully
	// writing the request (including its body, if any). This
	// time does not include the time to read the response body.
	ResponseHeaderTimeout = 20 * time.Second

	// HiddenHeaders lists headers that should not be logged unless DumpFormat is Verbose.
	HiddenHeaders = map[string]bool{"Authorization": true, "Cookie": true}
)

// For tests
var (
	OsStderr io.Writer = os.Stderr
)

type (
	// HTTPClient makes it easier to stub HTTP clients for testing.
	HTTPClient interface {
		// Do makes a regular http request and returns the response/error.
		Do(req *http.Request) (*http.Response, error)

		// DoWithContext performs a request and is context-aware.
		DoWithContext(ctx context.Context, req *http.Request) (*http.Response, error)

		// DoHidden prevents logging, useful for requests made during authorization.
		DoHidden(req *http.Request) (*http.Response, error)

		// DoHiddenWithContext prevents logging and performs a context-aware request.
		DoHiddenWithContext(ctx context.Context, req *http.Request) (*http.Response, error)
	}

	// Format is the request/response dump format.
	Format int

	// HTTP client that optionally dumps requests and responses.
	// This client also disables the default http client redirect handling.
	dumpClient struct {
		Client *http.Client
		Format Format
	}
)

// Default DumpFormat to NoDump
func init() {
	DumpFormat = NoDump
}

// New returns an HTTP client using the settings specified by this package variables.
func New() HTTPClient {
	return &dumpClient{Client: newRawClient(false)}
}

// NewNoRedirect returns an HTTP client that does not follow redirects.
func NewNoRedirect() HTTPClient {
	return &dumpClient{Client: newRawClient(true)}
}

// ShortToken creates a 6 bytes unique string.
// Not meant to be cryptographically unique but good enough for logs.
func ShortToken() string {
	b := make([]byte, 6)
	io.ReadFull(rand.Reader, b)
	return base64.StdEncoding.EncodeToString(b)
}

// newRawClient creates an http package Client taking into account both the parameters and package
// variables.
func newRawClient(noredirect bool) *http.Client {
	tr := http.Transport{ResponseHeaderTimeout: ResponseHeaderTimeout, Proxy: http.ProxyFromEnvironment}
	tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: NoCertCheck}
	c := http.Client{Transport: &tr}
	if noredirect {
		c.CheckRedirect = func(*http.Request, []*http.Request) error {
			return fmt.Errorf(noRedirectError)
		}
	}
	return &c
}

// IsDebug is a convenience wrapper that returns true if the Debug bit is set on the flag.
func (f Format) IsDebug() bool {
	return f&Debug != 0
}

// IsJSON is a convenience wrapper that returns true if the JSON bit is set on the flag.
func (f Format) IsJSON() bool {
	return f&JSON != 0
}

// IsVerbose is a convenience wrapper that returns true if the Verbose bit is set on the flag.
func (f Format) IsVerbose() bool {
	return f&Verbose != 0
}

// IsRecord is a convenience wrapper that returns true if the Record bit is set on the flag.
func (f Format) IsRecord() bool {
	return f&Record != 0
}

// DoHidden is equivalent to Do with the exception that nothing gets logged unless DumpFormat is
// set to Verbose.
func (d *dumpClient) DoHidden(req *http.Request) (*http.Response, error) {
	return d.doImp(req, true, nil)
}

// Do dumps the request, makes the request and dumps the response as specified by DumpFormat.
func (d *dumpClient) Do(req *http.Request) (*http.Response, error) {
	return d.doImp(req, false, nil)
}

func (d *dumpClient) DoWithContext(ctx context.Context, req *http.Request) (*http.Response, error) {
	return d.doImp(req, true, ctx)
}

func (d *dumpClient) DoHiddenWithContext(ctx context.Context, req *http.Request) (*http.Response, error) {
	return d.doImp(req, false, ctx)
}

// doImp actually performs the HTTP request logging according to the various settings.
func (d *dumpClient) doImp(req *http.Request, hidden bool, ctx context.Context) (*http.Response, error) {
	if Insecure {
		req.URL.Scheme = "http"
	} else {
		req.URL.Scheme = "https"
	}
	req.Header.Set("User-Agent", UA)

	var reqBody []byte
	startedAt := time.Now()
	id := ShortToken()
	log.Info("started", "id", id, req.Method, req.URL.String())
	hide := (DumpFormat == NoDump) || (hidden && !DumpFormat.IsVerbose())
	if !hide {
		startedAt = time.Now()
		reqBody = dumpRequest(req)
	}
	var resp *http.Response
	var err error
	if ctx == nil {
		resp, err = d.Client.Do(req)
	} else {
		resp, err = ctxhttp.Do(ctx, d.Client, req)
	}
	if urlError, ok := err.(*url.Error); ok {
		if urlError.Err.Error() == noRedirectError {
			err = nil
		}
	}
	if err != nil {
		return nil, err
	}
	if !hide {
		dumpResponse(resp, req, reqBody)
	}
	log.Info("completed", "id", id, "status", resp.Status, "time", time.Since(startedAt).String())

	return resp, nil
}

// Dump request if needed.
// Return request serialized as JSON if DumpFormat is JSON, nil otherwise.
func dumpRequest(req *http.Request) []byte {
	if DumpFormat == NoDump {
		return nil
	}
	reqBody, err := dumpReqBody(req)
	if err != nil {
		log.Error("Failed to load request body for dump", "error", err.Error())
	}
	if DumpFormat.IsDebug() {
		var buffer bytes.Buffer
		buffer.WriteString(req.Method + " " + req.URL.String() + "\n")
		writeHeaders(&buffer, req.Header)
		if reqBody != nil {
			buffer.WriteString("\n")
			buffer.Write(reqBody)
			buffer.WriteString("\n")
		}
		fmt.Fprint(OsStderr, buffer.String())
	} else if DumpFormat.IsJSON() {
		return reqBody
	}
	return nil
}

// dumpResponse dumps the response and optionally the request (in case of JSON format) according to
// DumpFormat.
// It also checks whether the special recorder pipe is opened and if so writes the dump to it.
func dumpResponse(resp *http.Response, req *http.Request, reqBody []byte) {
	if DumpFormat == NoDump {
		return
	}
	respBody, _ := dumpRespBody(resp)
	if DumpFormat.IsDebug() {
		var buffer bytes.Buffer
		buffer.WriteString("==> " + resp.Proto + " " + resp.Status + "\n")
		writeHeaders(&buffer, resp.Header)
		if respBody != nil {
			buffer.WriteString("\n")
			buffer.Write(respBody)
			buffer.WriteString("\n")
		}
		fmt.Fprint(OsStderr, buffer.String())
	} else if DumpFormat.IsJSON() {
		reqHeaders := make(http.Header)
		filterHeaders(req.Header, func(name string, value []string) {
			reqHeaders[name] = value
		})
		respHeaders := make(http.Header)
		filterHeaders(resp.Header, func(name string, value []string) {
			respHeaders[name] = value
		})
		dumped := recording.RequestResponse{
			Verb:       req.Method,
			URI:        req.URL.String(),
			ReqHeader:  reqHeaders,
			ReqBody:    string(reqBody),
			Status:     resp.StatusCode,
			RespHeader: respHeaders,
			RespBody:   string(respBody),
		}
		b, err := json.MarshalIndent(dumped, "", "    ")
		if err != nil {
			log.Error("Failed to dump request content", "error", err.Error())
			return
		}
		if DumpFormat.IsRecord() {
			f := os.NewFile(10, "fd10")
			_, err = f.Stat()
			if err == nil {
				// fd 10 is open, dump to it (used by recorder)
				fmt.Fprintf(f, "%s\n", string(b))
			}
		}
		fmt.Fprint(OsStderr, string(b))
	}
}

// writeHeaders is a helper function that writes the given HTTP headers to the given buffer as
// human readable strings. If DumpFormat is not Verbose then writeHeaders filters out headers whose
// names are keys of HiddenHeaders.
func writeHeaders(buffer *bytes.Buffer, headers http.Header) {
	filterHeaders(headers, func(name string, value []string) {
		buffer.WriteString(name)
		buffer.WriteString(": ")
		buffer.WriteString(strings.Join(value, ", "))
		buffer.WriteString("\n")
	})
}

// Dump request body, strongly inspired from httputil.DumpRequest
func dumpReqBody(req *http.Request) ([]byte, error) {
	if req.Body == nil {
		return nil, nil
	}
	var save io.ReadCloser
	var err error
	save, req.Body, err = drainBody(req.Body)
	if err != nil {
		return nil, err
	}
	var b bytes.Buffer
	var dest io.Writer = &b
	chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
	if chunked {
		dest = httputil.NewChunkedWriter(dest)
	}
	_, err = io.Copy(dest, req.Body)
	if chunked {
		dest.(io.Closer).Close()
		io.WriteString(&b, "\r\n")
	}
	req.Body = save
	return b.Bytes(), err
}

// Dump response body, strongly inspired from httputil.DumpResponse
func dumpRespBody(resp *http.Response) ([]byte, error) {
	if resp.Body == nil {
		return nil, nil
	}
	var b bytes.Buffer
	savecl := resp.ContentLength
	var save io.ReadCloser
	var err error
	save, resp.Body, err = drainBody(resp.Body)
	if err != nil {
		return nil, err
	}
	_, err = io.Copy(&b, resp.Body)
	if err != nil {
		return nil, err
	}
	resp.Body = save
	resp.ContentLength = savecl
	if err != nil {
		return nil, err
	}
	return b.Bytes(), nil
}

// One of the copies, say from b to r2, could be avoided by using a more
// elaborate trick where the other copy is made during Request/Response.Write.
// This would complicate things too much, given that these functions are for
// debugging only.
func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
	var buf bytes.Buffer
	if _, err = buf.ReadFrom(b); err != nil {
		return nil, nil, err
	}
	if err = b.Close(); err != nil {
		return nil, nil, err
	}
	return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
}

// headerIterator is a HTTP header iterator.
type headerIterator func(name string, value []string)

// filterHeaders iterates through the headers skipping hidden headers unless DumpFormat is Verbose.
// It calls the given iterator for each header name/value pair. The values are serialized as
// strings.
func filterHeaders(headers http.Header, iterator headerIterator) {
	for k, v := range headers {
		if !DumpFormat.IsVerbose() {
			if _, ok := HiddenHeaders[k]; ok {
				continue
			}
		}
		iterator(k, v)
	}
}
<commit_msg>IV-3932 increase timout to 5 minutes<commit_after>package httpclient

import (
	"bytes"
	"crypto/rand"
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
	"time"

	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"

	"github.com/rightscale/rsc/log"
	"github.com/rightscale/rsc/recording"
)

const (
	// NoDump is the default value for DumpFormat.
	NoDump Format = 1 << iota
	// Debug formats the dumps in human readable format, the use of this flag is exclusive with
	// JSON.
	Debug
	// JSON formats the dumps in JSON, the use of this flag is exclusive with Debug.
	JSON
	// Verbose enables the dumps for all requests and auth headers.
	Verbose
	// Record causes the dumps to be written to the recorder file descriptor (used by tests).
	Record
)

const (
	noRedirectError = "no redirect"
)

var (
	// DumpFormat dictates how HTTP requests and responses are logged: NoDump prevents logging
	// altogether, Debug generates logs in human readable format and JSON in JSON format.
	// Verbose causes all headers to be logged - including sensitive ones.
	DumpFormat Format

	// Insecure dictates whether HTTP (true) or HTTPS (false) should be used to connect to the
	// API endpoints.
	Insecure bool

	// NoCertCheck dictates whether the SSL handshakes should bypass X509 certificate
	// validation (true) or not (false).
	NoCertCheck bool

	// ResponseHeaderTimeout if non-zero, specifies the amount of
	// time to wait in seconds for a server's response headers after fully
	// writing the request (including its body, if any). This
	// time does not include the time to read the response body.
	ResponseHeaderTimeout = 300 * time.Second

	// HiddenHeaders lists headers that should not be logged unless DumpFormat is Verbose.
	HiddenHeaders = map[string]bool{"Authorization": true, "Cookie": true}
)

// For tests
var (
	OsStderr io.Writer = os.Stderr
)

type (
	// HTTPClient makes it easier to stub HTTP clients for testing.
	HTTPClient interface {
		// Do makes a regular http request and returns the response/error.
		Do(req *http.Request) (*http.Response, error)

		// DoWithContext performs a request and is context-aware.
		DoWithContext(ctx context.Context, req *http.Request) (*http.Response, error)

		// DoHidden prevents logging, useful for requests made during authorization.
		DoHidden(req *http.Request) (*http.Response, error)

		// DoHiddenWithContext prevents logging and performs a context-aware request.
		DoHiddenWithContext(ctx context.Context, req *http.Request) (*http.Response, error)
	}

	// Format is the request/response dump format.
	Format int

	// HTTP client that optionally dumps requests and responses.
	// This client also disables the default http client redirect handling.
	dumpClient struct {
		Client *http.Client
		Format Format
	}
)

// Default DumpFormat to NoDump
func init() {
	DumpFormat = NoDump
}

// New returns an HTTP client using the settings specified by this package variables.
func New() HTTPClient {
	return &dumpClient{Client: newRawClient(false)}
}

// NewNoRedirect returns an HTTP client that does not follow redirects.
func NewNoRedirect() HTTPClient {
	return &dumpClient{Client: newRawClient(true)}
}

// ShortToken creates a 6 bytes unique string.
// Not meant to be cryptographically unique but good enough for logs.
func ShortToken() string {
	b := make([]byte, 6)
	io.ReadFull(rand.Reader, b)
	return base64.StdEncoding.EncodeToString(b)
}

// newRawClient creates an http package Client taking into account both the parameters and package
// variables.
func newRawClient(noredirect bool) *http.Client {
	tr := http.Transport{ResponseHeaderTimeout: ResponseHeaderTimeout, Proxy: http.ProxyFromEnvironment}
	tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: NoCertCheck}
	c := http.Client{Transport: &tr}
	if noredirect {
		c.CheckRedirect = func(*http.Request, []*http.Request) error {
			return fmt.Errorf(noRedirectError)
		}
	}
	return &c
}

// IsDebug is a convenience wrapper that returns true if the Debug bit is set on the flag.
func (f Format) IsDebug() bool {
	return f&Debug != 0
}

// IsJSON is a convenience wrapper that returns true if the JSON bit is set on the flag.
func (f Format) IsJSON() bool {
	return f&JSON != 0
}

// IsVerbose is a convenience wrapper that returns true if the Verbose bit is set on the flag.
func (f Format) IsVerbose() bool {
	return f&Verbose != 0
}

// IsRecord is a convenience wrapper that returns true if the Record bit is set on the flag.
func (f Format) IsRecord() bool {
	return f&Record != 0
}

// DoHidden is equivalent to Do with the exception that nothing gets logged unless DumpFormat is
// set to Verbose.
func (d *dumpClient) DoHidden(req *http.Request) (*http.Response, error) {
	return d.doImp(req, true, nil)
}

// Do dumps the request, makes the request and dumps the response as specified by DumpFormat.
func (d *dumpClient) Do(req *http.Request) (*http.Response, error) {
	return d.doImp(req, false, nil)
}

func (d *dumpClient) DoWithContext(ctx context.Context, req *http.Request) (*http.Response, error) {
	return d.doImp(req, true, ctx)
}

func (d *dumpClient) DoHiddenWithContext(ctx context.Context, req *http.Request) (*http.Response, error) {
	return d.doImp(req, false, ctx)
}

// doImp actually performs the HTTP request logging according to the various settings.
func (d *dumpClient) doImp(req *http.Request, hidden bool, ctx context.Context) (*http.Response, error) {
	if Insecure {
		req.URL.Scheme = "http"
	} else {
		req.URL.Scheme = "https"
	}
	req.Header.Set("User-Agent", UA)

	var reqBody []byte
	startedAt := time.Now()
	id := ShortToken()
	log.Info("started", "id", id, req.Method, req.URL.String())
	hide := (DumpFormat == NoDump) || (hidden && !DumpFormat.IsVerbose())
	if !hide {
		startedAt = time.Now()
		reqBody = dumpRequest(req)
	}
	var resp *http.Response
	var err error
	if ctx == nil {
		resp, err = d.Client.Do(req)
	} else {
		resp, err = ctxhttp.Do(ctx, d.Client, req)
	}
	if urlError, ok := err.(*url.Error); ok {
		if urlError.Err.Error() == noRedirectError {
			err = nil
		}
	}
	if err != nil {
		return nil, err
	}
	if !hide {
		dumpResponse(resp, req, reqBody)
	}
	log.Info("completed", "id", id, "status", resp.Status, "time", time.Since(startedAt).String())

	return resp, nil
}

// Dump request if needed.
// Return request serialized as JSON if DumpFormat is JSON, nil otherwise.
func dumpRequest(req *http.Request) []byte {
	if DumpFormat == NoDump {
		return nil
	}
	reqBody, err := dumpReqBody(req)
	if err != nil {
		log.Error("Failed to load request body for dump", "error", err.Error())
	}
	if DumpFormat.IsDebug() {
		var buffer bytes.Buffer
		buffer.WriteString(req.Method + " " + req.URL.String() + "\n")
		writeHeaders(&buffer, req.Header)
		if reqBody != nil {
			buffer.WriteString("\n")
			buffer.Write(reqBody)
			buffer.WriteString("\n")
		}
		fmt.Fprint(OsStderr, buffer.String())
	} else if DumpFormat.IsJSON() {
		return reqBody
	}
	return nil
}

// dumpResponse dumps the response and optionally the request (in case of JSON format) according to
// DumpFormat.
// It also checks whether the special recorder pipe is opened and if so writes the dump to it.
func dumpResponse(resp *http.Response, req *http.Request, reqBody []byte) {
	if DumpFormat == NoDump {
		return
	}
	respBody, _ := dumpRespBody(resp)
	if DumpFormat.IsDebug() {
		var buffer bytes.Buffer
		buffer.WriteString("==> " + resp.Proto + " " + resp.Status + "\n")
		writeHeaders(&buffer, resp.Header)
		if respBody != nil {
			buffer.WriteString("\n")
			buffer.Write(respBody)
			buffer.WriteString("\n")
		}
		fmt.Fprint(OsStderr, buffer.String())
	} else if DumpFormat.IsJSON() {
		reqHeaders := make(http.Header)
		filterHeaders(req.Header, func(name string, value []string) {
			reqHeaders[name] = value
		})
		respHeaders := make(http.Header)
		filterHeaders(resp.Header, func(name string, value []string) {
			respHeaders[name] = value
		})
		dumped := recording.RequestResponse{
			Verb:       req.Method,
			URI:        req.URL.String(),
			ReqHeader:  reqHeaders,
			ReqBody:    string(reqBody),
			Status:     resp.StatusCode,
			RespHeader: respHeaders,
			RespBody:   string(respBody),
		}
		b, err := json.MarshalIndent(dumped, "", "    ")
		if err != nil {
			log.Error("Failed to dump request content", "error", err.Error())
			return
		}
		if DumpFormat.IsRecord() {
			f := os.NewFile(10, "fd10")
			_, err = f.Stat()
			if err == nil {
				// fd 10 is open, dump to it (used by recorder)
				fmt.Fprintf(f, "%s\n", string(b))
			}
		}
		fmt.Fprint(OsStderr, string(b))
	}
}

// writeHeaders is a helper function that writes the given HTTP headers to the given buffer as
// human readable strings. If DumpFormat is not Verbose then writeHeaders filters out headers whose
// names are keys of HiddenHeaders.
func writeHeaders(buffer *bytes.Buffer, headers http.Header) {
	filterHeaders(headers, func(name string, value []string) {
		buffer.WriteString(name)
		buffer.WriteString(": ")
		buffer.WriteString(strings.Join(value, ", "))
		buffer.WriteString("\n")
	})
}

// Dump request body, strongly inspired from httputil.DumpRequest
func dumpReqBody(req *http.Request) ([]byte, error) {
	if req.Body == nil {
		return nil, nil
	}
	var save io.ReadCloser
	var err error
	save, req.Body, err = drainBody(req.Body)
	if err != nil {
		return nil, err
	}
	var b bytes.Buffer
	var dest io.Writer = &b
	chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
	if chunked {
		dest = httputil.NewChunkedWriter(dest)
	}
	_, err = io.Copy(dest, req.Body)
	if chunked {
		dest.(io.Closer).Close()
		io.WriteString(&b, "\r\n")
	}
	req.Body = save
	return b.Bytes(), err
}

// Dump response body, strongly inspired from httputil.DumpResponse
func dumpRespBody(resp *http.Response) ([]byte, error) {
	if resp.Body == nil {
		return nil, nil
	}
	var b bytes.Buffer
	savecl := resp.ContentLength
	var save io.ReadCloser
	var err error
	save, resp.Body, err = drainBody(resp.Body)
	if err != nil {
		return nil, err
	}
	_, err = io.Copy(&b, resp.Body)
	if err != nil {
		return nil, err
	}
	resp.Body = save
	resp.ContentLength = savecl
	if err != nil {
		return nil, err
	}
	return b.Bytes(), nil
}

// One of the copies, say from b to r2, could be avoided by using a more
// elaborate trick where the other copy is made during Request/Response.Write.
// This would complicate things too much, given that these functions are for
// debugging only.
func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
	var buf bytes.Buffer
	if _, err = buf.ReadFrom(b); err != nil {
		return nil, nil, err
	}
	if err = b.Close(); err != nil {
		return nil, nil, err
	}
	return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
}

// headerIterator is a HTTP header iterator.
type headerIterator func(name string, value []string)

// filterHeaders iterates through the headers skipping hidden headers unless DumpFormat is Verbose.
// It calls the given iterator for each header name/value pair. The values are serialized as
// strings.
func filterHeaders(headers http.Header, iterator headerIterator) {
	for k, v := range headers {
		if !DumpFormat.IsVerbose() {
			if _, ok := HiddenHeaders[k]; ok {
				continue
			}
		}
		iterator(k, v)
	}
}
<|endoftext|>


<commit_before>// Copyright 2009 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.

package http

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"
)

// This implementation is done according to IETF draft-ietf-httpstate-cookie-23, found at
//
//    http://tools.ietf.org/html/draft-ietf-httpstate-cookie-23

// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
// HTTP response or the Cookie header of an HTTP request.
type Cookie struct {
	Name       string
	Value      string
	Path       string
	Domain     string
	Expires    time.Time
	RawExpires string

	// MaxAge=0 means no 'Max-Age' attribute specified.
	// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
	// MaxAge>0 means Max-Age attribute present and given in seconds
	MaxAge   int
	Secure   bool
	HttpOnly bool
	Raw      string
	Unparsed []string // Raw text of unparsed attribute-value pairs
}

// readSetCookies parses all "Set-Cookie" values from
// the header h, removes the successfully parsed values from the
// "Set-Cookie" key in h and returns the parsed Cookies.
func readSetCookies(h Header) []*Cookie {
	cookies := []*Cookie{}
	var unparsedLines []string
	for _, line := range h["Set-Cookie"] {
		parts := strings.Split(strings.TrimSpace(line), ";", -1)
		if len(parts) == 1 && parts[0] == "" {
			continue
		}
		parts[0] = strings.TrimSpace(parts[0])
		j := strings.Index(parts[0], "=")
		if j < 0 {
			unparsedLines = append(unparsedLines, line)
			continue
		}
		name, value := parts[0][:j], parts[0][j+1:]
		if !isCookieNameValid(name) {
			unparsedLines = append(unparsedLines, line)
			continue
		}
		value, success := parseCookieValue(value)
		if !success {
			unparsedLines = append(unparsedLines, line)
			continue
		}
		c := &Cookie{
			Name:  name,
			Value: value,
			Raw:   line,
		}
		for i := 1; i < len(parts); i++ {
			parts[i] = strings.TrimSpace(parts[i])
			if len(parts[i]) == 0 {
				continue
			}

			attr, val := parts[i], ""
			if j := strings.Index(attr, "="); j >= 0 {
				attr, val = attr[:j], attr[j+1:]
			}
			val, success = parseCookieValue(val)
			if !success {
				c.Unparsed = append(c.Unparsed, parts[i])
				continue
			}
			switch strings.ToLower(attr) {
			case "secure":
				c.Secure = true
				continue
			case "httponly":
				c.HttpOnly = true
				continue
			case "domain":
				c.Domain = val
				// TODO: Add domain parsing
				continue
			case "max-age":
				secs, err := strconv.Atoi(val)
				if err != nil || secs < 0 || secs != 0 && val[0] == '0' {
					break
				}
				if secs <= 0 {
					c.MaxAge = -1
				} else {
					c.MaxAge = secs
				}
				continue
			case "expires":
				c.RawExpires = val
				exptime, err := time.Parse(time.RFC1123, val)
				if err != nil {
					c.Expires = time.Time{}
					break
				}
				c.Expires = *exptime
				continue
			case "path":
				c.Path = val
				// TODO: Add path parsing
				continue
			}
			c.Unparsed = append(c.Unparsed, parts[i])
		}
		cookies = append(cookies, c)
	}
	h["Set-Cookie"] = unparsedLines, unparsedLines != nil
	return cookies
}

// writeSetCookies writes the wire representation of the set-cookies
// to w. Each cookie is written on a separate "Set-Cookie: " line.
// This choice is made because HTTP parsers tend to have a limit on
// line-length, so it seems safer to place cookies on separate lines.
func writeSetCookies(w io.Writer, kk []*Cookie) os.Error {
	if kk == nil {
		return nil
	}
	lines := make([]string, 0, len(kk))
	var b bytes.Buffer
	for _, c := range kk {
		b.Reset()
		fmt.Fprintf(&b, "%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value))
		if len(c.Path) > 0 {
			fmt.Fprintf(&b, "; Path=%s", sanitizeValue(c.Path))
		}
		if len(c.Domain) > 0 {
			fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(c.Domain))
		}
		if len(c.Expires.Zone) > 0 {
			fmt.Fprintf(&b, "; Expires=%s", c.Expires.Format(time.RFC1123))
		}
		if c.MaxAge > 0 {
			fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge)
		} else if c.MaxAge < 0 {
			fmt.Fprintf(&b, "; Max-Age=0")
		}
		if c.HttpOnly {
			fmt.Fprintf(&b, "; HttpOnly")
		}
		if c.Secure {
			fmt.Fprintf(&b, "; Secure")
		}
		lines = append(lines, "Set-Cookie: "+b.String()+"\r\n")
	}
	sort.SortStrings(lines)
	for _, l := range lines {
		if _, err := io.WriteString(w, l); err != nil {
			return err
		}
	}
	return nil
}

// readCookies parses all "Cookie" values from
// the header h, removes the successfully parsed values from the
// "Cookie" key in h and returns the parsed Cookies.
func readCookies(h Header) []*Cookie {
	cookies := []*Cookie{}
	lines, ok := h["Cookie"]
	if !ok {
		return cookies
	}
	unparsedLines := []string{}
	for _, line := range lines {
		parts := strings.Split(strings.TrimSpace(line), ";", -1)
		if len(parts) == 1 && parts[0] == "" {
			continue
		}
		// Per-line attributes
		parsedPairs := 0
		for i := 0; i < len(parts); i++ {
			parts[i] = strings.TrimSpace(parts[i])
			if len(parts[i]) == 0 {
				continue
			}
			attr, val := parts[i], ""
			if j := strings.Index(attr, "="); j >= 0 {
				attr, val = attr[:j], attr[j+1:]
			}
			if !isCookieNameValid(attr) {
				continue
			}
			val, success := parseCookieValue(val)
			if !success {
				continue
			}
			cookies = append(cookies, &Cookie{Name: attr, Value: val})
			parsedPairs++
		}
		if parsedPairs == 0 {
			unparsedLines = append(unparsedLines, line)
		}
	}
	h["Cookie"] = unparsedLines, len(unparsedLines) > 0
	return cookies
}

// writeCookies writes the wire representation of the cookies
// to w. Each cookie is written on a separate "Cookie: " line.
// This choice is made because HTTP parsers tend to have a limit on
// line-length, so it seems safer to place cookies on separate lines.
func writeCookies(w io.Writer, kk []*Cookie) os.Error {
	lines := make([]string, 0, len(kk))
	for _, c := range kk {
		lines = append(lines, fmt.Sprintf("Cookie: %s=%s\r\n", sanitizeName(c.Name), sanitizeValue(c.Value)))
	}
	sort.SortStrings(lines)
	for _, l := range lines {
		if _, err := io.WriteString(w, l); err != nil {
			return err
		}
	}
	return nil
}

func sanitizeName(n string) string {
	n = strings.Replace(n, "\n", "-", -1)
	n = strings.Replace(n, "\r", "-", -1)
	return n
}

func sanitizeValue(v string) string {
	v = strings.Replace(v, "\n", " ", -1)
	v = strings.Replace(v, "\r", " ", -1)
	v = strings.Replace(v, ";", " ", -1)
	return v
}

func unquoteCookieValue(v string) string {
	if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' {
		return v[1 : len(v)-1]
	}
	return v
}

func isCookieByte(c byte) bool {
	switch true {
	case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a,
		0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e:
		return true
	}
	return false
}

func parseCookieValue(raw string) (string, bool) {
	raw = unquoteCookieValue(raw)
	for i := 0; i < len(raw); i++ {
		if !isCookieByte(raw[i]) {
			return "", false
		}
	}
	return raw, true
}

func isCookieNameValid(raw string) bool {
	for _, c := range raw {
		if !isToken(byte(c)) {
			return false
		}
	}
	return true
}
<commit_msg>http: update cookie doc to reference new RFC 6265<commit_after>// Copyright 2009 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.

package http

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"
)

// This implementation is done according to RFC 6265:
//
//    http://tools.ietf.org/html/rfc6265

// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
// HTTP response or the Cookie header of an HTTP request.
type Cookie struct {
	Name       string
	Value      string
	Path       string
	Domain     string
	Expires    time.Time
	RawExpires string

	// MaxAge=0 means no 'Max-Age' attribute specified.
	// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
	// MaxAge>0 means Max-Age attribute present and given in seconds
	MaxAge   int
	Secure   bool
	HttpOnly bool
	Raw      string
	Unparsed []string // Raw text of unparsed attribute-value pairs
}

// readSetCookies parses all "Set-Cookie" values from
// the header h, removes the successfully parsed values from the
// "Set-Cookie" key in h and returns the parsed Cookies.
func readSetCookies(h Header) []*Cookie {
	cookies := []*Cookie{}
	var unparsedLines []string
	for _, line := range h["Set-Cookie"] {
		parts := strings.Split(strings.TrimSpace(line), ";", -1)
		if len(parts) == 1 && parts[0] == "" {
			continue
		}
		parts[0] = strings.TrimSpace(parts[0])
		j := strings.Index(parts[0], "=")
		if j < 0 {
			unparsedLines = append(unparsedLines, line)
			continue
		}
		name, value := parts[0][:j], parts[0][j+1:]
		if !isCookieNameValid(name) {
			unparsedLines = append(unparsedLines, line)
			continue
		}
		value, success := parseCookieValue(value)
		if !success {
			unparsedLines = append(unparsedLines, line)
			continue
		}
		c := &Cookie{
			Name:  name,
			Value: value,
			Raw:   line,
		}
		for i := 1; i < len(parts); i++ {
			parts[i] = strings.TrimSpace(parts[i])
			if len(parts[i]) == 0 {
				continue
			}

			attr, val := parts[i], ""
			if j := strings.Index(attr, "="); j >= 0 {
				attr, val = attr[:j], attr[j+1:]
			}
			val, success = parseCookieValue(val)
			if !success {
				c.Unparsed = append(c.Unparsed, parts[i])
				continue
			}
			switch strings.ToLower(attr) {
			case "secure":
				c.Secure = true
				continue
			case "httponly":
				c.HttpOnly = true
				continue
			case "domain":
				c.Domain = val
				// TODO: Add domain parsing
				continue
			case "max-age":
				secs, err := strconv.Atoi(val)
				if err != nil || secs < 0 || secs != 0 && val[0] == '0' {
					break
				}
				if secs <= 0 {
					c.MaxAge = -1
				} else {
					c.MaxAge = secs
				}
				continue
			case "expires":
				c.RawExpires = val
				exptime, err := time.Parse(time.RFC1123, val)
				if err != nil {
					c.Expires = time.Time{}
					break
				}
				c.Expires = *exptime
				continue
			case "path":
				c.Path = val
				// TODO: Add path parsing
				continue
			}
			c.Unparsed = append(c.Unparsed, parts[i])
		}
		cookies = append(cookies, c)
	}
	h["Set-Cookie"] = unparsedLines, unparsedLines != nil
	return cookies
}

// writeSetCookies writes the wire representation of the set-cookies
// to w. Each cookie is written on a separate "Set-Cookie: " line.
// This choice is made because HTTP parsers tend to have a limit on
// line-length, so it seems safer to place cookies on separate lines.
func writeSetCookies(w io.Writer, kk []*Cookie) os.Error {
	if kk == nil {
		return nil
	}
	lines := make([]string, 0, len(kk))
	var b bytes.Buffer
	for _, c := range kk {
		b.Reset()
		fmt.Fprintf(&b, "%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value))
		if len(c.Path) > 0 {
			fmt.Fprintf(&b, "; Path=%s", sanitizeValue(c.Path))
		}
		if len(c.Domain) > 0 {
			fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(c.Domain))
		}
		if len(c.Expires.Zone) > 0 {
			fmt.Fprintf(&b, "; Expires=%s", c.Expires.Format(time.RFC1123))
		}
		if c.MaxAge > 0 {
			fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge)
		} else if c.MaxAge < 0 {
			fmt.Fprintf(&b, "; Max-Age=0")
		}
		if c.HttpOnly {
			fmt.Fprintf(&b, "; HttpOnly")
		}
		if c.Secure {
			fmt.Fprintf(&b, "; Secure")
		}
		lines = append(lines, "Set-Cookie: "+b.String()+"\r\n")
	}
	sort.SortStrings(lines)
	for _, l := range lines {
		if _, err := io.WriteString(w, l); err != nil {
			return err
		}
	}
	return nil
}

// readCookies parses all "Cookie" values from
// the header h, removes the successfully parsed values from the
// "Cookie" key in h and returns the parsed Cookies.
func readCookies(h Header) []*Cookie {
	cookies := []*Cookie{}
	lines, ok := h["Cookie"]
	if !ok {
		return cookies
	}
	unparsedLines := []string{}
	for _, line := range lines {
		parts := strings.Split(strings.TrimSpace(line), ";", -1)
		if len(parts) == 1 && parts[0] == "" {
			continue
		}
		// Per-line attributes
		parsedPairs := 0
		for i := 0; i < len(parts); i++ {
			parts[i] = strings.TrimSpace(parts[i])
			if len(parts[i]) == 0 {
				continue
			}
			attr, val := parts[i], ""
			if j := strings.Index(attr, "="); j >= 0 {
				attr, val = attr[:j], attr[j+1:]
			}
			if !isCookieNameValid(attr) {
				continue
			}
			val, success := parseCookieValue(val)
			if !success {
				continue
			}
			cookies = append(cookies, &Cookie{Name: attr, Value: val})
			parsedPairs++
		}
		if parsedPairs == 0 {
			unparsedLines = append(unparsedLines, line)
		}
	}
	h["Cookie"] = unparsedLines, len(unparsedLines) > 0
	return cookies
}

// writeCookies writes the wire representation of the cookies
// to w. Each cookie is written on a separate "Cookie: " line.
// This choice is made because HTTP parsers tend to have a limit on
// line-length, so it seems safer to place cookies on separate lines.
func writeCookies(w io.Writer, kk []*Cookie) os.Error {
	lines := make([]string, 0, len(kk))
	for _, c := range kk {
		lines = append(lines, fmt.Sprintf("Cookie: %s=%s\r\n", sanitizeName(c.Name), sanitizeValue(c.Value)))
	}
	sort.SortStrings(lines)
	for _, l := range lines {
		if _, err := io.WriteString(w, l); err != nil {
			return err
		}
	}
	return nil
}

func sanitizeName(n string) string {
	n = strings.Replace(n, "\n", "-", -1)
	n = strings.Replace(n, "\r", "-", -1)
	return n
}

func sanitizeValue(v string) string {
	v = strings.Replace(v, "\n", " ", -1)
	v = strings.Replace(v, "\r", " ", -1)
	v = strings.Replace(v, ";", " ", -1)
	return v
}

func unquoteCookieValue(v string) string {
	if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' {
		return v[1 : len(v)-1]
	}
	return v
}

func isCookieByte(c byte) bool {
	switch true {
	case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a,
		0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e:
		return true
	}
	return false
}

func parseCookieValue(raw string) (string, bool) {
	raw = unquoteCookieValue(raw)
	for i := 0; i < len(raw); i++ {
		if !isCookieByte(raw[i]) {
			return "", false
		}
	}
	return raw, true
}

func isCookieNameValid(raw string) bool {
	for _, c := range raw {
		if !isToken(byte(c)) {
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>// Copyright 2009 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.

package image

// Color can convert itself to alpha-premultiplied RGBA, with a possible loss
// of precision. Each value ranges within [0, 0xFFFF], but is represented by a
// uint32 so that multiplying by a blend factor up to 0xFFFF will not overflow.
type Color interface {
	RGBA() (r, g, b, a uint32)
}

// RGBAColor represents a traditional 32-bit alpha-premultiplied color,
// having 8 bits for each of red, green, blue and alpha.
type RGBAColor struct {
	R, G, B, A uint8
}

func (c RGBAColor) RGBA() (r, g, b, a uint32) {
	r = uint32(c.R)
	r |= r << 8
	g = uint32(c.G)
	g |= g << 8
	b = uint32(c.B)
	b |= b << 8
	a = uint32(c.A)
	a |= a << 8
	return
}

// RGBA64Color represents a 64-bit alpha-premultiplied color,
// having 16 bits for each of red, green, blue and alpha.
type RGBA64Color struct {
	R, G, B, A uint16
}

func (c RGBA64Color) RGBA() (r, g, b, a uint32) {
	return uint32(c.R), uint32(c.G), uint32(c.B), uint32(c.A)
}

// NRGBAColor represents a non-alpha-premultiplied 32-bit color.
type NRGBAColor struct {
	R, G, B, A uint8
}

func (c NRGBAColor) RGBA() (r, g, b, a uint32) {
	r = uint32(c.R)
	r |= r << 8
	r *= uint32(c.A)
	r /= 0xff
	g = uint32(c.G)
	g |= g << 8
	g *= uint32(c.A)
	g /= 0xff
	b = uint32(c.B)
	b |= b << 8
	b *= uint32(c.A)
	b /= 0xff
	a = uint32(c.A)
	a |= a << 8
	return
}

// NRGBA64Color represents a non-alpha-premultiplied 64-bit color,
// having 16 bits for each of red, green, blue and alpha.
type NRGBA64Color struct {
	R, G, B, A uint16
}

func (c NRGBA64Color) RGBA() (r, g, b, a uint32) {
	r = uint32(c.R)
	r *= uint32(c.A)
	r /= 0xffff
	g = uint32(c.G)
	g *= uint32(c.A)
	g /= 0xffff
	b = uint32(c.B)
	b *= uint32(c.A)
	b /= 0xffff
	a = uint32(c.A)
	return
}

// AlphaColor represents an 8-bit alpha.
type AlphaColor struct {
	A uint8
}

func (c AlphaColor) RGBA() (r, g, b, a uint32) {
	a = uint32(c.A)
	a |= a << 8
	return a, a, a, a
}

// Alpha16Color represents a 16-bit alpha.
type Alpha16Color struct {
	A uint16
}

func (c Alpha16Color) RGBA() (r, g, b, a uint32) {
	a = uint32(c.A)
	return a, a, a, a
}

// GrayColor represents an 8-bit grayscale color.
type GrayColor struct {
	Y uint8
}

func (c GrayColor) RGBA() (r, g, b, a uint32) {
	y := uint32(c.Y)
	y |= y << 8
	return y, y, y, 0xffff
}

// Gray16Color represents a 16-bit grayscale color.
type Gray16Color struct {
	Y uint16
}

func (c Gray16Color) RGBA() (r, g, b, a uint32) {
	y := uint32(c.Y)
	return y, y, y, 0xffff
}

// ColorModel can convert foreign Colors, with a possible loss of precision,
// to a Color from its own color model.
type ColorModel interface {
	Convert(c Color) Color
}

// The ColorModelFunc type is an adapter to allow the use of an ordinary
// color conversion function as a ColorModel.  If f is such a function,
// ColorModelFunc(f) is a ColorModel object that invokes f to implement
// the conversion.
type ColorModelFunc func(Color) Color

func (f ColorModelFunc) Convert(c Color) Color {
	return f(c)
}

func toRGBAColor(c Color) Color {
	if _, ok := c.(RGBAColor); ok {
		return c
	}
	r, g, b, a := c.RGBA()
	return RGBAColor{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
}

func toRGBA64Color(c Color) Color {
	if _, ok := c.(RGBA64Color); ok {
		return c
	}
	r, g, b, a := c.RGBA()
	return RGBA64Color{uint16(r), uint16(g), uint16(b), uint16(a)}
}

func toNRGBAColor(c Color) Color {
	if _, ok := c.(NRGBAColor); ok {
		return c
	}
	r, g, b, a := c.RGBA()
	if a == 0xffff {
		return NRGBAColor{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), 0xff}
	}
	if a == 0 {
		return NRGBAColor{0, 0, 0, 0}
	}
	// Since Color.RGBA returns a alpha-premultiplied color, we should have r <= a && g <= a && b <= a.
	r = (r * 0xffff) / a
	g = (g * 0xffff) / a
	b = (b * 0xffff) / a
	return NRGBAColor{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
}

func toNRGBA64Color(c Color) Color {
	if _, ok := c.(NRGBA64Color); ok {
		return c
	}
	r, g, b, a := c.RGBA()
	if a == 0xffff {
		return NRGBA64Color{uint16(r), uint16(g), uint16(b), 0xffff}
	}
	if a == 0 {
		return NRGBA64Color{0, 0, 0, 0}
	}
	// Since Color.RGBA returns a alpha-premultiplied color, we should have r <= a && g <= a && b <= a.
	r = (r * 0xffff) / a
	g = (g * 0xffff) / a
	b = (b * 0xffff) / a
	return NRGBA64Color{uint16(r), uint16(g), uint16(b), uint16(a)}
}

func toAlphaColor(c Color) Color {
	if _, ok := c.(AlphaColor); ok {
		return c
	}
	_, _, _, a := c.RGBA()
	return AlphaColor{uint8(a >> 8)}
}

func toAlpha16Color(c Color) Color {
	if _, ok := c.(Alpha16Color); ok {
		return c
	}
	_, _, _, a := c.RGBA()
	return Alpha16Color{uint16(a)}
}

func toGrayColor(c Color) Color {
	if _, ok := c.(GrayColor); ok {
		return c
	}
	r, g, b, _ := c.RGBA()
	y := (299*r + 587*g + 114*b + 500) / 1000
	return GrayColor{uint8(y >> 8)}
}

func toGray16Color(c Color) Color {
	if _, ok := c.(Gray16Color); ok {
		return c
	}
	r, g, b, _ := c.RGBA()
	y := (299*r + 587*g + 114*b + 500) / 1000
	return Gray16Color{uint16(y)}
}

// The ColorModel associated with RGBAColor.
var RGBAColorModel ColorModel = ColorModelFunc(toRGBAColor)

// The ColorModel associated with RGBA64Color.
var RGBA64ColorModel ColorModel = ColorModelFunc(toRGBA64Color)

// The ColorModel associated with NRGBAColor.
var NRGBAColorModel ColorModel = ColorModelFunc(toNRGBAColor)

// The ColorModel associated with NRGBA64Color.
var NRGBA64ColorModel ColorModel = ColorModelFunc(toNRGBA64Color)

// The ColorModel associated with AlphaColor.
var AlphaColorModel ColorModel = ColorModelFunc(toAlphaColor)

// The ColorModel associated with Alpha16Color.
var Alpha16ColorModel ColorModel = ColorModelFunc(toAlpha16Color)

// The ColorModel associated with GrayColor.
var GrayColorModel ColorModel = ColorModelFunc(toGrayColor)

// The ColorModel associated with Gray16Color.
var Gray16ColorModel ColorModel = ColorModelFunc(toGray16Color)
<commit_msg>image: delete obsolete color.go<commit_after><|endoftext|>


<commit_before>package terminfo

import (
	"errors"
	"io"
	"log"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/nhooyr/terminfo/caps"
)

var (
	ErrSmallFile  = errors.New("terminfo: file too small")
	ErrBadString  = errors.New("terminfo: bad string")
	ErrBigSection = errors.New("terminfo: section too big")
	ErrBadHeader  = errors.New("terminfo: bad header")
)

// header represents a Terminfo file's header.
type header [5]int16

// No need to store magic.
const (
	lenNames = iota
	lenBools
	lenNumbers
	lenStrings
	lenTable
)

const (
	lenExtBools = iota
	lenExtNumbers
	lenExtStrings
	lenExtTable
	lastOff
)

// lenFile returns the length of the file the header describes in bytes.
func (h header) lenFile() int16 {
	return 2 + h.len() + h[lenNames] + h[lenBools] + h[lenNumbers]*2 + h[lenStrings]*2 + h[lenTable]
}

func (h header) lenExt() int16 {
	return h[lenExtBools] + h[lenExtNumbers]*2 + h[lenExtStrings]*2 + h[lenExtTable]
}

// len returns the length of the header in bytes.
func (h header) len() int16 {
	return int16(len(h) * 2)
}

// littleEndian decodes a int16 starting at i in buf using little-endian byte order.
func littleEndian(i int16, buf []byte) int16 {
	return int16(buf[i+1])<<8 | int16(buf[i])
}

type reader struct {
	pos int16
	buf []byte
	ti  *Terminfo
	// TODO: use pointers here or nah?
	h header
}

var readerPool = sync.Pool{
	New: func() interface{} {
		r := new(reader)
		// TODO: What is the max entry size talking about in terminfo(5)?
		r.buf = make([]byte, 4096)
		return r
	},
}

func (r *reader) sliceOff(off int16) []byte {
	// Just use off as ppos.
	off, r.pos = r.pos, r.pos+off
	return r.buf[off:r.pos]
}

// TODO read ncurses and find more sanity checks
func (r *reader) read(f *os.File) (err error) {
	log.SetFlags(0)
	fi, err := f.Stat()
	if err != nil {
		return
	}
	s := int(fi.Size())
	if s < len(r.h) {
		return ErrSmallFile
	}
	if s > cap(r.buf) {
		r.buf = make([]byte, s, s*2+1)
	} else {
		r.buf = r.buf[:s]
	}
	if _, err = io.ReadAtLeast(f, r.buf, s); err != nil {
		return
	}
	if littleEndian(0, r.buf) != 0x11A {
		return ErrBadHeader
	}
	r.pos = 2 // skip magic
	if err = r.readHeader(); err != nil {
		return
	}
	hl := int(r.h.lenFile())
	if s < hl {
		return ErrSmallFile
	}
	r.ti = new(Terminfo)
	r.ti.Names = strings.Split(string(r.sliceOff(r.h[lenNames])), "|")
	r.readBools()
	if r.pos%2 == 1 {
		// Skip extra null byte inserted to align everything on word boundaries.
		r.pos++
		hl++
	}
	r.readNumbers()
	if err = r.readStrings(); err != nil || s <= hl {
		return
	}
	if r.pos%2 == 1 {
		// Skip extra null byte inserted to align everything on word boundaries.
		r.pos++
		hl++
	}
	if err = r.readHeader(); err != nil {
		return
	}
	log.Println(r.h)
	r.ti.ExtBools = make(map[string]bool)
	for i, b := range r.sliceOff(r.h[lenExtBools]) {
		if b == 1 {
			r.ti.ExtBools[strconv.Itoa(i)] = true
		}
	}
	if r.h[lenExtBools]%2 == 1 {
		// Skip extra null byte inserted to align everything on word boundaries.
		r.pos++
		hl++
	}
	nbuf := r.sliceOff(r.h[lenExtNumbers] * 2)
	for i := int16(0); i < r.h[lenExtNumbers]; i++ {
		if n := littleEndian(i*2, nbuf); n > -1 {
			r.ti.ExtNumbers[strconv.Itoa(int(i))] = n
		}
	}
	log.Println(int(r.pos) - hl)
	sbuf := r.sliceOff(r.h[lenExtTable] * 2)
	log.Println(int(r.pos) - hl)
	table := r.sliceOff(r.h[lastOff])
	// log.Printf("%q\n\n", sbuf)
	log.Printf("%q\n\n", table)
	for i := int16(0); i < r.h[lenExtStrings]; i++ {
		if off := littleEndian(i*2, sbuf); off > -1 {
			j := off
			for {
				if j >= r.h[lenExtTable]+r.h[lastOff] {
					return ErrBadString
				}
				if table[j] == 0 {
					break
				}
				j++
			}
			log.Println(off, j)
			log.Printf("%q", string(table[off:j]))
		}
	}
	return
}

func (r *reader) readHeader() error {
	hbuf := r.sliceOff(r.h.len())
	for i := 0; i < len(r.h); i++ {
		n := littleEndian(int16(i*2), hbuf)
		if n < 0 {
			return ErrBadHeader
		}
		r.h[i] = n
	}
	return nil
}

func (r *reader) readBools() {
	if r.h[lenBools] >= caps.BoolCount {
		r.h[lenBools] = caps.BoolCount
	}
	for i, b := range r.sliceOff(r.h[lenBools]) {
		if b == 1 {
			r.ti.Bools[i] = true
		}
	}
}

func (r *reader) readNumbers() {
	if r.h[lenNumbers] >= caps.NumberCount {
		r.h[lenNumbers] = caps.NumberCount
	}
	nbuf := r.sliceOff(r.h[lenNumbers] * 2)
	for i := int16(0); i < r.h[lenNumbers]; i++ {
		if n := littleEndian(i*2, nbuf); n > -1 {
			r.ti.Numbers[i] = n
		}
	}
}

// readStrings reads the string and string table sections.
func (r *reader) readStrings() error {
	if r.h[lenStrings] >= caps.StringCount {
		r.h[lenStrings] = caps.StringCount
	}
	sbuf := r.sliceOff(r.h[lenStrings] * 2)
	table := r.sliceOff(r.h[lenTable])
	for i := int16(0); i < r.h[lenStrings]; i++ {
		if off := littleEndian(i*2, sbuf); off > -1 {
			j := off
			for {
				if j >= r.h[lenTable] {
					return ErrBadString
				}
				if table[j] == 0 {
					break
				}
				j++
			}
			r.ti.Strings[i] = string(table[off:j])
		}
	}
	return nil
}
<commit_msg>comment<commit_after>package terminfo

import (
	"errors"
	"io"
	"log"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/nhooyr/terminfo/caps"
)

var (
	ErrSmallFile  = errors.New("terminfo: file too small")
	ErrBadString  = errors.New("terminfo: bad string")
	ErrBigSection = errors.New("terminfo: section too big")
	ErrBadHeader  = errors.New("terminfo: bad header")
)

// header represents a Terminfo file's header.
type header [5]int16

// No need to store magic.
const (
	lenNames = iota
	lenBools
	lenNumbers
	lenStrings
	lenTable
)

const (
	lenExtBools = iota
	lenExtNumbers
	lenExtStrings
	lenExtTable
	lastOff
)

// lenFile returns the length of the file the header describes in bytes.
func (h header) lenFile() int16 {
	return 2 + h.len() + h[lenNames] + h[lenBools] + h[lenNumbers]*2 + h[lenStrings]*2 + h[lenTable]
}

func (h header) lenExt() int16 {
	return h[lenExtBools] + h[lenExtNumbers]*2 + h[lenExtStrings]*2 + h[lenExtTable]
}

// len returns the length of the header in bytes.
func (h header) len() int16 {
	return int16(len(h) * 2)
}

// littleEndian decodes a int16 starting at i in buf using little-endian byte order.
func littleEndian(i int16, buf []byte) int16 {
	return int16(buf[i+1])<<8 | int16(buf[i])
}

type reader struct {
	pos int16
	buf []byte
	ti  *Terminfo
	// TODO: use pointers here or nah?
	h header
}

var readerPool = sync.Pool{
	New: func() interface{} {
		r := new(reader)
		// TODO: What is the max entry size talking about in terminfo(5)?
		r.buf = make([]byte, 4096)
		return r
	},
}

func (r *reader) sliceOff(off int16) []byte {
	// Just use off as ppos.
	off, r.pos = r.pos, r.pos+off
	return r.buf[off:r.pos]
}

// TODO read ncurses and find more sanity checks
func (r *reader) read(f *os.File) (err error) {
	log.SetFlags(0)
	fi, err := f.Stat()
	if err != nil {
		return
	}
	s := int(fi.Size())
	if s < len(r.h) {
		return ErrSmallFile
	}
	if s > cap(r.buf) {
		r.buf = make([]byte, s, s*2+1)
	} else {
		r.buf = r.buf[:s]
	}
	if _, err = io.ReadAtLeast(f, r.buf, s); err != nil {
		return
	}
	if littleEndian(0, r.buf) != 0x11A {
		return ErrBadHeader
	}
	r.pos = 2 // skip magic
	if err = r.readHeader(); err != nil {
		return
	}
	hl := int(r.h.lenFile())
	if s < hl {
		return ErrSmallFile
	}
	r.ti = new(Terminfo)
	r.ti.Names = strings.Split(string(r.sliceOff(r.h[lenNames])), "|")
	r.readBools()
	if r.pos%2 == 1 {
		// Skip extra null byte inserted to align everything on word boundaries.
		r.pos++
		hl++
	}
	r.readNumbers()
	if err = r.readStrings(); err != nil || s <= hl {
		return
	}
	// HERE TO END IS THE EXTENDED READER
	if r.pos%2 == 1 {
		// Skip extra null byte inserted to align everything on word boundaries.
		r.pos++
		hl++
	}
	if err = r.readHeader(); err != nil {
		return
	}
	log.Println(r.h)
	r.ti.ExtBools = make(map[string]bool)
	for i, b := range r.sliceOff(r.h[lenExtBools]) {
		if b == 1 {
			r.ti.ExtBools[strconv.Itoa(i)] = true
		}
	}
	if r.h[lenExtBools]%2 == 1 {
		// Skip extra null byte inserted to align everything on word boundaries.
		r.pos++
		hl++
	}
	nbuf := r.sliceOff(r.h[lenExtNumbers] * 2)
	for i := int16(0); i < r.h[lenExtNumbers]; i++ {
		if n := littleEndian(i*2, nbuf); n > -1 {
			r.ti.ExtNumbers[strconv.Itoa(int(i))] = n
		}
	}
	log.Println(int(r.pos) - hl)
	sbuf := r.sliceOff(r.h[lenExtTable] * 2)
	log.Println(int(r.pos) - hl)
	table := r.sliceOff(r.h[lastOff])
	// log.Printf("%q\n\n", sbuf)
	log.Printf("%q\n\n", table)
	for i := int16(0); i < r.h[lenExtStrings]; i++ {
		if off := littleEndian(i*2, sbuf); off > -1 {
			j := off
			for {
				if j >= r.h[lenExtTable]+r.h[lastOff] {
					return ErrBadString
				}
				if table[j] == 0 {
					break
				}
				j++
			}
			log.Println(off, j)
			log.Printf("%q", string(table[off:j]))
		}
	}
	return
}

func (r *reader) readHeader() error {
	hbuf := r.sliceOff(r.h.len())
	for i := 0; i < len(r.h); i++ {
		n := littleEndian(int16(i*2), hbuf)
		if n < 0 {
			return ErrBadHeader
		}
		r.h[i] = n
	}
	return nil
}

func (r *reader) readBools() {
	if r.h[lenBools] >= caps.BoolCount {
		r.h[lenBools] = caps.BoolCount
	}
	for i, b := range r.sliceOff(r.h[lenBools]) {
		if b == 1 {
			r.ti.Bools[i] = true
		}
	}
}

func (r *reader) readNumbers() {
	if r.h[lenNumbers] >= caps.NumberCount {
		r.h[lenNumbers] = caps.NumberCount
	}
	nbuf := r.sliceOff(r.h[lenNumbers] * 2)
	for i := int16(0); i < r.h[lenNumbers]; i++ {
		if n := littleEndian(i*2, nbuf); n > -1 {
			r.ti.Numbers[i] = n
		}
	}
}

// readStrings reads the string and string table sections.
func (r *reader) readStrings() error {
	if r.h[lenStrings] >= caps.StringCount {
		r.h[lenStrings] = caps.StringCount
	}
	sbuf := r.sliceOff(r.h[lenStrings] * 2)
	table := r.sliceOff(r.h[lenTable])
	for i := int16(0); i < r.h[lenStrings]; i++ {
		if off := littleEndian(i*2, sbuf); off > -1 {
			j := off
			for {
				if j >= r.h[lenTable] {
					return ErrBadString
				}
				if table[j] == 0 {
					break
				}
				j++
			}
			r.ti.Strings[i] = string(table[off:j])
		}
	}
	return nil
}
<|endoftext|>