<commit_before>// Package api implements the HTTP API for Cassabon
package api

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/zenazn/goji/graceful"
	"github.com/zenazn/goji/web"

	"github.com/jeffpierce/cassabon/config"
)

type CassabonAPI struct {
	server   *web.Mux
	hostPort string
}

func (api *CassabonAPI) Start() {
	// Add to waitgroup and run go routine.
	api.hostPort = config.G.API.Listen
	config.G.OnReload1WG.Add(1)
	go api.run()
}

func (api *CassabonAPI) Stop() {
	config.G.Log.System.LogInfo("API received Stop command, gracefully shutting down.")
	graceful.Shutdown()
	config.G.OnReload1WG.Done()
}

func (api *CassabonAPI) run() {
	// Initialize API server
	api.server = web.New()

	// Define routes
	api.server.Get("/", api.rootHandler)
	api.server.Get("/paths", api.getPathHandler)
	api.server.Get("/metrics", api.getMetricHandler)
	api.server.Get("/healthcheck", api.healthHandler)
	api.server.Delete("/remove/path/:path", api.deletePathHandler)
	api.server.Delete("/remove/metric/:metric", api.deleteMetricHandler)
	api.server.NotFound(api.notFoundHandler)

	api.server.Use(requestLogger)

	config.G.Log.System.LogInfo("API initialized, serving!")
	graceful.ListenAndServe(api.hostPort, api.server)
}

// notFoundHandler is the global 404 handler, used by Goji.
func (api *CassabonAPI) notFoundHandler(w http.ResponseWriter, r *http.Request) {
	api.sendErrorResponse(w, http.StatusNotFound, "not found", r.RequestURI)
}

// healthHandler responds with either ALIVE or DEAD, for use by the load balancer.
func (api *CassabonAPI) healthHandler(w http.ResponseWriter, r *http.Request) {

	// We are alive, unless the healthcheck file says we are dead.
	var alive bool = true

	if health, err := ioutil.ReadFile(config.G.API.HealthCheckFile); err == nil {
		if strings.ToUpper(strings.TrimSpace(string(health))) == "DEAD" {
			alive = false
		}
	}

	if alive {
		fmt.Fprint(w, "ALIVE")
	} else {
		fmt.Fprint(w, "DEAD")
	}
}

// rootHandler provides information about the application, served from "/".
func (api *CassabonAPI) rootHandler(w http.ResponseWriter, r *http.Request) {
	resp := struct {
		Message string `json:"message"`
		Github  string `json:"github"`
		Version string `json:"version"`
	}{}
	resp.Message = "Cassabon.  You know, for stats!"
	resp.Github = "https://github.com/jeffpierce/cassabon"
	resp.Version = config.Version
	jsonText, _ := json.Marshal(resp)
	w.Write(jsonText)
}

// getPathHandler processes requests like "GET /paths?query=foo".
func (api *CassabonAPI) getPathHandler(w http.ResponseWriter, r *http.Request) {

	// Create the channel on which the response will be received.
	ch := make(chan config.DataQueryResponse)

	// Extract the query from the request URI.
	_ = r.ParseForm()
	q := config.DataQuery{r.Method, r.Form.Get("query"), ch}
	config.G.Log.System.LogDebug("Received query: %s", q.Query)

	// Forward the query.
	select {
	case config.G.Channels.IndexFetch <- q:
	default:
		config.G.Log.System.LogWarn(
			"Index query discarded, IndexFetch channel is full (max %d entries)",
			config.G.Channels.IndexFetchChanLen)
	}

	// Read the response.
	var resp config.DataQueryResponse
	select {
	case resp = <-q.Channel:
		// Nothing, we have our response.
	case <-time.After(time.Second):
		// The query died or wedged; simulate a timeout response.
		resp = config.DataQueryResponse{config.DQS_ERROR, "query timed out", []byte{}}
	}
	close(q.Channel)

	// Send the response to the client.
	api.sendResponse(w, resp)
}

// deletePathHandler removes paths from the index store.
func (api *CassabonAPI) deletePathHandler(c web.C, w http.ResponseWriter, r *http.Request) {
	// TODO:  Implement this in datastore.  c.URLParams["path"]
	api.sendErrorResponse(w, http.StatusNotImplemented, "not implemented", "")
}

// getMetricHandler processes requests like "GET /metrics?query=foo".
func (api *CassabonAPI) getMetricHandler(w http.ResponseWriter, r *http.Request) {

	// Create the channel on which the response will be received.
	ch := make(chan config.DataQueryResponse)

	// Extract the query from the request URI.
	_ = r.ParseForm()
	q := config.DataQuery{r.Method, r.Form.Get("query"), ch}
	config.G.Log.System.LogDebug("Received query: %s", q.Query)

	// Forward the query.
	select {
	case config.G.Channels.DataFetch <- q:
	default:
		config.G.Log.System.LogWarn(
			"Metrics query discarded, DataFetch channel is full (max %d entries)",
			config.G.Channels.DataFetchChanLen)
	}

	// Read the response.
	var resp config.DataQueryResponse
	select {
	case resp = <-q.Channel:
		// Nothing, we have our response.
	case <-time.After(time.Second):
		// The query died or wedged; simulate a timeout response.
		resp = config.DataQueryResponse{config.DQS_ERROR, "query timed out", []byte{}}
	}
	close(q.Channel)

	// Send the response to the client.
	api.sendResponse(w, resp)
}

// deleteMetricHandler removes data from the metrics store.
func (api *CassabonAPI) deleteMetricHandler(c web.C, w http.ResponseWriter, r *http.Request) {
	// TODO:  Implement this in datastore.  c.URLParams["metric"]
	api.sendErrorResponse(w, http.StatusNotImplemented, "not implemented", "")
}

func (api *CassabonAPI) sendResponse(w http.ResponseWriter, resp config.DataQueryResponse) {
	switch resp.Status {
	case config.DQS_OK:
		if len(resp.Payload) > 0 {
			w.Write(resp.Payload)
		} else {
			w.WriteHeader(http.StatusNoContent)
		}
	case config.DQS_NOTFOUND:
		api.sendErrorResponse(w, http.StatusNotFound, "not found", resp.Message)
	case config.DQS_BADREQUEST:
		api.sendErrorResponse(w, http.StatusBadRequest, "bad request", resp.Message)
	case config.DQS_ERROR:
		api.sendErrorResponse(w, http.StatusInternalServerError, "internal error", resp.Message)
	}
}

func (api *CassabonAPI) sendErrorResponse(w http.ResponseWriter, status int, text string, message string) {

	resp := struct {
		Status     int    `json:"status"`
		StatusText string `json:"statustext"`
		Message    string `json:"message"`
	}{}

	resp.Status = status
	resp.StatusText = text
	resp.Message = message
	jsonText, _ := json.Marshal(resp)

	w.WriteHeader(status)
	w.Write(jsonText)
}
<commit_msg>Refactor out duplicate code that reads the response channel<commit_after>// Package api implements the HTTP API for Cassabon
package api

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/zenazn/goji/graceful"
	"github.com/zenazn/goji/web"

	"github.com/jeffpierce/cassabon/config"
)

type CassabonAPI struct {
	server   *web.Mux
	hostPort string
}

func (api *CassabonAPI) Start() {
	// Add to waitgroup and run go routine.
	api.hostPort = config.G.API.Listen
	config.G.OnReload1WG.Add(1)
	go api.run()
}

func (api *CassabonAPI) Stop() {
	config.G.Log.System.LogInfo("API received Stop command, gracefully shutting down.")
	graceful.Shutdown()
	config.G.OnReload1WG.Done()
}

func (api *CassabonAPI) run() {
	// Initialize API server
	api.server = web.New()

	// Define routes
	api.server.Get("/", api.rootHandler)
	api.server.Get("/paths", api.getPathHandler)
	api.server.Get("/metrics", api.getMetricHandler)
	api.server.Get("/healthcheck", api.healthHandler)
	api.server.Delete("/remove/path/:path", api.deletePathHandler)
	api.server.Delete("/remove/metric/:metric", api.deleteMetricHandler)
	api.server.NotFound(api.notFoundHandler)

	api.server.Use(requestLogger)

	config.G.Log.System.LogInfo("API initialized, serving!")
	graceful.ListenAndServe(api.hostPort, api.server)
}

// notFoundHandler is the global 404 handler, used by Goji.
func (api *CassabonAPI) notFoundHandler(w http.ResponseWriter, r *http.Request) {
	api.sendErrorResponse(w, http.StatusNotFound, "not found", r.RequestURI)
}

// healthHandler responds with either ALIVE or DEAD, for use by the load balancer.
func (api *CassabonAPI) healthHandler(w http.ResponseWriter, r *http.Request) {

	// We are alive, unless the healthcheck file says we are dead.
	var alive bool = true

	if health, err := ioutil.ReadFile(config.G.API.HealthCheckFile); err == nil {
		if strings.ToUpper(strings.TrimSpace(string(health))) == "DEAD" {
			alive = false
		}
	}

	if alive {
		fmt.Fprint(w, "ALIVE")
	} else {
		fmt.Fprint(w, "DEAD")
	}
}

// rootHandler provides information about the application, served from "/".
func (api *CassabonAPI) rootHandler(w http.ResponseWriter, r *http.Request) {
	resp := struct {
		Message string `json:"message"`
		Github  string `json:"github"`
		Version string `json:"version"`
	}{}
	resp.Message = "Cassabon.  You know, for stats!"
	resp.Github = "https://github.com/jeffpierce/cassabon"
	resp.Version = config.Version
	jsonText, _ := json.Marshal(resp)
	w.Write(jsonText)
}

// getPathHandler processes requests like "GET /paths?query=foo".
func (api *CassabonAPI) getPathHandler(w http.ResponseWriter, r *http.Request) {

	// Create the channel on which the response will be received.
	ch := make(chan config.DataQueryResponse)

	// Extract the query from the request URI.
	_ = r.ParseForm()
	q := config.DataQuery{r.Method, r.Form.Get("query"), ch}
	config.G.Log.System.LogDebug("Received query: %s", q.Query)

	// Forward the query.
	select {
	case config.G.Channels.IndexFetch <- q:
	default:
		config.G.Log.System.LogWarn(
			"Index query discarded, IndexFetch channel is full (max %d entries)",
			config.G.Channels.IndexFetchChanLen)
	}

	// Send the response to the client.
	api.sendResponse(w, q)
}

// deletePathHandler removes paths from the index store.
func (api *CassabonAPI) deletePathHandler(c web.C, w http.ResponseWriter, r *http.Request) {
	// TODO:  Implement this in datastore.  c.URLParams["path"]
	api.sendErrorResponse(w, http.StatusNotImplemented, "not implemented", "")
}

// getMetricHandler processes requests like "GET /metrics?query=foo".
func (api *CassabonAPI) getMetricHandler(w http.ResponseWriter, r *http.Request) {

	// Create the channel on which the response will be received.
	ch := make(chan config.DataQueryResponse)

	// Extract the query from the request URI.
	_ = r.ParseForm()
	q := config.DataQuery{r.Method, r.Form.Get("query"), ch}
	config.G.Log.System.LogDebug("Received query: %s", q.Query)

	// Forward the query.
	select {
	case config.G.Channels.DataFetch <- q:
	default:
		config.G.Log.System.LogWarn(
			"Metrics query discarded, DataFetch channel is full (max %d entries)",
			config.G.Channels.DataFetchChanLen)
	}

	// Send the response to the client.
	api.sendResponse(w, q)
}

// deleteMetricHandler removes data from the metrics store.
func (api *CassabonAPI) deleteMetricHandler(c web.C, w http.ResponseWriter, r *http.Request) {
	// TODO:  Implement this in datastore.  c.URLParams["metric"]
	api.sendErrorResponse(w, http.StatusNotImplemented, "not implemented", "")
}

func (api *CassabonAPI) sendResponse(w http.ResponseWriter, q config.DataQuery) {

	// Read the response.
	var resp config.DataQueryResponse
	select {
	case resp = <-q.Channel:
		// Nothing, we have our response.
	case <-time.After(time.Second):
		// The query died or wedged; simulate a timeout response.
		resp = config.DataQueryResponse{config.DQS_ERROR, "query timed out", []byte{}}
	}
	close(q.Channel)

	// Inspect the response status, and send appropriate response headers/data to client.
	switch resp.Status {
	case config.DQS_OK:
		if len(resp.Payload) > 0 {
			w.Write(resp.Payload)
		} else {
			w.WriteHeader(http.StatusNoContent)
		}
	case config.DQS_NOTFOUND:
		api.sendErrorResponse(w, http.StatusNotFound, "not found", resp.Message)
	case config.DQS_BADREQUEST:
		api.sendErrorResponse(w, http.StatusBadRequest, "bad request", resp.Message)
	case config.DQS_ERROR:
		api.sendErrorResponse(w, http.StatusInternalServerError, "internal error", resp.Message)
	}
}

