<commit_before>package main

import (
	"flag"
	"github.com/aybabtme/gexf"
	"github.com/aybabtme/rubyobj"
	"log"
	"os"
	"runtime"
	"strconv"
)

func init() {
	runtime.GOMAXPROCS(runtime.NumCPU())
}

func main() {
	var (
		objDump  string
		gexfDump string
	)
	flag.StringVar(&objDump, "src", "", "source of the object dump JSON")
	flag.StringVar(&gexfDump, "dst", "", "destination of the GEXF object dump")
	flag.Parse()

	switch {
	case objDump == "":
		flag.PrintDefaults()
		log.Fatalln("Need to provide source ObjectSpace file")
	case gexfDump == "":
		flag.PrintDefaults()
		log.Fatalln("Need to specify destination GEXF file")
	}

	jsonF, err := os.Open(objDump)
	if err != nil {
		log.Fatalf("Opening JSON source file: %v", err)
	}
	defer jsonF.Close()

	gexfF, err := os.Create(gexfDump)
	if err != nil {
		log.Fatalf("Opening GEXF destination file: %v", err)
	}
	defer gexfF.Close()

	decoded, errc := rubyobj.ParallelDecode(jsonF, uint(runtime.NumCPU()))

	go func() {
		for err := range errc {
			log.Printf("error: %v", err)
		}
	}()

	g := gexf.NewGraph()
	g.SetNodeAttrs(attrs)

	i := -1
	var id string

	for rObj := range decoded {
		addr, label, attr := extractNode(&rObj)

		if rObj.Address == 0 {
			id = g.GetID(i)
			i--
		} else {
			id = g.GetID(addr)
		}

		g.AddNode(id, label, attr)

		for _, ref := range rObj.References {
			g.AddEdge(id, g.GetID(ref))
		}
	}

	if err := gexf.Encode(gexfF, g); err != nil {
		log.Fatalf("Error encoding graph to GEXF: %v", err)
	}
}

func extractNode(r *rubyobj.RubyObject) (addr string, label string, attr []gexf.AttrValue) {
	addr = strconv.FormatUint(r.Address, 16)
	label = r.Type.Name()

	attr = []gexf.AttrValue{
		{Title: "type", Value: label},
		{Title: "value", Value: r.Value},
		{Title: "name", Value: r.Name},
		{Title: "nodeType", Value: r.NodeType},
		{Title: "address", Value: addr},
		{Title: "class", Value: r.Class},
		{Title: "default", Value: r.Default},
		{Title: "generation", Value: r.Generation},
		{Title: "bytesize", Value: r.Bytesize},
		{Title: "fd", Value: r.Fd},
		{Title: "file", Value: r.File},
		{Title: "encoding", Value: r.Encoding},
		{Title: "method", Value: r.Method},
		{Title: "ivars", Value: r.Ivars},
		{Title: "length", Value: r.Length},
		{Title: "line", Value: r.Line},
		{Title: "memsize", Value: r.Memsize},
		{Title: "capacity", Value: r.Capacity},
		{Title: "size", Value: r.Size},
		{Title: "struct", Value: r.Struct},
		{Title: "wbProtected", Value: r.GcWbProtected()},
		{Title: "old", Value: r.GcOld()},
		{Title: "marked", Value: r.GcMarked()},
		{Title: "broken", Value: r.Broken()},
		{Title: "frozen", Value: r.Frozen()},
		{Title: "fstring", Value: r.Fstring()},
		{Title: "shared", Value: r.Shared()},
		{Title: "embedded", Value: r.Embedded()},
	}
	return
}

var attrs = []gexf.Attr{
	{Title: "type", Type: gexf.String},
	{Title: "value", Type: gexf.String},
	{Title: "name", Type: gexf.String},
	{Title: "nodeType", Type: gexf.String},
	{Title: "address", Type: gexf.String},
	{Title: "class", Type: gexf.String},
	{Title: "default", Type: gexf.Long},
	{Title: "generation", Type: gexf.Long},
	{Title: "bytesize", Type: gexf.Long},
	{Title: "fd", Type: gexf.Long},
	{Title: "file", Type: gexf.String},
	{Title: "encoding", Type: gexf.String},
	{Title: "method", Type: gexf.String},
	{Title: "ivars", Type: gexf.Long},
	{Title: "length", Type: gexf.Long},
	{Title: "line", Type: gexf.Long},
	{Title: "memsize", Type: gexf.Long},
	{Title: "capacity", Type: gexf.Long},
	{Title: "size", Type: gexf.Long},
	{Title: "struct", Type: gexf.String},
	{Title: "wbProtected", Type: gexf.Boolean},
	{Title: "old", Type: gexf.Boolean},
	{Title: "marked", Type: gexf.Boolean},
	{Title: "broken", Type: gexf.Boolean},
	{Title: "frozen", Type: gexf.Boolean},
	{Title: "fstring", Type: gexf.Boolean},
	{Title: "shared", Type: gexf.Boolean},
	{Title: "embedded", Type: gexf.Boolean},
}
<commit_msg>Avoid some nil values.<commit_after>package main

import (
	"flag"
	"github.com/aybabtme/gexf"
	"github.com/aybabtme/rubyobj"
	"log"
	"os"
	"runtime"
	"strconv"
)

func init() {
	runtime.GOMAXPROCS(runtime.NumCPU())
}

func main() {
	var (
		objDump  string
		gexfDump string
	)
	flag.StringVar(&objDump, "src", "", "source of the object dump JSON")
	flag.StringVar(&gexfDump, "dst", "", "destination of the GEXF object dump")
	flag.Parse()

	switch {
	case objDump == "":
		flag.PrintDefaults()
		log.Fatalln("Need to provide source ObjectSpace file")
	case gexfDump == "":
		flag.PrintDefaults()
		log.Fatalln("Need to specify destination GEXF file")
	}

	jsonF, err := os.Open(objDump)
	if err != nil {
		log.Fatalf("Opening JSON source file: %v", err)
	}
	defer jsonF.Close()

	gexfF, err := os.Create(gexfDump)
	if err != nil {
		log.Fatalf("Opening GEXF destination file: %v", err)
	}
	defer gexfF.Close()

	decoded, errc := rubyobj.ParallelDecode(jsonF, uint(runtime.NumCPU()))

	go func() {
		for err := range errc {
			log.Printf("error: %v", err)
		}
	}()

	g := gexf.NewGraph()
	g.SetNodeAttrs(attrs)

	i := -1
	var id string

	for rObj := range decoded {
		addr, label, attr := extractNode(&rObj)

		if rObj.Address == 0 {
			id = g.GetID(i)
			i--
		} else {
			id = g.GetID(addr)
		}

		g.AddNode(id, label, attr)

		for _, ref := range rObj.References {
			g.AddEdge(id, g.GetID(ref))
		}
	}

	if err := gexf.Encode(gexfF, g); err != nil {
		log.Fatalf("Error encoding graph to GEXF: %v", err)
	}
}

func extractNode(r *rubyobj.RubyObject) (addr string, label string, attr []gexf.AttrValue) {
	addr = strconv.FormatUint(r.Address, 16)
	label = r.Type.Name()

	attr = []gexf.AttrValue{
		{Title: "broken", Value: r.Broken()},
		{Title: "bytesize", Value: r.Bytesize},
		{Title: "capacity", Value: r.Capacity},
		{Title: "default", Value: r.Default},
		{Title: "embedded", Value: r.Embedded()},
		{Title: "fd", Value: r.Fd},
		{Title: "frozen", Value: r.Frozen()},
		{Title: "fstring", Value: r.Fstring()},
		{Title: "generation", Value: r.Generation},
		{Title: "ivars", Value: r.Ivars},
		{Title: "length", Value: r.Length},
		{Title: "line", Value: r.Line},
		{Title: "marked", Value: r.GcMarked()},
		{Title: "memsize", Value: r.Memsize},
		{Title: "old", Value: r.GcOld()},
		{Title: "shared", Value: r.Shared()},
		{Title: "size", Value: r.Size},
		{Title: "wbProtected", Value: r.GcWbProtected()},
	}

	accString := func(title string, val string) {
		if val != "" {
			attr = append(attr, gexf.AttrValue{Title: title, Value: val})
		}
	}

	accInterface := func(title string, val interface{}) {
		if val != nil {
			attr = append(attr, gexf.AttrValue{Title: title, Value: val})
		}
	}

	accString("address", addr)
	accString("class", strconv.FormatUint(r.Class, 16))
	accString("encoding", r.Encoding)
	accString("file", r.File)
	accString("method", r.Method)
	accString("name", r.Name)
	accString("nodeType", r.NodeType)
	accString("struct", r.Struct)
	accString("type", label)
	accInterface("value", r.Value)

	return
}

var attrs = []gexf.Attr{
	{Title: "type", Type: gexf.String},
	{Title: "value", Type: gexf.String},
	{Title: "name", Type: gexf.String},
	{Title: "nodeType", Type: gexf.String},
	{Title: "address", Type: gexf.String},
	{Title: "class", Type: gexf.String},
	{Title: "default", Type: gexf.Long},
	{Title: "generation", Type: gexf.Long},
	{Title: "bytesize", Type: gexf.Long},
	{Title: "fd", Type: gexf.Long},
	{Title: "file", Type: gexf.String},
	{Title: "encoding", Type: gexf.String},
	{Title: "method", Type: gexf.String},
	{Title: "ivars", Type: gexf.Long},
	{Title: "length", Type: gexf.Long},
	{Title: "line", Type: gexf.Long},
	{Title: "memsize", Type: gexf.Long},
	{Title: "capacity", Type: gexf.Long},
	{Title: "size", Type: gexf.Long},
	{Title: "struct", Type: gexf.String},
	{Title: "wbProtected", Type: gexf.Boolean},
	{Title: "old", Type: gexf.Boolean},
	{Title: "marked", Type: gexf.Boolean},
	{Title: "broken", Type: gexf.Boolean},
	{Title: "frozen", Type: gexf.Boolean},
	{Title: "fstring", Type: gexf.Boolean},
	{Title: "shared", Type: gexf.Boolean},
	{Title: "embedded", Type: gexf.Boolean},
}
<|endoftext|>


<commit_before>package agent

import (
	"errors"
	"fmt"
	"os"
	"reflect"

	"github.com/buildkite/agent/env"
	"github.com/buildkite/interpolate"

	// This is a fork of gopkg.in/yaml.v2 that fixes anchors with MapSlice
	yaml "github.com/vinzenz/yaml"
)

type Pipeline struct {
	yaml.MapSlice
}

func (p Pipeline) MarshalJSON() ([]byte, error) {
	return nil, errors.New("Nope")
}

type PipelineParser struct {
	Env      *env.Environment
	Filename string
	Pipeline []byte
}

func (p PipelineParser) Parse() (interface{}, error) {
	if p.Env == nil {
		p.Env = env.FromSlice(os.Environ())
	}

	var pipelineAsMap map[string]interface{}

	// Check we can parse this as a map, otherwise later inferences about map structures break
	if err := yaml.Unmarshal([]byte(p.Pipeline), &pipelineAsMap); err != nil {
		return nil, fmt.Errorf("Failed to parse %v", err)
	}

	var pipeline yaml.MapSlice

	// Initially we unmarshal this into a yaml.MapSlice so that we preserve the order of maps
	if err := yaml.Unmarshal([]byte(p.Pipeline), &pipeline); err != nil {
		return nil, fmt.Errorf("Failed to parse %v", err)
	}

	// Preprocess any env that are defined in the top level block and place them into env for
	if item, ok := mapSliceItem("env", pipeline); ok {
		if envMap, ok := item.Value.(yaml.MapSlice); ok {
			if err := p.interpolateEnvBlock(envMap); err != nil {
				return nil, err
			}
		} else {
			return nil, fmt.Errorf("Expected pipeline top-level env block to be a map, got %T", item)
		}
	}

	// Recursively go through the entire pipeline and perform environment
	// variable interpolation on strings
	interpolated, err := p.interpolate(pipeline)
	if err != nil {
		return nil, err
	}

	// Now we roundtrip this back into YAML bytes and back into a generic interface{}
	// that works with all upstream code (which likes working with JSON). Specifically we
	// need to convert the map[interface{}]interface{}'s that YAML likes into JSON compatible
	// map[string]interface{}
	b, err := yaml.Marshal(interpolated)
	if err != nil {
		return nil, err
	}

	var result interface{}
	if err := unmarshalAsStringMap(b, &result); err != nil {
		return nil, err
	}

	return result, nil
}

func mapSliceItem(key string, s yaml.MapSlice) (yaml.MapItem, bool) {
	for _, item := range s {
		if k, ok := item.Key.(string); ok && k == key {
			return item, true
		}
	}
	return yaml.MapItem{}, false
}

