<commit_before>package logger

import (
	"io"
	"log"
	"os"
	"strings"

	kv "gopkg.in/Clever/kayvee-go.v5"
	"gopkg.in/Clever/kayvee-go.v5/router"
)

/////////////////////
//
//  Helper definitions
//
/////////////////////

// Formatter is a function type that takes a map and returns a formatted string with the contents of the map
type Formatter func(data map[string]interface{}) string

// M is a convenience type for passing data into a log message.
type M map[string]interface{}

// LogLevel is an enum is used to denote level of logging
type LogLevel int

// Constants used to define different LogLevels supported
const (
	Debug LogLevel = iota
	Info
	Warning
	Error
	Critical
)

var logLevelNames = map[LogLevel]string{
	Debug:    "debug",
	Info:     "info",
	Warning:  "warning",
	Error:    "error",
	Critical: "critical",
}

func (l LogLevel) String() string {
	switch l {
	case Debug:
		fallthrough
	case Info:
		fallthrough
	case Warning:
		fallthrough
	case Error:
		fallthrough
	case Critical:
		return logLevelNames[l]
	}
	return ""
}

/////////////////////////////
//
//	Logger
//
/////////////////////////////

// Logger is the default implementation of KayveeLogger.
// It provides customization of globals, default log level, formatting, and output destination.
type Logger struct {
	globals   map[string]interface{}
	logLvl    LogLevel
	fLogger   formatLogger
	logRouter router.Router
}

// SetConfig allows configuration changes in one go
var reservedKeyNames = map[string]bool{
	"title":   true,
	"source":  true,
	"value":   true,
	"type":    true,
	"level":   true,
	"_kvmeta": true,
}

// SetConfig implements the method for the KayveeLogger interface.
func (l *Logger) SetConfig(source string, logLvl LogLevel, formatter Formatter, output io.Writer) {
	if l.globals == nil {
		l.globals = make(map[string]interface{})
	}
	l.globals["source"] = source
	l.logLvl = logLvl
	l.fLogger.setFormatter(formatter)
	l.fLogger.setOutput(output)
}

// AddContext implements the method for the KayveeLogger interface.
func (l *Logger) AddContext(key, val string) {
	updateContextMapIfNotReserved(l.globals, key, val)
}

// SetLogLevel implements the method for the KayveeLogger interface.
func (l *Logger) SetLogLevel(logLvl LogLevel) {
	l.logLvl = logLvl
}

// SetFormatter implements the method for the KayveeLogger interface.
func (l *Logger) SetFormatter(formatter Formatter) {
	l.fLogger.setFormatter(formatter)
}

// SetOutput implements the method for the KayveeLogger interface.
func (l *Logger) SetOutput(output io.Writer) {
	l.fLogger.setOutput(output)
}

// Debug implements the method for the KayveeLogger interface.
func (l *Logger) Debug(title string) {
	l.DebugD(title, M{})
}

// Info implements the method for the KayveeLogger interface.
func (l *Logger) Info(title string) {
	l.InfoD(title, M{})
}

// Warn implements the method for the KayveeLogger interface.
func (l *Logger) Warn(title string) {
	l.WarnD(title, M{})
}

// Error implements the method for the KayveeLogger interface.
func (l *Logger) Error(title string) {
	l.ErrorD(title, M{})
}

// Critical implements the method for the KayveeLogger interface.
func (l *Logger) Critical(title string) {
	l.CriticalD(title, M{})
}

// Counter implements the method for the KayveeLogger interface.
// Logs with type = gauge, and value = value
func (l *Logger) Counter(title string) {
	l.CounterD(title, 1, M{}) // Defaults to a value of 1
}

// GaugeInt implements the method for the KayveeLogger interface.
// Logs with type = gauge, and value = value
func (l *Logger) GaugeInt(title string, value int) {
	l.GaugeIntD(title, value, M{})
}

// GaugeFloat implements the method for the KayveeLogger interface.
// Logs with type = gauge, and value = value
func (l *Logger) GaugeFloat(title string, value float64) {
	l.GaugeFloatD(title, value, M{})
}

// DebugD implements the method for the KayveeLogger interface.
func (l *Logger) DebugD(title string, data map[string]interface{}) {
	data["title"] = title
	l.logWithLevel(Debug, data)
}

// InfoD implements the method for the KayveeLogger interface.
func (l *Logger) InfoD(title string, data map[string]interface{}) {
	data["title"] = title
	l.logWithLevel(Info, data)
}

// WarnD implements the method for the KayveeLogger interface.
func (l *Logger) WarnD(title string, data map[string]interface{}) {
	data["title"] = title
	l.logWithLevel(Warning, data)
}

// ErrorD implements the method for the KayveeLogger interface.
func (l *Logger) ErrorD(title string, data map[string]interface{}) {
	data["title"] = title
	l.logWithLevel(Error, data)
}

// CriticalD implements the method for the KayveeLogger interface.
func (l *Logger) CriticalD(title string, data map[string]interface{}) {
	data["title"] = title
	l.logWithLevel(Critical, data)
}

// CounterD implements the method for the KayveeLogger interface.
// Logs with type = gauge, and value = value
func (l *Logger) CounterD(title string, value int, data map[string]interface{}) {
	data["title"] = title
	data["value"] = value
	data["type"] = "counter"
	l.logWithLevel(Info, data)
}

// GaugeIntD implements the method for the KayveeLogger interface.
// Logs with type = gauge, and value = value
func (l *Logger) GaugeIntD(title string, value int, data map[string]interface{}) {
	l.gauge(title, value, data)
}

// GaugeFloatD implements the method for the KayveeLogger interface.
// Logs with type = gauge, and value = value
func (l *Logger) GaugeFloatD(title string, value float64, data map[string]interface{}) {
	l.gauge(title, value, data)
}

func (l *Logger) gauge(title string, value interface{}, data map[string]interface{}) {
	data["title"] = title
	data["value"] = value
	data["type"] = "gauge"
	l.logWithLevel(Info, data)
}

// Actual logging. Handles whether to output based on log level and
// unifies the passed in data with the stored globals
func (l *Logger) logWithLevel(logLvl LogLevel, data map[string]interface{}) {
	if logLvl < l.logLvl {
		// No log output
		return
	}
	data["level"] = logLvl.String()
	for key, value := range l.globals {
		if _, ok := data[key]; ok {
			// Values in the data map override the globals
			continue
		}
		data[key] = value
	}
	if l.logRouter != nil {
		data["_kvmeta"] = l.logRouter.Route(data)
	}

	l.fLogger.formatAndLog(data)
}

// updateContextMapIfNotReserved updates context[key] to val if key is not in the reserved list.
func updateContextMapIfNotReserved(context M, key string, val interface{}) {
	if reservedKeyNames[strings.ToLower(key)] {
		log.Printf("WARN: kayvee logger reserves '%s' from being set as context", key)
		return
	}
	context[key] = val
}

// WithRoutingConfig implements the method for the KayveeLogger interface.
func (l *Logger) WithRoutingConfig(filename string) (KayveeLogger, error) {
	routes, err := router.NewFromConfig(filename)
	if err != nil {
		return l, err
	}
	l.logRouter = routes
	return l, nil
}

// New creates a *logger.Logger. Default values are Debug LogLevel, kayvee Formatter, and std.err output.
func New(source string) *Logger {
	return NewWithContext(source, nil)
}

// NewWithContext creates a *logger.Logger. Default values are Debug LogLevel, kayvee Formatter, and std.err output.
func NewWithContext(source string, contextValues map[string]interface{}) *Logger {
	context := M{}
	for k, v := range contextValues {
		updateContextMapIfNotReserved(context, k, v)
	}
	if os.Getenv("_DEPLOY_ENV") != "" {
		context["deploy_env"] = os.Getenv("_DEPLOY_ENV")
	}

	logObj := Logger{
		globals: context,
	}

	fl := defaultFormatLogger{}
	logObj.fLogger = &fl

	var logLvl LogLevel
	strLogLvl := os.Getenv("KAYVEE_LOG_LEVEL")
	if strLogLvl == "" {
		logLvl = Debug
	} else {
		for key, val := range logLevelNames {
			if strings.ToLower(strLogLvl) == val {
				logLvl = key
				break
			}
		}
	}

	logObj.SetConfig(source, logLvl, kv.Format, os.Stderr)

	return &logObj
}

/////////////////////////////
//
//	FormatLogger
//
/////////////////////////////

// formatLogger is an interface that abstracts the last steps in submitting a log
// message to a Logger: formatting and log writing. It can be replaced for testing.
// This is not yet exported, but could be if clients want customization of the
// format and writing steps.
type formatLogger interface {
	// formatAndLog processes the given data map into a log line and writes it
	formatAndLog(data map[string]interface{})

	// setFormatter specifies the Formatter function to use in formatAndLog
	setFormatter(formatter Formatter)

	// setOutput specifies the output destination to use in formatAndLog
	setOutput(output io.Writer)
}

// defaultFormatLogger provides default implementation of a formatLogger.
type defaultFormatLogger struct {
	formatter Formatter
	logWriter *log.Logger
}

// formatAndLog implements the formatLogger interface for *defaultFormatLogger.
func (fl *defaultFormatLogger) formatAndLog(data map[string]interface{}) {
	logString := fl.formatter(data)
	fl.logWriter.Println(logString)
}

// setFormat implements the formatLogger interface for *defaultFormatLogger.
func (fl *defaultFormatLogger) setFormatter(formatter Formatter) {
	fl.formatter = formatter
}

// setOutput implements the formatLogger interface for *defaultFormatLogger.
func (fl *defaultFormatLogger) setOutput(output io.Writer) {
	fl.logWriter = log.New(output, "", 0) // No prefixes
}
<commit_msg>Removed unneeded comment<commit_after>package logger

import (
	"io"
	"log"
	"os"
	"strings"

	kv "gopkg.in/Clever/kayvee-go.v5"
	"gopkg.in/Clever/kayvee-go.v5/router"
)

/////////////////////
//
//  Helper definitions
//
/////////////////////

// Formatter is a function type that takes a map and returns a formatted string with the contents of the map
type Formatter func(data map[string]interface{}) string

// M is a convenience type for passing data into a log message.
type M map[string]interface{}

// LogLevel is an enum is used to denote level of logging
type LogLevel int

// Constants used to define different LogLevels supported
const (
	Debug LogLevel = iota
	Info
	Warning
	Error
	Critical
)

var logLevelNames = map[LogLevel]string{
	Debug:    "debug",
	Info:     "info",
	Warning:  "warning",
	Error:    "error",
	Critical: "critical",
}

func (l LogLevel) String() string {
	switch l {
	case Debug:
		fallthrough
	case Info:
		fallthrough
	case Warning:
		fallthrough
	case Error:
		fallthrough
	case Critical:
		return logLevelNames[l]
	}
	return ""
}

/////////////////////////////
//
//	Logger
//
/////////////////////////////

// Logger is the default implementation of KayveeLogger.
// It provides customization of globals, default log level, formatting, and output destination.
type Logger struct {
	globals   map[string]interface{}
	logLvl    LogLevel
	fLogger   formatLogger
	logRouter router.Router
}

var reservedKeyNames = map[string]bool{
	"title":   true,
	"source":  true,
	"value":   true,
	"type":    true,
	"level":   true,
	"_kvmeta": true,
}

// SetConfig implements the method for the KayveeLogger interface.
func (l *Logger) SetConfig(source string, logLvl LogLevel, formatter Formatter, output io.Writer) {
	if l.globals == nil {
		l.globals = make(map[string]interface{})
	}
	l.globals["source"] = source
	l.logLvl = logLvl
	l.fLogger.setFormatter(formatter)
	l.fLogger.setOutput(output)
}

// AddContext implements the method for the KayveeLogger interface.
func (l *Logger) AddContext(key, val string) {
	updateContextMapIfNotReserved(l.globals, key, val)
}

// SetLogLevel implements the method for the KayveeLogger interface.
func (l *Logger) SetLogLevel(logLvl LogLevel) {
	l.logLvl = logLvl
}

// SetFormatter implements the method for the KayveeLogger interface.
func (l *Logger) SetFormatter(formatter Formatter) {
	l.fLogger.setFormatter(formatter)
}

// SetOutput implements the method for the KayveeLogger interface.
func (l *Logger) SetOutput(output io.Writer) {
	l.fLogger.setOutput(output)
}

// Debug implements the method for the KayveeLogger interface.
func (l *Logger) Debug(title string) {
	l.DebugD(title, M{})
}

// Info implements the method for the KayveeLogger interface.
func (l *Logger) Info(title string) {
	l.InfoD(title, M{})
}

// Warn implements the method for the KayveeLogger interface.
func (l *Logger) Warn(title string) {
	l.WarnD(title, M{})
}

// Error implements the method for the KayveeLogger interface.
func (l *Logger) Error(title string) {
	l.ErrorD(title, M{})
}

// Critical implements the method for the KayveeLogger interface.
func (l *Logger) Critical(title string) {
	l.CriticalD(title, M{})
}