func (api *CassabonAPI) sendErrorResponse(w http.ResponseWriter, status int, text string, message string) {

	resp := struct {
		Status     int    `json:"status"`
		StatusText string `json:"statustext"`
		Message    string `json:"message"`
	}{}

	resp.Status = status
	resp.StatusText = text
	resp.Message = message
	jsonText, _ := json.Marshal(resp)

	w.WriteHeader(status)
	w.Write(jsonText)
}
<|endoftext|>


<commit_before>package backoff

import (
	"math"
	"math/rand"
	"time"
)

type (
	// Backoff is the interface to a backoff interval generator.
	Backoff interface {
		// Mark the next call to NextInterval as the "first" retry in a sequence.
		// If the generated intervals are dependent on the number of consecutive
		// (unsuccessful) retries, previous retries should be forgotten here.
		Reset()

		// Generate the next backoff interval.
		NextInterval() time.Duration
	}

	linearBackoff struct {
		minInterval time.Duration
		addInterval time.Duration
		maxInterval time.Duration
		current     time.Duration
	}

	exponentialBackoff struct {
		minInterval time.Duration
		maxInterval time.Duration
		multiplier  float64
		randFactor  float64
		attempts    uint
		maxAttempts uint
	}

	// ExponentialConfigFunc is a function used to initialize a new exponential backoff.
	ExponentialConfigFunc func(*exponentialBackoff)
)

// NewZeroBackoff creates a backoff interval generator which always returns
// a zero interval.
func NewZeroBackoff() Backoff {
	return NewConstantBackoff(0)
}

// NewConstantBackoff creates a backoff interval generator which always returns
// the same interval.
func NewConstantBackoff(interval time.Duration) Backoff {
	return NewLinearBackoff(interval, 0, interval)
}

// NewLinearBackoff creates a backoff interval generator which increases by a
// constant amount on each unsuccessful retry.
func NewLinearBackoff(minInterval, addInterval, maxInterval time.Duration) Backoff {
	b := &linearBackoff{
		minInterval: minInterval,
		addInterval: addInterval,
		maxInterval: maxInterval,
	}

	b.Reset()
	return b
}

func (b *linearBackoff) Reset() {
	b.current = b.minInterval
}

func (b *linearBackoff) NextInterval() time.Duration {
	current := b.current

	if current <= b.maxInterval-b.addInterval {
		b.current += b.addInterval
	} else {
		b.current = b.maxInterval
	}

	return current
}

//
// Exponential

// NewExponentialBackoff creates an exponential backoff interval generator using
// the given minimum and maximum interval. The base interval is given by the
// following function where n is the number of previous failed attempts in the
// current sequence.
//
//    `MinInterval * Multiplier ^ n`
//
// The value returned on each update is given by the following, where base is the
// value calculated above. A random factor of zero makes the generator deterministic.
// Some random jitter tends to work well in practice to avoid issues around a  thundering herd.
//
//     `min(MaxInterval, base +/- (base * RandFactor))`.
func NewExponentialBackoff(minInterval, maxInterval time.Duration, configs ...ExponentialConfigFunc) Backoff {
	backoff := &exponentialBackoff{
		minInterval: minInterval,
		maxInterval: maxInterval,
		multiplier:  2,
		randFactor:  0,
		attempts:    0,
	}

	for _, config := range configs {
		config(backoff)
	}

	// Calculate and stash the maximum number of attempts now. This may be
	// expensive as it involves logs. We need to know the max number of
	// attempts now so we can shield ourselves from overflow when dealing
	// with larger intervals.

	// min * mult ^ n <     max
	//       mult ^ n <     max / min
	//              n < log(max / min) / log(mult)

	var (
		num         = math.Log(float64(maxInterval / minInterval))
		denom       = math.Log(backoff.multiplier)
		maxAttempts = uint(num / denom)
	)

	backoff.maxAttempts = maxAttempts
	return backoff
}

// WithMultiplier sets the base of the exponential function (default is 2).
func WithMultiplier(multiplier float64) ExponentialConfigFunc {
	return func(b *exponentialBackoff) { b.multiplier = multiplier }
}

// WithRandomFactor sets the random factor (default is 0, no randomness).
func WithRandomFactor(randFactor float64) ExponentialConfigFunc {
	return func(b *exponentialBackoff) { b.randFactor = randFactor }
}

func (b *exponentialBackoff) Reset() {
	b.attempts = 0
}

func (b *exponentialBackoff) NextInterval() time.Duration {
	if b.attempts >= b.maxAttempts {
		return b.maxInterval
	}

	n := float64(b.attempts)
	b.attempts++

	return time.Duration(jitter(float64(b.minInterval)*math.Pow(b.multiplier, n), b.randFactor))
}

func jitter(value, ratio float64) float64 {
	min := value - (value * ratio)
	max := value + (value * ratio)

	return min + (max-min+1)*rand.Float64()
}
<commit_msg>Add clone method.<commit_after>package backoff

import (
	"math"
	"math/rand"
	"time"
)

type (
	// Backoff is the interface to a backoff interval generator.
	Backoff interface {
		// Mark the next call to NextInterval as the "first" retry in a sequence.
		// If the generated intervals are dependent on the number of consecutive
		// (unsuccessful) retries, previous retries should be forgotten here.
		Reset()

		// Generate the next backoff interval.
		NextInterval() time.Duration

		// Clone creates a copy of the backoff with a nil-internal state. This
		// allows a backoff object to be used as a prototype factory.
		Clone() Backoff
	}

	linearBackoff struct {
		minInterval time.Duration
		addInterval time.Duration
		maxInterval time.Duration
		current     time.Duration
	}

	exponentialBackoff struct {
		minInterval time.Duration
		maxInterval time.Duration
		multiplier  float64
		randFactor  float64
		attempts    uint
		maxAttempts uint
	}

	// ExponentialConfigFunc is a function used to initialize a new exponential backoff.
	ExponentialConfigFunc func(*exponentialBackoff)
)

// NewZeroBackoff creates a backoff interval generator which always returns
// a zero interval.
func NewZeroBackoff() Backoff {
	return NewConstantBackoff(0)
}

// NewConstantBackoff creates a backoff interval generator which always returns
// the same interval.
func NewConstantBackoff(interval time.Duration) Backoff {
	return NewLinearBackoff(interval, 0, interval)
}

// NewLinearBackoff creates a backoff interval generator which increases by a
// constant amount on each unsuccessful retry.
func NewLinearBackoff(minInterval, addInterval, maxInterval time.Duration) Backoff {
	b := &linearBackoff{
		minInterval: minInterval,
		addInterval: addInterval,
		maxInterval: maxInterval,
	}

	b.Reset()
	return b
}

func (b *linearBackoff) Reset() {
	b.current = b.minInterval
}

func (b *linearBackoff) NextInterval() time.Duration {
	current := b.current

	if current <= b.maxInterval-b.addInterval {
		b.current += b.addInterval
	} else {
		b.current = b.maxInterval
	}

	return current
}

func (b *linearBackoff) Clone() Backoff {
	return NewLinearBackoff(b.minInterval, b.addInterval, b.maxInterval)
}

//
// Exponential

// NewExponentialBackoff creates an exponential backoff interval generator using
// the given minimum and maximum interval. The base interval is given by the
// following function where n is the number of previous failed attempts in the
// current sequence.
//
//    `MinInterval * Multiplier ^ n`
//
// The value returned on each update is given by the following, where base is the
// value calculated above. A random factor of zero makes the generator deterministic.
// Some random jitter tends to work well in practice to avoid issues around a  thundering herd.
//
//     `min(MaxInterval, base +/- (base * RandFactor))`.
func NewExponentialBackoff(minInterval, maxInterval time.Duration, configs ...ExponentialConfigFunc) Backoff {
	backoff := &exponentialBackoff{
		minInterval: minInterval,
		maxInterval: maxInterval,
		multiplier:  2,
		randFactor:  0,
		attempts:    0,
	}

	for _, config := range configs {
		config(backoff)
	}

	// Calculate and stash the maximum number of attempts now. This may be
	// expensive as it involves logs. We need to know the max number of
	// attempts now so we can shield ourselves from overflow when dealing
	// with larger intervals.

	// min * mult ^ n <     max
	//       mult ^ n <     max / min
	//              n < log(max / min) / log(mult)

	var (
		num         = math.Log(float64(maxInterval / minInterval))
		denom       = math.Log(backoff.multiplier)
		maxAttempts = uint(num / denom)
	)

	backoff.maxAttempts = maxAttempts
	return backoff
}

// WithMultiplier sets the base of the exponential function (default is 2).
func WithMultiplier(multiplier float64) ExponentialConfigFunc {
	return func(b *exponentialBackoff) { b.multiplier = multiplier }
}

// WithRandomFactor sets the random factor (default is 0, no randomness).
func WithRandomFactor(randFactor float64) ExponentialConfigFunc {
	return func(b *exponentialBackoff) { b.randFactor = randFactor }
}

func (b *exponentialBackoff) Reset() {
	b.attempts = 0
}

func (b *exponentialBackoff) NextInterval() time.Duration {
	if b.attempts >= b.maxAttempts {
		return b.maxInterval
	}

	n := float64(b.attempts)
	b.attempts++

	return time.Duration(jitter(float64(b.minInterval)*math.Pow(b.multiplier, n), b.randFactor))
}

func (b *exponentialBackoff) Clone() Backoff {
	return &exponentialBackoff{
		minInterval: b.minInterval,
		maxInterval: b.maxInterval,
		multiplier:  b.multiplier,
		randFactor:  b.randFactor,
		maxAttempts: b.maxAttempts,
	}
}

func jitter(value, ratio float64) float64 {
	min := value - (value * ratio)
	max := value + (value * ratio)

	return min + (max-min+1)*rand.Float64()
}
<|endoftext|>


<commit_before>package apiauth

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"log"
	"net/http"
	"strings"
	"time"
)

var gmt *time.Location

func init() {
	loc, err := time.LoadLocation("Etc/GMT")
	if err != nil {
		log.Panic("apiauth: Can not load timezone Etc/GMT: ", err)
	}
	gmt = loc
}

// Sign computes the signature for the given HTTP request, and
// adds the resulting Authorization header value to it. If any
// of the prerequisite headers are absent, an error is returned.
func Sign(r *http.Request, accessID, secret string) error {
	if err := sufficientHeaders(r); err != nil {
		return err
	}

	preexisting := r.Header.Get("Authorization")
	if preexisting != "" {
		return fmt.Errorf("Authorization header already present")
	}

	sig := Compute(CanonicalString(r), secret)
	r.Header.Set("Authorization", fmt.Sprintf("APIAuth %s:%s", accessID, sig))

	return nil
}

// SignWithMethod computs the signature of the given HTTP request
// as in Sign except that the canonical string includes the HTTP
// request method.
func SignWithMethod(r *http.Request, accessID, secret string) error {
	if err := sufficientHeaders(r); err != nil {
		return err
	}

	preexisting := r.Header.Get("Authorization")
	if preexisting != "" {
		return fmt.Errorf("Authorization header already present")
	}

	sig := Compute(CanonicalStringWithMethod(r), secret)
	r.Header.Set("Authorization", fmt.Sprintf("APIAuth %s:%s", accessID, sig))

	return nil
}

// Verify checks a request for validity: all required headers
// are present and the signature matches.
func Verify(r *http.Request, secret string) error {
	if err := sufficientHeaders(r); err != nil {
		return err
	}

	auth := r.Header.Get("Authorization")
	if auth == "" {
		return fmt.Errorf("Authorization header not set")
	}

	_, sig, err := Parse(auth)
	if err != nil {
		return err
	}

	if VerifySignature(sig, CanonicalString(r), secret) || VerifySignature(sig, CanonicalStringWithMethod(r), secret) {
		return nil
	}

	return fmt.Errorf("Signature mismatch")
}