func (p PipelineParser) interpolateEnvBlock(envMap yaml.MapSlice) error {
	for _, item := range envMap {
		k, ok := item.Key.(string)
		if !ok {
			return fmt.Errorf("Unexpected type of %T for env block key %v", item.Key, item.Key)
		}
		switch tv := item.Value.(type) {
		case string:
			interpolated, err := interpolate.Interpolate(p.Env, tv)
			if err != nil {
				return err
			}
			p.Env.Set(k, interpolated)
		}
	}
	return nil
}

// interpolate function inspired from: https://gist.github.com/hvoecking/10772475

func (p PipelineParser) interpolate(obj interface{}) (interface{}, error) {
	// Make sure there's something actually to interpolate
	if obj == nil {
		return nil, nil
	}

	// Wrap the original in a reflect.Value
	original := reflect.ValueOf(obj)

	// Make a copy that we'll add the new values to
	copy := reflect.New(original.Type()).Elem()

	err := p.interpolateRecursive(copy, original)
	if err != nil {
		return nil, err
	}

	// Remove the reflection wrapper
	return copy.Interface(), nil
}

func (p PipelineParser) interpolateRecursive(copy, original reflect.Value) error {
	switch original.Kind() {
	// If it is a pointer we need to unwrap and call once again
	case reflect.Ptr:
		// To get the actual value of the original we have to call Elem()
		// At the same time this unwraps the pointer so we don't end up in
		// an infinite recursion
		originalValue := original.Elem()

		// Check if the pointer is nil
		if !originalValue.IsValid() {
			return nil
		}

		// Allocate a new object and set the pointer to it
		copy.Set(reflect.New(originalValue.Type()))

		// Unwrap the newly created pointer
		err := p.interpolateRecursive(copy.Elem(), originalValue)
		if err != nil {
			return err
		}

	// If it is an interface (which is very similar to a pointer), do basically the
	// same as for the pointer. Though a pointer is not the same as an interface so
	// note that we have to call Elem() after creating a new object because otherwise
	// we would end up with an actual pointer
	case reflect.Interface:
		// Get rid of the wrapping interface
		originalValue := original.Elem()

		// Check to make sure the interface isn't nil
		if !originalValue.IsValid() {
			return nil
		}

		// Create a new object. Now new gives us a pointer, but we want the value it
		// points to, so we have to call Elem() to unwrap it
		copyValue := reflect.New(originalValue.Type()).Elem()

		err := p.interpolateRecursive(copyValue, originalValue)
		if err != nil {
			return err
		}

		copy.Set(copyValue)

	// If it is a struct we interpolate each field
	case reflect.Struct:
		for i := 0; i < original.NumField(); i += 1 {
			err := p.interpolateRecursive(copy.Field(i), original.Field(i))
			if err != nil {
				return err
			}
		}

	// If it is a slice we create a new slice and interpolate each element
	case reflect.Slice:
		copy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap()))

		for i := 0; i < original.Len(); i += 1 {
			err := p.interpolateRecursive(copy.Index(i), original.Index(i))
			if err != nil {
				return err
			}
		}

	// If it is a map we create a new map and interpolate each value
	case reflect.Map:
		copy.Set(reflect.MakeMap(original.Type()))

		for _, key := range original.MapKeys() {
			originalValue := original.MapIndex(key)

			// New gives us a pointer, but again we want the value
			copyValue := reflect.New(originalValue.Type()).Elem()
			err := p.interpolateRecursive(copyValue, originalValue)
			if err != nil {
				return err
			}

			// Also interpolate the key if it's a string
			if key.Kind() == reflect.String {
				interpolatedKey, err := interpolate.Interpolate(p.Env, key.Interface().(string))
				if err != nil {
					return err
				}
				copy.SetMapIndex(reflect.ValueOf(interpolatedKey), copyValue)
			} else {
				copy.SetMapIndex(key, copyValue)
			}
		}

	// If it is a string interpolate it (yay finally we're doing what we came for)
	case reflect.String:
		interpolated, err := interpolate.Interpolate(p.Env, original.Interface().(string))
		if err != nil {
			return err
		}
		copy.SetString(interpolated)

	// And everything else will simply be taken from the original
	default:
		copy.Set(original)
	}

	return nil
}

// Unmarshal YAML to map[string]interface{} instead of map[interface{}]interface{}, such that
// we can Marshal cleanly into JSON
// Via https://github.com/go-yaml/yaml/issues/139#issuecomment-220072190
func unmarshalAsStringMap(in []byte, out interface{}) error {
	var res interface{}

	if err := yaml.Unmarshal(in, &res); err != nil {
		return err
	}
	*out.(*interface{}) = cleanupMapValue(res)

	return nil
}

func cleanupInterfaceArray(in []interface{}) []interface{} {
	res := make([]interface{}, len(in))
	for i, v := range in {
		res[i] = cleanupMapValue(v)
	}
	return res
}

func cleanupInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
	res := make(map[string]interface{})
	for k, v := range in {
		res[fmt.Sprintf("%v", k)] = cleanupMapValue(v)
	}
	return res
}

func cleanupMapValue(v interface{}) interface{} {
	switch v := v.(type) {
	case []interface{}:
		return cleanupInterfaceArray(v)
	case map[interface{}]interface{}:
		return cleanupInterfaceMap(v)
	case string:
		return v
	default:
		return fmt.Sprintf("%v", v)
	}
}
<commit_msg>Remove unused Pipeline struct<commit_after>package agent

import (
	"fmt"
	"os"
	"reflect"

	"github.com/buildkite/agent/env"
	"github.com/buildkite/interpolate"

	// This is a fork of gopkg.in/yaml.v2 that fixes anchors with MapSlice
	yaml "github.com/vinzenz/yaml"
)

type PipelineParser struct {
	Env      *env.Environment
	Filename string
	Pipeline []byte
}

func (p PipelineParser) Parse() (interface{}, error) {
	if p.Env == nil {
		p.Env = env.FromSlice(os.Environ())
	}

	var pipelineAsMap map[string]interface{}

	// Check we can parse this as a map, otherwise later inferences about map structures break
	if err := yaml.Unmarshal([]byte(p.Pipeline), &pipelineAsMap); err != nil {
		return nil, fmt.Errorf("Failed to parse %v", err)
	}

	var pipeline yaml.MapSlice

	// Initially we unmarshal this into a yaml.MapSlice so that we preserve the order of maps
	if err := yaml.Unmarshal([]byte(p.Pipeline), &pipeline); err != nil {
		return nil, fmt.Errorf("Failed to parse %v", err)
	}

	// Preprocess any env that are defined in the top level block and place them into env for
	if item, ok := mapSliceItem("env", pipeline); ok {
		if envMap, ok := item.Value.(yaml.MapSlice); ok {
			if err := p.interpolateEnvBlock(envMap); err != nil {
				return nil, err
			}
		} else {
			return nil, fmt.Errorf("Expected pipeline top-level env block to be a map, got %T", item)
		}
	}

	// Recursively go through the entire pipeline and perform environment
	// variable interpolation on strings
	interpolated, err := p.interpolate(pipeline)
	if err != nil {
		return nil, err
	}

	// Now we roundtrip this back into YAML bytes and back into a generic interface{}
	// that works with all upstream code (which likes working with JSON). Specifically we
	// need to convert the map[interface{}]interface{}'s that YAML likes into JSON compatible
	// map[string]interface{}
	b, err := yaml.Marshal(interpolated)
	if err != nil {
		return nil, err
	}

	var result interface{}
	if err := unmarshalAsStringMap(b, &result); err != nil {
		return nil, err
	}

	return result, nil
}

func mapSliceItem(key string, s yaml.MapSlice) (yaml.MapItem, bool) {
	for _, item := range s {
		if k, ok := item.Key.(string); ok && k == key {
			return item, true
		}
	}
	return yaml.MapItem{}, false
}

func (p PipelineParser) interpolateEnvBlock(envMap yaml.MapSlice) error {
	for _, item := range envMap {
		k, ok := item.Key.(string)
		if !ok {
			return fmt.Errorf("Unexpected type of %T for env block key %v", item.Key, item.Key)
		}
		switch tv := item.Value.(type) {
		case string:
			interpolated, err := interpolate.Interpolate(p.Env, tv)
			if err != nil {
				return err
			}
			p.Env.Set(k, interpolated)
		}
	}
	return nil
}

// interpolate function inspired from: https://gist.github.com/hvoecking/10772475

func (p PipelineParser) interpolate(obj interface{}) (interface{}, error) {
	// Make sure there's something actually to interpolate
	if obj == nil {
		return nil, nil
	}

	// Wrap the original in a reflect.Value
	original := reflect.ValueOf(obj)

	// Make a copy that we'll add the new values to
	copy := reflect.New(original.Type()).Elem()

	err := p.interpolateRecursive(copy, original)
	if err != nil {
		return nil, err
	}

	// Remove the reflection wrapper
	return copy.Interface(), nil
}

func (p PipelineParser) interpolateRecursive(copy, original reflect.Value) error {
	switch original.Kind() {
	// If it is a pointer we need to unwrap and call once again
	case reflect.Ptr:
		// To get the actual value of the original we have to call Elem()
		// At the same time this unwraps the pointer so we don't end up in
		// an infinite recursion
		originalValue := original.Elem()

		// Check if the pointer is nil
		if !originalValue.IsValid() {
			return nil
		}

		// Allocate a new object and set the pointer to it
		copy.Set(reflect.New(originalValue.Type()))

		// Unwrap the newly created pointer
		err := p.interpolateRecursive(copy.Elem(), originalValue)
		if err != nil {
			return err
		}

	// If it is an interface (which is very similar to a pointer), do basically the
	// same as for the pointer. Though a pointer is not the same as an interface so
	// note that we have to call Elem() after creating a new object because otherwise
	// we would end up with an actual pointer
	case reflect.Interface:
		// Get rid of the wrapping interface
		originalValue := original.Elem()

		// Check to make sure the interface isn't nil
		if !originalValue.IsValid() {
			return nil
		}

		// Create a new object. Now new gives us a pointer, but we want the value it
		// points to, so we have to call Elem() to unwrap it
		copyValue := reflect.New(originalValue.Type()).Elem()

		err := p.interpolateRecursive(copyValue, originalValue)
		if err != nil {
			return err
		}

		copy.Set(copyValue)

	// If it is a struct we interpolate each field
	case reflect.Struct:
		for i := 0; i < original.NumField(); i += 1 {
			err := p.interpolateRecursive(copy.Field(i), original.Field(i))
			if err != nil {
				return err
			}
		}

	// If it is a slice we create a new slice and interpolate each element
	case reflect.Slice:
		copy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap()))

		for i := 0; i < original.Len(); i += 1 {
			err := p.interpolateRecursive(copy.Index(i), original.Index(i))
			if err != nil {
				return err
			}
		}

	// If it is a map we create a new map and interpolate each value
	case reflect.Map:
		copy.Set(reflect.MakeMap(original.Type()))

		for _, key := range original.MapKeys() {
			originalValue := original.MapIndex(key)

			// New gives us a pointer, but again we want the value
			copyValue := reflect.New(originalValue.Type()).Elem()
			err := p.interpolateRecursive(copyValue, originalValue)
			if err != nil {
				return err
			}

			// Also interpolate the key if it's a string
			if key.Kind() == reflect.String {
				interpolatedKey, err := interpolate.Interpolate(p.Env, key.Interface().(string))
				if err != nil {
					return err
				}
				copy.SetMapIndex(reflect.ValueOf(interpolatedKey), copyValue)
			} else {
				copy.SetMapIndex(key, copyValue)
			}
		}

	// If it is a string interpolate it (yay finally we're doing what we came for)
	case reflect.String:
		interpolated, err := interpolate.Interpolate(p.Env, original.Interface().(string))
		if err != nil {
			return err
		}
		copy.SetString(interpolated)

	// And everything else will simply be taken from the original
	default:
		copy.Set(original)
	}

	return nil
}

// Unmarshal YAML to map[string]interface{} instead of map[interface{}]interface{}, such that
// we can Marshal cleanly into JSON
// Via https://github.com/go-yaml/yaml/issues/139#issuecomment-220072190
func unmarshalAsStringMap(in []byte, out interface{}) error {
	var res interface{}

	if err := yaml.Unmarshal(in, &res); err != nil {
		return err
	}
	*out.(*interface{}) = cleanupMapValue(res)

	return nil
}

func cleanupInterfaceArray(in []interface{}) []interface{} {
	res := make([]interface{}, len(in))
	for i, v := range in {
		res[i] = cleanupMapValue(v)
	}
	return res
}

func cleanupInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
	res := make(map[string]interface{})
	for k, v := range in {
		res[fmt.Sprintf("%v", k)] = cleanupMapValue(v)
	}
	return res
}