// Counter implements the method for the KayveeLogger interface.
// Logs with type = gauge, and value = value
func (l *Logger) Counter(title string) {
	l.CounterD(title, 1, M{}) // Defaults to a value of 1
}

// GaugeInt implements the method for the KayveeLogger interface.
// Logs with type = gauge, and value = value
func (l *Logger) GaugeInt(title string, value int) {
	l.GaugeIntD(title, value, M{})
}

// GaugeFloat implements the method for the KayveeLogger interface.
// Logs with type = gauge, and value = value
func (l *Logger) GaugeFloat(title string, value float64) {
	l.GaugeFloatD(title, value, M{})
}

// DebugD implements the method for the KayveeLogger interface.
func (l *Logger) DebugD(title string, data map[string]interface{}) {
	data["title"] = title
	l.logWithLevel(Debug, data)
}

// InfoD implements the method for the KayveeLogger interface.
func (l *Logger) InfoD(title string, data map[string]interface{}) {
	data["title"] = title
	l.logWithLevel(Info, data)
}

// WarnD implements the method for the KayveeLogger interface.
func (l *Logger) WarnD(title string, data map[string]interface{}) {
	data["title"] = title
	l.logWithLevel(Warning, data)
}

// ErrorD implements the method for the KayveeLogger interface.
func (l *Logger) ErrorD(title string, data map[string]interface{}) {
	data["title"] = title
	l.logWithLevel(Error, data)
}

// CriticalD implements the method for the KayveeLogger interface.
func (l *Logger) CriticalD(title string, data map[string]interface{}) {
	data["title"] = title
	l.logWithLevel(Critical, data)
}

// CounterD implements the method for the KayveeLogger interface.
// Logs with type = gauge, and value = value
func (l *Logger) CounterD(title string, value int, data map[string]interface{}) {
	data["title"] = title
	data["value"] = value
	data["type"] = "counter"
	l.logWithLevel(Info, data)
}

// GaugeIntD implements the method for the KayveeLogger interface.
// Logs with type = gauge, and value = value
func (l *Logger) GaugeIntD(title string, value int, data map[string]interface{}) {
	l.gauge(title, value, data)
}

// GaugeFloatD implements the method for the KayveeLogger interface.
// Logs with type = gauge, and value = value
func (l *Logger) GaugeFloatD(title string, value float64, data map[string]interface{}) {
	l.gauge(title, value, data)
}

func (l *Logger) gauge(title string, value interface{}, data map[string]interface{}) {
	data["title"] = title
	data["value"] = value
	data["type"] = "gauge"
	l.logWithLevel(Info, data)
}

// Actual logging. Handles whether to output based on log level and
// unifies the passed in data with the stored globals
func (l *Logger) logWithLevel(logLvl LogLevel, data map[string]interface{}) {
	if logLvl < l.logLvl {
		// No log output
		return
	}
	data["level"] = logLvl.String()
	for key, value := range l.globals {
		if _, ok := data[key]; ok {
			// Values in the data map override the globals
			continue
		}
		data[key] = value
	}
	if l.logRouter != nil {
		data["_kvmeta"] = l.logRouter.Route(data)
	}

	l.fLogger.formatAndLog(data)
}

// updateContextMapIfNotReserved updates context[key] to val if key is not in the reserved list.
func updateContextMapIfNotReserved(context M, key string, val interface{}) {
	if reservedKeyNames[strings.ToLower(key)] {
		log.Printf("WARN: kayvee logger reserves '%s' from being set as context", key)
		return
	}
	context[key] = val
}

// WithRoutingConfig implements the method for the KayveeLogger interface.
func (l *Logger) WithRoutingConfig(filename string) (KayveeLogger, error) {
	routes, err := router.NewFromConfig(filename)
	if err != nil {
		return l, err
	}
	l.logRouter = routes
	return l, nil
}

// New creates a *logger.Logger. Default values are Debug LogLevel, kayvee Formatter, and std.err output.
func New(source string) *Logger {
	return NewWithContext(source, nil)
}

// NewWithContext creates a *logger.Logger. Default values are Debug LogLevel, kayvee Formatter, and std.err output.
func NewWithContext(source string, contextValues map[string]interface{}) *Logger {
	context := M{}
	for k, v := range contextValues {
		updateContextMapIfNotReserved(context, k, v)
	}
	if os.Getenv("_DEPLOY_ENV") != "" {
		context["deploy_env"] = os.Getenv("_DEPLOY_ENV")
	}

	logObj := Logger{
		globals: context,
	}

	fl := defaultFormatLogger{}
	logObj.fLogger = &fl

	var logLvl LogLevel
	strLogLvl := os.Getenv("KAYVEE_LOG_LEVEL")
	if strLogLvl == "" {
		logLvl = Debug
	} else {
		for key, val := range logLevelNames {
			if strings.ToLower(strLogLvl) == val {
				logLvl = key
				break
			}
		}
	}

	logObj.SetConfig(source, logLvl, kv.Format, os.Stderr)

	return &logObj
}

/////////////////////////////
//
//	FormatLogger
//
/////////////////////////////

// formatLogger is an interface that abstracts the last steps in submitting a log
// message to a Logger: formatting and log writing. It can be replaced for testing.
// This is not yet exported, but could be if clients want customization of the
// format and writing steps.
type formatLogger interface {
	// formatAndLog processes the given data map into a log line and writes it
	formatAndLog(data map[string]interface{})

	// setFormatter specifies the Formatter function to use in formatAndLog
	setFormatter(formatter Formatter)

	// setOutput specifies the output destination to use in formatAndLog
	setOutput(output io.Writer)
}

// defaultFormatLogger provides default implementation of a formatLogger.
type defaultFormatLogger struct {
	formatter Formatter
	logWriter *log.Logger
}

// formatAndLog implements the formatLogger interface for *defaultFormatLogger.
func (fl *defaultFormatLogger) formatAndLog(data map[string]interface{}) {
	logString := fl.formatter(data)
	fl.logWriter.Println(logString)
}

// setFormat implements the formatLogger interface for *defaultFormatLogger.
func (fl *defaultFormatLogger) setFormatter(formatter Formatter) {
	fl.formatter = formatter
}

// setOutput implements the formatLogger interface for *defaultFormatLogger.
func (fl *defaultFormatLogger) setOutput(output io.Writer) {
	fl.logWriter = log.New(output, "", 0) // No prefixes
}
<|endoftext|>


<commit_before>package util

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	. "github.com/smartystreets/assertions"
)

func newTokenServer(a *Assertion) *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		a.So(r.RequestURI, ShouldEqual, "/token")
		a.So(r.Method, ShouldEqual, "POST")

		username, password, ok := r.BasicAuth()
		a.So(ok, ShouldBeTrue)
		a.So(username, ShouldEqual, "ttnctl")
		a.So(password, ShouldEqual, "")

		grantType := r.FormValue("grant_type")
		if grantType == "password" {
			handleNewToken(a, w, r)
		} else if grantType == "refresh_token" {
			handleRefreshToken(a, w, r)
		} else {
			w.WriteHeader(http.StatusNotFound)
		}
	}))
}

func handleNewToken(a *Assertion, w http.ResponseWriter, r *http.Request) {
	var resp token
	if r.FormValue("username") == "jantje@test.org" && r.FormValue("password") == "secret" {
		resp = token{
			AccessToken:  "123",
			RefreshToken: "ABC",
			ExpiresIn:    3600,
		}
		w.WriteHeader(http.StatusOK)
	} else {
		resp = token{
			Error:            "invalid_credentials",
			ErrorDescription: "Invalid credentials",
		}
		w.WriteHeader(http.StatusForbidden)
	}

	encoder := json.NewEncoder(w)
	err := encoder.Encode(&resp)
	a.So(err, ShouldBeNil)
}

func handleRefreshToken(a *Assertion, w http.ResponseWriter, r *http.Request) {
	var resp token
	if r.FormValue("refresh_token") == "ABC" {
		resp = token{
			AccessToken:  "456",
			RefreshToken: "DEF",
			ExpiresIn:    3600,
		}
		w.WriteHeader(http.StatusOK)
	} else {
		resp = token{
			Error:            "invalid_grant",
			ErrorDescription: "Refresh token not found",
		}
		w.WriteHeader(http.StatusBadRequest)
	}

	encoder := json.NewEncoder(w)
	err := encoder.Encode(&resp)
	a.So(err, ShouldBeNil)
}

func TestLogin(t *testing.T) {
	a := New(t)
	server := newTokenServer(a)
	defer server.Close()

	_, err := Login(server.URL, "pietje@test.org", "secret")
	a.So(err, ShouldNotBeNil)
	a.So(err.Error(), ShouldEqual, "Invalid credentials")

	loginAuth, err := Login(server.URL, "jantje@test.org", "secret")
	a.So(err, ShouldBeNil)
	a.So(loginAuth, ShouldNotBeNil)
	a.So(loginAuth.AccessToken, ShouldEqual, "123")
	a.So(loginAuth.RefreshToken, ShouldEqual, "ABC")
	a.So(loginAuth.Email, ShouldEqual, "jantje@test.org")

	loadedAuth, err := LoadAuth(server.URL)
	a.So(err, ShouldBeNil)
	a.So(loadedAuth, ShouldNotBeNil)
	a.So(loginAuth, ShouldResemble, loadedAuth)
}

func TestLogout(t *testing.T) {
	a := New(t)
	server := newTokenServer(a)
	defer server.Close()

	// Make sure we're not logged on
	err := Logout(server.URL)
	a.So(err, ShouldBeNil)
	loadedAuth, err := LoadAuth(server.URL)
	a.So(err, ShouldBeNil)
	a.So(loadedAuth, ShouldBeNil)

	// Login
	loginAuth, err := Login(server.URL, "jantje@test.org", "secret")
	a.So(err, ShouldBeNil)
	a.So(loginAuth, ShouldNotBeNil)

	// Logout
	err = Logout(server.URL)
	a.So(err, ShouldBeNil)
	loadedAuth, err = LoadAuth(server.URL)
	a.So(err, ShouldBeNil)
	a.So(loadedAuth, ShouldBeNil)
}

func TestLoadWithRefresh(t *testing.T) {
	a := New(t)
	server := newTokenServer(a)
	defer server.Close()

	// Make sure we're not logged on
	err := Logout(server.URL)
	a.So(err, ShouldBeNil)

	// Save an expired token
	expires := time.Now().Add(time.Duration(-1) * time.Hour)
	savedAuth, err := saveAuth(server.URL, "jantje@test.org", "123", "ABC", expires)
	a.So(err, ShouldBeNil)

	// Refresh the token
	loadedAuth, err := LoadAuth(server.URL)
	a.So(err, ShouldBeNil)
	a.So(loadedAuth, ShouldNotBeNil)
	a.So(savedAuth, ShouldNotResemble, loadedAuth)
	a.So(loadedAuth.AccessToken, ShouldEqual, "456")
	a.So(loadedAuth.RefreshToken, ShouldEqual, "DEF")
	a.So(loadedAuth.Email, ShouldEqual, "jantje@test.org")
}
<commit_msg>[ttnctl-user] Added test for invalid refresh token<commit_after>package util

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	. "github.com/smartystreets/assertions"
)

func newTokenServer(a *Assertion) *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		a.So(r.RequestURI, ShouldEqual, "/token")
		a.So(r.Method, ShouldEqual, "POST")

		username, password, ok := r.BasicAuth()
		a.So(ok, ShouldBeTrue)
		a.So(username, ShouldEqual, "ttnctl")
		a.So(password, ShouldEqual, "")

		grantType := r.FormValue("grant_type")
		if grantType == "password" {
			handleNewToken(a, w, r)
		} else if grantType == "refresh_token" {
			handleRefreshToken(a, w, r)
		} else {
			w.WriteHeader(http.StatusNotFound)
		}
	}))
}

func handleNewToken(a *Assertion, w http.ResponseWriter, r *http.Request) {
	var resp token
	if r.FormValue("username") == "jantje@test.org" && r.FormValue("password") == "secret" {
		resp = token{
			AccessToken:  "123",
			RefreshToken: "ABC",
			ExpiresIn:    3600,
		}
		w.WriteHeader(http.StatusOK)
	} else {
		resp = token{
			Error:            "invalid_credentials",
			ErrorDescription: "Invalid credentials",
		}
		w.WriteHeader(http.StatusForbidden)
	}

	encoder := json.NewEncoder(w)
	err := encoder.Encode(&resp)
	a.So(err, ShouldBeNil)
}

func handleRefreshToken(a *Assertion, w http.ResponseWriter, r *http.Request) {
	var resp token
	if r.FormValue("refresh_token") == "ABC" {
		resp = token{
			AccessToken:  "456",
			RefreshToken: "DEF",
			ExpiresIn:    3600,
		}
		w.WriteHeader(http.StatusOK)
	} else {
		resp = token{
			Error:            "invalid_grant",
			ErrorDescription: "Refresh token not found",
		}
		w.WriteHeader(http.StatusBadRequest)
	}

	encoder := json.NewEncoder(w)
	err := encoder.Encode(&resp)
	a.So(err, ShouldBeNil)
}