// VerifySignature computes the expected signature for a given
// canonical string and secret key pair, and returns true if the
// given signature matches.
func VerifySignature(sig, canonicalString, secret string) bool {
	expected := Compute(canonicalString, secret)
	return expected == sig
}

// Parse returns the access ID and signature present in the
// given string, presumably taken from a request's Authorization
// header. If the header does not match the expected `APIAuth access_id:signature`
// format, an error is returned.
func Parse(header string) (id, sig string, err error) {
	var tokens []string

	if !strings.HasPrefix(header, "APIAuth ") {
		goto malformed
	}

	tokens = strings.Split(header[8:], ":")
	if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" {
		goto malformed
	}

	return tokens[0], tokens[1], nil

malformed:
	return "", "", fmt.Errorf("Malformed header: %s", header)
}

// Date returns a suitable value for a request's Date header,
// based on the current time in GMT in RFC1123 format.
func Date() string {
	return DateForTime(time.Now())
}

// DateForTime converts the given time to GMT, and returns it
// in RFC1123 format. I would rather this use UTC, but Ruby's
// `Time#httpdate` spits out GMT, and I need to maintain
// fairly rigid compatibility.
func DateForTime(t time.Time) string {
	return t.In(gmt).Format(time.RFC1123)
}

// CanonicalString returns the canonical string used for the signature
// based on the headers in the given request.
func CanonicalString(r *http.Request) string {
	uri := r.URL.EscapedPath()
	if uri == "" {
		uri = "/"
	}

	if r.URL.RawQuery != "" {
		uri = uri + "?" + r.URL.RawQuery
	}

	header := r.Header

	return strings.Join([]string{
		header.Get("Content-Type"),
		header.Get("Content-MD5"),
		uri,
		header.Get("Date"),
	}, ",")
}

// CanonicalStringWithMethod returns a canonical string as in CanonicalString
// but also includes the request method
func CanonicalStringWithMethod(r *http.Request) string {
	return strings.Join([]string{
		strings.ToUpper(r.Method),
		CanonicalString(r),
	}, ",")
}

// Compute computes the signature for a given canonical string, using
// the HMAC-SHA1.
func Compute(canonicalString, secret string) string {
	mac := hmac.New(sha1.New, []byte(secret))
	mac.Write([]byte(canonicalString))
	return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}

func sufficientHeaders(r *http.Request) error {
	date := r.Header.Get("Date")
	if date == "" {
		return fmt.Errorf("No Date header present")
	}

	if r.Body != nil {
		contentType := r.Header.Get("Content-Type")
		if contentType == "" {
			return fmt.Errorf("No Content-Type header present")
		}

		contentMD5 := r.Header.Get("Content-MD5")
		if contentMD5 == "" {
			return fmt.Errorf("No Content-MD5 header present")
		}
	}

	return nil
}
<commit_msg>Make error message more descriptive<commit_after>package apiauth

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"log"
	"net/http"
	"strings"
	"time"
)

var gmt *time.Location

func init() {
	loc, err := time.LoadLocation("Etc/GMT")
	if err != nil {
		log.Fatalf("Failed to load apiauth -  Can not load timezone Etc/GMT: %s. See https://golang.org/pkg/time/#LoadLocation", err.Error())
	}
	gmt = loc
}

// Sign computes the signature for the given HTTP request, and
// adds the resulting Authorization header value to it. If any
// of the prerequisite headers are absent, an error is returned.
func Sign(r *http.Request, accessID, secret string) error {
	if err := sufficientHeaders(r); err != nil {
		return err
	}

	preexisting := r.Header.Get("Authorization")
	if preexisting != "" {
		return fmt.Errorf("Authorization header already present")
	}

	sig := Compute(CanonicalString(r), secret)
	r.Header.Set("Authorization", fmt.Sprintf("APIAuth %s:%s", accessID, sig))

	return nil
}

// SignWithMethod computs the signature of the given HTTP request
// as in Sign except that the canonical string includes the HTTP
// request method.
func SignWithMethod(r *http.Request, accessID, secret string) error {
	if err := sufficientHeaders(r); err != nil {
		return err
	}

	preexisting := r.Header.Get("Authorization")
	if preexisting != "" {
		return fmt.Errorf("Authorization header already present")
	}

	sig := Compute(CanonicalStringWithMethod(r), secret)
	r.Header.Set("Authorization", fmt.Sprintf("APIAuth %s:%s", accessID, sig))

	return nil
}

// Verify checks a request for validity: all required headers
// are present and the signature matches.
func Verify(r *http.Request, secret string) error {
	if err := sufficientHeaders(r); err != nil {
		return err
	}

	auth := r.Header.Get("Authorization")
	if auth == "" {
		return fmt.Errorf("Authorization header not set")
	}

	_, sig, err := Parse(auth)
	if err != nil {
		return err
	}

	if VerifySignature(sig, CanonicalString(r), secret) || VerifySignature(sig, CanonicalStringWithMethod(r), secret) {
		return nil
	}

	return fmt.Errorf("Signature mismatch")
}

// VerifySignature computes the expected signature for a given
// canonical string and secret key pair, and returns true if the
// given signature matches.
func VerifySignature(sig, canonicalString, secret string) bool {
	expected := Compute(canonicalString, secret)
	return expected == sig
}

// Parse returns the access ID and signature present in the
// given string, presumably taken from a request's Authorization
// header. If the header does not match the expected `APIAuth access_id:signature`
// format, an error is returned.
func Parse(header string) (id, sig string, err error) {
	var tokens []string

	if !strings.HasPrefix(header, "APIAuth ") {
		goto malformed
	}

	tokens = strings.Split(header[8:], ":")
	if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" {
		goto malformed
	}

	return tokens[0], tokens[1], nil

malformed:
	return "", "", fmt.Errorf("Malformed header: %s", header)
}

// Date returns a suitable value for a request's Date header,
// based on the current time in GMT in RFC1123 format.
func Date() string {
	return DateForTime(time.Now())
}

// DateForTime converts the given time to GMT, and returns it
// in RFC1123 format. I would rather this use UTC, but Ruby's
// `Time#httpdate` spits out GMT, and I need to maintain
// fairly rigid compatibility.
func DateForTime(t time.Time) string {
	return t.In(gmt).Format(time.RFC1123)
}

// CanonicalString returns the canonical string used for the signature
// based on the headers in the given request.
func CanonicalString(r *http.Request) string {
	uri := r.URL.EscapedPath()
	if uri == "" {
		uri = "/"
	}

	if r.URL.RawQuery != "" {
		uri = uri + "?" + r.URL.RawQuery
	}

	header := r.Header

	return strings.Join([]string{
		header.Get("Content-Type"),
		header.Get("Content-MD5"),
		uri,
		header.Get("Date"),
	}, ",")
}

// CanonicalStringWithMethod returns a canonical string as in CanonicalString
// but also includes the request method
func CanonicalStringWithMethod(r *http.Request) string {
	return strings.Join([]string{
		strings.ToUpper(r.Method),
		CanonicalString(r),
	}, ",")
}

// Compute computes the signature for a given canonical string, using
// the HMAC-SHA1.
func Compute(canonicalString, secret string) string {
	mac := hmac.New(sha1.New, []byte(secret))
	mac.Write([]byte(canonicalString))
	return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}

func sufficientHeaders(r *http.Request) error {
	date := r.Header.Get("Date")
	if date == "" {
		return fmt.Errorf("No Date header present")
	}

	if r.Body != nil {
		contentType := r.Header.Get("Content-Type")
		if contentType == "" {
			return fmt.Errorf("No Content-Type header present")
		}

		contentMD5 := r.Header.Get("Content-MD5")
		if contentMD5 == "" {
			return fmt.Errorf("No Content-MD5 header present")
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package app

import (
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"math/big"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"text/template"

	"github.com/braintree/manners"
	"github.com/elazarl/go-bindata-assetfs"
	"github.com/gorilla/websocket"
	"github.com/kr/pty"
	"github.com/yudai/hcl"
	"github.com/yudai/umutex"
)

type InitMessage struct {
	Arguments string `json:"Arguments,omitempty"`
	AuthToken string `json:"AuthToken,omitempty"`
}

type App struct {
	command []string
	options *Options

	upgrader *websocket.Upgrader
	server   *manners.GracefulServer

	titleTemplate *template.Template

	onceMutex *umutex.UnblockingMutex
}

type Options struct {
	Address             string                 `hcl:"address"`
	Port                string                 `hcl:"port"`
	PermitWrite         bool                   `hcl:"permit_write"`
	EnableBasicAuth     bool                   `hcl:"enable_basic_auth"`
	Credential          string                 `hcl:"credential"`
	EnableRandomUrl     bool                   `hcl:"enable_random_url"`
	RandomUrlLength     int                    `hcl:"random_url_length"`
	IndexFile           string                 `hcl:"index_file"`
	EnableTLS           bool                   `hcl:"enable_tls"`
	TLSCrtFile          string                 `hcl:"tls_crt_file"`
	TLSKeyFile          string                 `hcl:"tls_key_file"`
	EnableTLSClientAuth bool                   `hcl:"enable_tls_client_auth"`
	TLSCACrtFile        string                 `hcl:"tls_ca_crt_file"`
	TitleFormat         string                 `hcl:"title_format"`
	EnableReconnect     bool                   `hcl:"enable_reconnect"`
	ReconnectTime       int                    `hcl:"reconnect_time"`
	Once                bool                   `hcl:"once"`
	PermitArguments     bool                   `hcl:"permit_arguments"`
	Preferences         HtermPrefernces        `hcl:"preferences"`
	RawPreferences      map[string]interface{} `hcl:"preferences"`
}

var Version = "0.0.11"

var DefaultOptions = Options{
	Address:             "",
	Port:                "8080",
	PermitWrite:         false,
	EnableBasicAuth:     false,
	Credential:          "",
	EnableRandomUrl:     false,
	RandomUrlLength:     8,
	IndexFile:           "",
	EnableTLS:           false,
	TLSCrtFile:          "~/.gotty.crt",
	TLSKeyFile:          "~/.gotty.key",
	EnableTLSClientAuth: false,
	TLSCACrtFile:        "~/.gotty.ca.crt",
	TitleFormat:         "GoTTY - {{ .Command }} ({{ .Hostname }})",
	EnableReconnect:     false,
	ReconnectTime:       10,
	Once:                false,
	Preferences:         HtermPrefernces{},
}

func New(command []string, options *Options) (*App, error) {
	titleTemplate, err := template.New("title").Parse(options.TitleFormat)
	if err != nil {
		return nil, errors.New("Title format string syntax error")
	}

	return &App{
		command: command,
		options: options,

		upgrader: &websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
			Subprotocols:    []string{"gotty"},
		},

		titleTemplate: titleTemplate,

		onceMutex: umutex.New(),
	}, nil
}

func ApplyConfigFile(options *Options, filePath string) error {
	filePath = ExpandHomeDir(filePath)
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return err
	}

	fileString := []byte{}
	log.Printf("Loading config file at: %s", filePath)
	fileString, err := ioutil.ReadFile(filePath)
	if err != nil {
		return err
	}

	if err := hcl.Decode(options, string(fileString)); err != nil {
		return err
	}

	return nil
}

func CheckConfig(options *Options) error {
	if options.EnableTLSClientAuth && !options.EnableTLS {
		return errors.New("TLS client authentication is enabled, but TLS is not enabled")
	}
	return nil
}

func (app *App) Run() error {
	if app.options.PermitWrite {
		log.Printf("Permitting clients to write input to the PTY.")
	}

	if app.options.Once {
		log.Printf("Once option is provided, accepting only one client")
	}

	path := ""
	if app.options.EnableRandomUrl {
		path += "/" + generateRandomString(app.options.RandomUrlLength)
	}

	endpoint := net.JoinHostPort(app.options.Address, app.options.Port)

	wsHandler := http.HandlerFunc(app.handleWS)
	customIndexHandler := http.HandlerFunc(app.handleCustomIndex)
	authTokenHandler := http.HandlerFunc(app.handleAuthToken)
	staticHandler := http.FileServer(
		&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "static"},
	)

	var siteMux = http.NewServeMux()

	if app.options.IndexFile != "" {
		log.Printf("Using index file at " + app.options.IndexFile)
		siteMux.Handle(path+"/", customIndexHandler)
	} else {
		siteMux.Handle(path+"/", http.StripPrefix(path+"/", staticHandler))
	}
	siteMux.Handle(path+"/auth_token.js", authTokenHandler)
	siteMux.Handle(path+"/js/", http.StripPrefix(path+"/", staticHandler))
	siteMux.Handle(path+"/favicon.png", http.StripPrefix(path+"/", staticHandler))

	siteHandler := http.Handler(siteMux)

	if app.options.EnableBasicAuth {
		log.Printf("Using Basic Authentication")
		siteHandler = wrapBasicAuth(siteHandler, app.options.Credential)
	}

	siteHandler = wrapHeaders(siteHandler)

	wsMux := http.NewServeMux()
	wsMux.Handle("/", siteHandler)
	wsMux.Handle(path+"/ws", wsHandler)
	siteHandler = (http.Handler(wsMux))

	siteHandler = wrapLogger(siteHandler)

	scheme := "http"
	if app.options.EnableTLS {
		scheme = "https"
	}
	log.Printf(
		"Server is starting with command: %s",
		strings.Join(app.command, " "),
	)
	if app.options.Address != "" {
		log.Printf(
			"URL: %s",
			(&url.URL{Scheme: scheme, Host: endpoint, Path: path + "/"}).String(),
		)
	} else {
		for _, address := range listAddresses() {
			log.Printf(
				"URL: %s",
				(&url.URL{
					Scheme: scheme,
					Host:   net.JoinHostPort(address, app.options.Port),
					Path:   path + "/",
				}).String(),
			)
		}
	}

	server, err := app.makeServer(endpoint, &siteHandler)
	if err != nil {
		return errors.New("Failed to build server: " + err.Error())
	}
	app.server = manners.NewWithServer(
		server,
	)

	if app.options.EnableTLS {
		crtFile := ExpandHomeDir(app.options.TLSCrtFile)
		keyFile := ExpandHomeDir(app.options.TLSKeyFile)
		log.Printf("TLS crt file: " + crtFile)
		log.Printf("TLS key file: " + keyFile)

		err = app.server.ListenAndServeTLS(crtFile, keyFile)
	} else {
		err = app.server.ListenAndServe()
	}
	if err != nil {
		return err
	}

	log.Printf("Exiting...")

	return nil
}