func cleanupMapValue(v interface{}) interface{} {
	switch v := v.(type) {
	case []interface{}:
		return cleanupInterfaceArray(v)
	case map[interface{}]interface{}:
		return cleanupInterfaceMap(v)
	case string:
		return v
	default:
		return fmt.Sprintf("%v", v)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2017 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package spanner

import (
	"fmt"
	"regexp"
	"sync/atomic"
	"time"

	"cloud.google.com/go/internal/version"
	"golang.org/x/net/context"
	"google.golang.org/api/option"
	"google.golang.org/api/transport"
	sppb "google.golang.org/genproto/googleapis/spanner/v1"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
)

const (
	prodAddr = "spanner.googleapis.com:443"

	// resourcePrefixHeader is the name of the metadata header used to indicate
	// the resource being operated on.
	resourcePrefixHeader = "google-cloud-resource-prefix"
	// apiClientHeader is the name of the metadata header used to indicate client
	// information.
	apiClientHeader = "x-goog-api-client"

	// numChannels is the default value for NumChannels of client
	numChannels = 4
)

const (
	// Scope is the scope for Cloud Spanner Data API.
	Scope = "https://www.googleapis.com/auth/spanner.data"

	// AdminScope is the scope for Cloud Spanner Admin APIs.
	AdminScope = "https://www.googleapis.com/auth/spanner.admin"
)

var (
	validDBPattern  = regexp.MustCompile("^projects/[^/]+/instances/[^/]+/databases/[^/]+$")
	clientUserAgent = fmt.Sprintf("gl-go/%s gccl/%s grpc/%s", version.Go(), version.Repo, grpc.Version)
)

func validDatabaseName(db string) error {
	if matched := validDBPattern.MatchString(db); !matched {
		return fmt.Errorf("database name %q should conform to pattern %q",
			db, validDBPattern.String())
	}
	return nil
}

// Client is a client for reading and writing data to a Cloud Spanner database.  A
// client is safe to use concurrently, except for its Close method.
type Client struct {
	// rr must be accessed through atomic operations.
	rr       uint32
	conns    []*grpc.ClientConn
	clients  []sppb.SpannerClient
	database string
	// Metadata to be sent with each request.
	md           metadata.MD
	idleSessions *sessionPool
}

// ClientConfig has configurations for the client.
type ClientConfig struct {
	// NumChannels is the number of GRPC channels.
	// If zero, numChannels is used.
	NumChannels int
	co          []option.ClientOption
	// SessionPoolConfig is the configuration for session pool.
	SessionPoolConfig
}

// errDial returns error for dialing to Cloud Spanner.
func errDial(ci int, err error) error {
	e := toSpannerError(err).(*Error)
	e.decorate(fmt.Sprintf("dialing fails for channel[%v]", ci))
	return e
}

func contextWithOutgoingMetadata(ctx context.Context, md metadata.MD) context.Context {
	existing, ok := metadata.FromOutgoingContext(ctx)
	if ok {
		md = metadata.Join(existing, md)
	}
	return metadata.NewOutgoingContext(ctx, md)
}

// NewClient creates a client to a database. A valid database name has the
// form projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID. It uses a default
// configuration.
func NewClient(ctx context.Context, database string, opts ...option.ClientOption) (*Client, error) {
	return NewClientWithConfig(ctx, database, ClientConfig{}, opts...)
}

// NewClientWithConfig creates a client to a database. A valid database name has the
// form projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID.
func NewClientWithConfig(ctx context.Context, database string, config ClientConfig, opts ...option.ClientOption) (*Client, error) {
	// Validate database path.
	if err := validDatabaseName(database); err != nil {
		return nil, err
	}
	c := &Client{
		database: database,
		md: metadata.Pairs(
			resourcePrefixHeader, database,
			apiClientHeader, clientUserAgent),
	}
	allOpts := []option.ClientOption{option.WithEndpoint(prodAddr), option.WithScopes(Scope), option.WithUserAgent(clientUserAgent), option.WithGRPCDialOption(grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(100<<20), grpc.MaxCallRecvMsgSize(100<<20)))}
	allOpts = append(allOpts, opts...)
	// Prepare gRPC channels.
	if config.NumChannels == 0 {
		config.NumChannels = numChannels
	}
	// Default MaxOpened sessions
	if config.MaxOpened == 0 {
		config.MaxOpened = uint64(config.NumChannels * 100)
	}
	if config.MaxBurst == 0 {
		config.MaxBurst = 10
	}
	for i := 0; i < config.NumChannels; i++ {
		conn, err := transport.DialGRPC(ctx, allOpts...)
		if err != nil {
			return nil, errDial(i, err)
		}
		c.conns = append(c.conns, conn)
		c.clients = append(c.clients, sppb.NewSpannerClient(conn))
	}
	// Prepare session pool.
	config.SessionPoolConfig.getRPCClient = func() (sppb.SpannerClient, error) {
		// TODO: support more loadbalancing options.
		return c.rrNext(), nil
	}
	sp, err := newSessionPool(database, config.SessionPoolConfig, c.md)
	if err != nil {
		c.Close()
		return nil, err
	}
	c.idleSessions = sp
	return c, nil
}

// rrNext returns the next available Cloud Spanner RPC client in a round-robin manner.
func (c *Client) rrNext() sppb.SpannerClient {
	return c.clients[atomic.AddUint32(&c.rr, 1)%uint32(len(c.clients))]
}

// Close closes the client.
func (c *Client) Close() {
	if c.idleSessions != nil {
		c.idleSessions.close()
	}
	for _, conn := range c.conns {
		conn.Close()
	}
}

// Single provides a read-only snapshot transaction optimized for the case
// where only a single read or query is needed.  This is more efficient than
// using ReadOnlyTransaction() for a single read or query.
//
// Single will use a strong TimestampBound by default. Use
// ReadOnlyTransaction.WithTimestampBound to specify a different
// TimestampBound. A non-strong bound can be used to reduce latency, or
// "time-travel" to prior versions of the database, see the documentation of
// TimestampBound for details.
func (c *Client) Single() *ReadOnlyTransaction {
	t := &ReadOnlyTransaction{singleUse: true, sp: c.idleSessions}
	t.txReadOnly.txReadEnv = t
	return t
}

// ReadOnlyTransaction returns a ReadOnlyTransaction that can be used for
// multiple reads from the database.  You must call Close() when the
// ReadOnlyTransaction is no longer needed to release resources on the server.
//
// ReadOnlyTransaction will use a strong TimestampBound by default.  Use
// ReadOnlyTransaction.WithTimestampBound to specify a different
// TimestampBound.  A non-strong bound can be used to reduce latency, or
// "time-travel" to prior versions of the database, see the documentation of
// TimestampBound for details.
func (c *Client) ReadOnlyTransaction() *ReadOnlyTransaction {
	t := &ReadOnlyTransaction{
		singleUse:       false,
		sp:              c.idleSessions,
		txReadyOrClosed: make(chan struct{}),
	}
	t.txReadOnly.txReadEnv = t
	return t
}

type transactionInProgressKey struct{}

func checkNestedTxn(ctx context.Context) error {
	if ctx.Value(transactionInProgressKey{}) != nil {
		return spannerErrorf(codes.FailedPrecondition, "Cloud Spanner does not support nested transactions")
	}
	return nil
}

// ReadWriteTransaction executes a read-write transaction, with retries as
// necessary.
//
// The function f will be called one or more times. It must not maintain
// any state between calls.
//
// If the transaction cannot be committed or if f returns an IsAborted error,
// ReadWriteTransaction will call f again. It will continue to call f until the
// transaction can be committed or the Context times out or is cancelled.  If f
// returns an error other than IsAborted, ReadWriteTransaction will abort the
// transaction and return the error.
//
// To limit the number of retries, set a deadline on the Context rather than
// using a fixed limit on the number of attempts. ReadWriteTransaction will
// retry as needed until that deadline is met.
func (c *Client) ReadWriteTransaction(ctx context.Context, f func(context.Context, *ReadWriteTransaction) error) (time.Time, error) {
	if err := checkNestedTxn(ctx); err != nil {
		return time.Time{}, err
	}
	var (
		ts time.Time
		sh *sessionHandle
	)
	err := runRetryable(ctx, func(ctx context.Context) error {
		var (
			err error
			t   *ReadWriteTransaction
		)
		if sh == nil || sh.getID() == "" || sh.getClient() == nil {
			// Session handle hasn't been allocated or has been destroyed.
			sh, err = c.idleSessions.takeWriteSession(ctx)
			if err != nil {
				// If session retrieval fails, just fail the transaction.
				return err
			}
			t = &ReadWriteTransaction{
				sh: sh,
				tx: sh.getTransactionID(),
			}
		} else {
			t = &ReadWriteTransaction{
				sh: sh,
			}
		}
		t.txReadOnly.txReadEnv = t
		if err = t.begin(ctx); err != nil {
			// Mask error from begin operation as retryable error.
			return errRetry(err)
		}
		ts, err = t.runInTransaction(ctx, f)
		if err != nil {
			return err
		}
		return nil
	})
	if sh != nil {
		sh.recycle()
	}
	return ts, err
}

// applyOption controls the behavior of Client.Apply.
type applyOption struct {
	// If atLeastOnce == true, Client.Apply will execute the mutations on Cloud Spanner at least once.
	atLeastOnce bool
}

// An ApplyOption is an optional argument to Apply.
type ApplyOption func(*applyOption)

// ApplyAtLeastOnce returns an ApplyOption that removes replay protection.
//
// With this option, Apply may attempt to apply mutations more than once; if
// the mutations are not idempotent, this may lead to a failure being reported
// when the mutation was applied more than once. For example, an insert may
// fail with ALREADY_EXISTS even though the row did not exist before Apply was
// called. For this reason, most users of the library will prefer not to use
// this option.  However, ApplyAtLeastOnce requires only a single RPC, whereas
// Apply's default replay protection may require an additional RPC.  So this
// option may be appropriate for latency sensitive and/or high throughput blind
// writing.
func ApplyAtLeastOnce() ApplyOption {
	return func(ao *applyOption) {
		ao.atLeastOnce = true
	}
}

// Apply applies a list of mutations atomically to the database.
func (c *Client) Apply(ctx context.Context, ms []*Mutation, opts ...ApplyOption) (time.Time, error) {
	ao := &applyOption{}
	for _, opt := range opts {
		opt(ao)
	}
	if !ao.atLeastOnce {
		return c.ReadWriteTransaction(ctx, func(ctx context.Context, t *ReadWriteTransaction) error {
			t.BufferWrite(ms)
			return nil
		})
	}
	t := &writeOnlyTransaction{c.idleSessions}
	return t.applyAtLeastOnce(ctx, ms...)
}
<commit_msg>spanner: remove user agent<commit_after>/*
Copyright 2017 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package spanner

import (
	"fmt"
	"regexp"
	"sync/atomic"
	"time"

	"cloud.google.com/go/internal/version"
	"golang.org/x/net/context"
	"google.golang.org/api/option"
	"google.golang.org/api/transport"
	sppb "google.golang.org/genproto/googleapis/spanner/v1"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
)

const (
	prodAddr = "spanner.googleapis.com:443"

	// resourcePrefixHeader is the name of the metadata header used to indicate
	// the resource being operated on.
	resourcePrefixHeader = "google-cloud-resource-prefix"
	// xGoogHeaderKey is the name of the metadata header used to indicate client
	// information.
	xGoogHeaderKey = "x-goog-api-client"

	// numChannels is the default value for NumChannels of client
	numChannels = 4
)

const (
	// Scope is the scope for Cloud Spanner Data API.
	Scope = "https://www.googleapis.com/auth/spanner.data"

	// AdminScope is the scope for Cloud Spanner Admin APIs.
	AdminScope = "https://www.googleapis.com/auth/spanner.admin"
)

var (
	validDBPattern = regexp.MustCompile("^projects/[^/]+/instances/[^/]+/databases/[^/]+$")
	xGoogHeaderVal = fmt.Sprintf("gl-go/%s gccl/%s grpc/%s", version.Go(), version.Repo, grpc.Version)
)

func validDatabaseName(db string) error {
	if matched := validDBPattern.MatchString(db); !matched {
		return fmt.Errorf("database name %q should conform to pattern %q",
			db, validDBPattern.String())
	}
	return nil
}

// Client is a client for reading and writing data to a Cloud Spanner database.  A
// client is safe to use concurrently, except for its Close method.
type Client struct {
	// rr must be accessed through atomic operations.
	rr       uint32
	conns    []*grpc.ClientConn
	clients  []sppb.SpannerClient
	database string
	// Metadata to be sent with each request.
	md           metadata.MD
	idleSessions *sessionPool
}

// ClientConfig has configurations for the client.
type ClientConfig struct {
	// NumChannels is the number of GRPC channels.
	// If zero, numChannels is used.
	NumChannels int
	co          []option.ClientOption
	// SessionPoolConfig is the configuration for session pool.
	SessionPoolConfig
}

// errDial returns error for dialing to Cloud Spanner.
func errDial(ci int, err error) error {
	e := toSpannerError(err).(*Error)
	e.decorate(fmt.Sprintf("dialing fails for channel[%v]", ci))
	return e
}

func contextWithOutgoingMetadata(ctx context.Context, md metadata.MD) context.Context {
	existing, ok := metadata.FromOutgoingContext(ctx)
	if ok {
		md = metadata.Join(existing, md)
	}
	return metadata.NewOutgoingContext(ctx, md)
}

// NewClient creates a client to a database. A valid database name has the
// form projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID. It uses a default
// configuration.
func NewClient(ctx context.Context, database string, opts ...option.ClientOption) (*Client, error) {
	return NewClientWithConfig(ctx, database, ClientConfig{}, opts...)
}

// NewClientWithConfig creates a client to a database. A valid database name has the
// form projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID.
func NewClientWithConfig(ctx context.Context, database string, config ClientConfig, opts ...option.ClientOption) (*Client, error) {
	// Validate database path.
	if err := validDatabaseName(database); err != nil {
		return nil, err
	}
	c := &Client{
		database: database,
		md: metadata.Pairs(
			resourcePrefixHeader, database,
			xGoogHeaderKey, xGoogHeaderVal),
	}
	allOpts := []option.ClientOption{option.WithEndpoint(prodAddr), option.WithScopes(Scope), option.WithGRPCDialOption(grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(100<<20), grpc.MaxCallRecvMsgSize(100<<20)))}
	allOpts = append(allOpts, opts...)
	// Prepare gRPC channels.
	if config.NumChannels == 0 {
		config.NumChannels = numChannels
	}
	// Default MaxOpened sessions
	if config.MaxOpened == 0 {
		config.MaxOpened = uint64(config.NumChannels * 100)
	}
	if config.MaxBurst == 0 {
		config.MaxBurst = 10
	}
	for i := 0; i < config.NumChannels; i++ {
		conn, err := transport.DialGRPC(ctx, allOpts...)
		if err != nil {
			return nil, errDial(i, err)
		}
		c.conns = append(c.conns, conn)
		c.clients = append(c.clients, sppb.NewSpannerClient(conn))
	}
	// Prepare session pool.
	config.SessionPoolConfig.getRPCClient = func() (sppb.SpannerClient, error) {
		// TODO: support more loadbalancing options.
		return c.rrNext(), nil
	}
	sp, err := newSessionPool(database, config.SessionPoolConfig, c.md)
	if err != nil {
		c.Close()
		return nil, err
	}
	c.idleSessions = sp
	return c, nil
}

// rrNext returns the next available Cloud Spanner RPC client in a round-robin manner.
func (c *Client) rrNext() sppb.SpannerClient {
	return c.clients[atomic.AddUint32(&c.rr, 1)%uint32(len(c.clients))]
}

// Close closes the client.
func (c *Client) Close() {
	if c.idleSessions != nil {
		c.idleSessions.close()
	}
	for _, conn := range c.conns {
		conn.Close()
	}
}

// Single provides a read-only snapshot transaction optimized for the case
// where only a single read or query is needed.  This is more efficient than
// using ReadOnlyTransaction() for a single read or query.
//
// Single will use a strong TimestampBound by default. Use
// ReadOnlyTransaction.WithTimestampBound to specify a different
// TimestampBound. A non-strong bound can be used to reduce latency, or
// "time-travel" to prior versions of the database, see the documentation of
// TimestampBound for details.
func (c *Client) Single() *ReadOnlyTransaction {
	t := &ReadOnlyTransaction{singleUse: true, sp: c.idleSessions}
	t.txReadOnly.txReadEnv = t
	return t
}

// ReadOnlyTransaction returns a ReadOnlyTransaction that can be used for
// multiple reads from the database.  You must call Close() when the
// ReadOnlyTransaction is no longer needed to release resources on the server.
//
// ReadOnlyTransaction will use a strong TimestampBound by default.  Use
// ReadOnlyTransaction.WithTimestampBound to specify a different
// TimestampBound.  A non-strong bound can be used to reduce latency, or
// "time-travel" to prior versions of the database, see the documentation of
// TimestampBound for details.
func (c *Client) ReadOnlyTransaction() *ReadOnlyTransaction {
	t := &ReadOnlyTransaction{
		singleUse:       false,
		sp:              c.idleSessions,
		txReadyOrClosed: make(chan struct{}),
	}
	t.txReadOnly.txReadEnv = t
	return t
}

type transactionInProgressKey struct{}

func checkNestedTxn(ctx context.Context) error {
	if ctx.Value(transactionInProgressKey{}) != nil {
		return spannerErrorf(codes.FailedPrecondition, "Cloud Spanner does not support nested transactions")
	}
	return nil
}

// ReadWriteTransaction executes a read-write transaction, with retries as
// necessary.
//
// The function f will be called one or more times. It must not maintain
// any state between calls.
//
// If the transaction cannot be committed or if f returns an IsAborted error,
// ReadWriteTransaction will call f again. It will continue to call f until the
// transaction can be committed or the Context times out or is cancelled.  If f
// returns an error other than IsAborted, ReadWriteTransaction will abort the
// transaction and return the error.
//
// To limit the number of retries, set a deadline on the Context rather than
// using a fixed limit on the number of attempts. ReadWriteTransaction will
// retry as needed until that deadline is met.
func (c *Client) ReadWriteTransaction(ctx context.Context, f func(context.Context, *ReadWriteTransaction) error) (time.Time, error) {
	if err := checkNestedTxn(ctx); err != nil {
		return time.Time{}, err
	}
	var (
		ts time.Time
		sh *sessionHandle
	)
	err := runRetryable(ctx, func(ctx context.Context) error {
		var (
			err error
			t   *ReadWriteTransaction
		)
		if sh == nil || sh.getID() == "" || sh.getClient() == nil {
			// Session handle hasn't been allocated or has been destroyed.
			sh, err = c.idleSessions.takeWriteSession(ctx)
			if err != nil {
				// If session retrieval fails, just fail the transaction.
				return err
			}
			t = &ReadWriteTransaction{
				sh: sh,
				tx: sh.getTransactionID(),
			}
		} else {
			t = &ReadWriteTransaction{
				sh: sh,
			}
		}
		t.txReadOnly.txReadEnv = t
		if err = t.begin(ctx); err != nil {
			// Mask error from begin operation as retryable error.
			return errRetry(err)
		}
		ts, err = t.runInTransaction(ctx, f)
		if err != nil {
			return err
		}
		return nil
	})
	if sh != nil {
		sh.recycle()
	}
	return ts, err
}

// applyOption controls the behavior of Client.Apply.
type applyOption struct {
	// If atLeastOnce == true, Client.Apply will execute the mutations on Cloud Spanner at least once.
	atLeastOnce bool
}

// An ApplyOption is an optional argument to Apply.
type ApplyOption func(*applyOption)

// ApplyAtLeastOnce returns an ApplyOption that removes replay protection.
//
// With this option, Apply may attempt to apply mutations more than once; if
// the mutations are not idempotent, this may lead to a failure being reported
// when the mutation was applied more than once. For example, an insert may
// fail with ALREADY_EXISTS even though the row did not exist before Apply was
// called. For this reason, most users of the library will prefer not to use
// this option.  However, ApplyAtLeastOnce requires only a single RPC, whereas
// Apply's default replay protection may require an additional RPC.  So this
// option may be appropriate for latency sensitive and/or high throughput blind
// writing.
func ApplyAtLeastOnce() ApplyOption {
	return func(ao *applyOption) {
		ao.atLeastOnce = true
	}
}

// Apply applies a list of mutations atomically to the database.
func (c *Client) Apply(ctx context.Context, ms []*Mutation, opts ...ApplyOption) (time.Time, error) {
	ao := &applyOption{}
	for _, opt := range opts {
		opt(ao)
	}
	if !ao.atLeastOnce {
		return c.ReadWriteTransaction(ctx, func(ctx context.Context, t *ReadWriteTransaction) error {
			t.BufferWrite(ms)
			return nil
		})
	}
	t := &writeOnlyTransaction{c.idleSessions}
	return t.applyAtLeastOnce(ctx, ms...)
}
<|endoftext|>


<commit_before>package main

import (
	"html/template"
	"net/http"
	"strconv"
	"strings"
	"time"

	// "github.com/gin-contrib/static"
	"github.com/gin-contrib/multitemplate"
	"github.com/gin-gonic/gin"
)

func serve(host, port string) {
	gin.SetMode(gin.ReleaseMode)
	router := gin.Default()
	router.HTMLRender = loadTemplates("index.tmpl")
	// router.Use(static.Serve("/static/", static.LocalFile("./static", true)))
	router.GET("/", func(c *gin.Context) {
		c.Redirect(302, "/"+randomAlliterateCombo())
	})
	router.GET("/:page", func(c *gin.Context) {
		page := c.Param("page")
		c.Redirect(302, "/"+page+"/edit")
	})
	router.GET("/:page/*command", handlePageRequest)
	router.POST("/update", handlePageUpdate)
	router.POST("/prime", handlePrime)
	router.POST("/lock", handleLock)
	router.POST("/encrypt", handleEncrypt)
	router.DELETE("/oldlist", handleClearOldListItems)
	router.DELETE("/listitem", deleteListItem)

	router.Run(host + ":" + port)
}

func loadTemplates(list ...string) multitemplate.Render {
	r := multitemplate.New()

	for _, x := range list {
		templateString, err := Asset("templates/" + x)
		if err != nil {
			panic(err)
		}

		tmplMessage, err := template.New(x).Parse(string(templateString))
		if err != nil {
			panic(err)
		}

		r.Add(x, tmplMessage)
	}

	return r
}

func handlePageRequest(c *gin.Context) {
	page := c.Param("page")
	command := c.Param("command")
	if len(command) < 2 {
		command = "/ "
	}
	// Serve static content from memory
	if page == "static" {
		filename := page + command
		data, err := Asset(filename)
		if err != nil {
			c.String(http.StatusInternalServerError, "Could not find data")
		}
		c.Data(http.StatusOK, contentType(filename), data)
		return
	}

	version := c.DefaultQuery("version", "ajksldfjl")
	p := Open(page)

	// Disallow anything but viewing locked/encrypted pages
	if (p.IsEncrypted || p.IsLocked) &&
		(command[0:2] != "/v" && command[0:2] != "/r") {
		c.Redirect(302, "/"+page+"/view")
		return
	}

	// Destroy page if it is opened and primed
	if p.IsPrimedForSelfDestruct && !p.IsLocked && !p.IsEncrypted {
		p.Update("*This page has self-destructed. You can not return to it.*\n\n" + p.Text.GetCurrent())
		p.Erase()
	}
	if command == "/erase" {
		if !p.IsLocked && !p.IsEncrypted {
			p.Erase()
			c.Redirect(302, "/"+page+"/edit")
		} else {
			c.Redirect(302, "/"+page+"/view")
		}
		return
	}
	rawText := p.Text.GetCurrent()
	rawHTML := p.RenderedPage

	// Check to see if an old version is requested
	versionInt, versionErr := strconv.Atoi(version)
	if versionErr == nil && versionInt > 0 {
		versionText, err := p.Text.GetPreviousByTimestamp(int64(versionInt))
		if err == nil {
			rawText = versionText
			rawHTML = GithubMarkdownToHTML(rawText)
		}
	}

	// Get history
	var versionsInt64 []int64
	var versionsChangeSums []int
	var versionsText []string
	if command[0:2] == "/h" {
		versionsInt64, versionsChangeSums = p.Text.GetMajorSnapshotsAndChangeSums(60) // get snapshots 60 seconds apart
		versionsText = make([]string, len(versionsInt64))
		for i, v := range versionsInt64 {
			versionsText[i] = time.Unix(v/1000000000, 0).Format("Mon Jan 2 15:04:05 MST 2006")
		}
		versionsText = reverseSliceString(versionsText)
		versionsInt64 = reverseSliceInt64(versionsInt64)
		versionsChangeSums = reverseSliceInt(versionsChangeSums)
	}

	if command[0:2] == "/r" {
		c.Writer.Header().Set("Content-Type", contentType(p.Name))
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Max-Age", "86400")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Max")
		c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		c.Data(200, contentType(p.Name), []byte(rawText))
		return
	}
	log.Debug(command)
	log.Debug("%v", command[0:2] != "/e" &&
		command[0:2] != "/v" &&
		command[0:2] != "/l" &&
		command[0:2] != "/h")

	var FileNames, FileLastEdited []string
	var FileSizes, FileNumChanges []int
	if page == "ls" {
		command = "/view"
		FileNames, FileSizes, FileNumChanges, FileLastEdited = DirectoryList()
	}

	c.HTML(http.StatusOK, "index.tmpl", gin.H{
		"EditPage":    command[0:2] == "/e", // /edit
		"ViewPage":    command[0:2] == "/v", // /view
		"ListPage":    command[0:2] == "/l", // /list
		"HistoryPage": command[0:2] == "/h", // /history
		"DontKnowPage": command[0:2] != "/e" &&
			command[0:2] != "/v" &&
			command[0:2] != "/l" &&
			command[0:2] != "/h",
		"DirectoryPage":      page == "ls",
		"FileNames":          FileNames,
		"FileSizes":          FileSizes,
		"FileNumChanges":     FileNumChanges,
		"FileLastEdited":     FileLastEdited,
		"Page":               page,
		"RenderedPage":       template.HTML([]byte(rawHTML)),
		"RawPage":            rawText,
		"Versions":           versionsInt64,
		"VersionsText":       versionsText,
		"VersionsChangeSums": versionsChangeSums,
		"IsLocked":           p.IsLocked,
		"IsEncrypted":        p.IsEncrypted,
		"ListItems":          renderList(rawText),
		"Route":              "/" + page + command,
		"HasDotInName":       strings.Contains(page, "."),
	})
}

func handlePageUpdate(c *gin.Context) {
	type QueryJSON struct {
		Page    string `json:"page"`
		NewText string `json:"new_text"`
	}
	var json QueryJSON
	if c.BindJSON(&json) != nil {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"})
		return
	}
	if len(json.NewText) > 100000 {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Too much"})
		return
	}
	log.Trace("Update: %v", json)
	p := Open(json.Page)
	var message string
	if p.IsLocked {
		message = "Locked"
	} else if p.IsEncrypted {
		message = "Encrypted"
	} else {
		p.Update(json.NewText)
		p.Save()
		message = "Saved"
	}
	c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
}

func handlePrime(c *gin.Context) {
	type QueryJSON struct {
		Page string `json:"page"`
	}
	var json QueryJSON
	if c.BindJSON(&json) != nil {
		c.String(http.StatusBadRequest, "Problem binding keys")
		return
	}
	log.Trace("Update: %v", json)
	p := Open(json.Page)
	if p.IsLocked {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
		return
	} else if p.IsEncrypted {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
		return
	}
	p.IsPrimedForSelfDestruct = true
	p.Save()
	c.JSON(http.StatusOK, gin.H{"success": true, "message": "Primed"})
}

func handleLock(c *gin.Context) {
	type QueryJSON struct {
		Page       string `json:"page"`
		Passphrase string `json:"passphrase"`
	}

	var json QueryJSON
	if c.BindJSON(&json) != nil {
		c.String(http.StatusBadRequest, "Problem binding keys")
		return
	}
	p := Open(json.Page)
	if p.IsEncrypted {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
		return
	}
	var message string
	if p.IsLocked {
		err2 := CheckPasswordHash(json.Passphrase, p.PassphraseToUnlock)
		if err2 != nil {
			c.JSON(http.StatusOK, gin.H{"success": false, "message": "Can't unlock"})
			return
		}
		p.IsLocked = false
		message = "Unlocked"
	} else {
		p.IsLocked = true
		p.PassphraseToUnlock = HashPassword(json.Passphrase)
		message = "Locked"
	}
	p.Save()
	c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
}

func handleEncrypt(c *gin.Context) {
	type QueryJSON struct {
		Page       string `json:"page"`
		Passphrase string `json:"passphrase"`
	}

	var json QueryJSON
	if c.BindJSON(&json) != nil {
		c.String(http.StatusBadRequest, "Problem binding keys")
		return
	}
	p := Open(json.Page)
	if p.IsLocked {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
		return
	}
	q := Open(json.Page)
	var message string
	if p.IsEncrypted {
		decrypted, err2 := DecryptString(p.Text.GetCurrent(), json.Passphrase)
		if err2 != nil {
			c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong password"})
			return
		}
		q.Erase()
		q = Open(json.Page)
		q.Update(decrypted)
		q.IsEncrypted = false
		q.IsLocked = p.IsLocked
		q.IsPrimedForSelfDestruct = p.IsPrimedForSelfDestruct
		message = "Decrypted"
	} else {
		currentText := p.Text.GetCurrent()
		encrypted, _ := EncryptString(currentText, json.Passphrase)
		q.Erase()
		q = Open(json.Page)
		q.Update(encrypted)
		q.IsEncrypted = true
		q.IsLocked = p.IsLocked
		q.IsPrimedForSelfDestruct = p.IsPrimedForSelfDestruct
		message = "Encrypted"
	}
	q.Save()
	c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
}

func deleteListItem(c *gin.Context) {
	lineNum, err := strconv.Atoi(c.DefaultQuery("lineNum", "None"))
	page := c.Query("page") // shortcut for c.Request.URL.Query().Get("lastname")
	if err == nil {
		p := Open(page)

		_, listItems := reorderList(p.Text.GetCurrent())
		newText := p.Text.GetCurrent()
		for i, lineString := range listItems {
			// fmt.Println(i, lineString, lineNum)
			if i+1 == lineNum {
				// fmt.Println("MATCHED")
				if strings.Contains(lineString, "~~") == false {
					// fmt.Println(p.Text, "("+lineString[2:]+"\n"+")", "~~"+lineString[2:]+"~~"+"\n")
					newText = strings.Replace(newText+"\n", lineString[2:]+"\n", "~~"+strings.TrimSpace(lineString[2:])+"~~"+"\n", 1)
				} else {
					newText = strings.Replace(newText+"\n", lineString[2:]+"\n", lineString[4:len(lineString)-2]+"\n", 1)
				}
				p.Update(newText)
				break
			}
		}

		c.JSON(200, gin.H{
			"success": true,
			"message": "Done.",
		})
	} else {
		c.JSON(200, gin.H{
			"success": false,
			"message": err.Error(),
		})
	}
}

func handleClearOldListItems(c *gin.Context) {
	type QueryJSON struct {
		Page string `json:"page"`
	}

	var json QueryJSON
	if c.BindJSON(&json) != nil {
		c.String(http.StatusBadRequest, "Problem binding keys")
		return
	}
	p := Open(json.Page)
	if p.IsEncrypted {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
		return
	}
	if p.IsLocked {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
		return
	}
	lines := strings.Split(p.Text.GetCurrent(), "\n")
	newLines := make([]string, len(lines))
	newLinesI := 0
	for _, line := range lines {
		if strings.Count(line, "~~") != 2 {
			newLines[newLinesI] = line
			newLinesI++
		}
	}
	p.Update(strings.Join(newLines[0:newLinesI], "\n"))
	p.Save()
	c.JSON(http.StatusOK, gin.H{"success": true, "message": "Cleared"})
}
<commit_msg>Redirect to /page/view on /page/ request<commit_after>package main

import (
	"html/template"
	"net/http"
	"strconv"
	"strings"
	"time"

	// "github.com/gin-contrib/static"
	"github.com/gin-contrib/multitemplate"
	"github.com/gin-gonic/gin"
)

func serve(host, port string) {
	gin.SetMode(gin.ReleaseMode)
	router := gin.Default()
	router.HTMLRender = loadTemplates("index.tmpl")
	// router.Use(static.Serve("/static/", static.LocalFile("./static", true)))
	router.GET("/", func(c *gin.Context) {
		c.Redirect(302, "/"+randomAlliterateCombo())
	})
	router.GET("/:page", func(c *gin.Context) {
		page := c.Param("page")
		c.Redirect(302, "/"+page+"/edit")
	})
	router.GET("/:page/*command", handlePageRequest)
	router.POST("/update", handlePageUpdate)
	router.POST("/prime", handlePrime)
	router.POST("/lock", handleLock)
	router.POST("/encrypt", handleEncrypt)
	router.DELETE("/oldlist", handleClearOldListItems)
	router.DELETE("/listitem", deleteListItem)

	router.Run(host + ":" + port)
}

func loadTemplates(list ...string) multitemplate.Render {
	r := multitemplate.New()

	for _, x := range list {
		templateString, err := Asset("templates/" + x)
		if err != nil {
			panic(err)
		}

		tmplMessage, err := template.New(x).Parse(string(templateString))
		if err != nil {
			panic(err)
		}

		r.Add(x, tmplMessage)
	}

	return r
}

func handlePageRequest(c *gin.Context) {
	page := c.Param("page")
	command := c.Param("command")
	if len(command) < 2 {
		c.Redirect(302, "/"+page+"/view")
		return
	}
	// Serve static content from memory
	if page == "static" {
		filename := page + command
		data, err := Asset(filename)
		if err != nil {
			c.String(http.StatusInternalServerError, "Could not find data")
		}
		c.Data(http.StatusOK, contentType(filename), data)
		return
	}

	version := c.DefaultQuery("version", "ajksldfjl")
	p := Open(page)

	// Disallow anything but viewing locked/encrypted pages
	if (p.IsEncrypted || p.IsLocked) &&
		(command[0:2] != "/v" && command[0:2] != "/r") {
		c.Redirect(302, "/"+page+"/view")
		return
	}

	// Destroy page if it is opened and primed
	if p.IsPrimedForSelfDestruct && !p.IsLocked && !p.IsEncrypted {
		p.Update("*This page has self-destructed. You can not return to it.*\n\n" + p.Text.GetCurrent())
		p.Erase()
	}
	if command == "/erase" {
		if !p.IsLocked && !p.IsEncrypted {
			p.Erase()
			c.Redirect(302, "/"+page+"/edit")
		} else {
			c.Redirect(302, "/"+page+"/view")
		}
		return
	}
	rawText := p.Text.GetCurrent()
	rawHTML := p.RenderedPage

	// Check to see if an old version is requested
	versionInt, versionErr := strconv.Atoi(version)
	if versionErr == nil && versionInt > 0 {
		versionText, err := p.Text.GetPreviousByTimestamp(int64(versionInt))
		if err == nil {
			rawText = versionText
			rawHTML = GithubMarkdownToHTML(rawText)
		}
	}

	// Get history
	var versionsInt64 []int64
	var versionsChangeSums []int
	var versionsText []string
	if command[0:2] == "/h" {
		versionsInt64, versionsChangeSums = p.Text.GetMajorSnapshotsAndChangeSums(60) // get snapshots 60 seconds apart
		versionsText = make([]string, len(versionsInt64))
		for i, v := range versionsInt64 {
			versionsText[i] = time.Unix(v/1000000000, 0).Format("Mon Jan 2 15:04:05 MST 2006")
		}
		versionsText = reverseSliceString(versionsText)
		versionsInt64 = reverseSliceInt64(versionsInt64)
		versionsChangeSums = reverseSliceInt(versionsChangeSums)
	}

	if command[0:2] == "/r" {
		c.Writer.Header().Set("Content-Type", contentType(p.Name))
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Max-Age", "86400")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Max")
		c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		c.Data(200, contentType(p.Name), []byte(rawText))
		return
	}
	log.Debug(command)
	log.Debug("%v", command[0:2] != "/e" &&
		command[0:2] != "/v" &&
		command[0:2] != "/l" &&
		command[0:2] != "/h")

	var FileNames, FileLastEdited []string
	var FileSizes, FileNumChanges []int
	if page == "ls" {
		command = "/view"
		FileNames, FileSizes, FileNumChanges, FileLastEdited = DirectoryList()
	}

	c.HTML(http.StatusOK, "index.tmpl", gin.H{
		"EditPage":    command[0:2] == "/e", // /edit
		"ViewPage":    command[0:2] == "/v", // /view
		"ListPage":    command[0:2] == "/l", // /list
		"HistoryPage": command[0:2] == "/h", // /history
		"DontKnowPage": command[0:2] != "/e" &&
			command[0:2] != "/v" &&
			command[0:2] != "/l" &&
			command[0:2] != "/h",
		"DirectoryPage":      page == "ls",
		"FileNames":          FileNames,
		"FileSizes":          FileSizes,
		"FileNumChanges":     FileNumChanges,
		"FileLastEdited":     FileLastEdited,
		"Page":               page,
		"RenderedPage":       template.HTML([]byte(rawHTML)),
		"RawPage":            rawText,
		"Versions":           versionsInt64,
		"VersionsText":       versionsText,
		"VersionsChangeSums": versionsChangeSums,
		"IsLocked":           p.IsLocked,
		"IsEncrypted":        p.IsEncrypted,
		"ListItems":          renderList(rawText),
		"Route":              "/" + page + command,
		"HasDotInName":       strings.Contains(page, "."),
	})
}

func handlePageUpdate(c *gin.Context) {
	type QueryJSON struct {
		Page    string `json:"page"`
		NewText string `json:"new_text"`
	}
	var json QueryJSON
	if c.BindJSON(&json) != nil {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"})
		return
	}
	if len(json.NewText) > 100000 {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Too much"})
		return
	}
	log.Trace("Update: %v", json)
	p := Open(json.Page)
	var message string
	if p.IsLocked {
		message = "Locked"
	} else if p.IsEncrypted {
		message = "Encrypted"
	} else {
		p.Update(json.NewText)
		p.Save()
		message = "Saved"
	}
	c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
}

func handlePrime(c *gin.Context) {
	type QueryJSON struct {
		Page string `json:"page"`
	}
	var json QueryJSON
	if c.BindJSON(&json) != nil {
		c.String(http.StatusBadRequest, "Problem binding keys")
		return
	}
	log.Trace("Update: %v", json)
	p := Open(json.Page)
	if p.IsLocked {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
		return
	} else if p.IsEncrypted {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
		return
	}
	p.IsPrimedForSelfDestruct = true
	p.Save()
	c.JSON(http.StatusOK, gin.H{"success": true, "message": "Primed"})
}

func handleLock(c *gin.Context) {
	type QueryJSON struct {
		Page       string `json:"page"`
		Passphrase string `json:"passphrase"`
	}

	var json QueryJSON
	if c.BindJSON(&json) != nil {
		c.String(http.StatusBadRequest, "Problem binding keys")
		return
	}
	p := Open(json.Page)
	if p.IsEncrypted {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
		return
	}
	var message string
	if p.IsLocked {
		err2 := CheckPasswordHash(json.Passphrase, p.PassphraseToUnlock)
		if err2 != nil {
			c.JSON(http.StatusOK, gin.H{"success": false, "message": "Can't unlock"})
			return
		}
		p.IsLocked = false
		message = "Unlocked"
	} else {
		p.IsLocked = true
		p.PassphraseToUnlock = HashPassword(json.Passphrase)
		message = "Locked"
	}
	p.Save()
	c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
}

func handleEncrypt(c *gin.Context) {
	type QueryJSON struct {
		Page       string `json:"page"`
		Passphrase string `json:"passphrase"`
	}

	var json QueryJSON
	if c.BindJSON(&json) != nil {
		c.String(http.StatusBadRequest, "Problem binding keys")
		return
	}
	p := Open(json.Page)
	if p.IsLocked {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
		return
	}
	q := Open(json.Page)
	var message string
	if p.IsEncrypted {
		decrypted, err2 := DecryptString(p.Text.GetCurrent(), json.Passphrase)
		if err2 != nil {
			c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong password"})
			return
		}
		q.Erase()
		q = Open(json.Page)
		q.Update(decrypted)
		q.IsEncrypted = false
		q.IsLocked = p.IsLocked
		q.IsPrimedForSelfDestruct = p.IsPrimedForSelfDestruct
		message = "Decrypted"
	} else {
		currentText := p.Text.GetCurrent()
		encrypted, _ := EncryptString(currentText, json.Passphrase)
		q.Erase()
		q = Open(json.Page)
		q.Update(encrypted)
		q.IsEncrypted = true
		q.IsLocked = p.IsLocked
		q.IsPrimedForSelfDestruct = p.IsPrimedForSelfDestruct
		message = "Encrypted"
	}
	q.Save()
	c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
}

func deleteListItem(c *gin.Context) {
	lineNum, err := strconv.Atoi(c.DefaultQuery("lineNum", "None"))
	page := c.Query("page") // shortcut for c.Request.URL.Query().Get("lastname")
	if err == nil {
		p := Open(page)

		_, listItems := reorderList(p.Text.GetCurrent())
		newText := p.Text.GetCurrent()
		for i, lineString := range listItems {
			// fmt.Println(i, lineString, lineNum)
			if i+1 == lineNum {
				// fmt.Println("MATCHED")
				if strings.Contains(lineString, "~~") == false {
					// fmt.Println(p.Text, "("+lineString[2:]+"\n"+")", "~~"+lineString[2:]+"~~"+"\n")
					newText = strings.Replace(newText+"\n", lineString[2:]+"\n", "~~"+strings.TrimSpace(lineString[2:])+"~~"+"\n", 1)
				} else {
					newText = strings.Replace(newText+"\n", lineString[2:]+"\n", lineString[4:len(lineString)-2]+"\n", 1)
				}
				p.Update(newText)
				break
			}
		}

		c.JSON(200, gin.H{
			"success": true,
			"message": "Done.",
		})
	} else {
		c.JSON(200, gin.H{
			"success": false,
			"message": err.Error(),
		})
	}
}

func handleClearOldListItems(c *gin.Context) {
	type QueryJSON struct {
		Page string `json:"page"`
	}

	var json QueryJSON
	if c.BindJSON(&json) != nil {
		c.String(http.StatusBadRequest, "Problem binding keys")
		return
	}
	p := Open(json.Page)
	if p.IsEncrypted {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
		return
	}
	if p.IsLocked {
		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
		return
	}
	lines := strings.Split(p.Text.GetCurrent(), "\n")
	newLines := make([]string, len(lines))
	newLinesI := 0
	for _, line := range lines {
		if strings.Count(line, "~~") != 2 {
			newLines[newLinesI] = line
			newLinesI++
		}
	}
	p.Update(strings.Join(newLines[0:newLinesI], "\n"))
	p.Save()
	c.JSON(http.StatusOK, gin.H{"success": true, "message": "Cleared"})
}
<|endoftext|>


<commit_before>package gop

import (
	"fmt"
	"github.com/gorilla/mux"
	"github.com/gorilla/schema"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"runtime"
	"runtime/debug"
	"time"
)

var decoder = schema.NewDecoder() // Single-instance so struct info cached

func gopHandler(g *Req, w http.ResponseWriter, r *http.Request) {
	enabled, _ := g.Cfg.GetBool("gop", "enable_gop_urls", false)
	if !enabled {
		http.Error(w, "Not enabled", http.StatusNotFound)
		return
	}
	vars := mux.Vars(r)
	switch vars["action"] {
	case "status":
		{
			handleStatus(g, w, r)
			return
		}
	case "stack":
		{
			handleStack(g, w, r)
			return
		}
	case "mem":
		{
			handleMem(g, w, r)
			return
		}
	case "test":
		{
			handleTest(g, w, r)
			return
		}
	case "config":
		{
			handleConfig(g, w, r)
			return
		}
	default:
		{
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
	}
}

func handleConfig(g *Req, w http.ResponseWriter, r *http.Request) {
	// We can be called with and without section+key
	vars := mux.Vars(r)
	if r.Method == "PUT" {
		section := vars["section"]
		key := vars["key"]
		if section == "" {
			http.Error(w, "No section in url", http.StatusBadRequest)
			return
		}
		if key == "" {
			http.Error(w, "No key in url", http.StatusBadRequest)
			return
		}
		value, err := ioutil.ReadAll(r.Body)
		if err != nil {
			http.Error(w, "Failed to read value: "+err.Error(), http.StatusInternalServerError)
			return
		}
		g.Cfg.PersistentOverride(section, key, string(value))
	}

	configMap := g.Cfg.AsMap()
	g.SendJson(w, "config", configMap)
}

func handleMem(g *Req, w http.ResponseWriter, r *http.Request) {
	if r.Method == "POST" {
		type memParams struct {
			GCNow     int `schema:"gc_now"`
			GCPercent int `schema:"gc_percent"`
		}
		params := memParams{}
		err := g.Decoder.Decode(&params, r.Form)
		if err != nil {
			g.Error("Failed to decode params: "+err.Error(), http.StatusInternalServerError)
			http.Error(w, "Failed to decode params: "+err.Error(), http.StatusInternalServerError)
			return
		}
		msg := "Adjusting mem system\n"
		if params.GCNow > 0 {
			info := "Running GC by request to handler"
			g.Info(info)
			msg += info + "\n"

			runtime.GC()
		}
		if params.GCPercent > 0 {
			oldVal := debug.SetGCPercent(params.GCPercent)
			info := fmt.Sprintf("Set GC%% to [%d] was [%d]", params.GCPercent, oldVal)
			g.Info(info)
			msg += info + "\n"
		}
		io.WriteString(w, msg)
		return
	}
	var memStats runtime.MemStats
	runtime.ReadMemStats(&memStats)
	g.SendJson(w, "memstats", memStats)
}

func handleStack(g *Req, w http.ResponseWriter, r *http.Request) {
	buf := make([]byte, 1024)
	for {
		traceLen := runtime.Stack(buf, true)
		if traceLen < len(buf) {
			break
		}
		// Try a bigger buf
		buf = make([]byte, 2*len(buf))
	}
	w.Write(buf)
}

func handleStatus(g *Req, w http.ResponseWriter, r *http.Request) {
	type requestInfo struct {
		Id       int
		Method   string
		Url      string
		Duration float64
		RemoteIP string
		IsHTTPS  bool
	}
	type requestStatus struct {
		ProjectName   string
		AppName       string
		Pid           int
		StartTime     time.Time
		UptimeSeconds float64
		NumGoros      int
		RequestInfo   []requestInfo
	}
	appDuration := time.Since(g.app.startTime).Seconds()
	status := requestStatus{
		ProjectName:   g.app.ProjectName,
		AppName:       g.app.AppName,
		Pid:           os.Getpid(),
		StartTime:     g.app.startTime,
		UptimeSeconds: appDuration,
		NumGoros:      runtime.NumGoroutine(),
	}
	reqChan := make(chan *Req)
	g.app.getReqs <- reqChan
	for req := range reqChan {
		reqDuration := time.Since(req.startTime)
		info := requestInfo{
			Id:       req.id,
			Method:   req.r.Method,
			Url:      req.r.URL.String(),
			Duration: reqDuration.Seconds(),
			RemoteIP: req.RealRemoteIP,
			IsHTTPS:  req.IsHTTPS,
		}
		status.RequestInfo = append(status.RequestInfo, info)
	}
	g.SendJson(w, "status", status)
	/*
	   fmt.Fprintf(w, "%s - %s PID %d up for %.3fs (%s)\n\n", g.app.ProjectName, g.app.AppName, os.Getpid(), appDuration, g.app.startTime)
	   for req := range reqChan {
	       reqDuration := time.Since(req.startTime).Seconds()
	       fmt.Fprintf(w, "%d: %.3f\t%s\t%s\n", req.id, reqDuration, req.r.Method, req.r.URL.String())
	   }
	*/
}