func TestLogin(t *testing.T) {
	a := New(t)
	server := newTokenServer(a)
	defer server.Close()

	_, err := Login(server.URL, "pietje@test.org", "secret")
	a.So(err, ShouldNotBeNil)
	a.So(err.Error(), ShouldEqual, "Invalid credentials")

	loginAuth, err := Login(server.URL, "jantje@test.org", "secret")
	a.So(err, ShouldBeNil)
	a.So(loginAuth, ShouldNotBeNil)
	a.So(loginAuth.AccessToken, ShouldEqual, "123")
	a.So(loginAuth.RefreshToken, ShouldEqual, "ABC")
	a.So(loginAuth.Email, ShouldEqual, "jantje@test.org")

	loadedAuth, err := LoadAuth(server.URL)
	a.So(err, ShouldBeNil)
	a.So(loadedAuth, ShouldNotBeNil)
	a.So(loginAuth, ShouldResemble, loadedAuth)
}

func TestLogout(t *testing.T) {
	a := New(t)
	server := newTokenServer(a)
	defer server.Close()

	// Make sure we're not logged on
	err := Logout(server.URL)
	a.So(err, ShouldBeNil)
	loadedAuth, err := LoadAuth(server.URL)
	a.So(err, ShouldBeNil)
	a.So(loadedAuth, ShouldBeNil)

	// Login
	loginAuth, err := Login(server.URL, "jantje@test.org", "secret")
	a.So(err, ShouldBeNil)
	a.So(loginAuth, ShouldNotBeNil)

	// Logout
	err = Logout(server.URL)
	a.So(err, ShouldBeNil)
	loadedAuth, err = LoadAuth(server.URL)
	a.So(err, ShouldBeNil)
	a.So(loadedAuth, ShouldBeNil)
}

func TestLoadWithRefresh(t *testing.T) {
	a := New(t)
	server := newTokenServer(a)
	defer server.Close()

	// Make sure we're not logged on
	err := Logout(server.URL)
	a.So(err, ShouldBeNil)

	// Save an expired token
	expires := time.Now().Add(time.Duration(-1) * time.Hour)
	savedAuth, err := saveAuth(server.URL, "jantje@test.org", "123", "ABC", expires)
	a.So(err, ShouldBeNil)

	// Refresh the token
	loadedAuth, err := LoadAuth(server.URL)
	a.So(err, ShouldBeNil)
	a.So(loadedAuth, ShouldNotBeNil)
	a.So(savedAuth, ShouldNotResemble, loadedAuth)
	a.So(loadedAuth.AccessToken, ShouldEqual, "456")
	a.So(loadedAuth.RefreshToken, ShouldEqual, "DEF")
	a.So(loadedAuth.Email, ShouldEqual, "jantje@test.org")
}

func TestLoadWithInvalidRefresh(t *testing.T) {
	a := New(t)
	server := newTokenServer(a)
	defer server.Close()

	// Make sure we're not logged on
	err := Logout(server.URL)
	a.So(err, ShouldBeNil)

	// Save an expired token
	expires := time.Now().Add(time.Duration(-1) * time.Hour)
	_, err = saveAuth(server.URL, "pietje@test.org", "987", "ZYX", expires)
	a.So(err, ShouldBeNil)

	// Refresh the token
	loadedAuth, err := LoadAuth(server.URL)
	a.So(err, ShouldNotBeNil)
	a.So(err.Error(), ShouldEqual, "Refresh token not found")
	a.So(loadedAuth, ShouldBeNil)
}
<|endoftext|>


<commit_before>package hostedservice

import (
	"encoding/base64"
	"encoding/xml"
	"fmt"

	"github.com/Azure/azure-sdk-for-go/management"
	locationclient "github.com/Azure/azure-sdk-for-go/management/location"
)

const (
	azureXmlns                        = "http://schemas.microsoft.com/windowsazure"
	azureDeploymentListURL            = "services/hostedservices/%s/deployments"
	azureHostedServiceListURL         = "services/hostedservices"
	azureHostedServiceAvailabilityURL = "services/hostedservices/operations/isavailable/%s"
	azureDeploymentURL                = "services/hostedservices/%s/deployments/%s"
	deleteAzureDeploymentURL          = "services/hostedservices/%s/deployments/%s"
	getHostedServicePropertiesURL     = "services/hostedservices/%s"
	azureServiceCertificateURL        = "services/hostedservices/%s/certificates"

	errParamNotSpecified = "Parameter %s is not specified."
)

//NewClient is used to return a handle to the HostedService API
func NewClient(client management.Client) HostedServiceClient {
	return HostedServiceClient{client: client}
}

func (self HostedServiceClient) CreateHostedService(dnsName, location string, reverseDnsFqdn string, label string, description string) (management.OperationId, error) {
	if dnsName == "" {
		return "", fmt.Errorf(errParamNotSpecified, "dnsName")
	}
	if location == "" {
		return "", fmt.Errorf(errParamNotSpecified, "location")
	}

	hostedServiceDeployment := self.createHostedServiceDeploymentConfig(dnsName, location, reverseDnsFqdn, label, description)
	hostedServiceBytes, err := xml.Marshal(hostedServiceDeployment)
	if err != nil {
		return "", err
	}

	requestURL := azureHostedServiceListURL
	return self.client.SendAzurePostRequest(requestURL, hostedServiceBytes)
}

func (self HostedServiceClient) CheckHostedServiceNameAvailability(dnsName string) (bool, string, error) {
	if dnsName == "" {
		return false, "", fmt.Errorf(errParamNotSpecified, "dnsName")
	}

	requestURL := fmt.Sprintf(azureHostedServiceAvailabilityURL, dnsName)
	response, err := self.client.SendAzureGetRequest(requestURL)
	if err != nil {
		return false, "", err
	}

	availabilityResponse := new(AvailabilityResponse)
	err = xml.Unmarshal(response, availabilityResponse)
	if err != nil {
		return false, "", err
	}

	return availabilityResponse.Result, availabilityResponse.Reason, nil
}

func (self HostedServiceClient) DeleteHostedService(dnsName string, deleteDisksAndBlobs bool) (management.OperationId, error) {
	if dnsName == "" {
		return "", fmt.Errorf(errParamNotSpecified, "dnsName")
	}

	requestURL := fmt.Sprintf(getHostedServicePropertiesURL, dnsName)
	if deleteDisksAndBlobs {
		requestURL += "?comp=media"
	}
	return self.client.SendAzureDeleteRequest(requestURL)
}

func (self HostedServiceClient) GetHostedService(name string) (HostedService, error) {
	hostedService := HostedService{}
	if name == "" {
		return hostedService, fmt.Errorf(errParamNotSpecified, "name")
	}

	requestURL := fmt.Sprintf(getHostedServicePropertiesURL, name)
	response, err := self.client.SendAzureGetRequest(requestURL)
	if err != nil {
		return hostedService, err
	}

	err = xml.Unmarshal(response, &hostedService)
	if err != nil {
		return hostedService, err
	}

	decodedLabel, err := base64.StdEncoding.DecodeString(hostedService.LabelBase64)
	if err != nil {
		return hostedService, err
	}
	hostedService.Label = string(decodedLabel)
	return hostedService, nil
}

func (self HostedServiceClient) ListHostedServices() (ListHostedServiceResponse, error) {
	var response ListHostedServiceResponse

	data, err := self.client.SendAzureGetRequest(azureHostedServiceListURL)
	if err != nil {
		return response, err
	}

	err = xml.Unmarshal(data, &response)

	return response, err
}

func (self HostedServiceClient) createHostedServiceDeploymentConfig(dnsName, location string, reverseDnsFqdn string, label string, description string) CreateHostedService {
	encodedLabel := base64.StdEncoding.EncodeToString([]byte(label))
	deployment := CreateHostedService{
		ServiceName:    dnsName,
		Label:          encodedLabel,
		Description:    description,
		Location:       location,
		ReverseDnsFqdn: reverseDnsFqdn,
		Xmlns:          azureXmlns,
	}
	return deployment
}

func (self HostedServiceClient) AddCertificate(dnsName string, certData []byte, certificateFormat CertificateFormat, password string) (management.OperationId, error) {
	if dnsName == "" {
		return "", fmt.Errorf(errParamNotSpecified, "dnsName")
	}

	certBase64 := base64.StdEncoding.EncodeToString(certData)

	addCertificate := CertificateFile{
		Data:              certBase64,
		CertificateFormat: certificateFormat,
		Password:          password,
		Xmlns:             azureXmlns,
	}
	buffer, err := xml.Marshal(addCertificate)
	if err != nil {
		return "", err
	}

	requestURL := fmt.Sprintf(azureServiceCertificateURL, dnsName)
	return self.client.SendAzurePostRequest(requestURL, buffer)
}
<commit_msg>Fix unused import<commit_after>package hostedservice

import (
	"encoding/base64"
	"encoding/xml"
	"fmt"

	"github.com/Azure/azure-sdk-for-go/management"
)

const (
	azureXmlns                        = "http://schemas.microsoft.com/windowsazure"
	azureDeploymentListURL            = "services/hostedservices/%s/deployments"
	azureHostedServiceListURL         = "services/hostedservices"
	azureHostedServiceAvailabilityURL = "services/hostedservices/operations/isavailable/%s"
	azureDeploymentURL                = "services/hostedservices/%s/deployments/%s"
	deleteAzureDeploymentURL          = "services/hostedservices/%s/deployments/%s"
	getHostedServicePropertiesURL     = "services/hostedservices/%s"
	azureServiceCertificateURL        = "services/hostedservices/%s/certificates"

	errParamNotSpecified = "Parameter %s is not specified."
)

//NewClient is used to return a handle to the HostedService API
func NewClient(client management.Client) HostedServiceClient {
	return HostedServiceClient{client: client}
}

func (self HostedServiceClient) CreateHostedService(dnsName, location string, reverseDnsFqdn string, label string, description string) (management.OperationId, error) {
	if dnsName == "" {
		return "", fmt.Errorf(errParamNotSpecified, "dnsName")
	}
	if location == "" {
		return "", fmt.Errorf(errParamNotSpecified, "location")
	}

	hostedServiceDeployment := self.createHostedServiceDeploymentConfig(dnsName, location, reverseDnsFqdn, label, description)
	hostedServiceBytes, err := xml.Marshal(hostedServiceDeployment)
	if err != nil {
		return "", err
	}

	requestURL := azureHostedServiceListURL
	return self.client.SendAzurePostRequest(requestURL, hostedServiceBytes)
}

func (self HostedServiceClient) CheckHostedServiceNameAvailability(dnsName string) (bool, string, error) {
	if dnsName == "" {
		return false, "", fmt.Errorf(errParamNotSpecified, "dnsName")
	}

	requestURL := fmt.Sprintf(azureHostedServiceAvailabilityURL, dnsName)
	response, err := self.client.SendAzureGetRequest(requestURL)
	if err != nil {
		return false, "", err
	}

	availabilityResponse := new(AvailabilityResponse)
	err = xml.Unmarshal(response, availabilityResponse)
	if err != nil {
		return false, "", err
	}

	return availabilityResponse.Result, availabilityResponse.Reason, nil
}

func (self HostedServiceClient) DeleteHostedService(dnsName string, deleteDisksAndBlobs bool) (management.OperationId, error) {
	if dnsName == "" {
		return "", fmt.Errorf(errParamNotSpecified, "dnsName")
	}

	requestURL := fmt.Sprintf(getHostedServicePropertiesURL, dnsName)
	if deleteDisksAndBlobs {
		requestURL += "?comp=media"
	}
	return self.client.SendAzureDeleteRequest(requestURL)
}

func (self HostedServiceClient) GetHostedService(name string) (HostedService, error) {
	hostedService := HostedService{}
	if name == "" {
		return hostedService, fmt.Errorf(errParamNotSpecified, "name")
	}

	requestURL := fmt.Sprintf(getHostedServicePropertiesURL, name)
	response, err := self.client.SendAzureGetRequest(requestURL)
	if err != nil {
		return hostedService, err
	}

	err = xml.Unmarshal(response, &hostedService)
	if err != nil {
		return hostedService, err
	}

	decodedLabel, err := base64.StdEncoding.DecodeString(hostedService.LabelBase64)
	if err != nil {
		return hostedService, err
	}
	hostedService.Label = string(decodedLabel)
	return hostedService, nil
}

func (self HostedServiceClient) ListHostedServices() (ListHostedServiceResponse, error) {
	var response ListHostedServiceResponse

	data, err := self.client.SendAzureGetRequest(azureHostedServiceListURL)
	if err != nil {
		return response, err
	}

	err = xml.Unmarshal(data, &response)

	return response, err
}