func (app *App) makeServer(addr string, handler *http.Handler) (*http.Server, error) {
	server := &http.Server{
		Addr:    addr,
		Handler: *handler,
	}

	if app.options.EnableTLSClientAuth {
		caFile := ExpandHomeDir(app.options.TLSCACrtFile)
		log.Printf("CA file: " + caFile)
		caCert, err := ioutil.ReadFile(caFile)
		if err != nil {
			return nil, errors.New("Could not open CA crt file " + caFile)
		}
		caCertPool := x509.NewCertPool()
		if !caCertPool.AppendCertsFromPEM(caCert) {
			return nil, errors.New("Could not parse CA crt file data in " + caFile)
		}
		tlsConfig := &tls.Config{
			ClientCAs:  caCertPool,
			ClientAuth: tls.RequireAndVerifyClientCert,
		}
		server.TLSConfig = tlsConfig
	}

	return server, nil
}

func (app *App) handleWS(w http.ResponseWriter, r *http.Request) {
	log.Printf("New client connected: %s", r.RemoteAddr)

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	conn, err := app.upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("Failed to upgrade connection: " + err.Error())
		return
	}

	_, stream, err := conn.ReadMessage()
	if err != nil {
		log.Print("Failed to authenticate websocket connection")
		conn.Close()
		return
	}
	var init InitMessage

	err = json.Unmarshal(stream, &init)
	if err != nil {
		log.Printf("Failed to parse init message %v", err)
		conn.Close()
		return
	}
	if init.AuthToken != app.options.Credential {
		log.Print("Failed to authenticate websocket connection")
		conn.Close()
		return
	}
	argv := app.command[1:]
	if app.options.PermitArguments {
		if init.Arguments == "" {
			init.Arguments = "?"
		}
		query, err := url.Parse(init.Arguments)
		if err != nil {
			log.Print("Failed to parse arguments")
			conn.Close()
			return
		}
		params := query.Query()["arg"]
		if len(params) != 0 {
			argv = append(argv, params...)
		}
	}

	app.server.StartRoutine()

	if app.options.Once {
		if app.onceMutex.TryLock() { // no unlock required, it will die soon
			log.Printf("Last client accepted, closing the listener.")
			app.server.Close()
		} else {
			log.Printf("Server is already closing.")
			conn.Close()
			return
		}
	}

	cmd := exec.Command(app.command[0], argv...)
	ptyIo, err := pty.Start(cmd)
	if err != nil {
		log.Print("Failed to execute command")
		return
	}
	log.Printf("Command is running for client %s with PID %d", r.RemoteAddr, cmd.Process.Pid)

	context := &clientContext{
		app:        app,
		request:    r,
		connection: conn,
		command:    cmd,
		pty:        ptyIo,
		writeMutex: &sync.Mutex{},
	}

	context.goHandleClient()
}

func (app *App) handleCustomIndex(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, ExpandHomeDir(app.options.IndexFile))
}

func (app *App) handleAuthToken(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("var gotty_auth_token = '" + app.options.Credential + "';"))
}

func (app *App) Exit() (firstCall bool) {
	if app.server != nil {
		firstCall = app.server.Close()
		if firstCall {
			log.Printf("Received Exit command, waiting for all clients to close sessions...")
		}
		return firstCall
	}
	return true
}

func wrapLogger(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		rw := &responseWrapper{w, 200}
		handler.ServeHTTP(rw, r)
		log.Printf("%s %d %s %s", r.RemoteAddr, rw.status, r.Method, r.URL.Path)
	})
}

func wrapHeaders(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Server", "GoTTY/"+Version)
		handler.ServeHTTP(w, r)
	})
}

func wrapBasicAuth(handler http.Handler, credential string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		token := strings.SplitN(r.Header.Get("Authorization"), " ", 2)

		if len(token) != 2 || strings.ToLower(token[0]) != "basic" {
			w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
			http.Error(w, "Bad Request", http.StatusUnauthorized)
			return
		}

		payload, err := base64.StdEncoding.DecodeString(token[1])
		if err != nil {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			return
		}

		if credential != string(payload) {
			w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
			http.Error(w, "authorization failed", http.StatusUnauthorized)
			return
		}

		log.Printf("Basic Authentication Succeeded: %s", r.RemoteAddr)
		handler.ServeHTTP(w, r)
	})
}

func generateRandomString(length int) string {
	const base = 36
	size := big.NewInt(base)
	n := make([]byte, length)
	for i, _ := range n {
		c, _ := rand.Int(rand.Reader, size)
		n[i] = strconv.FormatInt(c.Int64(), base)[0]
	}
	return string(n)
}

func listAddresses() (addresses []string) {
	ifaces, _ := net.Interfaces()

	addresses = make([]string, 0, len(ifaces))

	for _, iface := range ifaces {
		ifAddrs, _ := iface.Addrs()
		for _, ifAddr := range ifAddrs {
			switch v := ifAddr.(type) {
			case *net.IPNet:
				addresses = append(addresses, v.IP.String())
			case *net.IPAddr:
				addresses = append(addresses, v.IP.String())
			}
		}
	}

	return
}

func ExpandHomeDir(path string) string {
	if path[0:2] == "~/" {
		return os.Getenv("HOME") + path[1:]
	} else {
		return path
	}
}
<commit_msg>Log passed arguments<commit_after>package app

import (
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"math/big"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"text/template"

	"github.com/braintree/manners"
	"github.com/elazarl/go-bindata-assetfs"
	"github.com/gorilla/websocket"
	"github.com/kr/pty"
	"github.com/yudai/hcl"
	"github.com/yudai/umutex"
)

type InitMessage struct {
	Arguments string `json:"Arguments,omitempty"`
	AuthToken string `json:"AuthToken,omitempty"`
}

type App struct {
	command []string
	options *Options

	upgrader *websocket.Upgrader
	server   *manners.GracefulServer

	titleTemplate *template.Template

	onceMutex *umutex.UnblockingMutex
}

type Options struct {
	Address             string                 `hcl:"address"`
	Port                string                 `hcl:"port"`
	PermitWrite         bool                   `hcl:"permit_write"`
	EnableBasicAuth     bool                   `hcl:"enable_basic_auth"`
	Credential          string                 `hcl:"credential"`
	EnableRandomUrl     bool                   `hcl:"enable_random_url"`
	RandomUrlLength     int                    `hcl:"random_url_length"`
	IndexFile           string                 `hcl:"index_file"`
	EnableTLS           bool                   `hcl:"enable_tls"`
	TLSCrtFile          string                 `hcl:"tls_crt_file"`
	TLSKeyFile          string                 `hcl:"tls_key_file"`
	EnableTLSClientAuth bool                   `hcl:"enable_tls_client_auth"`
	TLSCACrtFile        string                 `hcl:"tls_ca_crt_file"`
	TitleFormat         string                 `hcl:"title_format"`
	EnableReconnect     bool                   `hcl:"enable_reconnect"`
	ReconnectTime       int                    `hcl:"reconnect_time"`
	Once                bool                   `hcl:"once"`
	PermitArguments     bool                   `hcl:"permit_arguments"`
	Preferences         HtermPrefernces        `hcl:"preferences"`
	RawPreferences      map[string]interface{} `hcl:"preferences"`
}

var Version = "0.0.11"

var DefaultOptions = Options{
	Address:             "",
	Port:                "8080",
	PermitWrite:         false,
	EnableBasicAuth:     false,
	Credential:          "",
	EnableRandomUrl:     false,
	RandomUrlLength:     8,
	IndexFile:           "",
	EnableTLS:           false,
	TLSCrtFile:          "~/.gotty.crt",
	TLSKeyFile:          "~/.gotty.key",
	EnableTLSClientAuth: false,
	TLSCACrtFile:        "~/.gotty.ca.crt",
	TitleFormat:         "GoTTY - {{ .Command }} ({{ .Hostname }})",
	EnableReconnect:     false,
	ReconnectTime:       10,
	Once:                false,
	Preferences:         HtermPrefernces{},
}

func New(command []string, options *Options) (*App, error) {
	titleTemplate, err := template.New("title").Parse(options.TitleFormat)
	if err != nil {
		return nil, errors.New("Title format string syntax error")
	}

	return &App{
		command: command,
		options: options,

		upgrader: &websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
			Subprotocols:    []string{"gotty"},
		},

		titleTemplate: titleTemplate,

		onceMutex: umutex.New(),
	}, nil
}

func ApplyConfigFile(options *Options, filePath string) error {
	filePath = ExpandHomeDir(filePath)
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return err
	}

	fileString := []byte{}
	log.Printf("Loading config file at: %s", filePath)
	fileString, err := ioutil.ReadFile(filePath)
	if err != nil {
		return err
	}

	if err := hcl.Decode(options, string(fileString)); err != nil {
		return err
	}

	return nil
}

func CheckConfig(options *Options) error {
	if options.EnableTLSClientAuth && !options.EnableTLS {
		return errors.New("TLS client authentication is enabled, but TLS is not enabled")
	}
	return nil
}