func handleTest(g *Req, w http.ResponseWriter, r *http.Request) {
	type details struct {
		Kbytes int `schema:"kbytes"`
		Secs   int `schema:"secs"`
	}
	args := details{}
	err := g.Decoder.Decode(&args, r.Form)
	if err != nil {
		http.Error(w, "Failed to decode params: "+err.Error(), http.StatusInternalServerError)
		return
	}
	g.Debug("Test req - taking %d secs, %d KB", args.Secs, args.Kbytes)
	buf := make([]byte, args.Kbytes*1024)
	// Touch/do something with the mem to ensure it's actually allocated
	for i := range buf {
		buf[i] = 1
	}
	time.Sleep(time.Second * time.Duration(args.Secs))
	fmt.Fprintf(w, "Slow request took additional %d secs and allocated additional %d KB\n", args.Secs, args.Kbytes)
}

func (a *App) registerGopHandlers() {
	a.HandleFunc("/gop/{action}", gopHandler)
	a.HandleFunc("/gop/config/{section}/{key}", handleConfig)
}
<commit_msg>support GET of /gop/config/<section> and /gop/config/<section>/<key>, for NickG<commit_after>package gop

import (
	"fmt"
	"github.com/gorilla/mux"
	"github.com/gorilla/schema"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"runtime"
	"runtime/debug"
	"time"
)