func (self HostedServiceClient) createHostedServiceDeploymentConfig(dnsName, location string, reverseDnsFqdn string, label string, description string) CreateHostedService {
	encodedLabel := base64.StdEncoding.EncodeToString([]byte(label))
	deployment := CreateHostedService{
		ServiceName:    dnsName,
		Label:          encodedLabel,
		Description:    description,
		Location:       location,
		ReverseDnsFqdn: reverseDnsFqdn,
		Xmlns:          azureXmlns,
	}
	return deployment
}

func (self HostedServiceClient) AddCertificate(dnsName string, certData []byte, certificateFormat CertificateFormat, password string) (management.OperationId, error) {
	if dnsName == "" {
		return "", fmt.Errorf(errParamNotSpecified, "dnsName")
	}

	certBase64 := base64.StdEncoding.EncodeToString(certData)

	addCertificate := CertificateFile{
		Data:              certBase64,
		CertificateFormat: certificateFormat,
		Password:          password,
		Xmlns:             azureXmlns,
	}
	buffer, err := xml.Marshal(addCertificate)
	if err != nil {
		return "", err
	}

	requestURL := fmt.Sprintf(azureServiceCertificateURL, dnsName)
	return self.client.SendAzurePostRequest(requestURL, buffer)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"testing"
)

func TestSlack(t *testing.T) {
	fmt.Println("Hello testing")
}
<commit_msg>touch<commit_after>package main

import (
	"fmt"
	"testing"
)

func TestSlack(t *testing.T) {
	fmt.Println("Hello testing!")
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"strings"
	"sync"

	"github.com/inominate/apicache"
)

// Prototype for page specific handlers.
type APIHandler func(url string, params map[string]string) *apicache.Response

// Default straight through handler.
func defaultHandler(url string, params map[string]string) *apicache.Response {
	resp, err := APIReq(url, params)
	if err != nil {
		debugLog.Printf("API Error %s: %s - %+v", err, url, params)
	}

	return resp
}

// Defines valid API pages and what special handler they should use.
// nil handlers will attempt to use defaultHandler which is a straight
// passthrough.
var validPages = map[string]APIHandler{
	//	"/control/":                             controlHandler,
	"/account/accountstatus.xml.aspx":       nil,
	"/account/apikeyinfo.xml.aspx":          nil,
	"/account/characters.xml.aspx":          nil,
	"/char/accountbalance.xml.aspx":         nil,
	"/char/assetlist.xml.aspx":              nil,
	"/char/calendareventattendees.xml.aspx": nil,
	"/char/charactersheet.xml.aspx":         nil,
	"/char/contactlist.xml.aspx":            nil,
	"/char/contactnotifications.xml.aspx":   nil,
	"/char/contracts.xml.aspx":              nil,
	"/char/contractitems.xml.aspx":          nil,
	"/char/contractbids.xml.aspx":           nil,
	"/char/facwarstats.xml.aspx":            nil,
	"/char/industryjobs.xml.aspx":           nil,
	"/char/killlog.xml.aspx":                nil,
	"/char/locations.xml.aspx":              idsListHandler,
	"/char/mailbodies.xml.aspx":             idsListHandler,
	"/char/mailinglists.xml.aspx":           nil,
	"/char/mailmessages.xml.aspx":           nil,
	"/char/marketorders.xml.aspx":           nil,
	"/char/medals.xml.aspx":                 nil,
	"/char/notifications.xml.aspx":          nil,
	"/char/notificationtexts.xml.aspx":      idsListHandler,
	"char/planetarycolonies.xml.aspx":       nil,
	"char/planetarylinks.xml.aspx":          nil,
	"char/planetarypins.xml.aspx":           nil,
	"char/planetaryroutes.xml.aspx":         nil,
	"/char/research.xml.aspx":               nil,
	"/char/skillintraining.xml.aspx":        nil,
	"/char/skillqueue.xml.aspx":             nil,
	"/char/standings.xml.aspx":              nil,
	"/char/upcomingcalendarevents.xml.aspx": nil,
	"/char/walletjournal.xml.aspx":          nil,
	"/char/wallettransactions.xml.aspx":     nil,
	"/corp/accountbalance.xml.aspx":         nil,
	"/corp/assetlist.xml.aspx":              nil,
	"/corp/contactlist.xml.aspx":            nil,
	"/corp/containerlog.xml.aspx":           nil,
	"/corp/contracts.xml.aspx":              nil,
	"/corp/contractitems.xml.aspx":          nil,
	"/corp/contractbids.xml.aspx":           nil,
	"/corp/corporationsheet.xml.aspx":       nil,
	"/corp/facwarstats.xml.aspx":            nil,
	"/corp/industryjobs.xml.aspx":           nil,
	"/corp/killlog.xml.aspx":                nil,
	"/corp/locations.xml.aspx":              idsListHandler,
	"/corp/marketorders.xml.aspx":           nil,
	"/corp/medals.xml.aspx":                 nil,
	"/corp/membermedals.xml.aspx":           nil,
	"/corp/membersecurity.xml.aspx":         nil,
	"/corp/membersecuritylog.xml.aspx":      nil,
	"/corp/membertracking.xml.aspx":         nil,
	"/corp/outpostlist.xml.aspx":            nil,
	"/corp/outpostservicedetail.xml.aspx":   nil,
	"/corp/shareholders.xml.aspx":           nil,
	"/corp/standings.xml.aspx":              nil,
	"/corp/starbasedetail.xml.aspx":         nil,
	"/corp/starbaselist.xml.aspx":           nil,
	"/corp/titles.xml.aspx":                 nil,
	"/corp/walletjournal.xml.aspx":          nil,
	"/corp/wallettransactions.xml.aspx":     nil,
	"/eve/alliancelist.xml.aspx":            nil,
	"/eve/certificatetree.xml.aspx":         nil,
	"/eve/characterid.xml.aspx":             nil,
	"/eve/characterinfo.xml.aspx":           nil,
	"/eve/charactername.xml.aspx":           nil,
	"/eve/conquerablestationlist.xml.aspx":  nil,
	"/eve/errorlist.xml.aspx":               nil,
	"/eve/facwarstats.xml.aspx":             nil,
	"/eve/facwartopstats.xml.aspx":          nil,
	"/eve/reftypes.xml.aspx":                nil,
	"/eve/skilltree.xml.aspx":               nil,
	"/eve/typename.xml.aspx":                nil,
	"/map/facwarsystems.xml.aspx":           nil,
	"/map/jumps.xml.aspx":                   nil,
	"/map/kills.xml.aspx":                   nil,
	"/map/sovereignty.xml.aspx":             nil,
	"/map/sovereigntystatus.xml.aspx":       nil,
	"/server/serverstatus.xml.aspx":         nil,
	"/api/calllist.xml.aspx":                nil,
}

/*
Note that this is a best-attempt number only, actual error count can go
significantly higher as massed concurrent requests run. This isn't to prevent
errors being sent to the API so much as to prevent things from getting out of
control in response to a pathlogical request.
*/
const maxIDErrors = 16

// Bug Correcting Handler for endpoints using comma separated ID lists which
// will fail entirely in case of a single invalid ID.
//
// Note: Can generate many errors so should only be used with applications
// that know to behave themselves. Add a form value of fix with any content
// to enable the correction.
func idsListHandler(url string, params map[string]string) *apicache.Response {
	var runFixer bool
	if _, ok := params["fix"]; ok {
		delete(params, "fix")
		runFixer = true
	}

	resp, err := APIReq(url, params)
	if err != nil {
		debugLog.Printf("API Error %s: %s - %+v", err, url, params)
	}
	if !runFixer {
		return resp
	}

	var ids []string
	if idsParam, ok := params["ids"]; ok {
		ids = strings.Split(idsParam, ",")
	}

	// If we have no ids or just one, we're not doing anything special.
	// If there's more than 250 ids, that's beyond the API limit so we won't
	// touch that either.
	if len(ids) == 0 || len(ids) == 1 || len(ids) > 250 {
		return resp
	}
	// If the request didn't have an invalid id, errorcode 135, there's nothing
	// we can do to help.
	if resp.Error.ErrorCode != 135 {
		return resp
	}

	// If we got this far there's more than one ID, at least one of which is
	// invalid.
	debugLog.Printf("idsListHandler going into action for %d ids: %s", len(ids), params["ids"])

	var errCount errCount
	delete(params, "ids")

	validIDs, err := findValidIDs(url, params, ids, &errCount)
	if err != nil {
		debugLog.Printf("findValidIDs failed: %s", err)
		return resp
	}

	idsBuf := &bytes.Buffer{}
	fmt.Fprintf(idsBuf, "%s", validIDs[0])
	for i := 1; i < len(validIDs); i++ {
		fmt.Fprintf(idsBuf, ",%s", validIDs[i])
	}
	idsParam := idsBuf.String()
	params["ids"] = idsParam

	resp, err = APIReq(url, params)
	if err != nil {
		debugLog.Printf("API Error %s: %s - %+v", err, url, params)
	}
	debugLog.Printf("Completed with: %d errors.", errCount.Get())
	return resp
}

type errCount struct {
	count int
	sync.Mutex
}

func (e *errCount) Get() int {
	e.Lock()
	defer e.Unlock()

	count := e.count
	return count
}

func (e *errCount) Add() int {
	e.Lock()
	defer e.Unlock()

	e.count++
	count := e.count
	return count
}

func findValidIDs(url string, params map[string]string, ids []string, errCount *errCount) ([]string, error) {
	if false && len(ids) == 1 {
		valid, err := isValidIDList(url, params, ids, errCount)
		if valid {
			return ids, err
		} else {
			return nil, err
		}
	}

	if count := errCount.Get(); count >= maxIDErrors {
		return nil, fmt.Errorf("failed to get ids, hit %d errors limit", count)
	}

	var leftIDs, rightIDs []string
	var leftErr, rightErr error

	left := ids[0 : len(ids)/2]
	leftValid, leftErr := isValidIDList(url, params, left, errCount)
	if leftErr != nil {
		return nil, leftErr
	}
	if leftValid {
		leftIDs = left
	} else {
		if len(left) > 1 {
			leftIDs, leftErr = findValidIDs(url, params, left, errCount)
			if rightErr != nil {
				return nil, leftErr
			}
		}
	}

	right := ids[len(ids)/2:]
	rightValid, rightErr := isValidIDList(url, params, right, errCount)
	if rightErr != nil {
		return nil, rightErr
	}
	if rightValid {
		rightIDs = right
	} else {
		if len(right) > 1 {
			rightIDs, rightErr = findValidIDs(url, params, right, errCount)
			if rightErr != nil {
				return nil, rightErr
			}
		}
	}

	validIDs := append(leftIDs, rightIDs...)
	return validIDs, nil
}

func isValidIDList(url string, params map[string]string, ids []string, errCount *errCount) (bool, error) {
	if count := errCount.Get(); count >= maxIDErrors {
		return false, fmt.Errorf("failed to get ids, hit %d errors limit", count)
	}

	idsBuf := &bytes.Buffer{}
	fmt.Fprintf(idsBuf, "%s", ids[0])
	for i := 1; i < len(ids); i++ {
		fmt.Fprintf(idsBuf, ",%s", ids[i])
	}
	idsParam := idsBuf.String()

	var newParams = make(map[string]string)
	for k, v := range params {
		newParams[k] = v
	}
	newParams["ids"] = idsParam

	resp, err := APIReq(url, newParams)
	// Bail completely if the API itself fails for any reason.
	if err != nil {
		return false, err
	}
	// If there is no error then this batch is okay.
	if resp.Error.ErrorCode == 0 {
		return true, nil
	}
	// Bail if we got a non-api failure error other than invalid ID
	if resp.Error.ErrorCode != 135 {
		return false, resp.Error
	}

	debugLog.Printf("Adding Error %d for: %v", errCount.Get(), ids)
	errCount.Add()

	return false, nil
}
<commit_msg>fix pi endpoints<commit_after>package main

import (
	"bytes"
	"fmt"
	"strings"
	"sync"

	"github.com/inominate/apicache"
)

// Prototype for page specific handlers.
type APIHandler func(url string, params map[string]string) *apicache.Response

// Default straight through handler.
func defaultHandler(url string, params map[string]string) *apicache.Response {
	resp, err := APIReq(url, params)
	if err != nil {
		debugLog.Printf("API Error %s: %s - %+v", err, url, params)
	}

	return resp
}