func (app *App) Run() error {
	if app.options.PermitWrite {
		log.Printf("Permitting clients to write input to the PTY.")
	}

	if app.options.Once {
		log.Printf("Once option is provided, accepting only one client")
	}

	path := ""
	if app.options.EnableRandomUrl {
		path += "/" + generateRandomString(app.options.RandomUrlLength)
	}

	endpoint := net.JoinHostPort(app.options.Address, app.options.Port)

	wsHandler := http.HandlerFunc(app.handleWS)
	customIndexHandler := http.HandlerFunc(app.handleCustomIndex)
	authTokenHandler := http.HandlerFunc(app.handleAuthToken)
	staticHandler := http.FileServer(
		&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "static"},
	)

	var siteMux = http.NewServeMux()

	if app.options.IndexFile != "" {
		log.Printf("Using index file at " + app.options.IndexFile)
		siteMux.Handle(path+"/", customIndexHandler)
	} else {
		siteMux.Handle(path+"/", http.StripPrefix(path+"/", staticHandler))
	}
	siteMux.Handle(path+"/auth_token.js", authTokenHandler)
	siteMux.Handle(path+"/js/", http.StripPrefix(path+"/", staticHandler))
	siteMux.Handle(path+"/favicon.png", http.StripPrefix(path+"/", staticHandler))

	siteHandler := http.Handler(siteMux)

	if app.options.EnableBasicAuth {
		log.Printf("Using Basic Authentication")
		siteHandler = wrapBasicAuth(siteHandler, app.options.Credential)
	}

	siteHandler = wrapHeaders(siteHandler)

	wsMux := http.NewServeMux()
	wsMux.Handle("/", siteHandler)
	wsMux.Handle(path+"/ws", wsHandler)
	siteHandler = (http.Handler(wsMux))

	siteHandler = wrapLogger(siteHandler)

	scheme := "http"
	if app.options.EnableTLS {
		scheme = "https"
	}
	log.Printf(
		"Server is starting with command: %s",
		strings.Join(app.command, " "),
	)
	if app.options.Address != "" {
		log.Printf(
			"URL: %s",
			(&url.URL{Scheme: scheme, Host: endpoint, Path: path + "/"}).String(),
		)
	} else {
		for _, address := range listAddresses() {
			log.Printf(
				"URL: %s",
				(&url.URL{
					Scheme: scheme,
					Host:   net.JoinHostPort(address, app.options.Port),
					Path:   path + "/",
				}).String(),
			)
		}
	}

	server, err := app.makeServer(endpoint, &siteHandler)
	if err != nil {
		return errors.New("Failed to build server: " + err.Error())
	}
	app.server = manners.NewWithServer(
		server,
	)

	if app.options.EnableTLS {
		crtFile := ExpandHomeDir(app.options.TLSCrtFile)
		keyFile := ExpandHomeDir(app.options.TLSKeyFile)
		log.Printf("TLS crt file: " + crtFile)
		log.Printf("TLS key file: " + keyFile)

		err = app.server.ListenAndServeTLS(crtFile, keyFile)
	} else {
		err = app.server.ListenAndServe()
	}
	if err != nil {
		return err
	}

	log.Printf("Exiting...")

	return nil
}

func (app *App) makeServer(addr string, handler *http.Handler) (*http.Server, error) {
	server := &http.Server{
		Addr:    addr,
		Handler: *handler,
	}

	if app.options.EnableTLSClientAuth {
		caFile := ExpandHomeDir(app.options.TLSCACrtFile)
		log.Printf("CA file: " + caFile)
		caCert, err := ioutil.ReadFile(caFile)
		if err != nil {
			return nil, errors.New("Could not open CA crt file " + caFile)
		}
		caCertPool := x509.NewCertPool()
		if !caCertPool.AppendCertsFromPEM(caCert) {
			return nil, errors.New("Could not parse CA crt file data in " + caFile)
		}
		tlsConfig := &tls.Config{
			ClientCAs:  caCertPool,
			ClientAuth: tls.RequireAndVerifyClientCert,
		}
		server.TLSConfig = tlsConfig
	}

	return server, nil
}

func (app *App) handleWS(w http.ResponseWriter, r *http.Request) {
	log.Printf("New client connected: %s", r.RemoteAddr)

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	conn, err := app.upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("Failed to upgrade connection: " + err.Error())
		return
	}

	_, stream, err := conn.ReadMessage()
	if err != nil {
		log.Print("Failed to authenticate websocket connection")
		conn.Close()
		return
	}
	var init InitMessage

	err = json.Unmarshal(stream, &init)
	if err != nil {
		log.Printf("Failed to parse init message %v", err)
		conn.Close()
		return
	}
	if init.AuthToken != app.options.Credential {
		log.Print("Failed to authenticate websocket connection")
		conn.Close()
		return
	}
	argv := app.command[1:]
	if app.options.PermitArguments {
		if init.Arguments == "" {
			init.Arguments = "?"
		}
		query, err := url.Parse(init.Arguments)
		if err != nil {
			log.Print("Failed to parse arguments")
			conn.Close()
			return
		}
		params := query.Query()["arg"]
		if len(params) != 0 {
			log.Printf("%s passed arguments are: %q", r.RemoteAddr, strings.Join(params, " "))
			argv = append(argv, params...)
		}
	}

	app.server.StartRoutine()

	if app.options.Once {
		if app.onceMutex.TryLock() { // no unlock required, it will die soon
			log.Printf("Last client accepted, closing the listener.")
			app.server.Close()
		} else {
			log.Printf("Server is already closing.")
			conn.Close()
			return
		}
	}

	cmd := exec.Command(app.command[0], argv...)
	ptyIo, err := pty.Start(cmd)
	if err != nil {
		log.Print("Failed to execute command")
		return
	}
	log.Printf("Command is running for client %s with PID %d", r.RemoteAddr, cmd.Process.Pid)

	context := &clientContext{
		app:        app,
		request:    r,
		connection: conn,
		command:    cmd,
		pty:        ptyIo,
		writeMutex: &sync.Mutex{},
	}

	context.goHandleClient()
}

func (app *App) handleCustomIndex(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, ExpandHomeDir(app.options.IndexFile))
}

func (app *App) handleAuthToken(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("var gotty_auth_token = '" + app.options.Credential + "';"))
}

func (app *App) Exit() (firstCall bool) {
	if app.server != nil {
		firstCall = app.server.Close()
		if firstCall {
			log.Printf("Received Exit command, waiting for all clients to close sessions...")
		}
		return firstCall
	}
	return true
}

func wrapLogger(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		rw := &responseWrapper{w, 200}
		handler.ServeHTTP(rw, r)
		log.Printf("%s %d %s %s", r.RemoteAddr, rw.status, r.Method, r.URL.Path)
	})
}

func wrapHeaders(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Server", "GoTTY/"+Version)
		handler.ServeHTTP(w, r)
	})
}

func wrapBasicAuth(handler http.Handler, credential string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		token := strings.SplitN(r.Header.Get("Authorization"), " ", 2)

		if len(token) != 2 || strings.ToLower(token[0]) != "basic" {
			w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
			http.Error(w, "Bad Request", http.StatusUnauthorized)
			return
		}

		payload, err := base64.StdEncoding.DecodeString(token[1])
		if err != nil {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			return
		}

		if credential != string(payload) {
			w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
			http.Error(w, "authorization failed", http.StatusUnauthorized)
			return
		}

		log.Printf("Basic Authentication Succeeded: %s", r.RemoteAddr)
		handler.ServeHTTP(w, r)
	})
}

func generateRandomString(length int) string {
	const base = 36
	size := big.NewInt(base)
	n := make([]byte, length)
	for i, _ := range n {
		c, _ := rand.Int(rand.Reader, size)
		n[i] = strconv.FormatInt(c.Int64(), base)[0]
	}
	return string(n)
}

func listAddresses() (addresses []string) {
	ifaces, _ := net.Interfaces()

	addresses = make([]string, 0, len(ifaces))

	for _, iface := range ifaces {
		ifAddrs, _ := iface.Addrs()
		for _, ifAddr := range ifAddrs {
			switch v := ifAddr.(type) {
			case *net.IPNet:
				addresses = append(addresses, v.IP.String())
			case *net.IPAddr:
				addresses = append(addresses, v.IP.String())
			}
		}
	}

	return
}

func ExpandHomeDir(path string) string {
	if path[0:2] == "~/" {
		return os.Getenv("HOME") + path[1:]
	} else {
		return path
	}
}
<|endoftext|>


<commit_before>// +build linux,arm

package library

import (
	"github.com/mrmorphic/hwio"                // hwio
	"github.com/nytlabs/streamtools/st/blocks" // blocks
	"github.com/nytlabs/streamtools/st/util"
)

// specify those channels we're going to use to communicate with streamtools
type AnalogPin struct {
	blocks.Block
	queryrule chan chan interface{}
	inrule    chan interface{}
	inpoll    chan interface{}
	out       chan interface{}
	quit      chan interface{}
}

// we need to build a simple factory so that streamtools can make new blocks of this kind
func NewAnalogPin() blocks.BlockInterface {
	return &AnalogPin{}
}

// Setup is called once before running the block. We build up the channels and specify what kind of block this is.
func (b *AnalogPin) Setup() {
	b.Kind = "AnalogPin"
	b.inrule = b.InRoute("rule")
	b.inpoll = b.InRoute("poll")
	b.queryrule = b.QueryRoute("rule")
	b.quit = b.Quit()
	b.out = b.Broadcast()
}

// Run is the block's main loop. Here we listen on the different channels we set up.
func (b *AnalogPin) Run() {
	var pin hwio.Pin
	var pinStr string
	var err error
	// Get the module
	m, e := hwio.GetAnalogModule()
	if e != nil {
		b.Log(e)
	}
	// Enable it.
	e = m.Enable()
	if e != nil {
		b.Log(e)
	}
	for {
		select {
		case ruleI := <-b.inrule:
			// set a parameter of the block
			rule, ok := ruleI.(map[string]interface{})
			if !ok {
				b.Error("couldn't conver rule to map")
				continue
			}
			if pinStr != "" {
				err = hwio.ClosePin(pin)
				b.Error(err)
			}
			pinStr, err = util.ParseString(rule, "Pin")
			if err != nil {
				b.Error(err)
				continue
			}
			pin, err = hwio.GetPin(pinStr)
			if err != nil {
				b.Error(err)
				continue
			}
			err = hwio.PinMode(pin, hwio.INPUT)
			if err != nil {
				b.Error(err)
				continue
			}
		case <-b.quit:
			// quit the block
			err = hwio.ClosePin(pin)
			b.Error(err)
			return
		case c := <-b.queryrule:
			// deal with a query request
			c <- map[string]interface{}{
				"Pin": pinStr,
			}

		case <-b.inpoll:
			if pin == 0 {
				continue
			}
			v, err := hwio.AnalogRead(pin)
			if err != nil {
				b.Error(err)
				continue
			}
			out := map[string]interface{}{
				"value": float64(v),
				"pin":   pinStr,
			}
			b.out <- out
		}
	}
}
<commit_msg>changed build constraint to refer to 'arm' instead of 'linux,arm'<commit_after>// +build arm

package library

import (
	"github.com/mrmorphic/hwio"                // hwio
	"github.com/nytlabs/streamtools/st/blocks" // blocks
	"github.com/nytlabs/streamtools/st/util"
)

// specify those channels we're going to use to communicate with streamtools
type AnalogPin struct {
	blocks.Block
	queryrule chan chan interface{}
	inrule    chan interface{}
	inpoll    chan interface{}
	out       chan interface{}
	quit      chan interface{}
}

// we need to build a simple factory so that streamtools can make new blocks of this kind
func NewAnalogPin() blocks.BlockInterface {
	return &AnalogPin{}
}

// Setup is called once before running the block. We build up the channels and specify what kind of block this is.
func (b *AnalogPin) Setup() {
	b.Kind = "AnalogPin"
	b.inrule = b.InRoute("rule")
	b.inpoll = b.InRoute("poll")
	b.queryrule = b.QueryRoute("rule")
	b.quit = b.Quit()
	b.out = b.Broadcast()
}

// Run is the block's main loop. Here we listen on the different channels we set up.
func (b *AnalogPin) Run() {
	var pin hwio.Pin
	var pinStr string
	var err error
	// Get the module
	m, e := hwio.GetAnalogModule()
	if e != nil {
		b.Log(e)
	}
	// Enable it.
	e = m.Enable()
	if e != nil {
		b.Log(e)
	}
	for {
		select {
		case ruleI := <-b.inrule:
			// set a parameter of the block
			rule, ok := ruleI.(map[string]interface{})
			if !ok {
				b.Error("couldn't conver rule to map")
				continue
			}
			if pinStr != "" {
				err = hwio.ClosePin(pin)
				b.Error(err)
			}
			pinStr, err = util.ParseString(rule, "Pin")
			if err != nil {
				b.Error(err)
				continue
			}
			pin, err = hwio.GetPin(pinStr)
			if err != nil {
				b.Error(err)
				continue
			}
			err = hwio.PinMode(pin, hwio.INPUT)
			if err != nil {
				b.Error(err)
				continue
			}
		case <-b.quit:
			// quit the block
			err = hwio.ClosePin(pin)
			b.Error(err)
			return
		case c := <-b.queryrule:
			// deal with a query request
			c <- map[string]interface{}{
				"Pin": pinStr,
			}

		case <-b.inpoll:
			if pin == 0 {
				continue
			}
			v, err := hwio.AnalogRead(pin)
			if err != nil {
				b.Error(err)
				continue
			}
			out := map[string]interface{}{
				"value": float64(v),
				"pin":   pinStr,
			}
			b.out <- out
		}
	}
}
<|endoftext|>


<commit_before>package jsonpath

import (
	"bytes"
	"encoding/json"
	"fmt"
)

type Slice struct{ start, stop int }