var decoder = schema.NewDecoder() // Single-instance so struct info cached

func gopHandler(g *Req, w http.ResponseWriter, r *http.Request) {
	enabled, _ := g.Cfg.GetBool("gop", "enable_gop_urls", false)
	if !enabled {
		http.Error(w, "Not enabled", http.StatusNotFound)
		return
	}
	vars := mux.Vars(r)
	switch vars["action"] {
	case "status":
		{
			handleStatus(g, w, r)
			return
		}
	case "stack":
		{
			handleStack(g, w, r)
			return
		}
	case "mem":
		{
			handleMem(g, w, r)
			return
		}
	case "test":
		{
			handleTest(g, w, r)
			return
		}
	case "config":
		{
			handleConfig(g, w, r)
			return
		}
	default:
		{
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
	}
}

func handleConfig(g *Req, w http.ResponseWriter, r *http.Request) {
	// We can be called with and without section+key
	vars := mux.Vars(r)
	section := vars["section"]
	key := vars["key"]
	if r.Method == "PUT" {
		if section == "" {
			http.Error(w, "No section in url", http.StatusBadRequest)
			return
		}
		if key == "" {
			http.Error(w, "No key in url", http.StatusBadRequest)
			return
		}
		value, err := ioutil.ReadAll(r.Body)
		if err != nil {
			http.Error(w, "Failed to read value: "+err.Error(), http.StatusInternalServerError)
			return
		}
		g.Cfg.PersistentOverride(section, key, string(value))
	}

	if section != "" {
		if key != "" {
			strVal, found := g.Cfg.Get(section, key, "")
			if found {
				g.SendJson(w, "config", strVal)
				return
			} else {
				http.Error(w, "No such key in section", http.StatusNotFound)
				return
			}
		} else {
			sectionKeys := g.Cfg.SectionKeys(section)
			sectionMap := make(map[string]string)
			for _, key := range sectionKeys {
				strVal, _ := g.Cfg.Get(section, key, "")
				sectionMap[key] = strVal
			}
			g.SendJson(w, "config", sectionMap)
			return
		}
	} else {
		configMap := g.Cfg.AsMap()
		g.SendJson(w, "config", configMap)
		return
	}
}

func handleMem(g *Req, w http.ResponseWriter, r *http.Request) {
	if r.Method == "POST" {
		type memParams struct {
			GCNow     int `schema:"gc_now"`
			GCPercent int `schema:"gc_percent"`
		}
		params := memParams{}
		err := g.Decoder.Decode(&params, r.Form)
		if err != nil {
			g.Error("Failed to decode params: "+err.Error(), http.StatusInternalServerError)
			http.Error(w, "Failed to decode params: "+err.Error(), http.StatusInternalServerError)
			return
		}
		msg := "Adjusting mem system\n"
		if params.GCNow > 0 {
			info := "Running GC by request to handler"
			g.Info(info)
			msg += info + "\n"

			runtime.GC()
		}
		if params.GCPercent > 0 {
			oldVal := debug.SetGCPercent(params.GCPercent)
			info := fmt.Sprintf("Set GC%% to [%d] was [%d]", params.GCPercent, oldVal)
			g.Info(info)
			msg += info + "\n"
		}
		io.WriteString(w, msg)
		return
	}
	var memStats runtime.MemStats
	runtime.ReadMemStats(&memStats)
	g.SendJson(w, "memstats", memStats)
}

func handleStack(g *Req, w http.ResponseWriter, r *http.Request) {
	buf := make([]byte, 1024)
	for {
		traceLen := runtime.Stack(buf, true)
		if traceLen < len(buf) {
			break
		}
		// Try a bigger buf
		buf = make([]byte, 2*len(buf))
	}
	w.Write(buf)
}

func handleStatus(g *Req, w http.ResponseWriter, r *http.Request) {
	type requestInfo struct {
		Id       int
		Method   string
		Url      string
		Duration float64
		RemoteIP string
		IsHTTPS  bool
	}
	type requestStatus struct {
		ProjectName   string
		AppName       string
		Pid           int
		StartTime     time.Time
		UptimeSeconds float64
		NumGoros      int
		RequestInfo   []requestInfo
	}
	appDuration := time.Since(g.app.startTime).Seconds()
	status := requestStatus{
		ProjectName:   g.app.ProjectName,
		AppName:       g.app.AppName,
		Pid:           os.Getpid(),
		StartTime:     g.app.startTime,
		UptimeSeconds: appDuration,
		NumGoros:      runtime.NumGoroutine(),
	}
	reqChan := make(chan *Req)
	g.app.getReqs <- reqChan
	for req := range reqChan {
		reqDuration := time.Since(req.startTime)
		info := requestInfo{
			Id:       req.id,
			Method:   req.r.Method,
			Url:      req.r.URL.String(),
			Duration: reqDuration.Seconds(),
			RemoteIP: req.RealRemoteIP,
			IsHTTPS:  req.IsHTTPS,
		}
		status.RequestInfo = append(status.RequestInfo, info)
	}
	g.SendJson(w, "status", status)
	/*
	   fmt.Fprintf(w, "%s - %s PID %d up for %.3fs (%s)\n\n", g.app.ProjectName, g.app.AppName, os.Getpid(), appDuration, g.app.startTime)
	   for req := range reqChan {
	       reqDuration := time.Since(req.startTime).Seconds()
	       fmt.Fprintf(w, "%d: %.3f\t%s\t%s\n", req.id, reqDuration, req.r.Method, req.r.URL.String())
	   }
	*/
}

func handleTest(g *Req, w http.ResponseWriter, r *http.Request) {
	type details struct {
		Kbytes int `schema:"kbytes"`
		Secs   int `schema:"secs"`
	}
	args := details{}
	err := g.Decoder.Decode(&args, r.Form)
	if err != nil {
		http.Error(w, "Failed to decode params: "+err.Error(), http.StatusInternalServerError)
		return
	}
	g.Debug("Test req - taking %d secs, %d KB", args.Secs, args.Kbytes)
	buf := make([]byte, args.Kbytes*1024)
	// Touch/do something with the mem to ensure it's actually allocated
	for i := range buf {
		buf[i] = 1
	}
	time.Sleep(time.Second * time.Duration(args.Secs))
	fmt.Fprintf(w, "Slow request took additional %d secs and allocated additional %d KB\n", args.Secs, args.Kbytes)
}

func (a *App) registerGopHandlers() {
	a.HandleFunc("/gop/{action}", gopHandler)
	a.HandleFunc("/gop/config/{section}", handleConfig)
	a.HandleFunc("/gop/config/{section}/{key}", handleConfig)
}
<|endoftext|>


<commit_before>package main

import (
	"html/template"
	"net/http"
	"path"
	"strings"
)

var tmpl = make(map[string]*template.Template)

func init() {
	tmpl["gallery"] = template.Must(template.ParseFiles("assets/templates/gallery.html", "assets/templates/base.html"))
}

type Page struct {
	BaseURL string
	JSON    string
	Path    string
	Dirs    []string
	Images  []ImageInfo
}

func galleryStaticHandler(w http.ResponseWriter, r *http.Request, basePath string) {
	// Check path
	cleanPath := path.Clean(path.Join(basePath, r.URL.Path))
	if !strings.HasPrefix(cleanPath, basePath) {
		http.NotFound(w, r)
		return
	}

	// Serve it
	noDirFileServer(http.FileServer(http.Dir(basePath))).ServeHTTP(w, r)
}

// Serve static images for galleries
func ImageHandler(w http.ResponseWriter, r *http.Request) {
	// Check the gallery header
	g := getGallery(r)
	if g == "" {
		http.NotFound(w, r)
		return
	}
	gallery := Config.Gallery[g]

	galleryStaticHandler(w, r, gallery.ImagePath)
}

// Service static thumbnails for galleries
func ThumbHandler(w http.ResponseWriter, r *http.Request) {
	// Check the gallery header
	g := getGallery(r)
	if g == "" {
		http.NotFound(w, r)
		return
	}
	gallery := Config.Gallery[g]

	galleryStaticHandler(w, r, gallery.ThumbPath)
}

func GalleryHandler(w http.ResponseWriter, r *http.Request) {
	// Check the gallery header
	g := getGallery(r)
	if g == "" {
		http.NotFound(w, r)
		return
	}
	gallery := Config.Gallery[g]

	// Check path
	cleanPath := path.Clean(path.Join(gallery.ImagePath, r.URL.Path))
	if !strings.HasPrefix(cleanPath, gallery.ImagePath) {
		http.NotFound(w, r)
		return
	}

	// Scan the directory
	dirs, images, err := tn.ScanFolder(gallery, cleanPath)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Render the page
	p := &Page{
		BaseURL: gallery.BaseURL,
		Path:    r.URL.Path,
		Dirs:    dirs,
		Images:  images,
	}
	renderTemplate(w, "gallery", p)
}

// Render a template
func renderTemplate(w http.ResponseWriter, t string, p *Page) {
	err := tmpl[t].ExecuteTemplate(w, "base", p)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}
<commit_msg>gofmt zz<commit_after>package main

import (
	"html/template"
	"net/http"
	"path"
	"strings"
)

var tmpl = make(map[string]*template.Template)

func init() {
	tmpl["gallery"] = template.Must(template.ParseFiles("assets/templates/gallery.html", "assets/templates/base.html"))
}

type Page struct {
	BaseURL string
	JSON    string
	Path    string
	Dirs    []string
	Images  []ImageInfo
}

func galleryStaticHandler(w http.ResponseWriter, r *http.Request, basePath string) {
	// Check path
	cleanPath := path.Clean(path.Join(basePath, r.URL.Path))
	if !strings.HasPrefix(cleanPath, basePath) {
		http.NotFound(w, r)
		return
	}

	// Serve it
	noDirFileServer(http.FileServer(http.Dir(basePath))).ServeHTTP(w, r)
}

// Serve static images for galleries
func ImageHandler(w http.ResponseWriter, r *http.Request) {
	// Check the gallery header
	g := getGallery(r)
	if g == "" {
		http.NotFound(w, r)
		return
	}
	gallery := Config.Gallery[g]

	galleryStaticHandler(w, r, gallery.ImagePath)
}

// Service static thumbnails for galleries
func ThumbHandler(w http.ResponseWriter, r *http.Request) {
	// Check the gallery header
	g := getGallery(r)
	if g == "" {
		http.NotFound(w, r)
		return
	}
	gallery := Config.Gallery[g]

	galleryStaticHandler(w, r, gallery.ThumbPath)
}

func GalleryHandler(w http.ResponseWriter, r *http.Request) {
	// Check the gallery header
	g := getGallery(r)
	if g == "" {
		http.NotFound(w, r)
		return
	}
	gallery := Config.Gallery[g]

	// Check path
	cleanPath := path.Clean(path.Join(gallery.ImagePath, r.URL.Path))
	if !strings.HasPrefix(cleanPath, gallery.ImagePath) {
		http.NotFound(w, r)
		return
	}

	// Scan the directory
	dirs, images, err := tn.ScanFolder(gallery, cleanPath)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Render the page
	p := &Page{
		BaseURL: gallery.BaseURL,
		Path:    r.URL.Path,
		Dirs:    dirs,
		Images:  images,
	}
	renderTemplate(w, "gallery", p)
}

// Render a template
func renderTemplate(w http.ResponseWriter, t string, p *Page) {
	err := tmpl[t].ExecuteTemplate(w, "base", p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/hashicorp/golang-lru"
)

// a signaturerequest is sent by an autograph client to request
// a signature on input data
type signaturerequest struct {
	Template string `json:"template,omitempty"`
	HashWith string `json:"hashwith,omitempty"`
	Input    string `json:"input"`
	KeyID    string `json:"keyid,omitempty"`
	Encoding string `json:"signature_encoding,omitempty"`
}

// a signatureresponse is returned by autograph to a client with
// a signature computed on input data
type signatureresponse struct {
	Ref              string `json:"ref"`
	X5U              string `json:"x5u,omitempty"`
	PublicKey        string `json:"public_key,omitempty"`
	Hash             string `json:"hash_algorithm,omitempty"`
	Encoding         string `json:"signature_encoding,omitempty"`
	Signature        string `json:"signature"`
	ContentSignature string `json:"content-signature,omitempty"`
}

// A autographer signs input data with a private key
type autographer struct {
	signers     []signer
	auths       map[string]authorization
	signerIndex map[string]int
	nonces      *lru.Cache
	debug       bool
}

func newAutographer(cachesize int) (a *autographer, err error) {
	a = new(autographer)
	a.nonces, err = lru.New(cachesize)
	return
}

func (a *autographer) enableDebug() {
	a.debug = true
	return
}

func (a *autographer) disableDebug() {
	a.debug = false
	return
}

// addSigners initializes each signer specified in the configuration by parsing
// and loading their private keys. The signers are then copied over to the
// autographer handler.
func (a *autographer) addSigners(signers []signer) {
	for _, signer := range signers {
		err := signer.init()
		if err != nil {
			panic(err)
		}
		a.signers = append(a.signers, signer)
	}
}

// addAuthorizations reads a list of authorizations from the configuration and
// stores them into the autographer handler as a map indexed by user id, for fast lookup.
func (a *autographer) addAuthorizations(auths []authorization) {
	a.auths = make(map[string]authorization)
	for _, auth := range auths {
		if _, ok := a.auths[auth.ID]; ok {
			panic("authorization id '" + auth.ID + "' already defined, duplicates are not permitted")
		}
		a.auths[auth.ID] = auth
	}
}

// makeSignerIndex creates a map of authorization IDs and signer IDs to
// quickly locate a signer based on the user requesting the signature.
func (a *autographer) makeSignerIndex() {
	a.signerIndex = make(map[string]int)
	// add an entry for each authid+signerid pair
	for _, auth := range a.auths {
		for _, sid := range auth.Signers {
			for pos, s := range a.signers {
				if sid == s.ID {
					log.Printf("Mapping auth id %q and signer id %q to signer %d", auth.ID, s.ID, pos)
					tag := auth.ID + "+" + s.ID
					a.signerIndex[tag] = pos
				}
			}
		}
	}
	// add a fallback entry with just the authid, to use when no signerid
	// is specified in the signing request. This entry maps to the first
	// authorized signer
	for _, auth := range a.auths {
		// if the authorization has no signer configured, skip it
		if len(auth.Signers) < 1 {
			continue
		}
		for pos, signer := range a.signers {
			if auth.Signers[0] == signer.ID {
				log.Printf("Mapping auth id %q to default signer %d", auth.ID, pos)
				tag := auth.ID + "+"
				a.signerIndex[tag] = pos
				break
			}
		}
	}
}

// handleSignature endpoint accepts a list of signature requests in a HAWK authenticated POST request
// and calls the signers to generate signature responses.
func (a *autographer) handleSignature(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		httpError(w, http.StatusMethodNotAllowed, "%s method not allowed; endpoint accepts POST only", r.Method)
		return
	}
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
		return
	}
	userid, authorized, err := a.authorize(r, body)
	if err != nil || !authorized {
		httpError(w, http.StatusUnauthorized, "authorization verification failed: %v", err)
		return
	}
	var sigreqs []signaturerequest
	err = json.Unmarshal(body, &sigreqs)
	if err != nil {
		httpError(w, http.StatusBadRequest, "failed to parse request body: %v", err)
		return
	}
	if a.debug {
		log.Printf("signature request: %s", body)
	}
	sigresps := make([]signatureresponse, len(sigreqs))
	// Each signature requested in the http request body is processed individually.
	// For each, a signer is looked up, and used to compute a raw signature
	// the signature is then encoded appropriately, and added to the response slice
	for i, sigreq := range sigreqs {
		var (
			isHashReq                  bool
			hash                       []byte
			alg, encodedcs, encodedsig string
		)
		signerID, err := a.getSignerID(userid, sigreq.KeyID)
		if err != nil || signerID < 0 {
			httpError(w, http.StatusUnauthorized, "%v", err)
			return
		}
		switch r.URL.RequestURI() {
		case "/sign/hash":
			isHashReq = true
			// the '/sign/hash' endpoint does not allow requesting a particular hash or template
			// since those need to be computed before calling autograph.
			if sigreq.HashWith != "" || sigreq.Template != "" {
				httpError(w, http.StatusBadRequest,
					"hashwith and template parameters are not permitted on the /sign/hash endpoint")
				return
			}
			hash, err = fromBase64URL(sigreq.Input)
			if err != nil {
				httpError(w, http.StatusBadRequest, "%v", err)
				return
			}
		case "/sign/data":
			// other endpoints, like '/sign/data', will template and hash the input prior to signing
			alg, hash, err = templateAndHash(sigreq, a.signers[signerID].ecdsaPrivKey.Curve.Params().Name)
			if err != nil {
				httpError(w, http.StatusBadRequest, "%v", err)
				return
			}
		}
		ecdsaSig, err := a.signers[signerID].sign(hash)
		if err != nil {
			httpError(w, http.StatusInternalServerError, "signing failed with error: %v", err)
			return
		}
		encodedsig, err = encode(ecdsaSig, a.signers[signerID].siglen, sigreq.Encoding)
		if err != nil {
			httpError(w, http.StatusInternalServerError, "encoding failed with error: %v", err)
			return
		}
		if isHashReq || sigreq.Template == "content-signature" {
			encodedcs, err = a.signers[signerID].ContentSignature(ecdsaSig)
			if err != nil {
				httpError(w, http.StatusInternalServerError, "failed to retrieve content-signature: %v", err)
				return
			}
		}
		sigresps[i] = signatureresponse{
			Ref:              id(),
			X5U:              a.signers[signerID].X5U,
			PublicKey:        a.signers[signerID].PublicKey,
			Hash:             alg,
			Encoding:         sigreq.Encoding,
			Signature:        encodedsig,
			ContentSignature: encodedcs,
		}
	}
	respdata, err := json.Marshal(sigresps)
	if err != nil {
		httpError(w, http.StatusInternalServerError, "signing failed with error: %v", err)
		return
	}
	if a.debug {
		log.Printf("signature response: %s", respdata)
	}
	w.Header().Add("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	w.Write(respdata)
	log.Printf("signing operation from %q succeeded", userid)
}

// handleHeartbeat returns a simple message indicating that the API is alive and well
func (a *autographer) handleHeartbeat(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		httpError(w, http.StatusMethodNotAllowed, "%s method not allowed; endpoint accepts GET only", r.Method)
		return
	}
	w.Write([]byte("ohai"))
}