// Defines valid API pages and what special handler they should use.
// nil handlers will attempt to use defaultHandler which is a straight
// passthrough.
var validPages = map[string]APIHandler{
	//	"/control/":                             controlHandler,
	"/account/accountstatus.xml.aspx":       nil,
	"/account/apikeyinfo.xml.aspx":          nil,
	"/account/characters.xml.aspx":          nil,
	"/char/accountbalance.xml.aspx":         nil,
	"/char/assetlist.xml.aspx":              nil,
	"/char/calendareventattendees.xml.aspx": nil,
	"/char/charactersheet.xml.aspx":         nil,
	"/char/contactlist.xml.aspx":            nil,
	"/char/contactnotifications.xml.aspx":   nil,
	"/char/contracts.xml.aspx":              nil,
	"/char/contractitems.xml.aspx":          nil,
	"/char/contractbids.xml.aspx":           nil,
	"/char/facwarstats.xml.aspx":            nil,
	"/char/industryjobs.xml.aspx":           nil,
	"/char/killlog.xml.aspx":                nil,
	"/char/locations.xml.aspx":              idsListHandler,
	"/char/mailbodies.xml.aspx":             idsListHandler,
	"/char/mailinglists.xml.aspx":           nil,
	"/char/mailmessages.xml.aspx":           nil,
	"/char/marketorders.xml.aspx":           nil,
	"/char/medals.xml.aspx":                 nil,
	"/char/notifications.xml.aspx":          nil,
	"/char/notificationtexts.xml.aspx":      idsListHandler,
	"/char/planetarycolonies.xml.aspx":      nil,
	"/char/planetarylinks.xml.aspx":         nil,
	"/char/planetarypins.xml.aspx":          nil,
	"/char/planetaryroutes.xml.aspx":        nil,
	"/char/research.xml.aspx":               nil,
	"/char/skillintraining.xml.aspx":        nil,
	"/char/skillqueue.xml.aspx":             nil,
	"/char/standings.xml.aspx":              nil,
	"/char/upcomingcalendarevents.xml.aspx": nil,
	"/char/walletjournal.xml.aspx":          nil,
	"/char/wallettransactions.xml.aspx":     nil,
	"/corp/accountbalance.xml.aspx":         nil,
	"/corp/assetlist.xml.aspx":              nil,
	"/corp/contactlist.xml.aspx":            nil,
	"/corp/containerlog.xml.aspx":           nil,
	"/corp/contracts.xml.aspx":              nil,
	"/corp/contractitems.xml.aspx":          nil,
	"/corp/contractbids.xml.aspx":           nil,
	"/corp/corporationsheet.xml.aspx":       nil,
	"/corp/facwarstats.xml.aspx":            nil,
	"/corp/industryjobs.xml.aspx":           nil,
	"/corp/killlog.xml.aspx":                nil,
	"/corp/locations.xml.aspx":              idsListHandler,
	"/corp/marketorders.xml.aspx":           nil,
	"/corp/medals.xml.aspx":                 nil,
	"/corp/membermedals.xml.aspx":           nil,
	"/corp/membersecurity.xml.aspx":         nil,
	"/corp/membersecuritylog.xml.aspx":      nil,
	"/corp/membertracking.xml.aspx":         nil,
	"/corp/outpostlist.xml.aspx":            nil,
	"/corp/outpostservicedetail.xml.aspx":   nil,
	"/corp/shareholders.xml.aspx":           nil,
	"/corp/standings.xml.aspx":              nil,
	"/corp/starbasedetail.xml.aspx":         nil,
	"/corp/starbaselist.xml.aspx":           nil,
	"/corp/titles.xml.aspx":                 nil,
	"/corp/walletjournal.xml.aspx":          nil,
	"/corp/wallettransactions.xml.aspx":     nil,
	"/eve/alliancelist.xml.aspx":            nil,
	"/eve/certificatetree.xml.aspx":         nil,
	"/eve/characterid.xml.aspx":             nil,
	"/eve/characterinfo.xml.aspx":           nil,
	"/eve/charactername.xml.aspx":           nil,
	"/eve/conquerablestationlist.xml.aspx":  nil,
	"/eve/errorlist.xml.aspx":               nil,
	"/eve/facwarstats.xml.aspx":             nil,
	"/eve/facwartopstats.xml.aspx":          nil,
	"/eve/reftypes.xml.aspx":                nil,
	"/eve/skilltree.xml.aspx":               nil,
	"/eve/typename.xml.aspx":                nil,
	"/map/facwarsystems.xml.aspx":           nil,
	"/map/jumps.xml.aspx":                   nil,
	"/map/kills.xml.aspx":                   nil,
	"/map/sovereignty.xml.aspx":             nil,
	"/map/sovereigntystatus.xml.aspx":       nil,
	"/server/serverstatus.xml.aspx":         nil,
	"/api/calllist.xml.aspx":                nil,
}

/*
Note that this is a best-attempt number only, actual error count can go
significantly higher as massed concurrent requests run. This isn't to prevent
errors being sent to the API so much as to prevent things from getting out of
control in response to a pathlogical request.
*/
const maxIDErrors = 16

// Bug Correcting Handler for endpoints using comma separated ID lists which
// will fail entirely in case of a single invalid ID.
//
// Note: Can generate many errors so should only be used with applications
// that know to behave themselves. Add a form value of fix with any content
// to enable the correction.
func idsListHandler(url string, params map[string]string) *apicache.Response {
	var runFixer bool
	if _, ok := params["fix"]; ok {
		delete(params, "fix")
		runFixer = true
	}

	resp, err := APIReq(url, params)
	if err != nil {
		debugLog.Printf("API Error %s: %s - %+v", err, url, params)
	}
	if !runFixer {
		return resp
	}

	var ids []string
	if idsParam, ok := params["ids"]; ok {
		ids = strings.Split(idsParam, ",")
	}

	// If we have no ids or just one, we're not doing anything special.
	// If there's more than 250 ids, that's beyond the API limit so we won't
	// touch that either.
	if len(ids) == 0 || len(ids) == 1 || len(ids) > 250 {
		return resp
	}
	// If the request didn't have an invalid id, errorcode 135, there's nothing
	// we can do to help.
	if resp.Error.ErrorCode != 135 {
		return resp
	}

	// If we got this far there's more than one ID, at least one of which is
	// invalid.
	debugLog.Printf("idsListHandler going into action for %d ids: %s", len(ids), params["ids"])

	var errCount errCount
	delete(params, "ids")

	validIDs, err := findValidIDs(url, params, ids, &errCount)
	if err != nil {
		debugLog.Printf("findValidIDs failed: %s", err)
		return resp
	}

	idsBuf := &bytes.Buffer{}
	fmt.Fprintf(idsBuf, "%s", validIDs[0])
	for i := 1; i < len(validIDs); i++ {
		fmt.Fprintf(idsBuf, ",%s", validIDs[i])
	}
	idsParam := idsBuf.String()
	params["ids"] = idsParam

	resp, err = APIReq(url, params)
	if err != nil {
		debugLog.Printf("API Error %s: %s - %+v", err, url, params)
	}
	debugLog.Printf("Completed with: %d errors.", errCount.Get())
	return resp
}

type errCount struct {
	count int
	sync.Mutex
}

func (e *errCount) Get() int {
	e.Lock()
	defer e.Unlock()

	count := e.count
	return count
}

func (e *errCount) Add() int {
	e.Lock()
	defer e.Unlock()

	e.count++
	count := e.count
	return count
}

func findValidIDs(url string, params map[string]string, ids []string, errCount *errCount) ([]string, error) {
	if false && len(ids) == 1 {
		valid, err := isValidIDList(url, params, ids, errCount)
		if valid {
			return ids, err
		} else {
			return nil, err
		}
	}

	if count := errCount.Get(); count >= maxIDErrors {
		return nil, fmt.Errorf("failed to get ids, hit %d errors limit", count)
	}

	var leftIDs, rightIDs []string
	var leftErr, rightErr error

	left := ids[0 : len(ids)/2]
	leftValid, leftErr := isValidIDList(url, params, left, errCount)
	if leftErr != nil {
		return nil, leftErr
	}
	if leftValid {
		leftIDs = left
	} else {
		if len(left) > 1 {
			leftIDs, leftErr = findValidIDs(url, params, left, errCount)
			if rightErr != nil {
				return nil, leftErr
			}
		}
	}

	right := ids[len(ids)/2:]
	rightValid, rightErr := isValidIDList(url, params, right, errCount)
	if rightErr != nil {
		return nil, rightErr
	}
	if rightValid {
		rightIDs = right
	} else {
		if len(right) > 1 {
			rightIDs, rightErr = findValidIDs(url, params, right, errCount)
			if rightErr != nil {
				return nil, rightErr
			}
		}
	}

	validIDs := append(leftIDs, rightIDs...)
	return validIDs, nil
}

func isValidIDList(url string, params map[string]string, ids []string, errCount *errCount) (bool, error) {
	if count := errCount.Get(); count >= maxIDErrors {
		return false, fmt.Errorf("failed to get ids, hit %d errors limit", count)
	}

	idsBuf := &bytes.Buffer{}
	fmt.Fprintf(idsBuf, "%s", ids[0])
	for i := 1; i < len(ids); i++ {
		fmt.Fprintf(idsBuf, ",%s", ids[i])
	}
	idsParam := idsBuf.String()

	var newParams = make(map[string]string)
	for k, v := range params {
		newParams[k] = v
	}
	newParams["ids"] = idsParam

	resp, err := APIReq(url, newParams)
	// Bail completely if the API itself fails for any reason.
	if err != nil {
		return false, err
	}
	// If there is no error then this batch is okay.
	if resp.Error.ErrorCode == 0 {
		return true, nil
	}
	// Bail if we got a non-api failure error other than invalid ID
	if resp.Error.ErrorCode != 135 {
		return false, resp.Error
	}

	debugLog.Printf("Adding Error %d for: %v", errCount.Get(), ids)
	errCount.Add()

	return false, nil
}
<|endoftext|>


<commit_before>package main

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

	"github.com/gorilla/context"
	"github.com/gorilla/mux"
	"github.com/thermokarst/jwt"
)

type Claims struct {
	Name string
	Iss  string
	Sub  int64
	Role string
	Iat  int64
	Exp  int64
}

func Handler() http.Handler {
	claimsFunc := func(email string) (map[string]interface{}, error) {
		currentTime := time.Now()
		user, err := dbGetUserByEmail(email)
		if err != nil {
			return nil, err
		}
		return map[string]interface{}{
			"name": user.Name,
			"iss":  "bactdb",
			"sub":  user.Id,
			"role": user.Role,
			"iat":  currentTime.Unix(),
			"exp":  currentTime.Add(time.Minute * 60 * 24).Unix(),
		}, nil
	}

	verifyClaims := func(claims []byte, r *http.Request) error {
		currentTime := time.Now()
		var c Claims
		err := json.Unmarshal(claims, &c)
		if err != nil {
			return err
		}
		if currentTime.After(time.Unix(c.Exp, 0)) {
			return errors.New("this token has expired")
		}
		context.Set(r, "claims", c)
		return nil
	}

	config := &jwt.Config{
		Secret: os.Getenv("SECRET"),
		Auth:   dbAuthenticate,
		Claims: claimsFunc,
	}

	j, err := jwt.New(config)
	if err != nil {
		panic(err)
	}

	m := mux.NewRouter()
	userService := UserService{}
	strainService := StrainService{}
	speciesService := SpeciesService{}
	characteristicService := CharacteristicService{}
	characteristicTypeService := CharacteristicTypeService{}
	measurementService := MeasurementService{}

	// Non-auth routes
	m.Handle("/authenticate", tokenHandler(j.GenerateToken())).Methods("POST")
	m.Handle("/users", errorHandler(handleCreater(userService))).Methods("POST")

	// Auth routes
	m.Handle("/users", j.Secure(errorHandler(handleLister(userService)), verifyClaims)).Methods("GET")
	m.Handle("/users/{Id:.+}", j.Secure(errorHandler(handleGetter(userService)), verifyClaims)).Methods("GET")
	m.Handle("/users/{Id:.+}", j.Secure(errorHandler(handleUpdater(userService)), verifyClaims)).Methods("PUT")

	// Path-based pattern matching subrouter
	s := m.PathPrefix("/{genus}").Subrouter()

	type r struct {
		f errorHandler
		m string
		p string
	}

	routes := []r{
		r{handleLister(speciesService), "GET", "/species"},
		r{handleCreater(speciesService), "POST", "/species"},
		r{handleGetter(speciesService), "GET", "/species/{Id:.+}"},
		r{handleUpdater(speciesService), "PUT", "/species/{Id:.+}"},
		r{handleLister(strainService), "GET", "/strains"},
		r{handleCreater(strainService), "POST", "/strains"},
		r{handleGetter(strainService), "GET", "/strains/{Id:.+}"},
		r{handleUpdater(strainService), "PUT", "/strains/{Id:.+}"},
		r{handleLister(characteristicService), "GET", "/characteristics"},
		r{handleGetter(characteristicService), "GET", "/characteristics/{Id:.+}"},
		r{handleLister(characteristicTypeService), "GET", "/characteristicTypes"},
		r{handleGetter(characteristicTypeService), "GET", "/characteristicTypes/{Id:.+}"},
		r{handleLister(measurementService), "GET", "/measurements"},
		r{handleGetter(measurementService), "GET", "/measurements/{Id:.+}"},
	}

	for _, route := range routes {
		s.Handle(route.p, j.Secure(errorHandler(route.f), verifyClaims)).Methods(route.m)
	}

	return jsonHandler(corsHandler(m))
}