type pathTypeMismatch struct{}
type notFound struct{}
type unknownPathType struct{}

func recursiveGet(data interface{}, path []interface{}) interface{} {

	if len(path) == 0 {
		switch data.(type) {
		case string:
			return data.(string)
		case float64:
			return data.(float64)
		case bool:
			return data.(bool)
		case nil:
			return nil
		case []interface{}:
			return data
		case map[string]interface{}:
			return data
		}
	}

	switch path[0].(type) {

	case string:
		switch data.(type) {
		case map[string]interface{}:
			for k, v := range data.(map[string]interface{}) {
				if k == path[0].(string) {
					return recursiveGet(v, path[1:])
				}
			}
			return notFound{}
		default:
			return pathTypeMismatch{}
		}

	case int:
		switch data.(type) {
		case []interface{}:
			for i, v := range data.([]interface{}) {
				if i == path[0].(int) {
					return recursiveGet(v, path[1:])
				}
			}
			return notFound{}
		default:
			return pathTypeMismatch{}
		}

	case func(int, interface{}) bool:
		switch data.(type) {
		case []interface{}:
			ret := make([]interface{}, 0)
			for i, v := range data.([]interface{}) {
				if path[0].(func(int, interface{}) bool)(i, v) {
					val := recursiveGet(v, path[1:])
					switch val.(type) {
					case pathTypeMismatch, notFound:
						continue
					case unknownPathType:
						return val
					}
					ret = append(ret, val)
				}
			}
			return ret
		}

	case func(string, interface{}) bool:
		switch data.(type) {
		case map[string]interface{}:
			ret := make([]interface{}, 0)
			for k, v := range data.(map[string]interface{}) {
				if path[0].(func(string, interface{}) bool)(k, v) {
					val := recursiveGet(v, path[1:])
					switch val.(type) {
					case pathTypeMismatch, notFound:
						continue
					case unknownPathType:
						return val
					}
					ret = append(ret, val)
				}
			}
			return ret
		}

	case Slice:
		switch data.(type) {
		case []interface{}:
			ret := make([]interface{}, 0)
			start := path[0].(Slice).start
			stop := path[0].(Slice).stop
			for i, v := range data.([]interface{}) {
				if start <= i && i < stop {
					ret = append(ret, recursiveGet(v, path[1:]))
				}
			}
			return ret
		}
	}

	return unknownPathType{}

}

func Get(decoded interface{}, path []interface{}, defaultValue interface{}) (interface{}, error) {
	val := recursiveGet(decoded, path)
	switch val.(type) {
	case notFound:
		return defaultValue, fmt.Errorf("not found")
	case unknownPathType:
		return defaultValue, fmt.Errorf("unknown path type")
	case pathTypeMismatch:
		return defaultValue, fmt.Errorf("mismatched path type")
	}
	return val, nil
}

func GetString(decoded interface{}, path []interface{}, defaultValue string) (string, error) {
	value, err := Get(decoded, path, nil)
	if err != nil {
		return defaultValue, err
	}
	switch value.(type) {
	case string:
		return value.(string), nil
	}
	return defaultValue, fmt.Errorf("unexpected type")
}

func GetNumber(decoded interface{}, path []interface{}, defaultValue float64) (float64, error) {
	value, err := Get(decoded, path, nil)
	if err != nil {
		return defaultValue, err
	}
	switch value.(type) {
	case float64:
		return value.(float64), nil
	}
	return defaultValue, fmt.Errorf("unexpected type")
}

func GetBool(decoded interface{}, path []interface{}, defaultValue bool) (bool, error) {
	value, err := Get(decoded, path, nil)
	if err != nil {
		return defaultValue, err
	}
	switch value.(type) {
	case bool:
		return value.(bool), nil
	}
	return defaultValue, fmt.Errorf("unexpected type")
}

func DecodeJsonString(s string) (interface{}, error) {
	r := bytes.NewBufferString(s)
	var data interface{}
	dec := json.NewDecoder(r)
	err := dec.Decode(&data)
	return data, err
}

func Read(s string, path []interface{}, defaultValue interface{}) (interface{}, error) {
	data, err := DecodeJsonString(s)
	if err != nil {
		return nil, err
	}
	return Get(data, path, defaultValue)
}

func ReadString(s string, path []interface{}, defaultValue string) (string, error) {
	data, err := DecodeJsonString(s)
	if err != nil {
		return defaultValue, err
	}
	return GetString(data, path, defaultValue)
}

func ReadNumber(s string, path []interface{}, defaultValue float64) (float64, error) {
	data, err := DecodeJsonString(s)
	if err != nil {
		return defaultValue, err
	}
	return GetNumber(data, path, defaultValue)
}

func ReadBool(s string, path []interface{}, defaultValue bool) (bool, error) {
	data, err := DecodeJsonString(s)
	if err != nil {
		return defaultValue, err
	}
	return GetBool(data, path, defaultValue)
}
<commit_msg>Added interface to io.reader.<commit_after>package jsonpath

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
)

type Slice struct{ start, stop int }

type pathTypeMismatch struct{}
type notFound struct{}
type unknownPathType struct{}

func recursiveGet(data interface{}, path []interface{}) interface{} {

	if len(path) == 0 {
		switch data.(type) {
		case string:
			return data.(string)
		case float64:
			return data.(float64)
		case bool:
			return data.(bool)
		case nil:
			return nil
		case []interface{}:
			return data
		case map[string]interface{}:
			return data
		}
	}

	switch path[0].(type) {

	case string:
		switch data.(type) {
		case map[string]interface{}:
			for k, v := range data.(map[string]interface{}) {
				if k == path[0].(string) {
					return recursiveGet(v, path[1:])
				}
			}
			return notFound{}
		default:
			return pathTypeMismatch{}
		}

	case int:
		switch data.(type) {
		case []interface{}:
			for i, v := range data.([]interface{}) {
				if i == path[0].(int) {
					return recursiveGet(v, path[1:])
				}
			}
			return notFound{}
		default:
			return pathTypeMismatch{}
		}

	case func(int, interface{}) bool:
		switch data.(type) {
		case []interface{}:
			ret := make([]interface{}, 0)
			for i, v := range data.([]interface{}) {
				if path[0].(func(int, interface{}) bool)(i, v) {
					val := recursiveGet(v, path[1:])
					switch val.(type) {
					case pathTypeMismatch, notFound:
						continue
					case unknownPathType:
						return val
					}
					ret = append(ret, val)
				}
			}
			return ret
		}

	case func(string, interface{}) bool:
		switch data.(type) {
		case map[string]interface{}:
			ret := make([]interface{}, 0)
			for k, v := range data.(map[string]interface{}) {
				if path[0].(func(string, interface{}) bool)(k, v) {
					val := recursiveGet(v, path[1:])
					switch val.(type) {
					case pathTypeMismatch, notFound:
						continue
					case unknownPathType:
						return val
					}
					ret = append(ret, val)
				}
			}
			return ret
		}

	case Slice:
		switch data.(type) {
		case []interface{}:
			ret := make([]interface{}, 0)
			start := path[0].(Slice).start
			stop := path[0].(Slice).stop
			for i, v := range data.([]interface{}) {
				if start <= i && i < stop {
					ret = append(ret, recursiveGet(v, path[1:]))
				}
			}
			return ret
		}
	}

	return unknownPathType{}

}

func Get(decoded interface{}, path []interface{}, defaultValue interface{}) (interface{}, error) {
	val := recursiveGet(decoded, path)
	switch val.(type) {
	case notFound:
		return defaultValue, fmt.Errorf("not found")
	case unknownPathType:
		return defaultValue, fmt.Errorf("unknown path type")
	case pathTypeMismatch:
		return defaultValue, fmt.Errorf("mismatched path type")
	}
	return val, nil
}

func GetString(decoded interface{}, path []interface{}, defaultValue string) (string, error) {
	value, err := Get(decoded, path, nil)
	if err != nil {
		return defaultValue, err
	}
	switch value.(type) {
	case string:
		return value.(string), nil
	}
	return defaultValue, fmt.Errorf("unexpected type")
}

func GetNumber(decoded interface{}, path []interface{}, defaultValue float64) (float64, error) {
	value, err := Get(decoded, path, nil)
	if err != nil {
		return defaultValue, err
	}
	switch value.(type) {
	case float64:
		return value.(float64), nil
	}
	return defaultValue, fmt.Errorf("unexpected type")
}

func GetBool(decoded interface{}, path []interface{}, defaultValue bool) (bool, error) {
	value, err := Get(decoded, path, nil)
	if err != nil {
		return defaultValue, err
	}
	switch value.(type) {
	case bool:
		return value.(bool), nil
	}
	return defaultValue, fmt.Errorf("unexpected type")
}

func DecodeJsonString(s string) (interface{}, error) {
	r := bytes.NewBufferString(s)
	return DecodeJsonReader(r)
}

func DecodeJsonReader(r io.Reader) (interface{}, error) {
	var data interface{}
	dec := json.NewDecoder(r)
	err := dec.Decode(&data)
	return data, err
}

func Read(s string, path []interface{}, defaultValue interface{}) (interface{}, error) {
	data, err := DecodeJsonString(s)
	if err != nil {
		return nil, err
	}
	return Get(data, path, defaultValue)
}

func ReadString(s string, path []interface{}, defaultValue string) (string, error) {
	data, err := DecodeJsonString(s)
	if err != nil {
		return defaultValue, err
	}
	return GetString(data, path, defaultValue)
}

func ReadNumber(s string, path []interface{}, defaultValue float64) (float64, error) {
	data, err := DecodeJsonString(s)
	if err != nil {
		return defaultValue, err
	}
	return GetNumber(data, path, defaultValue)
}

func ReadBool(s string, path []interface{}, defaultValue bool) (bool, error) {
	data, err := DecodeJsonString(s)
	if err != nil {
		return defaultValue, err
	}
	return GetBool(data, path, defaultValue)
}
<|endoftext|>


<commit_before>package market

import "os"

func GetTestClientID() string {
	cid := os.Getenv("PAYPAL_CLIENT_ID")
	if len(cid) == 0 {
		panic("PAYPAL_CLIENT_ID environment variable is not set, but is needed to run tests!\n")
	}
	return cid
}

func GetTestSecret() string {
	secret := os.Getenv("PAYPAL_SECRET")
	if len(secret) == 0 {
		panic("PAYPAL_SECRET environment variable is not set, but is needed to run tests!\n")
	}
	return secret
}

func GetTestBNCode() string {
	bnCode := os.Getenv("PAYPAL_BN_CODE")
	if len(bnCode) == 0 {
		panic("PAYPAL_BN_CODE environment variable is not set, but is needed to run tests!\n")
	}
	return bnCode
}
<commit_msg>BN code is optional<commit_after>package market

import "os"

func GetTestClientID() string {
	cid := os.Getenv("PAYPAL_CLIENT_ID")
	if len(cid) == 0 {
		panic("PAYPAL_CLIENT_ID environment variable is not set, but is needed to run tests!\n")
	}
	return cid
}

func GetTestSecret() string {
	secret := os.Getenv("PAYPAL_SECRET")
	if len(secret) == 0 {
		panic("PAYPAL_SECRET environment variable is not set, but is needed to run tests!\n")
	}
	return secret
}

func GetTestBNCode() string {
	return os.Getenv("PAYPAL_BN_CODE")
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"log"
	"path"
	"strconv"
	"strings"
	"time"
	"unicode"

	"github.com/russross/blackfriday"
)

type PageType int

const (
	Post PageType = iota
	Page
	Snippet
)

type Article struct {
	Author       string
	DateModified *time.Time
	DateUpdated  *time.Time
	Title        string
	Content      string
	Description  string
	Filename     string
	Link         string
	Identifier   string
	Snippet      bool
	Type         PageType
	Draft        bool
	Tags         []string
	AppID        string
}

func (a Article) HasTag(aTag string) bool {
	for _, tag := range a.Tags {
		if aTag == tag {
			return true
		}
	}

	return false
}

func (a Article) BasePath() string {
	switch a.Type {
	case Post, Snippet:
		return path.Join(strconv.Itoa(a.DateModified.Year()), fmt.Sprintf("%02d", int(a.DateModified.Month())))
	case Page:
		return ""
	}

	return ""
}

func (a Article) FullPath() string {
	return path.Join(a.BasePath(), a.Filename)
}

type Articles []*Article

func (a Articles) Len() int {
	return len(a)
}