// handleVersion returns the current version of the API
func (a *autographer) handleVersion(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		httpError(w, http.StatusMethodNotAllowed, "%s method not allowed; endpoint accepts GET only", r.Method)
		return
	}
	w.Write([]byte(fmt.Sprintf(`{
"source": "https://github.com/mozilla-services/autograph",
"version": "%s",
"commit": "%s"
}`, version, commit)))
}
<commit_msg>Add location of build CI to version endpoint<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/hashicorp/golang-lru"
)

// a signaturerequest is sent by an autograph client to request
// a signature on input data
type signaturerequest struct {
	Template string `json:"template,omitempty"`
	HashWith string `json:"hashwith,omitempty"`
	Input    string `json:"input"`
	KeyID    string `json:"keyid,omitempty"`
	Encoding string `json:"signature_encoding,omitempty"`
}

// a signatureresponse is returned by autograph to a client with
// a signature computed on input data
type signatureresponse struct {
	Ref              string `json:"ref"`
	X5U              string `json:"x5u,omitempty"`
	PublicKey        string `json:"public_key,omitempty"`
	Hash             string `json:"hash_algorithm,omitempty"`
	Encoding         string `json:"signature_encoding,omitempty"`
	Signature        string `json:"signature"`
	ContentSignature string `json:"content-signature,omitempty"`
}