func handleGetter(g getter) errorHandler {
	return func(w http.ResponseWriter, r *http.Request) *appError {
		id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0)
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}

		e, appErr := g.get(id, mux.Vars(r)["genus"])
		if appErr != nil {
			return appErr
		}

		data, err := e.marshal()
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}
		w.Write(data)
		return nil
	}
}

func handleLister(l lister) errorHandler {
	return func(w http.ResponseWriter, r *http.Request) *appError {
		opt := r.URL.Query()
		opt.Add("Genus", mux.Vars(r)["genus"])

		es, appErr := l.list(&opt)
		if appErr != nil {
			return appErr
		}
		data, err := es.marshal()
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}
		w.Write(data)
		return nil
	}
}

func handleUpdater(u updater) errorHandler {
	return func(w http.ResponseWriter, r *http.Request) *appError {
		id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0)
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}

		bodyBytes, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}

		e, err := u.unmarshal(bodyBytes)
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}

		c := context.Get(r, "claims")
		var claims Claims = c.(Claims)

		appErr := u.update(id, &e, claims)
		if appErr != nil {
			return appErr
		}

		data, err := e.marshal()
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}
		w.Write(data)
		return nil
	}
}

func handleCreater(c creater) errorHandler {
	return func(w http.ResponseWriter, r *http.Request) *appError {
		bodyBytes, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}

		e, err := c.unmarshal(bodyBytes)
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}

		con := context.Get(r, "claims")
		var claims Claims = con.(Claims)

		appErr := c.create(&e, claims)
		if appErr != nil {
			return appErr
		}

		data, err := e.marshal()
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}
		w.Write(data)
		return nil
	}
}

func tokenHandler(h http.Handler) http.Handler {
	token := func(w http.ResponseWriter, r *http.Request) {
		// Hackish, but we want the token in a JSON object
		w.Write([]byte(`{"token":"`))
		h.ServeHTTP(w, r)
		w.Write([]byte(`"}`))
	}
	return http.HandlerFunc(token)
}

func corsHandler(h http.Handler) http.Handler {
	cors := func(w http.ResponseWriter, r *http.Request) {
		domains := os.Getenv("DOMAINS")
		allowedDomains := strings.Split(domains, ",")
		if origin := r.Header.Get("Origin"); origin != "" {
			for _, s := range allowedDomains {
				if s == origin {
					w.Header().Set("Access-Control-Allow-Origin", origin)
					w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
					w.Header().Set("Access-Control-Allow-Methods", r.Header.Get("Access-Control-Request-Method"))
				}
			}
		}
		if r.Method != "OPTIONS" {
			h.ServeHTTP(w, r)
		}
	}
	return http.HandlerFunc(cors)
}

func jsonHandler(h http.Handler) http.Handler {
	j := func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json; charset=UTF-8")
		h.ServeHTTP(w, r)
	}
	return http.HandlerFunc(j)
}

type errorHandler func(http.ResponseWriter, *http.Request) *appError

func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if err := fn(w, r); err != nil {
		w.WriteHeader(err.Status)
		fmt.Fprintln(w, err.Error.Error())
	}
}
<commit_msg>nil claims<commit_after>package main

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

	"github.com/gorilla/context"
	"github.com/gorilla/mux"
	"github.com/thermokarst/jwt"
)

type Claims struct {
	Name string
	Iss  string
	Sub  int64
	Role string
	Iat  int64
	Exp  int64
}

func Handler() http.Handler {
	claimsFunc := func(email string) (map[string]interface{}, error) {
		currentTime := time.Now()
		user, err := dbGetUserByEmail(email)
		if err != nil {
			return nil, err
		}
		return map[string]interface{}{
			"name": user.Name,
			"iss":  "bactdb",
			"sub":  user.Id,
			"role": user.Role,
			"iat":  currentTime.Unix(),
			"exp":  currentTime.Add(time.Minute * 60 * 24).Unix(),
		}, nil
	}

	verifyClaims := func(claims []byte, r *http.Request) error {
		currentTime := time.Now()
		var c Claims
		err := json.Unmarshal(claims, &c)
		if err != nil {
			return err
		}
		if currentTime.After(time.Unix(c.Exp, 0)) {
			return errors.New("this token has expired")
		}
		context.Set(r, "claims", c)
		return nil
	}

	config := &jwt.Config{
		Secret: os.Getenv("SECRET"),
		Auth:   dbAuthenticate,
		Claims: claimsFunc,
	}

	j, err := jwt.New(config)
	if err != nil {
		panic(err)
	}

	m := mux.NewRouter()
	userService := UserService{}
	strainService := StrainService{}
	speciesService := SpeciesService{}
	characteristicService := CharacteristicService{}
	characteristicTypeService := CharacteristicTypeService{}
	measurementService := MeasurementService{}

	// Non-auth routes
	m.Handle("/authenticate", tokenHandler(j.GenerateToken())).Methods("POST")
	m.Handle("/users", errorHandler(handleCreater(userService))).Methods("POST")

	// Auth routes
	m.Handle("/users", j.Secure(errorHandler(handleLister(userService)), verifyClaims)).Methods("GET")
	m.Handle("/users/{Id:.+}", j.Secure(errorHandler(handleGetter(userService)), verifyClaims)).Methods("GET")
	m.Handle("/users/{Id:.+}", j.Secure(errorHandler(handleUpdater(userService)), verifyClaims)).Methods("PUT")

	// Path-based pattern matching subrouter
	s := m.PathPrefix("/{genus}").Subrouter()

	type r struct {
		f errorHandler
		m string
		p string
	}

	routes := []r{
		r{handleLister(speciesService), "GET", "/species"},
		r{handleCreater(speciesService), "POST", "/species"},
		r{handleGetter(speciesService), "GET", "/species/{Id:.+}"},
		r{handleUpdater(speciesService), "PUT", "/species/{Id:.+}"},
		r{handleLister(strainService), "GET", "/strains"},
		r{handleCreater(strainService), "POST", "/strains"},
		r{handleGetter(strainService), "GET", "/strains/{Id:.+}"},
		r{handleUpdater(strainService), "PUT", "/strains/{Id:.+}"},
		r{handleLister(characteristicService), "GET", "/characteristics"},
		r{handleGetter(characteristicService), "GET", "/characteristics/{Id:.+}"},
		r{handleLister(characteristicTypeService), "GET", "/characteristicTypes"},
		r{handleGetter(characteristicTypeService), "GET", "/characteristicTypes/{Id:.+}"},
		r{handleLister(measurementService), "GET", "/measurements"},
		r{handleGetter(measurementService), "GET", "/measurements/{Id:.+}"},
	}

	for _, route := range routes {
		s.Handle(route.p, j.Secure(errorHandler(route.f), verifyClaims)).Methods(route.m)
	}

	return jsonHandler(corsHandler(m))
}

func handleGetter(g getter) errorHandler {
	return func(w http.ResponseWriter, r *http.Request) *appError {
		id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0)
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}

		e, appErr := g.get(id, mux.Vars(r)["genus"])
		if appErr != nil {
			return appErr
		}

		data, err := e.marshal()
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}
		w.Write(data)
		return nil
	}
}

func handleLister(l lister) errorHandler {
	return func(w http.ResponseWriter, r *http.Request) *appError {
		opt := r.URL.Query()
		opt.Add("Genus", mux.Vars(r)["genus"])

		es, appErr := l.list(&opt)
		if appErr != nil {
			return appErr
		}
		data, err := es.marshal()
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}
		w.Write(data)
		return nil
	}
}

func handleUpdater(u updater) errorHandler {
	return func(w http.ResponseWriter, r *http.Request) *appError {
		id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0)
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}

		bodyBytes, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}

		e, err := u.unmarshal(bodyBytes)
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}

		c := context.Get(r, "claims")
		var claims Claims = c.(Claims)

		appErr := u.update(id, &e, claims)
		if appErr != nil {
			return appErr
		}

		data, err := e.marshal()
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}
		w.Write(data)
		return nil
	}
}

func handleCreater(c creater) errorHandler {
	return func(w http.ResponseWriter, r *http.Request) *appError {
		bodyBytes, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}

		e, err := c.unmarshal(bodyBytes)
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}

		con := context.Get(r, "claims")
		var claims Claims
		if con != nil {
			claims = con.(Claims)
		}

		appErr := c.create(&e, claims)
		if appErr != nil {
			return appErr
		}

		data, err := e.marshal()
		if err != nil {
			return newJSONError(err, http.StatusInternalServerError)
		}
		w.Write(data)
		return nil
	}
}

func tokenHandler(h http.Handler) http.Handler {
	token := func(w http.ResponseWriter, r *http.Request) {
		// Hackish, but we want the token in a JSON object
		w.Write([]byte(`{"token":"`))
		h.ServeHTTP(w, r)
		w.Write([]byte(`"}`))
	}
	return http.HandlerFunc(token)
}

func corsHandler(h http.Handler) http.Handler {
	cors := func(w http.ResponseWriter, r *http.Request) {
		domains := os.Getenv("DOMAINS")
		allowedDomains := strings.Split(domains, ",")
		if origin := r.Header.Get("Origin"); origin != "" {
			for _, s := range allowedDomains {
				if s == origin {
					w.Header().Set("Access-Control-Allow-Origin", origin)
					w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
					w.Header().Set("Access-Control-Allow-Methods", r.Header.Get("Access-Control-Request-Method"))
				}
			}
		}
		if r.Method != "OPTIONS" {
			h.ServeHTTP(w, r)
		}
	}
	return http.HandlerFunc(cors)
}

func jsonHandler(h http.Handler) http.Handler {
	j := func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json; charset=UTF-8")
		h.ServeHTTP(w, r)
	}
	return http.HandlerFunc(j)
}

type errorHandler func(http.ResponseWriter, *http.Request) *appError

func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if err := fn(w, r); err != nil {
		w.WriteHeader(err.Status)
		fmt.Fprintln(w, err.Error.Error())
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"io"
	"log"
	"os"
	"os/exec"
	"strings"
	"sync"

	"github.com/flynn/go-shlex"
	"github.com/kr/pty"
	"golang.org/x/crypto/ssh"
)

func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel, execHandler []string) {
	ch, reqs, err := newChan.Accept()
	if err != nil {
		log.Println("newChan.Accept failed:", err)
		return
	}

	// Setup stdout/stderr
	var stdout, stderr io.Writer
	if *debug {
		stdout = io.MultiWriter(ch, os.Stdout)
		stderr = io.MultiWriter(ch.Stderr(), os.Stdout)
	} else {
		stdout = ch
		stderr = ch.Stderr()
	}

	handler := sshHandler{
		ExecHandler: execHandler,
		channel:     ch,
		stdout:      stdout,
		stderr:      stderr,
	}

	// Load default environment
	if *env {
		handler.Env = os.Environ()
	}
	if conn.Permissions != nil {
		// Using Permissions.Extensions as a way to get state from PublicKeyCallback
		if conn.Permissions.Extensions["environ"] != "" {
			handler.Env = append(handler.Env, strings.Split(conn.Permissions.Extensions["environ"], "\n")...)
		}
		handler.Env = append(handler.Env, "USER="+conn.Permissions.Extensions["user"])
	}

	for req := range reqs {
		go handler.Request(req)
	}
}

// sshHandler is a stateful handler for requests within an SSH channel
type sshHandler struct {
	sync.Mutex
	Env         []string
	ExecHandler []string
	channel     ssh.Channel
	stdout      io.Writer
	stderr      io.Writer
	ptyShell    *os.File
}

func (h sshHandler) assert(at string, err error) bool {
	if err != nil {
		log.Printf("%s failed: %s", at, err)
		h.stderr.Write([]byte("Internal error.\n"))
		return true
	}
	return false
}

// Exit sends an exit-status request to the channel based on the err.
func (h *sshHandler) Exit(err error) error {
	defer h.channel.Close()

	status, err := exitStatus(err)
	if !h.assert("exit", err) {
		_, err := h.channel.SendRequest("exit-status", false, ssh.Marshal(&status))
		h.assert("status", err)
		return err
	}
	return err
}

func (h *sshHandler) Request(req *ssh.Request) {
	switch req.Type {
	case "exec":
		h.handleExec(req)
	case "pty-req":
		h.handlePty(req)
	case "window-change":
		h.handleWinch(req)
	default:
		if req.WantReply {
			req.Reply(true, nil)
		}
	}
}

func (h *sshHandler) handleExec(req *ssh.Request) {
	h.Lock()
	defer h.Unlock()

	cmdline := string(req.Payload[4:])

	// Initialize Cmd
	var cmd *exec.Cmd
	if *shell {
		shellcmd := flag.Arg(1) + " " + cmdline
		cmd = exec.Command(os.Getenv("SHELL"), "-c", shellcmd)
	} else {
		cmdargs, err := shlex.Split(cmdline)
		if h.assert("exec shlex.Split", err) {
			h.channel.Close()
			return
		}
		cmd = exec.Command(h.ExecHandler[0], append(h.ExecHandler[1:], cmdargs...)...)
	}

	cmd.Env = append(h.Env, "SSH_ORIGINAL_COMMAND="+cmdline)
	cmd.Stdout = h.stdout
	cmd.Stderr = h.stderr

	// cmd.Wait closes the stdin when it's done, so we need to proxy it through a pipe
	stdinPipe, err := cmd.StdinPipe()
	if h.assert("exec cmd.StdinPipe", err) {
		h.channel.Close()
		return
	}
	go io.Copy(stdinPipe, h.channel)

	if req.WantReply {
		req.Reply(true, nil)
	}

	// We run inline to prevent concurrent exec requests for the channel as the lock is held.
	h.Exit(cmd.Run())
}