func (a Articles) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func (a Articles) Less(i, j int) bool {
	left := *(a[i].DateModified)
	right := *(a[j].DateModified)

	return right.Before(left)
}

func (a Article) Print() {
	fmt.Println("---")

	var articleType string

	switch a.Type {
	case Post:
		articleType = "Post"
	case Snippet:
		articleType = "Snippet"
	case Page:
		articleType = "Page"
	}

	if a.Type != Snippet {
		fmt.Printf("title: %s\n", a.Title)
	}

	fmt.Printf("author: %s\n", a.Author)
	fmt.Printf("type: %s\n", articleType)
	fmt.Println("tags: ")
	if a.DateModified != nil {
		fmt.Printf("date: %v\n", a.DateModified.Format(time.RFC3339))
	}

	fmt.Println("---")
	fmt.Println("")
}

func ParseFrontMatter(reader *bufio.Reader) (map[string]string, error) {

	data := make(map[string]string)

	line, lineErr := reader.ReadString('\n')

	if !strings.HasPrefix(line, "---") {
		return data, errors.New("Invalid front matter header")
	}

	for lineErr == nil {
		line, lineErr = reader.ReadString('\n')
		if lineErr != nil {
			continue
		}

		if strings.HasPrefix(line, "---") {
			break
		}

		values := strings.SplitN(line, ":", 2)

		if len(values) < 2 {
			line, lineErr = reader.ReadString('\n')
			continue
		}

		key, value := values[0], values[1]

		key = strings.Trim(key, " \t\r\n")
		value = strings.Trim(value, " \t\r\n")

		data[key] = value
	}

	return data, nil
}

func ReadArticle(reader *bufio.Reader) (Article, error) {
	article := Article{}

	frontMatter, matterErr := ParseFrontMatter(reader)

	if matterErr != nil {
		return article, errors.New("Invalid article header")
	}

	for key, value := range frontMatter {

		switch key {
		case "title":
			article.Title = value
		case "author":
			article.Author = value
		case "description":
			article.Description = value
		case "link":
			article.Link = value
		case "date":
			dateStr := value
			modTime, timeErr := time.Parse(time.RFC3339, dateStr)
			if timeErr == nil {
				article.DateModified = &modTime
				break
			}

			modTime, timeErr = time.Parse("January 02, 2006 at 03:04PM", dateStr)
			if timeErr == nil {
				article.DateModified = &modTime
			}

			if timeErr != nil {
				log.Fatalf("Could not parse date \"%s\"", dateStr)
			}

		case "updated":
			dateStr := value
			modTime, timeErr := time.Parse(time.RFC3339, dateStr)
			if timeErr == nil {
				article.DateUpdated = &modTime
				break
			}

			modTime, timeErr = time.Parse("January 02, 2006 at 03:04PM", dateStr)
			if timeErr == nil {
				article.DateUpdated = &modTime
			}

			if timeErr != nil {
				log.Fatalf("Could not parse date \"%s\"", dateStr)
			}

		case "appid":
			article.AppID = value
		case "type":
			switch value {
			case "Post":
				article.Type = Post
			case "Page":
				article.Type = Page
			case "Snippet":
				article.Type = Snippet
			}

		case "tags":
			fieldsFunc := func(divider rune) bool {
				return unicode.IsSpace(divider) || divider == ',' || divider == ';'
			}

			for _, tag := range strings.FieldsFunc(value, fieldsFunc) {
				article.Tags = append(article.Tags, strings.ToLower(tag))
			}
		}

	}

	contentBuffer := bytes.NewBufferString("")
	reader.WriteTo(contentBuffer)

	htmlFlags := 0
	htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS
	htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
	htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
	renderer := blackfriday.HtmlRenderer(htmlFlags, "", "")
	extensions := 0
	extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
	extensions |= blackfriday.EXTENSION_TABLES
	extensions |= blackfriday.EXTENSION_FENCED_CODE
	extensions |= blackfriday.EXTENSION_AUTOLINK
	extensions |= blackfriday.EXTENSION_STRIKETHROUGH
	extensions |= blackfriday.EXTENSION_SPACE_HEADERS
	extensions |= blackfriday.EXTENSION_HEADER_IDS

	md := blackfriday.Markdown(contentBuffer.Bytes(), renderer, extensions)

	article.Content = string(md)

	if len(article.Description) == 0 {
		article.Description = article.Content
	}

	return article, nil
}
<commit_msg>Custom meta for articles enabled. Allows adding meta tags to html header<commit_after>package main

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"log"
	"path"
	"strconv"
	"strings"
	"time"
	"unicode"

	"github.com/russross/blackfriday"
)

type PageType int

const (
	Post PageType = iota
	Page
	Snippet
)

type Article struct {
	Author       string
	DateModified *time.Time
	DateUpdated  *time.Time
	Title        string
	Content      string
	Description  string
	Filename     string
	Link         string
	Identifier   string
	Snippet      bool
	Type         PageType
	Draft        bool
	Tags         []string
	AppID        string
	Meta         map[string]string
}

func (a Article) HasTag(aTag string) bool {
	for _, tag := range a.Tags {
		if aTag == tag {
			return true
		}
	}

	return false
}

func (a Article) BasePath() string {
	switch a.Type {
	case Post, Snippet:
		return path.Join(strconv.Itoa(a.DateModified.Year()), fmt.Sprintf("%02d", int(a.DateModified.Month())))
	case Page:
		return ""
	}

	return ""
}

func (a Article) FullPath() string {
	return path.Join(a.BasePath(), a.Filename)
}

type Articles []*Article

func (a Articles) Len() int {
	return len(a)
}

func (a Articles) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func (a Articles) Less(i, j int) bool {
	left := *(a[i].DateModified)
	right := *(a[j].DateModified)

	return right.Before(left)
}

func (a Article) Print() {
	fmt.Println("---")

	var articleType string

	switch a.Type {
	case Post:
		articleType = "Post"
	case Snippet:
		articleType = "Snippet"
	case Page:
		articleType = "Page"
	}

	if a.Type != Snippet {
		fmt.Printf("title: %s\n", a.Title)
	}

	fmt.Printf("author: %s\n", a.Author)
	fmt.Printf("type: %s\n", articleType)
	fmt.Println("tags: ")
	if a.DateModified != nil {
		fmt.Printf("date: %v\n", a.DateModified.Format(time.RFC3339))
	}

	fmt.Println("---")
	fmt.Println("")
}

func ParseFrontMatter(reader *bufio.Reader) (map[string]string, error) {

	data := make(map[string]string)

	line, lineErr := reader.ReadString('\n')

	if !strings.HasPrefix(line, "---") {
		return data, errors.New("Invalid front matter header")
	}

	for lineErr == nil {
		line, lineErr = reader.ReadString('\n')
		if lineErr != nil {
			continue
		}

		if strings.HasPrefix(line, "---") {
			break
		}

		values := strings.SplitN(line, ":", 2)

		if len(values) < 2 {
			line, lineErr = reader.ReadString('\n')
			continue
		}

		key, value := values[0], values[1]

		key = strings.Trim(key, " \t\r\n")
		value = strings.Trim(value, " \t\r\n")

		data[key] = value
	}

	return data, nil
}