// A autographer signs input data with a private key
type autographer struct {
	signers     []signer
	auths       map[string]authorization
	signerIndex map[string]int
	nonces      *lru.Cache
	debug       bool
}

func newAutographer(cachesize int) (a *autographer, err error) {
	a = new(autographer)
	a.nonces, err = lru.New(cachesize)
	return
}

func (a *autographer) enableDebug() {
	a.debug = true
	return
}

func (a *autographer) disableDebug() {
	a.debug = false
	return
}

// addSigners initializes each signer specified in the configuration by parsing
// and loading their private keys. The signers are then copied over to the
// autographer handler.
func (a *autographer) addSigners(signers []signer) {
	for _, signer := range signers {
		err := signer.init()
		if err != nil {
			panic(err)
		}
		a.signers = append(a.signers, signer)
	}
}

// addAuthorizations reads a list of authorizations from the configuration and
// stores them into the autographer handler as a map indexed by user id, for fast lookup.
func (a *autographer) addAuthorizations(auths []authorization) {
	a.auths = make(map[string]authorization)
	for _, auth := range auths {
		if _, ok := a.auths[auth.ID]; ok {
			panic("authorization id '" + auth.ID + "' already defined, duplicates are not permitted")
		}
		a.auths[auth.ID] = auth
	}
}