func (h *sshHandler) handlePty(req *ssh.Request) {
	h.Lock()
	defer h.Unlock()

	if h.ptyShell != nil {
		// Only allow one pty per channel
		req.Reply(false, nil)
		return
	}

	width, height, okSize := parsePtyRequest(req.Payload)

	// Initialize Cmd
	var cmd *exec.Cmd
	if *shell {
		cmd = exec.Command(os.Getenv("SHELL"))
	} else {
		cmd = exec.Command(h.ExecHandler[0], h.ExecHandler[1:]...)
	}
	cmd.Env = h.Env

	// attachShell does cmd.Start() so we need to do cmd.Wait() later
	ptyShell, _, err := attachShell(cmd, h.stdout, h.channel)
	if h.assert("pty attachShell", err) {
		h.channel.Close()
		return
	}
	h.ptyShell = ptyShell

	if okSize {
		setWinsize(ptyShell.Fd(), width, height)
	}

	// Ready to receive input
	req.Reply(true, nil)

	// We run this concurrently so that the lock is released for window-change events.
	go h.Exit(cmd.Wait())
}

func (h sshHandler) handleWinch(req *ssh.Request) {
	h.Lock()
	defer h.Unlock()

	width, height, okSize := parsePtyRequest(req.Payload)
	if okSize && h.ptyShell != nil {
		setWinsize(h.ptyShell.Fd(), width, height)
	}
}

func attachShell(cmd *exec.Cmd, stdout io.Writer, stdin io.Reader) (*os.File, *sync.WaitGroup, error) {
	var wg sync.WaitGroup
	wg.Add(2)

	// Note that pty merges stdout and stderr.
	cmdPty, err := pty.Start(cmd)
	if err != nil {
		return nil, nil, err
	}
	go func() {
		io.Copy(stdout, cmdPty)
		wg.Done()
	}()
	go func() {
		io.Copy(cmdPty, stdin)
		wg.Done()
	}()

	return cmdPty, &wg, nil
}
<commit_msg>Unmarshal the payload of a request.<commit_after>package main

import (
	"flag"
	"io"
	"log"
	"os"
	"os/exec"
	"strings"
	"sync"

	"github.com/flynn/go-shlex"
	"github.com/kr/pty"
	"golang.org/x/crypto/ssh"
)

func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel, execHandler []string) {
	ch, reqs, err := newChan.Accept()
	if err != nil {
		log.Println("newChan.Accept failed:", err)
		return
	}

	// Setup stdout/stderr
	var stdout, stderr io.Writer
	if *debug {
		stdout = io.MultiWriter(ch, os.Stdout)
		stderr = io.MultiWriter(ch.Stderr(), os.Stdout)
	} else {
		stdout = ch
		stderr = ch.Stderr()
	}

	handler := sshHandler{
		ExecHandler: execHandler,
		channel:     ch,
		stdout:      stdout,
		stderr:      stderr,
	}

	// Load default environment
	if *env {
		handler.Env = os.Environ()
	}
	if conn.Permissions != nil {
		// Using Permissions.Extensions as a way to get state from PublicKeyCallback
		if conn.Permissions.Extensions["environ"] != "" {
			handler.Env = append(handler.Env, strings.Split(conn.Permissions.Extensions["environ"], "\n")...)
		}
		handler.Env = append(handler.Env, "USER="+conn.Permissions.Extensions["user"])
	}

	for req := range reqs {
		go handler.Request(req)
	}
}

// sshHandler is a stateful handler for requests within an SSH channel
type sshHandler struct {
	sync.Mutex
	Env         []string
	ExecHandler []string
	channel     ssh.Channel
	stdout      io.Writer
	stderr      io.Writer
	ptyShell    *os.File
}

func (h sshHandler) assert(at string, err error) bool {
	if err != nil {
		log.Printf("%s failed: %s", at, err)
		h.stderr.Write([]byte("Internal error.\n"))
		return true
	}
	return false
}

// Exit sends an exit-status request to the channel based on the err.
func (h *sshHandler) Exit(err error) error {
	defer h.channel.Close()

	status, err := exitStatus(err)
	if !h.assert("exit", err) {
		_, err := h.channel.SendRequest("exit-status", false, ssh.Marshal(&status))
		h.assert("status", err)
		return err
	}
	return err
}

func (h *sshHandler) Request(req *ssh.Request) {
	switch req.Type {
	case "exec":
		h.handleExec(req)
	case "pty-req":
		h.handlePty(req)
	case "window-change":
		h.handleWinch(req)
	default:
		if req.WantReply {
			req.Reply(true, nil)
		}
	}
}

func (h *sshHandler) handleExec(req *ssh.Request) {
	h.Lock()
	defer h.Unlock()

	var payload = struct{ Value string }{}
	ssh.Unmarshal(req.Payload, &payload)
	cmdline := payload.Value

	// Initialize Cmd
	var cmd *exec.Cmd
	if *shell {
		shellcmd := flag.Arg(1) + " " + cmdline
		cmd = exec.Command(os.Getenv("SHELL"), "-c", shellcmd)
	} else {
		cmdargs, err := shlex.Split(cmdline)
		if h.assert("exec shlex.Split", err) {
			h.channel.Close()
			return
		}
		cmd = exec.Command(h.ExecHandler[0], append(h.ExecHandler[1:], cmdargs...)...)
	}

	cmd.Env = append(h.Env, "SSH_ORIGINAL_COMMAND="+cmdline)
	cmd.Stdout = h.stdout
	cmd.Stderr = h.stderr

	// cmd.Wait closes the stdin when it's done, so we need to proxy it through a pipe
	stdinPipe, err := cmd.StdinPipe()
	if h.assert("exec cmd.StdinPipe", err) {
		h.channel.Close()
		return
	}
	go io.Copy(stdinPipe, h.channel)

	if req.WantReply {
		req.Reply(true, nil)
	}

	// We run inline to prevent concurrent exec requests for the channel as the lock is held.
	h.Exit(cmd.Run())
}

func (h *sshHandler) handlePty(req *ssh.Request) {
	h.Lock()
	defer h.Unlock()

	if h.ptyShell != nil {
		// Only allow one pty per channel
		req.Reply(false, nil)
		return
	}

	width, height, okSize := parsePtyRequest(req.Payload)

	// Initialize Cmd
	var cmd *exec.Cmd
	if *shell {
		cmd = exec.Command(os.Getenv("SHELL"))
	} else {
		cmd = exec.Command(h.ExecHandler[0], h.ExecHandler[1:]...)
	}
	cmd.Env = h.Env

	// attachShell does cmd.Start() so we need to do cmd.Wait() later
	ptyShell, _, err := attachShell(cmd, h.stdout, h.channel)
	if h.assert("pty attachShell", err) {
		h.channel.Close()
		return
	}
	h.ptyShell = ptyShell

	if okSize {
		setWinsize(ptyShell.Fd(), width, height)
	}

	// Ready to receive input
	req.Reply(true, nil)

	// We run this concurrently so that the lock is released for window-change events.
	go h.Exit(cmd.Wait())
}

func (h sshHandler) handleWinch(req *ssh.Request) {
	h.Lock()
	defer h.Unlock()

	width, height, okSize := parsePtyRequest(req.Payload)
	if okSize && h.ptyShell != nil {
		setWinsize(h.ptyShell.Fd(), width, height)
	}
}

func attachShell(cmd *exec.Cmd, stdout io.Writer, stdin io.Reader) (*os.File, *sync.WaitGroup, error) {
	var wg sync.WaitGroup
	wg.Add(2)

	// Note that pty merges stdout and stderr.
	cmdPty, err := pty.Start(cmd)
	if err != nil {
		return nil, nil, err
	}
	go func() {
		io.Copy(stdout, cmdPty)
		wg.Done()
	}()
	go func() {
		io.Copy(cmdPty, stdin)
		wg.Done()
	}()

	return cmdPty, &wg, nil
}
<|endoftext|>


<commit_before>package admin

import (
	"fmt"
	"github.com/goji/httpauth"
	"github.com/kardianos/osext"
	"io"
	"io/ioutil"
	"net/http"
	pprofH "net/http/pprof"
	"os"
	"runtime"
	"runtime/debug"
	"runtime/pprof"
	"time"
)

func WithGenericValue(path string, value interface{}) RouteConfig {
	return WithGetHandler(path, genericContentAsJSON(value))
}

func WithGetHandlerFunc(path string, handler http.HandlerFunc) RouteConfig {
	return WithGetHandler(path, handler)
}

func WithGetHandler(path string, handler http.Handler) RouteConfig {
	return WithHandler("GET", path, handler)
}

func WithHandlerFunc(method, path string, handler http.HandlerFunc) RouteConfig {
	return WithHandler(method, path, handler)
}

func WithHandler(method, path string, handler http.Handler) RouteConfig {
	return RouteConfig{Route: Route{Method: method, Path: path, Handler: handler}}
}

func WithRedirect(from, to string) RouteConfig {
	return WithHandler("", from, http.RedirectHandler(to, http.StatusTemporaryRedirect))
}

func WithMetrics(registry MetricsRegistry) RouteConfig {
	return Describe(
		"The current content of the MetricsRegistry",
		WithGenericValue("/metrics", registry))
}

func WithForceGC() RouteConfig {
	return Describe(
		"Forces a run of the garbage collector.",
		WithHandlerFunc("POST", "/gc/run", func(w http.ResponseWriter, req *http.Request) {
			start := time.Now()

			// do a gc now.
			runtime.GC()

			w.WriteHeader(http.StatusOK)
			w.Write([]byte(fmt.Sprintf("gc took %s", time.Since(start))))
		}))
}

var appStartTime = time.Now()

func WithBuildInfo(buildInfo BuildInfo) RouteConfig {
	type appInfoWithTime struct {
		BuildInfo
		Hostname   string `json:",omitempty"`
		StartTime  time.Time
		ServerTime time.Time
		Uptime     string
	}

	return Describe(
		"Information about the current build",
		WithGenericValue("/info", func() appInfoWithTime {
			hostname, _ := os.Hostname()
			return appInfoWithTime{
				BuildInfo:  buildInfo,
				Hostname:   hostname,
				StartTime:  appStartTime,
				ServerTime: time.Now(),
				Uptime:     time.Since(appStartTime).String(),
			}
		}))
}

func WithHeapDump() RouteConfig {
	return Describe(
		"Creates a snapshot of the processes heap.",
		WithHandlerFunc("GET", "pprof/heapdump", func(w http.ResponseWriter, req *http.Request) {
			w.Header().Set("Content-Type", "application/octet-stream")

			// we can not write the heap-dump directly to the network socket,
			// so write it down to disk first.
			file, err := ioutil.TempFile("/tmp/", "heapdump")
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}

			// cleanup after ourselves
			defer os.Remove(file.Name())
			defer file.Close()

			// write the dump to a file
			debug.WriteHeapDump(file.Fd())

			// looks good, serve the file.
			filename := fmt.Sprintf("heapdump-%s.heap", time.Now().Format("20060102-150405"))
			w.Header().Set("Content-Disposition", "attachment; filename="+filename)

			file.Seek(0, os.SEEK_SET)
			io.Copy(w, file)
		}))
}

func WithPProfHandlers() RouteConfig {
	rc := RouteConfig{children: []RouteConfig{
		Describe(
			"The command line of the running process. Arguments are seperated by null bytes.",
			WithHandler("GET", "pprof/cmdline", http.HandlerFunc(pprofH.Cmdline))),

		Describe(
			"Profiles the application. Use with 'go tool pprof http://host/pprof/profile'",
			WithHandler("GET", "pprof/profile", http.HandlerFunc(pprofH.Profile))),

		Describe(
			"Performes a trace of cpu, io and more. Accepts an url parameter 'seconds'",
			WithHandler("GET", "pprof/trace", http.HandlerFunc(pprofH.Trace))),

		// symbol can handle post data, register it for GET and POST.
		Describe(
			"Resolves addresses to symbols. Used by ppprof.",
			WithHandler("", "pprof/symbol", http.HandlerFunc(pprofH.Symbol))),

		Describe(
			"Provides a memory profile of the application as done by 'WriteHeapProfile'",
			WithHandlerFunc("GET", "pprof/memprofile", func(w http.ResponseWriter, req *http.Request) {
				w.Header().Set("Content-Type", "application/octet-stream")
				pprof.WriteHeapProfile(w)
			})),
	}}

	// also expose the currently used binary - this simplifies profiling.
	if exe, err := osext.Executable(); err == nil {
		rc.children = append(rc.children, Describe(
			"Downloads the process binary that is currently running",
			WithHandlerFunc("GET", "pprof/exe", func(w http.ResponseWriter, req *http.Request) {
				http.ServeFile(w, req, exe)
			})))
	}

	return rc
}