func ReadArticle(reader *bufio.Reader) (Article, error) {
	article := Article{}

	frontMatter, matterErr := ParseFrontMatter(reader)

	if matterErr != nil {
		return article, errors.New("Invalid article header")
	}

	for key, value := range frontMatter {

		if strings.HasPrefix(key, "meta-") {
			if article.Meta == nil {
				article.Meta = make(map[string]string)
			}
			metaName := strings.TrimPrefix(key, "meta-")
			log.Printf("AAA meta %s", metaName)
			article.Meta[metaName] = value
			continue
		}

		switch key {
		case "title":
			article.Title = value
		case "author":
			article.Author = value
		case "description":
			article.Description = value
		case "link":
			article.Link = value
		case "date":
			dateStr := value
			modTime, timeErr := time.Parse(time.RFC3339, dateStr)
			if timeErr == nil {
				article.DateModified = &modTime
				break
			}

			modTime, timeErr = time.Parse("January 02, 2006 at 03:04PM", dateStr)
			if timeErr == nil {
				article.DateModified = &modTime
			}

			if timeErr != nil {
				log.Fatalf("Could not parse date \"%s\"", dateStr)
			}

		case "updated":
			dateStr := value
			modTime, timeErr := time.Parse(time.RFC3339, dateStr)
			if timeErr == nil {
				article.DateUpdated = &modTime
				break
			}

			modTime, timeErr = time.Parse("January 02, 2006 at 03:04PM", dateStr)
			if timeErr == nil {
				article.DateUpdated = &modTime
			}

			if timeErr != nil {
				log.Fatalf("Could not parse date \"%s\"", dateStr)
			}

		case "appid":
			article.AppID = value
		case "type":
			switch value {
			case "Post":
				article.Type = Post
			case "Page":
				article.Type = Page
			case "Snippet":
				article.Type = Snippet
			}

		case "tags":
			fieldsFunc := func(divider rune) bool {
				return unicode.IsSpace(divider) || divider == ',' || divider == ';'
			}

			for _, tag := range strings.FieldsFunc(value, fieldsFunc) {
				article.Tags = append(article.Tags, strings.ToLower(tag))
			}
		}

	}

	contentBuffer := bytes.NewBufferString("")
	reader.WriteTo(contentBuffer)

	htmlFlags := 0
	htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS
	htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
	htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
	renderer := blackfriday.HtmlRenderer(htmlFlags, "", "")
	extensions := 0
	extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
	extensions |= blackfriday.EXTENSION_TABLES
	extensions |= blackfriday.EXTENSION_FENCED_CODE
	extensions |= blackfriday.EXTENSION_AUTOLINK
	extensions |= blackfriday.EXTENSION_STRIKETHROUGH
	extensions |= blackfriday.EXTENSION_SPACE_HEADERS
	extensions |= blackfriday.EXTENSION_HEADER_IDS

	md := blackfriday.Markdown(contentBuffer.Bytes(), renderer, extensions)

	article.Content = string(md)

	if len(article.Description) == 0 {
		article.Description = article.Content
	}

	return article, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2014, Rob Thornton
// All rights reserved.
// This source code is governed by a Simplied BSD-License. Please see the
// LICENSE included in this distribution for a copy of the full license
// or, if one is not included, you may also find a copy at
// http://opensource.org/licenses/BSD-2-Clause

package ast

import (
	"github.com/rthornton128/calc/token"
)

type Node interface {
	Pos() token.Pos
	End() token.Pos
}

type Expr interface {
	Node
	exprNode()
}

type AssignExpr struct {
	Expression
	Equal token.Pos
	Name  *Ident
	Value Expr
}

type BasicLit struct {
	LitPos token.Pos
	Kind   token.Token
	Lit    string
}

type BinaryExpr struct {
	Expression
	Op    token.Token
	OpPos token.Pos
	List  []Expr
}

type CallExpr struct {
	Expression
	Call token.Pos
	Name *Ident
	Args []Expr
}

type DeclExpr struct {
	Expression
	Decl   token.Pos
	Name   *Ident
	Type   *Ident
	Params []*Ident
	Body   Expr
	Scope  *Scope
}

type Expression struct {
	Opening token.Pos
	Closing token.Pos
}

type ExprList struct {
	Expression
	List []Expr
}

type File struct {
	Scope *Scope
}

type Ident struct {
	NamePos token.Pos
	Name    string
	Object  *Object // may be nil (ie. Name is a type keyword)
}

type IfExpr struct {
	Expression
	If    token.Pos
	Type  *Ident
	Cond  Expr
	Then  Expr
	Else  Expr
	Scope *Scope
}

type Object struct {
	NamePos token.Pos
	Name    string
	Kind    ObKind
	Offset  int
	Type    *Ident // variable type, function return type, etc
	Value   Expr
}

type ObKind int

type Package struct {
	Scope *Scope
	Files []*File
}

type Scope struct {
	Parent *Scope
	Table  map[string]*Object
}

type VarExpr struct {
	Expression
	Var    token.Pos
	Name   *Ident
	Object *Object
}

func (b *BasicLit) Pos() token.Pos   { return b.LitPos }
func (e *Expression) Pos() token.Pos { return e.Opening }
func (f *File) Pos() token.Pos       { return token.NoPos }
func (i *Ident) Pos() token.Pos      { return i.NamePos }
func (p *Package) Pos() token.Pos    { return token.NoPos }

func (b *BasicLit) End() token.Pos   { return b.LitPos + token.Pos(len(b.Lit)) }
func (e *Expression) End() token.Pos { return e.Closing }
func (f *File) End() token.Pos       { return token.NoPos }
func (i *Ident) End() token.Pos      { return i.NamePos + token.Pos(len(i.Name)) }
func (p *Package) End() token.Pos    { return token.NoPos }

func (b *BasicLit) exprNode()   {}
func (e *Expression) exprNode() {}
func (i *Ident) exprNode()      {}

const (
	Decl ObKind = iota
	Var
)

func NewScope(parent *Scope) *Scope {
	return &Scope{Parent: parent, Table: make(map[string]*Object)}
}

func MergeScopes(files []*File) *Scope {
	table := make(map[string]*Object)
	for _, f := range files {
		for k, v := range f.Scope.Table {
			table[k] = v
		}
	}
	return &Scope{Parent: nil, Table: table}
}

func (s *Scope) Insert(ob *Object) *Object {
	if old, ok := s.Table[ob.Name]; ok {
		return old
	}
	s.Table[ob.Name] = ob
	return nil
}

func (s *Scope) Lookup(ident string) *Object {
	ob, ok := s.Table[ident]
	if ok || s.Parent == nil {
		return ob
	}
	return s.Parent.Lookup(ident)
}

func (s *Scope) Size() int {
	return len(s.Table)
}
<commit_msg>fix scoping bug when compiling directory<commit_after>// Copyright (c) 2014, Rob Thornton
// All rights reserved.
// This source code is governed by a Simplied BSD-License. Please see the
// LICENSE included in this distribution for a copy of the full license
// or, if one is not included, you may also find a copy at
// http://opensource.org/licenses/BSD-2-Clause

package ast

import (
	"github.com/rthornton128/calc/token"
)

type Node interface {
	Pos() token.Pos
	End() token.Pos
}

type Expr interface {
	Node
	exprNode()
}

type AssignExpr struct {
	Expression
	Equal token.Pos
	Name  *Ident
	Value Expr
}

type BasicLit struct {
	LitPos token.Pos
	Kind   token.Token
	Lit    string
}

type BinaryExpr struct {
	Expression
	Op    token.Token
	OpPos token.Pos
	List  []Expr
}

type CallExpr struct {
	Expression
	Call token.Pos
	Name *Ident
	Args []Expr
}

type DeclExpr struct {
	Expression
	Decl   token.Pos
	Name   *Ident
	Type   *Ident
	Params []*Ident
	Body   Expr
	Scope  *Scope
}

type Expression struct {
	Opening token.Pos
	Closing token.Pos
}

type ExprList struct {
	Expression
	List []Expr
}

type File struct {
	Scope *Scope
}

type Ident struct {
	NamePos token.Pos
	Name    string
	Object  *Object // may be nil (ie. Name is a type keyword)
}

type IfExpr struct {
	Expression
	If    token.Pos
	Type  *Ident
	Cond  Expr
	Then  Expr
	Else  Expr
	Scope *Scope
}

type Object struct {
	NamePos token.Pos
	Name    string
	Kind    ObKind
	Offset  int
	Type    *Ident // variable type, function return type, etc
	Value   Expr
}

type ObKind int

type Package struct {
	Scope *Scope
	Files []*File
}

type Scope struct {
	Parent *Scope
	Table  map[string]*Object
}

type VarExpr struct {
	Expression
	Var    token.Pos
	Name   *Ident
	Object *Object
}

func (b *BasicLit) Pos() token.Pos   { return b.LitPos }
func (e *Expression) Pos() token.Pos { return e.Opening }
func (f *File) Pos() token.Pos       { return token.NoPos }
func (i *Ident) Pos() token.Pos      { return i.NamePos }
func (p *Package) Pos() token.Pos    { return token.NoPos }

func (b *BasicLit) End() token.Pos   { return b.LitPos + token.Pos(len(b.Lit)) }
func (e *Expression) End() token.Pos { return e.Closing }
func (f *File) End() token.Pos       { return token.NoPos }
func (i *Ident) End() token.Pos      { return i.NamePos + token.Pos(len(i.Name)) }
func (p *Package) End() token.Pos    { return token.NoPos }

func (b *BasicLit) exprNode()   {}
func (e *Expression) exprNode() {}
func (i *Ident) exprNode()      {}

const (
	Decl ObKind = iota
	Var
)

func NewScope(parent *Scope) *Scope {
	return &Scope{Parent: parent, Table: make(map[string]*Object)}
}

func MergeScopes(files []*File) *Scope {
	table := make(map[string]*Object)
	scope := &Scope{Parent: nil, Table: table}
	for _, f := range files {
		f.Scope.Parent = scope
		for k, v := range f.Scope.Table {
			table[k] = v
		}
	}
	return scope
}

func (s *Scope) Insert(ob *Object) *Object {
	if old, ok := s.Table[ob.Name]; ok {
		return old
	}
	s.Table[ob.Name] = ob
	return nil
}

func (s *Scope) Lookup(ident string) *Object {
	ob, ok := s.Table[ident]
	if ok || s.Parent == nil {
		return ob
	}
	return s.Parent.Lookup(ident)
}

func (s *Scope) Size() int {
	return len(s.Table)
}
<|endoftext|>


<commit_before>package ast

import (
	"bytes"

	"github.com/Tamrin007/monkey/token"
)

type Node interface {
	TokenLiteral() string
	String() string
}

type Statement interface {
	Node
	statementNode()
}

type Expression interface {
	Node
	expressionNode()
}

type Program struct {
	Statements []Statement
}

func (p *Program) TokenLiteral() string {
	if len(p.Statements) > 0 {
		return p.Statements[0].TokenLiteral()
	}
	return ""
}

func (p *Program) String() string {
	var out bytes.Buffer

	for _, s := range p.Statements {
		out.WriteString(s.String())
	}

	return out.String()
}

type LetStatement struct {
	Token token.Token
	Name  *Identifier
	Value Expression
}

func (ls *LetStatement) statementNode() {}

func (ls *LetStatement) TokenLiteral() string {
	return ls.Token.Literal
}

func (ls *LetStatement) String() string {
	var out bytes.Buffer

	out.WriteString(ls.TokenLiteral() + " ")
	out.WriteString(ls.Name.String())
	out.WriteString(" = ")

	if ls.Value != nil {
		out.WriteString(ls.Value.String())
	}

	out.WriteString(";")

	return out.String()
}

type ReturnStatement struct {
	Token       token.Token
	ReturnValue Expression
}

func (rs *ReturnStatement) statementNode() {}

func (rs *ReturnStatement) TokenLiteral() string {
	return rs.Token.Literal
}

func (rs *ReturnStatement) String() string {
	var out bytes.Buffer

	out.WriteString(rs.TokenLiteral() + " ")

	if rs.ReturnValue != nil {
		out.WriteString(rs.ReturnValue.String())
	}

	out.WriteString(";")

	return out.String()
}

type ExpressionStatement struct {
	Token      token.Token
	Expression Expression
}

func (es *ExpressionStatement) statementNode() {}

func (es *ExpressionStatement) TokenLiteral() string {
	return es.Token.Literal
}

func (es *ExpressionStatement) String() string {
	if es.Expression != nil {
		return es.Expression.String()
	}
	return ""
}

type Identifier struct {
	Token token.Token
	Value string
}

func (i *Identifier) expressionNode() {}

func (i *Identifier) TokenLiteral() string {
	return i.Token.Literal
}

func (i *Identifier) String() string {
	return i.Value
}

type IntegerLiteral struct {
	Token token.Token
	Value int64
}

func (il *IntegerLiteral) expressionNode() {}

func (il *IntegerLiteral) TokenLiteral() string {
	return il.Token.Literal
}

func (il *IntegerLiteral) String() string {
	return il.Token.Literal
}

type PrefixExpression struct {
	Token    token.Token
	Operator string
	Right    Expression
}

func (pe *PrefixExpression) expressionNode() {}

func (pe *PrefixExpression) TokenLiteral() string {
	return pe.Token.Literal
}

func (pe *PrefixExpression) String() string {
	var out bytes.Buffer

	out.WriteString("(")
	out.WriteString(pe.Operator)
	out.WriteString(pe.Right.String())
	out.WriteString(")")

	return pe.Token.Literal
}

type InfixExpression struct {
	Token    token.Token
	Left     Expression
	Operator string
	Right    Expression
}

func (oe *InfixExpression) expressionNode() {}

func (oe *InfixExpression) TokenLiteral() string {
	return oe.Token.Literal
}

func (oe *InfixExpression) String() string {
	var out bytes.Buffer

	out.WriteString("(")
	out.WriteString(oe.Left.String())
	out.WriteString(" " + oe.Operator + " ")
	out.WriteString(oe.Right.String())
	out.WriteString(")")

	return oe.Token.Literal
}
<commit_msg>fix return value String() method of prefixExpression and infixExpression<commit_after>package ast

import (
	"bytes"

	"github.com/Tamrin007/monkey/token"
)

type Node interface {
	TokenLiteral() string
	String() string
}

type Statement interface {
	Node
	statementNode()
}

type Expression interface {
	Node
	expressionNode()
}

type Program struct {
	Statements []Statement
}

func (p *Program) TokenLiteral() string {
	if len(p.Statements) > 0 {
		return p.Statements[0].TokenLiteral()
	}
	return ""
}

func (p *Program) String() string {
	var out bytes.Buffer

	for _, s := range p.Statements {
		out.WriteString(s.String())
	}

	return out.String()
}

type LetStatement struct {
	Token token.Token
	Name  *Identifier
	Value Expression
}

func (ls *LetStatement) statementNode() {}

func (ls *LetStatement) TokenLiteral() string {
	return ls.Token.Literal
}

func (ls *LetStatement) String() string {
	var out bytes.Buffer

	out.WriteString(ls.TokenLiteral() + " ")
	out.WriteString(ls.Name.String())
	out.WriteString(" = ")

	if ls.Value != nil {
		out.WriteString(ls.Value.String())
	}

	out.WriteString(";")

	return out.String()
}

type ReturnStatement struct {
	Token       token.Token
	ReturnValue Expression
}

func (rs *ReturnStatement) statementNode() {}

func (rs *ReturnStatement) TokenLiteral() string {
	return rs.Token.Literal
}

func (rs *ReturnStatement) String() string {
	var out bytes.Buffer

	out.WriteString(rs.TokenLiteral() + " ")

	if rs.ReturnValue != nil {
		out.WriteString(rs.ReturnValue.String())
	}

	out.WriteString(";")

	return out.String()
}

type ExpressionStatement struct {
	Token      token.Token
	Expression Expression
}

func (es *ExpressionStatement) statementNode() {}

func (es *ExpressionStatement) TokenLiteral() string {
	return es.Token.Literal
}

func (es *ExpressionStatement) String() string {
	if es.Expression != nil {
		return es.Expression.String()
	}
	return ""
}

type Identifier struct {
	Token token.Token
	Value string
}

func (i *Identifier) expressionNode() {}

func (i *Identifier) TokenLiteral() string {
	return i.Token.Literal
}

func (i *Identifier) String() string {
	return i.Value
}

type IntegerLiteral struct {
	Token token.Token
	Value int64
}

func (il *IntegerLiteral) expressionNode() {}

func (il *IntegerLiteral) TokenLiteral() string {
	return il.Token.Literal
}

func (il *IntegerLiteral) String() string {
	return il.Token.Literal
}

type PrefixExpression struct {
	Token    token.Token
	Operator string
	Right    Expression
}

func (pe *PrefixExpression) expressionNode() {}

func (pe *PrefixExpression) TokenLiteral() string {
	return pe.Token.Literal
}

func (pe *PrefixExpression) String() string {
	var out bytes.Buffer

	out.WriteString("(")
	out.WriteString(pe.Operator)
	out.WriteString(pe.Right.String())
	out.WriteString(")")

	return out.String()
}

type InfixExpression struct {
	Token    token.Token
	Left     Expression
	Operator string
	Right    Expression
}

func (oe *InfixExpression) expressionNode() {}

func (oe *InfixExpression) TokenLiteral() string {
	return oe.Token.Literal
}

func (oe *InfixExpression) String() string {
	var out bytes.Buffer

	out.WriteString("(")
	out.WriteString(oe.Left.String())
	out.WriteString(" " + oe.Operator + " ")
	out.WriteString(oe.Right.String())
	out.WriteString(")")

	return out.String()
}
<|endoftext|>