// makeSignerIndex creates a map of authorization IDs and signer IDs to
// quickly locate a signer based on the user requesting the signature.
func (a *autographer) makeSignerIndex() {
	a.signerIndex = make(map[string]int)
	// add an entry for each authid+signerid pair
	for _, auth := range a.auths {
		for _, sid := range auth.Signers {
			for pos, s := range a.signers {
				if sid == s.ID {
					log.Printf("Mapping auth id %q and signer id %q to signer %d", auth.ID, s.ID, pos)
					tag := auth.ID + "+" + s.ID
					a.signerIndex[tag] = pos
				}
			}
		}
	}
	// add a fallback entry with just the authid, to use when no signerid
	// is specified in the signing request. This entry maps to the first
	// authorized signer
	for _, auth := range a.auths {
		// if the authorization has no signer configured, skip it
		if len(auth.Signers) < 1 {
			continue
		}
		for pos, signer := range a.signers {
			if auth.Signers[0] == signer.ID {
				log.Printf("Mapping auth id %q to default signer %d", auth.ID, pos)
				tag := auth.ID + "+"
				a.signerIndex[tag] = pos
				break
			}
		}
	}
}

// handleSignature endpoint accepts a list of signature requests in a HAWK authenticated POST request
// and calls the signers to generate signature responses.
func (a *autographer) handleSignature(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		httpError(w, http.StatusMethodNotAllowed, "%s method not allowed; endpoint accepts POST only", r.Method)
		return
	}
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
		return
	}
	userid, authorized, err := a.authorize(r, body)
	if err != nil || !authorized {
		httpError(w, http.StatusUnauthorized, "authorization verification failed: %v", err)
		return
	}
	var sigreqs []signaturerequest
	err = json.Unmarshal(body, &sigreqs)
	if err != nil {
		httpError(w, http.StatusBadRequest, "failed to parse request body: %v", err)
		return
	}
	if a.debug {
		log.Printf("signature request: %s", body)
	}
	sigresps := make([]signatureresponse, len(sigreqs))
	// Each signature requested in the http request body is processed individually.
	// For each, a signer is looked up, and used to compute a raw signature
	// the signature is then encoded appropriately, and added to the response slice
	for i, sigreq := range sigreqs {
		var (
			isHashReq                  bool
			hash                       []byte
			alg, encodedcs, encodedsig string
		)
		signerID, err := a.getSignerID(userid, sigreq.KeyID)
		if err != nil || signerID < 0 {
			httpError(w, http.StatusUnauthorized, "%v", err)
			return
		}
		switch r.URL.RequestURI() {
		case "/sign/hash":
			isHashReq = true
			// the '/sign/hash' endpoint does not allow requesting a particular hash or template
			// since those need to be computed before calling autograph.
			if sigreq.HashWith != "" || sigreq.Template != "" {
				httpError(w, http.StatusBadRequest,
					"hashwith and template parameters are not permitted on the /sign/hash endpoint")
				return
			}
			hash, err = fromBase64URL(sigreq.Input)
			if err != nil {
				httpError(w, http.StatusBadRequest, "%v", err)
				return
			}
		case "/sign/data":
			// other endpoints, like '/sign/data', will template and hash the input prior to signing
			alg, hash, err = templateAndHash(sigreq, a.signers[signerID].ecdsaPrivKey.Curve.Params().Name)
			if err != nil {
				httpError(w, http.StatusBadRequest, "%v", err)
				return
			}
		}
		ecdsaSig, err := a.signers[signerID].sign(hash)
		if err != nil {
			httpError(w, http.StatusInternalServerError, "signing failed with error: %v", err)
			return
		}
		encodedsig, err = encode(ecdsaSig, a.signers[signerID].siglen, sigreq.Encoding)
		if err != nil {
			httpError(w, http.StatusInternalServerError, "encoding failed with error: %v", err)
			return
		}
		if isHashReq || sigreq.Template == "content-signature" {
			encodedcs, err = a.signers[signerID].ContentSignature(ecdsaSig)
			if err != nil {
				httpError(w, http.StatusInternalServerError, "failed to retrieve content-signature: %v", err)
				return
			}
		}
		sigresps[i] = signatureresponse{
			Ref:              id(),
			X5U:              a.signers[signerID].X5U,
			PublicKey:        a.signers[signerID].PublicKey,
			Hash:             alg,
			Encoding:         sigreq.Encoding,
			Signature:        encodedsig,
			ContentSignature: encodedcs,
		}
	}
	respdata, err := json.Marshal(sigresps)
	if err != nil {
		httpError(w, http.StatusInternalServerError, "signing failed with error: %v", err)
		return
	}
	if a.debug {
		log.Printf("signature response: %s", respdata)
	}
	w.Header().Add("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	w.Write(respdata)
	log.Printf("signing operation from %q succeeded", userid)
}

// handleHeartbeat returns a simple message indicating that the API is alive and well
func (a *autographer) handleHeartbeat(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		httpError(w, http.StatusMethodNotAllowed, "%s method not allowed; endpoint accepts GET only", r.Method)
		return
	}
	w.Write([]byte("ohai"))
}

// handleVersion returns the current version of the API
func (a *autographer) handleVersion(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		httpError(w, http.StatusMethodNotAllowed, "%s method not allowed; endpoint accepts GET only", r.Method)
		return
	}
	w.Write([]byte(fmt.Sprintf(`{
"source": "https://github.com/mozilla-services/autograph",
"version": "%s",
"commit": "%s",
"build": "https://travis-ci.org/mozilla-services/autograph"
}`, version, commit)))
}
<|endoftext|>