func WithEnvironmentVariables() RouteConfig {
	return Describe(
		"A map containing all environment variables.",
		WithGenericValue("env", os.Environ))
}

func RequireAuth(user, pass string, configs ...RouteConfig) RouteConfig {
	auth := httpauth.SimpleBasicAuth(user, pass)

	var secured []RouteConfig
	for _, config := range configs {
		config.Handler = auth(config.Handler)
		secured = append(secured, config)
	}

	return RouteConfig{children: secured}
}

func WithPingPong() RouteConfig {
	return Describe(
		"Always returns the static json '{\"pong\": true}'",
		WithGenericValue("/ping", map[string]bool{"pong": true}))
}

func WithGCStats() RouteConfig {
	return Describe(
		"Displays the current gc- and memory-statistics from the golang runtime.",
		WithGenericValue("/gc/stats", func() interface{} {
			var stats struct {
				GarbageCollectorStats debug.GCStats
				MemStats              runtime.MemStats
			}

			debug.ReadGCStats(&stats.GarbageCollectorStats)
			runtime.ReadMemStats(&stats.MemStats)

			return stats
		}))
}

func WithDefaults() RouteConfig {
	return RouteConfig{children: []RouteConfig{
		WithPingPong(),
		WithEnvironmentVariables(),
		WithGCStats(),
	}}
}
<commit_msg>ping should support more methods<commit_after>package admin

import (
	"fmt"
	"github.com/goji/httpauth"
	"github.com/kardianos/osext"
	"io"
	"io/ioutil"
	"net/http"
	pprofH "net/http/pprof"
	"os"
	"runtime"
	"runtime/debug"
	"runtime/pprof"
	"time"
)

func WithGenericValue(path string, value interface{}) RouteConfig {
	return WithGetHandler(path, genericContentAsJSON(value))
}

func WithGetHandlerFunc(path string, handler http.HandlerFunc) RouteConfig {
	return WithGetHandler(path, handler)
}

func WithGetHandler(path string, handler http.Handler) RouteConfig {
	return WithHandler("GET", path, handler)
}

func WithHandlerFunc(method, path string, handler http.HandlerFunc) RouteConfig {
	return WithHandler(method, path, handler)
}

func WithHandler(method, path string, handler http.Handler) RouteConfig {
	return RouteConfig{Route: Route{Method: method, Path: path, Handler: handler}}
}

func WithRedirect(from, to string) RouteConfig {
	return WithHandler("", from, http.RedirectHandler(to, http.StatusTemporaryRedirect))
}

func WithMetrics(registry MetricsRegistry) RouteConfig {
	return Describe(
		"The current content of the MetricsRegistry",
		WithGenericValue("/metrics", registry))
}

func WithForceGC() RouteConfig {
	return Describe(
		"Forces a run of the garbage collector.",
		WithHandlerFunc("POST", "/gc/run", func(w http.ResponseWriter, req *http.Request) {
			start := time.Now()

			// do a gc now.
			runtime.GC()

			w.WriteHeader(http.StatusOK)
			w.Write([]byte(fmt.Sprintf("gc took %s", time.Since(start))))
		}))
}

var appStartTime = time.Now()

func WithBuildInfo(buildInfo BuildInfo) RouteConfig {
	type appInfoWithTime struct {
		BuildInfo
		Hostname   string `json:",omitempty"`
		StartTime  time.Time
		ServerTime time.Time
		Uptime     string
	}

	return Describe(
		"Information about the current build",
		WithGenericValue("/info", func() appInfoWithTime {
			hostname, _ := os.Hostname()
			return appInfoWithTime{
				BuildInfo:  buildInfo,
				Hostname:   hostname,
				StartTime:  appStartTime,
				ServerTime: time.Now(),
				Uptime:     time.Since(appStartTime).String(),
			}
		}))
}

func WithHeapDump() RouteConfig {
	return Describe(
		"Creates a snapshot of the processes heap.",
		WithHandlerFunc("GET", "pprof/heapdump", func(w http.ResponseWriter, req *http.Request) {
			w.Header().Set("Content-Type", "application/octet-stream")

			// we can not write the heap-dump directly to the network socket,
			// so write it down to disk first.
			file, err := ioutil.TempFile("/tmp/", "heapdump")
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}

			// cleanup after ourselves
			defer os.Remove(file.Name())
			defer file.Close()

			// write the dump to a file
			debug.WriteHeapDump(file.Fd())

			// looks good, serve the file.
			filename := fmt.Sprintf("heapdump-%s.heap", time.Now().Format("20060102-150405"))
			w.Header().Set("Content-Disposition", "attachment; filename="+filename)

			file.Seek(0, os.SEEK_SET)
			io.Copy(w, file)
		}))
}

func WithPProfHandlers() RouteConfig {
	rc := RouteConfig{children: []RouteConfig{
		Describe(
			"The command line of the running process. Arguments are seperated by null bytes.",
			WithHandler("GET", "pprof/cmdline", http.HandlerFunc(pprofH.Cmdline))),

		Describe(
			"Profiles the application. Use with 'go tool pprof http://host/pprof/profile'",
			WithHandler("GET", "pprof/profile", http.HandlerFunc(pprofH.Profile))),

		Describe(
			"Performes a trace of cpu, io and more. Accepts an url parameter 'seconds'",
			WithHandler("GET", "pprof/trace", http.HandlerFunc(pprofH.Trace))),

		// symbol can handle post data, register it for GET and POST.
		Describe(
			"Resolves addresses to symbols. Used by ppprof.",
			WithHandler("", "pprof/symbol", http.HandlerFunc(pprofH.Symbol))),

		Describe(
			"Provides a memory profile of the application as done by 'WriteHeapProfile'",
			WithHandlerFunc("GET", "pprof/memprofile", func(w http.ResponseWriter, req *http.Request) {
				w.Header().Set("Content-Type", "application/octet-stream")
				pprof.WriteHeapProfile(w)
			})),
	}}

	// also expose the currently used binary - this simplifies profiling.
	if exe, err := osext.Executable(); err == nil {
		rc.children = append(rc.children, Describe(
			"Downloads the process binary that is currently running",
			WithHandlerFunc("GET", "pprof/exe", func(w http.ResponseWriter, req *http.Request) {
				http.ServeFile(w, req, exe)
			})))
	}

	return rc
}

func WithEnvironmentVariables() RouteConfig {
	return Describe(
		"A map containing all environment variables.",
		WithGenericValue("env", os.Environ))
}

func RequireAuth(user, pass string, configs ...RouteConfig) RouteConfig {
	auth := httpauth.SimpleBasicAuth(user, pass)

	var secured []RouteConfig
	for _, config := range configs {
		config.Handler = auth(config.Handler)
		secured = append(secured, config)
	}

	return RouteConfig{children: secured}
}

func WithPingPong() RouteConfig {
	return Describe(
		"Always returns the static json '{\"pong\": true}'",
		WithHandlerFunc("", "/ping", func(w http.ResponseWriter, r *http.Request) {
			w.Header().Add("Content-Type", "application/json")
			w.Write([]byte(`{"pong":true}`))
		}))
}

func WithGCStats() RouteConfig {
	return Describe(
		"Displays the current gc- and memory-statistics from the golang runtime.",
		WithGenericValue("/gc/stats", func() interface{} {
			var stats struct {
				GarbageCollectorStats debug.GCStats
				MemStats              runtime.MemStats
			}

			debug.ReadGCStats(&stats.GarbageCollectorStats)
			runtime.ReadMemStats(&stats.MemStats)

			return stats
		}))
}

func WithDefaults() RouteConfig {
	return RouteConfig{children: []RouteConfig{
		WithPingPong(),
		WithEnvironmentVariables(),
		WithGCStats(),
	}}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"text/template"
	"time"
)

type Post struct {
	ID   int
	Post string
	Date time.Time
}

var indexPage string

// index page
func indexPageHandler(response http.ResponseWriter, request *http.Request) {
	var posts []Post

	rows, err := DB.Query("select id, post, date from post")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	for rows.Next() {
		var id int
		var post string
		var date time.Time
		rows.Scan(&id, &post, &date)
		posts = append(posts, Post{ID: id, Post: post, Date: date})
	}
	rows.Close()

	type Page struct {
		Posts []Post
	}
	var page = Page{Posts: posts}

	bufIndexPage, _ := ioutil.ReadFile("pages/index.html")
	indexPage = string(bufIndexPage)

	t := template.Must(template.New("page").Parse(indexPage))

	t.Execute(response, page)
}

// new post page
func postHandler(response http.ResponseWriter, request *http.Request) {
	v := request.URL.Query()
	pID := v.Get("id")

	stmt, err := DB.Prepare("select id, post, date from post where id = ?")
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close()
	var id int
	var post string
	var date time.Time
	err = stmt.QueryRow(pID).Scan(&id, &post, &date)
	if err != nil {
		log.Fatal(err)
	}

	type Page struct {
		ID   int
		Post string
	}
	var page = Page{ID: id, Post: post}

	bufIndexPage, _ := ioutil.ReadFile("pages/post.html")
	indexPage = string(bufIndexPage)

	t := template.Must(template.New("page").Parse(indexPage))

	t.Execute(response, page)
}

// new post page
func saveHandler(response http.ResponseWriter, request *http.Request) {
	request.ParseForm()
	pID := request.FormValue("id")
	pPost := request.FormValue("post")
	pDate := time.Now()

	tx, err := DB.Begin()
	if err != nil {
		log.Fatal(err)
	}
	stmt, err := tx.Prepare("update post set post=?, date=? where id = ?")
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close()
	_, err = stmt.Exec(pPost, pDate, pID)
	if err != nil {
		log.Fatal(err)
	}
	tx.Commit()

	fmt.Printf("RequestURI: %v \n", request.Referer())
	http.Redirect(response, request, request.Referer(), 302)
}
<commit_msg>add post insert<commit_after>package main

import (
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"text/template"
	"time"
)

type Post struct {
	ID   int
	Post string
	Date time.Time
}

var indexPage string

// index page
func indexPageHandler(response http.ResponseWriter, request *http.Request) {
	var posts []Post

	rows, err := DB.Query("select id, post, date from post")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	for rows.Next() {
		var id int
		var post string
		var date time.Time
		rows.Scan(&id, &post, &date)
		posts = append(posts, Post{ID: id, Post: post, Date: date})
	}
	rows.Close()

	type Page struct {
		Posts []Post
	}
	var page = Page{Posts: posts}

	bufIndexPage, _ := ioutil.ReadFile("pages/index.html")
	indexPage = string(bufIndexPage)

	t := template.Must(template.New("page").Parse(indexPage))

	t.Execute(response, page)
}

// new post page
func postHandler(response http.ResponseWriter, request *http.Request) {
	var id = -1
	var post string
	var date time.Time

	v := request.URL.Query()
	pID := v.Get("id")
	if len(pID) > 0 {
		stmt, err := DB.Prepare("select id, post, date from post where id = ?")
		if err != nil {
			log.Fatal(err)
		}
		defer stmt.Close()
		err = stmt.QueryRow(pID).Scan(&id, &post, &date)
		if err != nil {
			log.Fatal(err)
		}
	}

	type Page struct {
		ID   int
		Post string
	}
	var page = Page{ID: id, Post: post}

	bufIndexPage, _ := ioutil.ReadFile("pages/post.html")
	indexPage = string(bufIndexPage)

	t := template.Must(template.New("page").Parse(indexPage))

	t.Execute(response, page)
}

// new post page
func saveHandler(response http.ResponseWriter, request *http.Request) {
	request.ParseForm()
	pID := request.FormValue("id")
	pPost := request.FormValue("post")
	pDate := time.Now()

	if pID == "-1" {
		tx, err := DB.Begin()
		if err != nil {
			log.Fatal(err)
		}
		stmt, err := tx.Prepare("insert into post (post, date) values (?, ?)")
		if err != nil {
			log.Fatal(err)
		}
		defer stmt.Close()
		r, err := stmt.Exec(pPost, pDate)
		lastID, _ := r.LastInsertId()
		pID = strconv.Itoa(int(lastID))
		if err != nil {
			log.Fatal(err)
		}
		tx.Commit()
		http.Redirect(response, request, "/post.html?id="+pID, 302)
	} else {
		tx, err := DB.Begin()
		if err != nil {
			log.Fatal(err)
		}
		stmt, err := tx.Prepare("update post set post=?, date=? where id = ?")
		if err != nil {
			log.Fatal(err)
		}
		defer stmt.Close()
		_, err = stmt.Exec(pPost, pDate, pID)
		if err != nil {
			log.Fatal(err)
		}
		tx.Commit()
		http.Redirect(response, request, request.Referer(), 302)
	}
}
<|endoftext|>