<commit_before>package gendoc

import (
	"github.com/golang/protobuf/protoc-gen-go/descriptor"
	"github.com/pseudomuto/protokit"

	"encoding/json"
	"fmt"
	"sort"
	"strings"
)

// Template is a type for encapsulating all the parsed files, messages, fields, enums, services, extensions, etc. into
// an object that will be supplied to a go template.
type Template struct {
	// The files that were parsed
	Files []*File `json:"files"`
	// Details about the scalar values and their respective types in supported languages.
	Scalars []*ScalarValue `json:"scalarValueTypes"`
}

// NewTemplate creates a Template object from a set of descriptors.
func NewTemplate(descs []*protokit.FileDescriptor) *Template {
	files := make([]*File, 0, len(descs))

	for _, f := range descs {
		file := &File{
			Name:          f.GetName(),
			Description:   description(f.GetSyntaxComments().String()),
			Package:       f.GetPackage(),
			HasEnums:      len(f.Enums) > 0,
			HasExtensions: len(f.Extensions) > 0,
			HasMessages:   len(f.Messages) > 0,
			HasServices:   len(f.Services) > 0,
			Enums:         make(orderedEnums, 0, len(f.Enums)),
			Extensions:    make(orderedExtensions, 0, len(f.Extensions)),
			Messages:      make(orderedMessages, 0, len(f.Messages)),
			Services:      make(orderedServices, 0, len(f.Services)),
		}

		for _, e := range f.Enums {
			file.Enums = append(file.Enums, parseEnum(e))
		}

		for _, e := range f.Extensions {
			file.Extensions = append(file.Extensions, parseFileExtension(e))
		}

		for _, m := range f.Messages {
			file.Messages = append(file.Messages, parseMessage(m))

			for _, n := range m.Messages {
				file.Messages = append(file.Messages, parseMessage(n))
			}

			for _, e := range m.Enums {
				file.Enums = append(file.Enums, parseEnum(e))
			}
		}

		for _, s := range f.Services {
			file.Services = append(file.Services, parseService(s))
		}

		sort.Sort(file.Enums)
		sort.Sort(file.Extensions)
		sort.Sort(file.Messages)
		sort.Sort(file.Services)

		files = append(files, file)
	}

	return &Template{Files: files, Scalars: makeScalars()}
}

func makeScalars() []*ScalarValue {
	data, _ := fetchResource("scalars.json")

	var scalars []*ScalarValue
	json.Unmarshal(data, &scalars)

	return scalars
}

// File wraps all the relevant parsed info about a proto file. File objects guarantee that their top-level enums,
// extensions, messages, and services are sorted alphabetically based on their "long name". Other values (enum values,
// fields, service methods) will be in the order that they're defined within their respective proto files.
//
// In the case of proto3 files, HasExtensions will always be false, and Extensions will be empty.
type File struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Package     string `json:"package"`

	HasEnums      bool `json:"hasEnums"`
	HasExtensions bool `json:"hasExtensions"`
	HasMessages   bool `json:"hasMessages"`
	HasServices   bool `json:"hasServices"`

	Enums      orderedEnums      `json:"enums"`
	Extensions orderedExtensions `json:"extensions"`
	Messages   orderedMessages   `json:"messages"`
	Services   orderedServices   `json:"services"`
}

// FileExtension contains details about top-level extensions within a proto(2) file.
type FileExtension struct {
	Name               string `json:"name"`
	LongName           string `json:"longName"`
	FullName           string `json:"fullName"`
	Description        string `json:"description"`
	Label              string `json:"label"`
	Type               string `json:"type"`
	LongType           string `json:"longType"`
	FullType           string `json:"fullType"`
	Number             int    `json:"number"`
	DefaultValue       string `json:"defaultValue"`
	ContainingType     string `json:"containingType"`
	ContainingLongType string `json:"containingLongType"`
	ContainingFullType string `json:"containingFullType"`
}

// Message contains details about a protobuf message.
//
// In the case of proto3 files, HasExtensions will always be false, and Extensions will be empty.
type Message struct {
	Name        string `json:"name"`
	LongName    string `json:"longName"`
	FullName    string `json:"fullName"`
	Description string `json:"description"`

	HasExtensions bool `json:"hasExtensions"`
	HasFields     bool `json:"hasFields"`

	Extensions []*MessageExtension `json:"extensions"`
	Fields     []*MessageField     `json:"fields"`
}

// MessageField contains details about an individual field within a message.
//
// In the case of proto3 files, DefaultValue will always be empty. Similarly, label will be empty unless the field is
// repeated (in which case it'll be "repeated").
type MessageField struct {
	Name         string `json:"name"`
	Description  string `json:"description"`
	Label        string `json:"label"`
	Type         string `json:"type"`
	LongType     string `json:"longType"`
	FullType     string `json:"fullType"`
	DefaultValue string `json:"defaultValue"`
}

// MessageExtension contains details about message-scoped extensions in proto(2) files.
type MessageExtension struct {
	FileExtension

	ScopeType     string `json:"scopeType"`
	ScopeLongType string `json:"scopeLongType"`
	ScopeFullType string `json:"scopeFullType"`
}

// Enum contains details about enumerations. These can be either top level enums, or nested (defined within a message).
type Enum struct {
	Name        string       `json:"name"`
	LongName    string       `json:"longName"`
	FullName    string       `json:"fullName"`
	Description string       `json:"description"`
	Values      []*EnumValue `json:"values"`
}

// EnumValue contains details about an individual value within an enumeration.
type EnumValue struct {
	Name        string `json:"name"`
	Number      string `json:"number"`
	Description string `json:"description"`
}

// Service contains details about a service definition within a proto file.
type Service struct {
	Name        string           `json:"name"`
	LongName    string           `json:"longName"`
	FullName    string           `json:"fullName"`
	Description string           `json:"description"`
	Methods     []*ServiceMethod `json:"methods"`
}

// ServiceMethod contains details about an individual method within a service.
type ServiceMethod struct {
	Name             string `json:"name"`
	Description      string `json:"description"`
	RequestType      string `json:"requestType"`
	RequestLongType  string `json:"requestLongType"`
	RequestFullType  string `json:"requestFullType"`
	ResponseType     string `json:"responseType"`
	ResponseLongType string `json:"responseLongType"`
	ResponseFullType string `json:"responseFullType"`
}

// ScalarValue contains information about scalar value types in protobuf. The common use case for this type is to know
// which language specific type maps to the protobuf type.
//
// For example, the protobuf type `int64` maps to `long` in C#, and `Bignum` in Ruby. For the full list, take a look at
// https://developers.google.com/protocol-buffers/docs/proto3#scalar
type ScalarValue struct {
	ProtoType  string `json:"protoType"`
	Notes      string `json:"notes"`
	CppType    string `json:"cppType"`
	CSharp     string `json:"csType"`
	GoType     string `json:"goType"`
	JavaType   string `json:"javaType"`
	PhpType    string `json:"phpType"`
	PythonType string `json:"pythonType"`
	RubyType   string `json:"rubyType"`
}

func parseEnum(pe *protokit.EnumDescriptor) *Enum {
	enum := &Enum{
		Name:        pe.GetName(),
		LongName:    pe.GetLongName(),
		FullName:    pe.GetFullName(),
		Description: description(pe.GetComments().String()),
	}

	for _, val := range pe.GetValues() {
		enum.Values = append(enum.Values, &EnumValue{
			Name:        val.GetName(),
			Number:      fmt.Sprint(val.GetNumber()),
			Description: description(val.GetComments().String()),
		})
	}

	return enum
}

func parseFileExtension(pe *protokit.ExtensionDescriptor) *FileExtension {
	t, lt, ft := parseType(pe)

	return &FileExtension{
		Name:               pe.GetName(),
		LongName:           pe.GetLongName(),
		FullName:           pe.GetFullName(),
		Description:        description(pe.GetComments().String()),
		Label:              labelName(pe.GetLabel(), pe.IsProto3()),
		Type:               t,
		LongType:           lt,
		FullType:           ft,
		Number:             int(pe.GetNumber()),
		DefaultValue:       pe.GetDefaultValue(),
		ContainingType:     baseName(pe.GetExtendee()),
		ContainingLongType: strings.TrimPrefix(pe.GetExtendee(), "."+pe.GetPackage()+"."),
		ContainingFullType: strings.TrimPrefix(pe.GetExtendee(), "."),
	}
}

func parseMessage(pm *protokit.Descriptor) *Message {
	msg := &Message{
		Name:          pm.GetName(),
		LongName:      pm.GetLongName(),
		FullName:      pm.GetFullName(),
		Description:   description(pm.GetComments().String()),
		HasExtensions: len(pm.GetExtensions()) > 0,
		HasFields:     len(pm.GetMessageFields()) > 0,
		Extensions:    make([]*MessageExtension, 0, len(pm.Extensions)),
		Fields:        make([]*MessageField, 0, len(pm.Fields)),
	}

	for _, ext := range pm.Extensions {
		msg.Extensions = append(msg.Extensions, parseMessageExtension(ext))
	}

	for _, f := range pm.Fields {
		msg.Fields = append(msg.Fields, parseMessageField(f))
	}

	return msg
}

func parseMessageExtension(pe *protokit.ExtensionDescriptor) *MessageExtension {
	return &MessageExtension{
		FileExtension: *parseFileExtension(pe),
		ScopeType:     pe.GetParent().GetName(),
		ScopeLongType: pe.GetParent().GetLongName(),
		ScopeFullType: pe.GetParent().GetFullName(),
	}
}

func parseMessageField(pf *protokit.FieldDescriptor) *MessageField {
	t, lt, ft := parseType(pf)

	return &MessageField{
		Name:         pf.GetName(),
		Description:  description(pf.GetComments().String()),
		Label:        labelName(pf.GetLabel(), pf.IsProto3()),
		Type:         t,
		LongType:     lt,
		FullType:     ft,
		DefaultValue: pf.GetDefaultValue(),
	}
}

func parseService(ps *protokit.ServiceDescriptor) *Service {
	service := &Service{
		Name:        ps.GetName(),
		LongName:    ps.GetLongName(),
		FullName:    ps.GetFullName(),
		Description: description(ps.GetComments().String()),
	}

	for _, sm := range ps.Methods {
		service.Methods = append(service.Methods, parseServiceMethod(sm))
	}

	return service
}

func parseServiceMethod(pm *protokit.MethodDescriptor) *ServiceMethod {
	return &ServiceMethod{
		Name:             pm.GetName(),
		Description:      description(pm.GetComments().String()),
		RequestType:      baseName(pm.GetInputType()),
		RequestLongType:  strings.TrimPrefix(pm.GetInputType(), "."+pm.GetPackage()+"."),
		RequestFullType:  strings.TrimPrefix(pm.GetInputType(), "."),
		ResponseType:     baseName(pm.GetOutputType()),
		ResponseLongType: strings.TrimPrefix(pm.GetOutputType(), "."+pm.GetPackage()+"."),
		ResponseFullType: strings.TrimPrefix(pm.GetOutputType(), "."),
	}
}

func baseName(name string) string {
	parts := strings.Split(name, ".")
	return parts[len(parts)-1]
}

func labelName(lbl descriptor.FieldDescriptorProto_Label, proto3 bool) string {
	if proto3 && lbl != descriptor.FieldDescriptorProto_LABEL_REPEATED {
		return ""
	}

	return strings.ToLower(strings.TrimPrefix(lbl.String(), "LABEL_"))
}

type typeContainer interface {
	GetType() descriptor.FieldDescriptorProto_Type
	GetTypeName() string
	GetPackage() string
}

func parseType(tc typeContainer) (string, string, string) {
	name := tc.GetTypeName()

	if strings.HasPrefix(name, ".") {
		name = strings.TrimPrefix(name, ".")
		return baseName(name), strings.TrimPrefix(name, tc.GetPackage()+"."), name
	}

	name = strings.ToLower(strings.TrimPrefix(tc.GetType().String(), "TYPE_"))
	return name, name, name
}

func description(comment string) string {
	val := strings.TrimLeft(comment, "*/\n ")
	if strings.HasPrefix(val, "@exclude") {
		return ""
	}

	return val
}

type orderedEnums []*Enum

func (oe orderedEnums) Len() int           { return len(oe) }
func (oe orderedEnums) Swap(i, j int)      { oe[i], oe[j] = oe[j], oe[i] }
func (oe orderedEnums) Less(i, j int) bool { return oe[i].LongName < oe[j].LongName }

type orderedExtensions []*FileExtension

func (oe orderedExtensions) Len() int           { return len(oe) }
func (oe orderedExtensions) Swap(i, j int)      { oe[i], oe[j] = oe[j], oe[i] }
func (oe orderedExtensions) Less(i, j int) bool { return oe[i].LongName < oe[j].LongName }

type orderedMessages []*Message

func (om orderedMessages) Len() int           { return len(om) }
func (om orderedMessages) Swap(i, j int)      { om[i], om[j] = om[j], om[i] }
func (om orderedMessages) Less(i, j int) bool { return om[i].LongName < om[j].LongName }

type orderedServices []*Service

func (os orderedServices) Len() int           { return len(os) }
func (os orderedServices) Swap(i, j int)      { os[i], os[j] = os[j], os[i] }
func (os orderedServices) Less(i, j int) bool { return os[i].LongName < os[j].LongName }
<commit_msg>Added IsMap<commit_after>package gendoc

import (
	"github.com/golang/protobuf/protoc-gen-go/descriptor"
	"github.com/pseudomuto/protokit"

	"encoding/json"
	"fmt"
	"sort"
	"strings"
)

// Template is a type for encapsulating all the parsed files, messages, fields, enums, services, extensions, etc. into
// an object that will be supplied to a go template.
type Template struct {
	// The files that were parsed
	Files []*File `json:"files"`
	// Details about the scalar values and their respective types in supported languages.
	Scalars []*ScalarValue `json:"scalarValueTypes"`
}

// NewTemplate creates a Template object from a set of descriptors.
func NewTemplate(descs []*protokit.FileDescriptor) *Template {
	files := make([]*File, 0, len(descs))

	for _, f := range descs {
		file := &File{
			Name:          f.GetName(),
			Description:   description(f.GetSyntaxComments().String()),
			Package:       f.GetPackage(),
			HasEnums:      len(f.Enums) > 0,
			HasExtensions: len(f.Extensions) > 0,
			HasMessages:   len(f.Messages) > 0,
			HasServices:   len(f.Services) > 0,
			Enums:         make(orderedEnums, 0, len(f.Enums)),
			Extensions:    make(orderedExtensions, 0, len(f.Extensions)),
			Messages:      make(orderedMessages, 0, len(f.Messages)),
			Services:      make(orderedServices, 0, len(f.Services)),
		}

		for _, e := range f.Enums {
			file.Enums = append(file.Enums, parseEnum(e))
		}

		for _, e := range f.Extensions {
			file.Extensions = append(file.Extensions, parseFileExtension(e))
		}

		for _, m := range f.Messages {
			file.Messages = append(file.Messages, parseMessage(m))

			for _, n := range m.Messages {
				file.Messages = append(file.Messages, parseMessage(n))
			}

			for _, e := range m.Enums {
				file.Enums = append(file.Enums, parseEnum(e))
			}
		}

		for _, s := range f.Services {
			file.Services = append(file.Services, parseService(s))
		}

		sort.Sort(file.Enums)
		sort.Sort(file.Extensions)
		sort.Sort(file.Messages)
		sort.Sort(file.Services)

		files = append(files, file)
	}

	return &Template{Files: files, Scalars: makeScalars()}
}

func makeScalars() []*ScalarValue {
	data, _ := fetchResource("scalars.json")

	var scalars []*ScalarValue
	json.Unmarshal(data, &scalars)

	return scalars
}

// File wraps all the relevant parsed info about a proto file. File objects guarantee that their top-level enums,
// extensions, messages, and services are sorted alphabetically based on their "long name". Other values (enum values,
// fields, service methods) will be in the order that they're defined within their respective proto files.
//
// In the case of proto3 files, HasExtensions will always be false, and Extensions will be empty.
type File struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Package     string `json:"package"`

	HasEnums      bool `json:"hasEnums"`
	HasExtensions bool `json:"hasExtensions"`
	HasMessages   bool `json:"hasMessages"`
	HasServices   bool `json:"hasServices"`

	Enums      orderedEnums      `json:"enums"`
	Extensions orderedExtensions `json:"extensions"`
	Messages   orderedMessages   `json:"messages"`
	Services   orderedServices   `json:"services"`
}

// FileExtension contains details about top-level extensions within a proto(2) file.
type FileExtension struct {
	Name               string `json:"name"`
	LongName           string `json:"longName"`
	FullName           string `json:"fullName"`
	Description        string `json:"description"`
	Label              string `json:"label"`
	Type               string `json:"type"`
	LongType           string `json:"longType"`
	FullType           string `json:"fullType"`
	Number             int    `json:"number"`
	DefaultValue       string `json:"defaultValue"`
	ContainingType     string `json:"containingType"`
	ContainingLongType string `json:"containingLongType"`
	ContainingFullType string `json:"containingFullType"`
}

// Message contains details about a protobuf message.
//
// In the case of proto3 files, HasExtensions will always be false, and Extensions will be empty.
type Message struct {
	Name        string `json:"name"`
	LongName    string `json:"longName"`
	FullName    string `json:"fullName"`
	Description string `json:"description"`

	HasExtensions bool `json:"hasExtensions"`
	HasFields     bool `json:"hasFields"`

	Extensions []*MessageExtension `json:"extensions"`
	Fields     []*MessageField     `json:"fields"`
}

// MessageField contains details about an individual field within a message.
//
// In the case of proto3 files, DefaultValue will always be empty. Similarly, label will be empty unless the field is
// repeated (in which case it'll be "repeated").
type MessageField struct {
	Name         string `json:"name"`
	Description  string `json:"description"`
	Label        string `json:"label"`
	Type         string `json:"type"`
	LongType     string `json:"longType"`
	FullType     string `json:"fullType"`
	IsMap        bool   `json:"ismap"`
	DefaultValue string `json:"defaultValue"`
}

// MessageExtension contains details about message-scoped extensions in proto(2) files.
type MessageExtension struct {
	FileExtension

	ScopeType     string `json:"scopeType"`
	ScopeLongType string `json:"scopeLongType"`
	ScopeFullType string `json:"scopeFullType"`
}

// Enum contains details about enumerations. These can be either top level enums, or nested (defined within a message).
type Enum struct {
	Name        string       `json:"name"`
	LongName    string       `json:"longName"`
	FullName    string       `json:"fullName"`
	Description string       `json:"description"`
	Values      []*EnumValue `json:"values"`
}

// EnumValue contains details about an individual value within an enumeration.
type EnumValue struct {
	Name        string `json:"name"`
	Number      string `json:"number"`
	Description string `json:"description"`
}

// Service contains details about a service definition within a proto file.
type Service struct {
	Name        string           `json:"name"`
	LongName    string           `json:"longName"`
	FullName    string           `json:"fullName"`
	Description string           `json:"description"`
	Methods     []*ServiceMethod `json:"methods"`
}

// ServiceMethod contains details about an individual method within a service.
type ServiceMethod struct {
	Name             string `json:"name"`
	Description      string `json:"description"`
	RequestType      string `json:"requestType"`
	RequestLongType  string `json:"requestLongType"`
	RequestFullType  string `json:"requestFullType"`
	ResponseType     string `json:"responseType"`
	ResponseLongType string `json:"responseLongType"`
	ResponseFullType string `json:"responseFullType"`
}

// ScalarValue contains information about scalar value types in protobuf. The common use case for this type is to know
// which language specific type maps to the protobuf type.
//
// For example, the protobuf type `int64` maps to `long` in C#, and `Bignum` in Ruby. For the full list, take a look at
// https://developers.google.com/protocol-buffers/docs/proto3#scalar
type ScalarValue struct {
	ProtoType  string `json:"protoType"`
	Notes      string `json:"notes"`
	CppType    string `json:"cppType"`
	CSharp     string `json:"csType"`
	GoType     string `json:"goType"`
	JavaType   string `json:"javaType"`
	PhpType    string `json:"phpType"`
	PythonType string `json:"pythonType"`
	RubyType   string `json:"rubyType"`
}

func parseEnum(pe *protokit.EnumDescriptor) *Enum {
	enum := &Enum{
		Name:        pe.GetName(),
		LongName:    pe.GetLongName(),
		FullName:    pe.GetFullName(),
		Description: description(pe.GetComments().String()),
	}

	for _, val := range pe.GetValues() {
		enum.Values = append(enum.Values, &EnumValue{
			Name:        val.GetName(),
			Number:      fmt.Sprint(val.GetNumber()),
			Description: description(val.GetComments().String()),
		})
	}

	return enum
}

func parseFileExtension(pe *protokit.ExtensionDescriptor) *FileExtension {
	t, lt, ft := parseType(pe)

	return &FileExtension{
		Name:               pe.GetName(),
		LongName:           pe.GetLongName(),
		FullName:           pe.GetFullName(),
		Description:        description(pe.GetComments().String()),
		Label:              labelName(pe.GetLabel(), pe.IsProto3()),
		Type:               t,
		LongType:           lt,
		FullType:           ft,
		Number:             int(pe.GetNumber()),
		DefaultValue:       pe.GetDefaultValue(),
		ContainingType:     baseName(pe.GetExtendee()),
		ContainingLongType: strings.TrimPrefix(pe.GetExtendee(), "."+pe.GetPackage()+"."),
		ContainingFullType: strings.TrimPrefix(pe.GetExtendee(), "."),
	}
}

func parseMessage(pm *protokit.Descriptor) *Message {
	msg := &Message{
		Name:          pm.GetName(),
		LongName:      pm.GetLongName(),
		FullName:      pm.GetFullName(),
		Description:   description(pm.GetComments().String()),
		HasExtensions: len(pm.GetExtensions()) > 0,
		HasFields:     len(pm.GetMessageFields()) > 0,
		Extensions:    make([]*MessageExtension, 0, len(pm.Extensions)),
		Fields:        make([]*MessageField, 0, len(pm.Fields)),
	}

	for _, ext := range pm.Extensions {
		msg.Extensions = append(msg.Extensions, parseMessageExtension(ext))
	}

	for _, f := range pm.Fields {
		msg.Fields = append(msg.Fields, parseMessageField(f))
	}

	return msg
}

func parseMessageExtension(pe *protokit.ExtensionDescriptor) *MessageExtension {
	return &MessageExtension{
		FileExtension: *parseFileExtension(pe),
		ScopeType:     pe.GetParent().GetName(),
		ScopeLongType: pe.GetParent().GetLongName(),
		ScopeFullType: pe.GetParent().GetFullName(),
	}
}

func parseMessageField(pf *protokit.FieldDescriptor) *MessageField {
	t, lt, ft := parseType(pf)

	m := &MessageField{
		Name:         pf.GetName(),
		Description:  description(pf.GetComments().String()),
		Label:        labelName(pf.GetLabel(), pf.IsProto3()),
		Type:         t,
		LongType:     lt,
		FullType:     ft,
		DefaultValue: pf.GetDefaultValue(),
	}

	// Check if this is a map.
	if m.Label == "repeated" &&
		strings.Contains(m.LongType, ".") &&
		strings.HasSuffix(m.Type, "Entry") &&
		strings.HasSuffix(m.LongType, "Entry") &&
		strings.HasSuffix(m.FullType, "Entry") {
		m.IsMap = true
	}

	return m
}

func parseService(ps *protokit.ServiceDescriptor) *Service {
	service := &Service{
		Name:        ps.GetName(),
		LongName:    ps.GetLongName(),
		FullName:    ps.GetFullName(),
		Description: description(ps.GetComments().String()),
	}

	for _, sm := range ps.Methods {
		service.Methods = append(service.Methods, parseServiceMethod(sm))
	}

	return service
}

func parseServiceMethod(pm *protokit.MethodDescriptor) *ServiceMethod {
	return &ServiceMethod{
		Name:             pm.GetName(),
		Description:      description(pm.GetComments().String()),
		RequestType:      baseName(pm.GetInputType()),
		RequestLongType:  strings.TrimPrefix(pm.GetInputType(), "."+pm.GetPackage()+"."),
		RequestFullType:  strings.TrimPrefix(pm.GetInputType(), "."),
		ResponseType:     baseName(pm.GetOutputType()),
		ResponseLongType: strings.TrimPrefix(pm.GetOutputType(), "."+pm.GetPackage()+"."),
		ResponseFullType: strings.TrimPrefix(pm.GetOutputType(), "."),
	}
}

func baseName(name string) string {
	parts := strings.Split(name, ".")
	return parts[len(parts)-1]
}

func labelName(lbl descriptor.FieldDescriptorProto_Label, proto3 bool) string {
	if proto3 && lbl != descriptor.FieldDescriptorProto_LABEL_REPEATED {
		return ""
	}

	return strings.ToLower(strings.TrimPrefix(lbl.String(), "LABEL_"))
}

type typeContainer interface {
	GetType() descriptor.FieldDescriptorProto_Type
	GetTypeName() string
	GetPackage() string
}

func parseType(tc typeContainer) (string, string, string) {
	name := tc.GetTypeName()

	if strings.HasPrefix(name, ".") {
		name = strings.TrimPrefix(name, ".")
		return baseName(name), strings.TrimPrefix(name, tc.GetPackage()+"."), name
	}

	name = strings.ToLower(strings.TrimPrefix(tc.GetType().String(), "TYPE_"))
	return name, name, name
}

func description(comment string) string {
	val := strings.TrimLeft(comment, "*/\n ")
	if strings.HasPrefix(val, "@exclude") {
		return ""
	}

	return val
}

type orderedEnums []*Enum

func (oe orderedEnums) Len() int           { return len(oe) }
func (oe orderedEnums) Swap(i, j int)      { oe[i], oe[j] = oe[j], oe[i] }
func (oe orderedEnums) Less(i, j int) bool { return oe[i].LongName < oe[j].LongName }

type orderedExtensions []*FileExtension

func (oe orderedExtensions) Len() int           { return len(oe) }
func (oe orderedExtensions) Swap(i, j int)      { oe[i], oe[j] = oe[j], oe[i] }
func (oe orderedExtensions) Less(i, j int) bool { return oe[i].LongName < oe[j].LongName }

type orderedMessages []*Message

func (om orderedMessages) Len() int           { return len(om) }
func (om orderedMessages) Swap(i, j int)      { om[i], om[j] = om[j], om[i] }
func (om orderedMessages) Less(i, j int) bool { return om[i].LongName < om[j].LongName }

type orderedServices []*Service

func (os orderedServices) Len() int           { return len(os) }
func (os orderedServices) Swap(i, j int)      { os[i], os[j] = os[j], os[i] }
func (os orderedServices) Less(i, j int) bool { return os[i].LongName < os[j].LongName }
<|endoftext|>


<commit_before>package dalga

import (
	"encoding/json"
	"errors"
	"net/http"
	"strconv"

	"github.com/cenkalti/dalga/vendor/github.com/bmizerany/pat"
)

func (d *Dalga) serveHTTP() error {
	const path = "/jobs/:routingKey/:jobDescription"
	m := pat.New()
	m.Get(path, handler(d.handleGet))
	m.Put(path, handler(d.handleSchedule))
	m.Post(path, handler(d.handleTrigger))
	m.Del(path, handler(d.handleCancel))
	return http.Serve(d.listener, m)
}

func handler(f func(w http.ResponseWriter, r *http.Request, description, routingKey string)) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		debug("http:", r.Method, r.RequestURI)
		routingKey := r.URL.Query().Get(":routingKey")
		if routingKey == "" {
			http.Error(w, "empty routing key", http.StatusBadRequest)
			return
		}
		jobDescription := r.URL.Query().Get(":jobDescription")
		if jobDescription == "" {
			http.Error(w, "empty job", http.StatusBadRequest)
			return
		}
		f(w, r, jobDescription, routingKey)
	})
}

func getInterval(r *http.Request) (uint32, error) {
	s := r.FormValue("interval")
	if s == "" {
		return 0, errors.New("empty interval")
	}
	i64, err := strconv.ParseUint(s, 10, 32)
	if err != nil {
		return 0, errors.New("cannot parse interval")
	}
	return uint32(i64), nil
}

func (d *Dalga) handleGet(w http.ResponseWriter, r *http.Request, description, routingKey string) {
	job, err := d.table.Get(description, routingKey)
	if err == ErrNotExist {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	data, err := json.Marshal(job)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(data)
}

func (d *Dalga) handleSchedule(w http.ResponseWriter, r *http.Request, description, routingKey string) {
	interval, err := getInterval(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	oneOff := r.FormValue("one-off") != ""
	if !oneOff && interval == 0 {
		http.Error(w, "interval can't be 0 for periodic jobs", http.StatusBadRequest)
		return
	}
	job, err := d.Schedule(description, routingKey, interval, oneOff)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	data, err := json.Marshal(job)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	w.Write(data)
}

func (d *Dalga) handleTrigger(w http.ResponseWriter, r *http.Request, description, routingKey string) {
	job, err := d.Trigger(description, routingKey)
	if err == ErrNotExist {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	data, err := json.Marshal(job)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(data)
}

func (d *Dalga) handleCancel(w http.ResponseWriter, r *http.Request, description, routingKey string) {
	err := d.Cancel(description, routingKey)
	if err == ErrNotExist {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}
<commit_msg>unescape get params<commit_after>package dalga

import (
	"encoding/json"
	"errors"
	"net/http"
	"net/url"
	"strconv"

	"github.com/cenkalti/dalga/vendor/github.com/bmizerany/pat"
)

func (d *Dalga) serveHTTP() error {
	const path = "/jobs/:routingKey/:jobDescription"
	m := pat.New()
	m.Get(path, handler(d.handleGet))
	m.Put(path, handler(d.handleSchedule))
	m.Post(path, handler(d.handleTrigger))
	m.Del(path, handler(d.handleCancel))
	return http.Serve(d.listener, m)
}

func handler(f func(w http.ResponseWriter, r *http.Request, description, routingKey string)) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		debug("http:", r.Method, r.RequestURI)

		routingKeyParam := r.URL.Query().Get(":routingKey")
		if routingKeyParam == "" {
			http.Error(w, "empty routing key", http.StatusBadRequest)
			return
		}
		routingKey, err := url.QueryUnescape(routingKeyParam)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		jobDescriptionParam := r.URL.Query().Get(":jobDescription")
		if jobDescriptionParam == "" {
			http.Error(w, "empty job", http.StatusBadRequest)
			return
		}
		jobDescription, err := url.QueryUnescape(jobDescriptionParam)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		f(w, r, jobDescription, routingKey)
	})
}

func getInterval(r *http.Request) (uint32, error) {
	s := r.FormValue("interval")
	if s == "" {
		return 0, errors.New("empty interval")
	}
	i64, err := strconv.ParseUint(s, 10, 32)
	if err != nil {
		return 0, errors.New("cannot parse interval")
	}
	return uint32(i64), nil
}

func (d *Dalga) handleGet(w http.ResponseWriter, r *http.Request, description, routingKey string) {
	job, err := d.table.Get(description, routingKey)
	if err == ErrNotExist {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	data, err := json.Marshal(job)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(data)
}

func (d *Dalga) handleSchedule(w http.ResponseWriter, r *http.Request, description, routingKey string) {
	interval, err := getInterval(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	oneOff := r.FormValue("one-off") != ""
	if !oneOff && interval == 0 {
		http.Error(w, "interval can't be 0 for periodic jobs", http.StatusBadRequest)
		return
	}
	job, err := d.Schedule(description, routingKey, interval, oneOff)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	data, err := json.Marshal(job)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	w.Write(data)
}

func (d *Dalga) handleTrigger(w http.ResponseWriter, r *http.Request, description, routingKey string) {
	job, err := d.Trigger(description, routingKey)
	if err == ErrNotExist {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	data, err := json.Marshal(job)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(data)
}

func (d *Dalga) handleCancel(w http.ResponseWriter, r *http.Request, description, routingKey string) {
	err := d.Cancel(description, routingKey)
	if err == ErrNotExist {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}
<|endoftext|>


<commit_before>package main

import (
//	"net"
	"net/http"
	"log"
//	"io/ioutil"
	"flag"
	"os"
	"path/filepath"
	"regexp"
	"fmt"
//	"reflect"
	"strconv"
	"time"
//	"strings"
	"html/template"
	"encoding/json"
	"runtime/pprof"
)

var (
	rootdir string
	port string
	address string
	verbose bool
)

var (
	d Query
)

var templates *template.Template

func init() {
	flag.StringVar(&rootdir, "root", "current directory", "webroot directory")
	flag.StringVar(&rootdir, "d", "current directory", "webroot directory" + " (shorthand)")
	flag.StringVar(&port,"port", "8080", "listen port")
	flag.StringVar(&port,"p", "8080", "listen port" + " (shorthand)")
	flag.StringVar(&address, "address", "*", "listen address")
	flag.StringVar(&address, "l", "*", "listen address" + " (shorthand)")
	flag.BoolVar(&verbose, "verbose", false, "be verbose")
	flag.BoolVar(&verbose, "v", false, "be verbose" + " (shorthand)")
}

func debug(s string) {
	if verbose {
		log.Print(s)
	}
}

func debugln(v ...interface{}) {
	if verbose {
		log.Println(v ...)
	}
}

var validLog = regexp.MustCompile("^/log/([a-zA-Z0-9-]+)/([0-9]+[.]{0,1}[0-9]*)(/([ts])/([0-9]+))?/?$")
var validQuery = regexp.MustCompile("^/q/([a-zA-Z0-9-]+)/?$")
var validURLs = regexp.MustCompile("^/")

func logHandler(w http.ResponseWriter, r *http.Request) {
	m := validLog.FindStringSubmatch(r.URL.Path)
	if len(m) == 0 {
		http.Error(w, "Sensor not found", http.StatusNotFound)
		return
	}
	debug("Sensor " + m[1] + " sent value " + m[2])
	//if ! d.Exists(m[1]) {
	//	d.Add(m[1])
	//}
	//v, _ := strconv.ParseFloat(m[2], 64)
	if m[4] != "" {
		t, _ := strconv.ParseInt(m[5], 10, 64)
		_, told := d.Last(m[1])
		if m[4] == "t" {
			if time.Unix(t, 0).After(told) {
				d.StoreT(m[1], m[2], time.Unix(t, 0))
			} else {
				http.Error(w, "Sensor send out of order timestamp", http.StatusNotFound)
				return
			}
		} else {
			if time.Unix(time.Now().Unix() - t, 0).After(told) {
				d.StoreT(m[1], m[2], time.Unix(time.Now().Unix() - t, 0))
			} else {
                                http.Error(w, "Sensor send out of order timestamp", http.StatusNotFound)
                                return
                        }
		}
	} else {
		d.Store(m[1], m[2])
	}
	debugln("Sensor " + m[1] + " now contains:", d.Load(m[1]))
	fmt.Fprintf(w, "ok")
}

func queryHandler(w http.ResponseWriter, r *http.Request) {
	m := validQuery.FindStringSubmatch(r.URL.Path)
        if len(m) == 0 {
                http.Error(w, "Sensor not found", http.StatusNotFound)
                return
        }
	if ! d.Exists(m[1]) {
		http.Error(w, "Sensor not found", http.StatusNotFound)
                return
        }
	debugln("Query for sensor " + m[1])
	a, _ := json.Marshal(d.Load(m[1]))
	fmt.Fprintf(w, string(a))
}

type HomePage struct {
	SensorList template.HTML
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	var sl string
	for _, s := range d.List() {
		sl += `

                   <a href="view/` + s + `">` + s + `

`
	}
	templates = template.Must(template.ParseFiles(rootdir + "/templates/home.html")) // Remove when finish frontend
	err := templates.ExecuteTemplate(w, "home.html", HomePage{template.HTML(sl)})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func makeHandler(fn func (http.ResponseWriter, *http.Request)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		m := validURLs.FindStringSubmatch(r.URL.Path)
		if m == nil {
			http.NotFound(w, r)
			return
		}
		fn(w, r)
	}
}

var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")

func main() {
	flag.Parse()
	var err error
	if rootdir == "current directory" {
		rootdir, err =  filepath.Abs(filepath.Dir(os.Args[0]))
	} else {
		rootdir, err = filepath.Abs(filepath.Dir(rootdir))
	}
	if err != nil {
		log.Fatal(err)
	}

	if address == "*" {
		address = ""
	}

	// CPU profiling
	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	t := Database{ make(map[string] sensorlog) }
	d = t
	templates = template.Must(template.ParseFiles(rootdir + "/templates/home.html"))

	http.HandleFunc("/log/", logHandler)
	http.HandleFunc("/q/", queryHandler)
	http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir(rootdir + "/assets"))))
//	http.Handle("/", http.FileServer(http.Dir(rootdir)))
	http.HandleFunc("/", makeHandler(homeHandler))

	log.Print("Starting webserver. Listening on " + address + ":" + port)
	log.Print("Webroot set to \"" + rootdir + "\".")
	err = http.ListenAndServe(address + ":" + port, nil)
	if err != nil {
		log.Fatal("Couldn't start server. ListenAndServe: ", err)
	}
}
<commit_msg>re-wrote the timecheck code a bit more clear and added support for negative sensor values<commit_after>package main

import (
//	"net"
	"net/http"
	"log"
//	"io/ioutil"
	"flag"
	"os"
	"path/filepath"
	"regexp"
	"fmt"
//	"reflect"
	"strconv"
	"time"
//	"strings"
	"html/template"
	"encoding/json"
	"runtime/pprof"
)

var (
	rootdir string
	port string
	address string
	verbose bool
)

var (
	d Query
)

var templates *template.Template

func init() {
	flag.StringVar(&rootdir, "root", "current directory", "webroot directory")
	flag.StringVar(&rootdir, "d", "current directory", "webroot directory" + " (shorthand)")
	flag.StringVar(&port,"port", "8080", "listen port")
	flag.StringVar(&port,"p", "8080", "listen port" + " (shorthand)")
	flag.StringVar(&address, "address", "*", "listen address")
	flag.StringVar(&address, "l", "*", "listen address" + " (shorthand)")
	flag.BoolVar(&verbose, "verbose", false, "be verbose")
	flag.BoolVar(&verbose, "v", false, "be verbose" + " (shorthand)")
}

func debug(s string) {
	if verbose {
		log.Print(s)
	}
}

func debugln(v ...interface{}) {
	if verbose {
		log.Println(v ...)
	}
}

var validLog = regexp.MustCompile("^/log/([a-zA-Z0-9-]+)/(-?[0-9]+[.]{0,1}[0-9]*)(/([ts])/([0-9]+))?/?$")
var validQuery = regexp.MustCompile("^/q/([a-zA-Z0-9-]+)/?$")
var validURLs = regexp.MustCompile("^/")

func logHandler(w http.ResponseWriter, r *http.Request) {
	m := validLog.FindStringSubmatch(r.URL.Path)
	if len(m) == 0 {
		http.Error(w, "Sensor not found", http.StatusNotFound)
		return
	}
	debug("Sensor " + m[1] + " sent value " + m[2])
	if m[4] != "" {
		t, _ := strconv.ParseInt(m[5], 10, 64)
		var tnew time.Time
		if m[4] == "t" {
			tnew = time.Unix(t, 0)
		} else { // m[4] == "s"
			tnew = time.Unix(time.Now().Unix() - t, 0)
		}
		_, told := d.Last(m[1])

		if tnew.After(told) {
			d.StoreT(m[1], m[2], tnew)
		} else {
			http.Error(w, "Sensor send out of order timestamp", http.StatusNotFound)
			return
		}
	} else {
		d.Store(m[1], m[2])
	}
	debugln("Sensor " + m[1] + " now contains:", d.Load(m[1]))
	fmt.Fprintf(w, "ok")
}

func queryHandler(w http.ResponseWriter, r *http.Request) {
	m := validQuery.FindStringSubmatch(r.URL.Path)
        if len(m) == 0 {
                http.Error(w, "Sensor not found", http.StatusNotFound)
                return
        }
	if ! d.Exists(m[1]) {
		http.Error(w, "Sensor not found", http.StatusNotFound)
                return
        }
	debugln("Query for sensor " + m[1])
	a, _ := json.Marshal(d.Load(m[1]))
	fmt.Fprintf(w, string(a))
}

type HomePage struct {
	SensorList template.HTML
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	var sl string
	for _, s := range d.List() {
		sl += `

                   <a href="view/` + s + `">` + s + `

`
	}
	templates = template.Must(template.ParseFiles(rootdir + "/templates/home.html")) // Remove when finish frontend
	err := templates.ExecuteTemplate(w, "home.html", HomePage{template.HTML(sl)})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func makeHandler(fn func (http.ResponseWriter, *http.Request)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		m := validURLs.FindStringSubmatch(r.URL.Path)
		if m == nil {
			http.NotFound(w, r)
			return
		}
		fn(w, r)
	}
}

var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")

func main() {
	flag.Parse()
	var err error
	if rootdir == "current directory" {
		rootdir, err =  filepath.Abs(filepath.Dir(os.Args[0]))
	} else {
		rootdir, err = filepath.Abs(filepath.Dir(rootdir))
	}
	if err != nil {
		log.Fatal(err)
	}

	if address == "*" {
		address = ""
	}

	// CPU profiling
	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	t := Database{ make(map[string] sensorlog) }
	d = t
	templates = template.Must(template.ParseFiles(rootdir + "/templates/home.html"))

	http.HandleFunc("/log/", logHandler)
	http.HandleFunc("/q/", queryHandler)
	http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir(rootdir + "/assets"))))
//	http.Handle("/", http.FileServer(http.Dir(rootdir)))
	http.HandleFunc("/", makeHandler(homeHandler))

	log.Print("Starting webserver. Listening on " + address + ":" + port)
	log.Print("Webroot set to \"" + rootdir + "\".")
	err = http.ListenAndServe(address + ":" + port, nil)
	if err != nil {
		log.Fatal("Couldn't start server. ListenAndServe: ", err)
	}
}
<|endoftext|>


<commit_before>// This implementation of Context interface uses tokeninfo API to validate
// bearer token.
//
// It is intended to be used only on dev server.

package hukabuk

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

	"appengine"
)

const tokeninfoEndpointURL = "https://www.googleapis.com/oauth2/v2/tokeninfo"

type tokeninfo struct {
	IssuedTo      string `json:"issued_to"`
	Audience      string `json:"audience"`
	UserID        string `json:"user_id"` // Equivalent of `Subject`
	Scope         string `json:"scope"`
	ExpiresIn     int    `json:"expires_in"`
	Email         string `json:"email"`
	VerifiedEmail bool   `json:"verified_email"`
	AccessType    string `json:"access_type"`
	// ErrorDescription is populated when an error occurs. Usually, the response
	// either contains only ErrorDescription or the fields above
	ErrorDescription string `json:"error_description"`
}

// A context that uses tokeninfo API to validate bearer token
type cachingTokeninfoContext struct {
	appengine.Context
	r *http.Request
	// map keys as scopes
	responseCache map[string]*[]byte
	// mutex for oauthResponseCache
	sync.Mutex
}

func populateTokenInfoResponse(c *cachingTokeninfoContext, token string, scope string) error {
	// Only one scope should be cached at once, so we just destroy the cache
	c.responseCache = map[string]*[]byte{}

	// Construct the URL to get the token.
	url := tokeninfoEndpointURL + "?access_token=" + token
	c.Debugf("Fetching token info from %q", url)
	resp, err := newHTTPClient(c).Get(url)
	if err != nil {
		return err
	}
	// Make sure to close if the request did not fail.
	defer resp.Body.Close()

	c.Debugf("Tokeninfo replied with %s", resp.Status)
	if resp.StatusCode != http.StatusOK {
		errMsg := fmt.Sprintf("Error fetching tokeninfo (status %d)", resp.StatusCode)
		return errors.New(errMsg)
	}

	var content []byte
	content, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return err

	}

	c.responseCache[scope] = &content
	return nil
}

func getTokenInfoResponse(c *cachingTokeninfoContext, token string, scope string) (*[]byte, error) {
	res, ok := c.responseCache[scope]

	if !ok {
		c.Lock()
		defer c.Unlock()
		if err := populateTokenInfoResponse(c, token, scope); err != nil {
			return nil, err
		}
		res = c.responseCache[scope]
	}

	return res, nil
}

// fetchTokeninfo retrieves token info from tokeninfoEndpointURL  (tokeninfo API)
func fetchTokeninfo(c *cachingTokeninfoContext, token string, scope string) (*tokeninfo, error) {
	responseBody, err := getTokenInfoResponse(c, token, scope)
	if err != nil {
		return nil, err
	}

	ti := &tokeninfo{}
	if err = json.Unmarshal(*responseBody, ti); err != nil {
		return nil, err
	}

	switch {
	case ti.ExpiresIn <= 0:
		return nil, errors.New("Token is expired")
	case !ti.VerifiedEmail:
		return nil, fmt.Errorf("Unverified email %q", ti.Email)
	case ti.Email == "":
		return nil, fmt.Errorf("Invalid email address")
	}

	return ti, err
}

// getScopedTokeninfo validates fetched token by matching tokeinfo.Scope
// with scope arg.
func getScopedTokeninfo(c *cachingTokeninfoContext, scope string) (*tokeninfo, error) {
	token := getToken(c.HTTPRequest())
	if token == "" {
		return nil, errors.New("No token found")
	}
	ti, err := fetchTokeninfo(c, token, scope)
	if err != nil {
		return nil, err
	}
	for _, s := range strings.Split(ti.Scope, " ") {
		if s == scope {
			return ti, nil
		}
	}
	return nil, fmt.Errorf("No scope matches: expected one of %q, got %q",
		ti.Scope, scope)
}

func newTokenCachingContext(c appengine.Context, r *http.Request) Context {
	return &cachingTokeninfoContext{c, r, map[string]*[]byte{}, sync.Mutex{}}
}

func (c *cachingTokeninfoContext) HTTPRequest() *http.Request {
	// WAS: c.Request().(*http.Request)
	return c.r
}

// Namespace returns a replacement context that operates within the given namespace.
func (c *cachingTokeninfoContext) Namespace(name string) (Context, error) {
	nc, err := appengine.Namespace(c, name)
	if err != nil {
		return nil, err
	}
	return newTokenCachingContext(nc, c.r), nil
}

// CurrentOAuthClientID returns a clientID associated with the scope.
func (c *cachingTokeninfoContext) CurrentOAuthClientID(scope string) (string, error) {
	ti, err := getScopedTokeninfo(c, scope)
	if err != nil {
		return "", err
	}
	return ti.IssuedTo, nil
}

// CurrentOAuthUser returns a user associated with the request in context.
func (c *cachingTokeninfoContext) CurrentOAuthUser(scope string) (*userLocal, error) {
	ti, err := getScopedTokeninfo(c, scope)
	if err != nil {
		return nil, err
	}
	return &userLocal{
		Email:        ti.Email,
		GooglePlusID: ti.UserID,
	}, nil
}

// cachingTokeninfoContextFactory creates a new cachingTokeninfoContext from r.
// To be used as auth.go/ContextFactory.
func cachingTokeninfoContextFactory(r *http.Request) Context {
	return newTokenCachingContext(appengine.NewContext(r), r)
}
<commit_msg>Caching parsed tokeninfo instead of the request body.<commit_after>// This implementation of Context interface uses tokeninfo API to validate
// bearer token.
//
// It is intended to be used only on dev server.

package hukabuk

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"
	"sync"

	"appengine"
)

const tokeninfoEndpointURL = "https://www.googleapis.com/oauth2/v2/tokeninfo"

type tokeninfo struct {
	IssuedTo      string `json:"issued_to"`
	Audience      string `json:"audience"`
	UserID        string `json:"user_id"` // Equivalent of `Subject`
	Scope         string `json:"scope"`
	ExpiresIn     int    `json:"expires_in"`
	Email         string `json:"email"`
	VerifiedEmail bool   `json:"verified_email"`
	AccessType    string `json:"access_type"`
	// ErrorDescription is populated when an error occurs. Usually, the response
	// either contains only ErrorDescription or the fields above
	ErrorDescription string `json:"error_description"`
}

// A context that uses tokeninfo API to validate bearer token
type cachingTokeninfoContext struct {
	appengine.Context
	r *http.Request
	// map keys as scopes
	tokeninfoCache map[string]*tokeninfo
	// mutex for tokeninfoCache
	sync.Mutex
}

// fetchTokeninfo retrieves token info from tokeninfoEndpointURL  (tokeninfo API)
func fetchTokeninfo(c *cachingTokeninfoContext, token string) (*tokeninfo, error) {
	url := tokeninfoEndpointURL + "?access_token=" + token
	c.Debugf("Fetching token info from %q", url)
	resp, err := newHTTPClient(c).Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	c.Debugf("Tokeninfo replied with %s", resp.Status)

	ti := &tokeninfo{}
	if err = json.NewDecoder(resp.Body).Decode(ti); err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		errMsg := fmt.Sprintf("Error fetching tokeninfo (status %d)", resp.StatusCode)
		if ti.ErrorDescription != "" {
			errMsg += ": " + ti.ErrorDescription
		}
		return nil, errors.New(errMsg)
	}

	switch {
	case ti.ExpiresIn <= 0:
		return nil, errors.New("Token is expired")
	case !ti.VerifiedEmail:
		return nil, fmt.Errorf("Unverified email %q", ti.Email)
	case ti.Email == "":
		return nil, fmt.Errorf("Invalid email address")
	}

	return ti, err
}

func populateTokenInfo(c *cachingTokeninfoContext, scope string) error {
	// Only one scope should be cached at once, so we just destroy the cache
	c.tokeninfoCache = map[string]*tokeninfo{}

	token := getToken(c.HTTPRequest())
	if token == "" {
		return errors.New("No token found")
	}
	ti, err := fetchTokeninfo(c, token)
	if err != nil {
		return err
	}
	for _, s := range strings.Split(ti.Scope, " ") {
		if s == scope {
			c.tokeninfoCache[scope] = ti
			return nil
		}
	}
	return fmt.Errorf("No scope matches: expected one of %q, got %q",
		ti.Scope, scope)
}

// getScopedTokeninfo validates fetched token by matching tokeninfo.Scope
// with scope arg.
func getScopedTokeninfo(c *cachingTokeninfoContext, scope string) (*tokeninfo, error) {
	ti, ok := c.tokeninfoCache[scope]

	if !ok {
		c.Lock()
		defer c.Unlock()
		if err := populateTokenInfo(c, scope); err != nil {
			return nil, err
		}
		ti = c.tokeninfoCache[scope]
	}

	return ti, nil
}

func newTokenCachingContext(c appengine.Context, r *http.Request) Context {
	return &cachingTokeninfoContext{c, r, map[string]*tokeninfo{}, sync.Mutex{}}
}

func (c *cachingTokeninfoContext) HTTPRequest() *http.Request {
	// WAS: c.Request().(*http.Request)
	return c.r
}

// Namespace returns a replacement context that operates within the given namespace.
func (c *cachingTokeninfoContext) Namespace(name string) (Context, error) {
	nc, err := appengine.Namespace(c, name)
	if err != nil {
		return nil, err
	}
	return newTokenCachingContext(nc, c.r), nil
}

// CurrentOAuthClientID returns a clientID associated with the scope.
func (c *cachingTokeninfoContext) CurrentOAuthClientID(scope string) (string, error) {
	ti, err := getScopedTokeninfo(c, scope)
	if err != nil {
		return "", err
	}
	return ti.IssuedTo, nil
}

// CurrentOAuthUser returns a user associated with the request in context.
func (c *cachingTokeninfoContext) CurrentOAuthUser(scope string) (*userLocal, error) {
	ti, err := getScopedTokeninfo(c, scope)
	if err != nil {
		return nil, err
	}
	return &userLocal{
		Email:        ti.Email,
		GooglePlusID: ti.UserID,
	}, nil
}

// cachingTokeninfoContextFactory creates a new cachingTokeninfoContext from r.
// To be used as auth.go/ContextFactory.
func cachingTokeninfoContextFactory(r *http.Request) Context {
	return newTokenCachingContext(appengine.NewContext(r), r)
}
<|endoftext|>


<commit_before>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package middleware

import (
	"html/template"
	"net/url"
	"time"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/middleware/tplfunc"
	"github.com/webx-top/echo/param"
	"github.com/webx-top/echo/subdomains"

	"github.com/admpub/nging/v3/application/handler"
	"github.com/admpub/nging/v3/application/library/codec"
	"github.com/admpub/nging/v3/application/library/common"
	"github.com/admpub/nging/v3/application/library/config"
	"github.com/admpub/nging/v3/application/library/license"
	"github.com/admpub/nging/v3/application/library/modal"
	"github.com/admpub/nging/v3/application/registry/dashboard"
	"github.com/admpub/nging/v3/application/registry/navigate"
	"github.com/admpub/nging/v3/application/registry/perm"
	"github.com/admpub/nging/v3/application/registry/upload/checker"
	"github.com/admpub/nging/v3/application/registry/upload/helper"
)

var (
	DefaultAvatarURL = `/public/assets/backend/images/user_128.png`
	EmptyURL         = &url.URL{}
)

func ErrorPageFunc(c echo.Context) error {
	c.SetFunc(`Context`, func() echo.Context {
		return c
	})
	c.SetFunc(`Ext`, c.DefaultExtension)
	c.SetFunc(`URLFor`, subdomains.Default.URL)
	c.SetFunc(`URLByName`, subdomains.Default.URLByName)
	c.SetFunc(`IsMessage`, common.IsMessage)
	c.SetFunc(`Stored`, c.Stored)
	c.SetFunc(`Languages`, func() []string {
		return config.DefaultConfig.Language.AllList
	})
	c.SetFunc(`IsError`, common.IsError)
	c.SetFunc(`IsOk`, common.IsOk)
	c.SetFunc(`Message`, common.Message)
	c.SetFunc(`Ok`, common.OkString)
	c.SetFunc(`Version`, func() *config.VersionInfo { return config.Version })
	c.SetFunc(`VersionNumber`, func() string { return config.Version.Number })
	c.SetFunc(`CommitID`, func() string { return config.Version.CommitID })
	c.SetFunc(`BuildTime`, func() string { return config.Version.BuildTime })
	c.SetFunc(`TrackerURL`, license.TrackerURL)
	c.SetFunc(`Fetch`, func(tmpl string, data interface{}) template.HTML {
		b, e := c.Fetch(tmpl, data)
		if e != nil {
			return template.HTML(e.Error())
		}
		return template.HTML(string(b))
	})
	c.SetFunc(`Prefix`, func() string {
		return c.Route().Prefix
	})
	c.SetFunc(`Path`, c.Path)
	c.SetFunc(`Queries`, c.Queries)
	c.SetFunc(`Domain`, c.Domain)
	c.SetFunc(`Port`, c.Port)
	c.SetFunc(`Scheme`, c.Scheme)
	c.SetFunc(`Site`, c.Site)
	configs := config.Setting()
	c.SetFunc(`Config`, func(args ...string) echo.H {
		if len(args) > 0 {
			return config.Setting(args...)
		}
		return configs
	})
	var siteURI *url.URL
	siteURL := configs.GetStore(`base`).String(`siteURL`)
	if len(siteURL) > 0 {
		siteURI, _ = url.Parse(siteURL)
	}
	c.Internal().Set(`siteURI`, siteURI)
	c.SetFunc(`SiteURI`, func() *url.URL {
		if siteURI == nil {
			return EmptyURL
		}
		return siteURI
	})
	c.SetFunc(`RequestURI`, c.RequestURI)
	c.SetFunc(`GetNextURL`, func(varNames ...string) string {
		return common.GetNextURL(c, varNames...)
	})
	c.SetFunc(`ReturnToCurrentURL`, func(varNames ...string) string {
		return common.ReturnToCurrentURL(c, varNames...)
	})
	c.SetFunc(`WithNextURL`, func(urlStr string, varNames ...string) string {
		return common.WithNextURL(c, urlStr, varNames...)
	})
	c.SetFunc(`WithURLParams`, common.WithURLParams)
	c.SetFunc(`FullURL`, common.FullURL)
	c.SetFunc(`CaptchaForm`, func(args ...interface{}) template.HTML {
		options := tplfunc.MakeMap(args)
		options.Set("captchaId", common.GetHistoryOrNewCaptchaId(c))
		return tplfunc.CaptchaFormWithURLPrefix(c.Echo().Prefix(), options)
	})
	return nil
}

func FuncMap() echo.MiddlewareFunc {
	return func(h echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			now := com.NewTime(time.Now())
			c.SetFunc(`Now`, func() *com.Time {
				return now
			})
			c.SetFunc(`UnixTime`, now.Local().Unix)
			c.SetFunc(`HasString`, hasString)
			c.SetFunc(`Date`, date)
			c.SetFunc(`Token`, checker.Token)
			c.SetFunc(`BackendUploadURL`, checker.BackendUploadURL)
			c.SetFunc(`FrontendUploadURL`, checker.FrontendUploadURL)
			c.SetFunc(`Modal`, func(data interface{}) template.HTML {
				return modal.Render(c, data)
			})
			ErrorPageFunc(c)
			c.SetFunc(`IndexStrSlice`, indexStrSlice)

			if !config.DefaultConfig.ConnectedDB(false) {
				return h.Handle(c)
			}
			c.SetFunc(`Avatar`, func(avatar string, defaults ...string) string {
				if len(avatar) > 0 {
					return tplfunc.AddSuffix(avatar, `_200_200`)
				}
				if len(defaults) > 0 && len(defaults[0]) > 0 {
					return defaults[0]
				}
				return DefaultAvatarURL
			})
			c.SetFunc(`FileTypeByName`, helper.FileTypeByName)
			c.SetFunc(`FileTypeIcon`, helper.FileTypeIcon)
			c.SetFunc(`Project`, func(ident string) *navigate.ProjectItem {
				return navigate.ProjectGet(ident)
			})

			c.SetFunc(`ProjectSearchIdent`, func(ident string) int {
				return navigate.ProjectSearchIdent(ident)
			})
			c.SetFunc(`Projects`, func() navigate.ProjectList {
				return navigate.ProjectListAll()
			})
			c.SetFunc(`SM2PublicKey`, codec.DefaultPublicKeyHex)
			return h.Handle(c)
		})
	}
}

func BackendFuncMap() echo.MiddlewareFunc {
	return func(h echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {

			//用户相关函数
			user := handler.User(c)
			if user != nil {
				c.Set(`user`, user)
				c.SetFunc(`Username`, func() string { return user.Username })
				c.Set(`roleList`, handler.UserRoles(c))
			}
			c.SetFunc(`ProjectIdent`, func() string {
				return GetProjectIdent(c)
			})
			c.SetFunc(`TopButtons`, func() dashboard.TopButtons {
				buttons := dashboard.TopButtonAll(c)
				buttons.Ready(c)
				return buttons
			})
			c.SetFunc(`GlobalHeads`, func() dashboard.GlobalHeads {
				heads := dashboard.GlobalHeadAll(c)
				heads.Ready(c)
				return heads
			})
			c.SetFunc(`GlobalFooters`, func() dashboard.GlobalFooters {
				footers := dashboard.GlobalFooterAll(c)
				footers.Ready(c)
				return footers
			})
			c.SetFunc(`Navigate`, func(side string) navigate.List {
				return GetBackendNavigate(c, side)
			})
			return h.Handle(c)
		})
	}
}

func UserPermission(c echo.Context) *perm.RolePermission {
	permission, ok := c.Internal().Get(`userPermission`).(*perm.RolePermission)
	if !ok || permission == nil {
		permission = perm.New().Init(handler.UserRoles(c))
		c.Internal().Set(`userPermission`, permission)
	}
	return permission
}

func GetProjectIdent(c echo.Context) string {
	projectIdent := c.Internal().String(`projectIdent`)
	if len(projectIdent) == 0 {
		projectIdent = navigate.ProjectIdent(c.Path())
		if len(projectIdent) == 0 {
			if proj := navigate.ProjectFirst(true); proj != nil {
				projectIdent = proj.Ident
			}
		}
		c.Internal().Set(`projectIdent`, projectIdent)
	}
	return projectIdent
}

func GetBackendNavigate(c echo.Context, side string) navigate.List {
	switch side {
	case `top`:
		navList, ok := c.Internal().Get(`navigate.top`).(navigate.List)
		if ok {
			return navList
		}
		user := handler.User(c)
		if user != nil && user.Id == 1 {
			if navigate.TopNavigate == nil {
				return navigate.EmptyList
			}
			return *navigate.TopNavigate
		}
		permission := UserPermission(c)
		navList = permission.FilterNavigate(navigate.TopNavigate)
		c.Internal().Set(`navigate.top`, navList)
		return navList
	case `left`:
		fallthrough
	default:
		navList, ok := c.Internal().Get(`navigate.left`).(navigate.List)
		if ok {
			return navList
		}
		user := handler.User(c)
		var leftNav *navigate.List
		ident := GetProjectIdent(c)
		if len(ident) > 0 {
			if proj := navigate.ProjectGet(ident); proj != nil {
				leftNav = proj.NavList
			}
		}
		if user != nil && user.Id == 1 {
			if leftNav == nil {
				return navigate.EmptyList
			}
			return *leftNav
		}
		permission := UserPermission(c)
		navList = permission.FilterNavigate(leftNav)
		c.Internal().Set(`navigate.left`, navList)
		return navList
	}
}

func indexStrSlice(slice []string, index int) string {
	if slice == nil {
		return ``
	}
	if index >= len(slice) {
		return ``
	}
	return slice[index]
}

func hasString(slice []string, str string) bool {
	if slice == nil {
		return false
	}
	for _, v := range slice {
		if v == str {
			return true
		}
	}
	return false
}

func date(timestamp interface{}) time.Time {
	v := param.AsInt64(timestamp)
	return time.Unix(v, 0)
}
<commit_msg>update<commit_after>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package middleware

import (
	"html/template"
	"net/url"
	"time"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/middleware/tplfunc"
	"github.com/webx-top/echo/param"
	"github.com/webx-top/echo/subdomains"

	"github.com/admpub/nging/v3/application/handler"
	"github.com/admpub/nging/v3/application/library/codec"
	"github.com/admpub/nging/v3/application/library/common"
	"github.com/admpub/nging/v3/application/library/config"
	"github.com/admpub/nging/v3/application/library/license"
	"github.com/admpub/nging/v3/application/library/modal"
	"github.com/admpub/nging/v3/application/registry/dashboard"
	"github.com/admpub/nging/v3/application/registry/navigate"
	"github.com/admpub/nging/v3/application/registry/perm"
	"github.com/admpub/nging/v3/application/registry/upload/checker"
	"github.com/admpub/nging/v3/application/registry/upload/helper"
)

var (
	DefaultAvatarURL = `/public/assets/backend/images/user_128.png`
	EmptyURL         = &url.URL{}
)

func ErrorPageFunc(c echo.Context) error {
	c.SetFunc(`Context`, func() echo.Context {
		return c
	})
	c.SetFunc(`Ext`, c.DefaultExtension)
	c.SetFunc(`URLFor`, subdomains.Default.URL)
	c.SetFunc(`URLByName`, subdomains.Default.URLByName)
	c.SetFunc(`IsMessage`, common.IsMessage)
	c.SetFunc(`Stored`, c.Stored)
	c.SetFunc(`Languages`, func() []string {
		return config.DefaultConfig.Language.AllList
	})
	c.SetFunc(`IsError`, common.IsError)
	c.SetFunc(`IsOk`, common.IsOk)
	c.SetFunc(`Message`, common.Message)
	c.SetFunc(`Ok`, common.OkString)
	c.SetFunc(`Version`, func() config.VersionInfo { return *config.Version })
	c.SetFunc(`VersionNumber`, func() string { return config.Version.Number })
	c.SetFunc(`CommitID`, func() string { return config.Version.CommitID })
	c.SetFunc(`BuildTime`, func() string { return config.Version.BuildTime })
	c.SetFunc(`TrackerURL`, license.TrackerURL)
	c.SetFunc(`Fetch`, func(tmpl string, data interface{}) template.HTML {
		b, e := c.Fetch(tmpl, data)
		if e != nil {
			return template.HTML(e.Error())
		}
		return template.HTML(string(b))
	})
	c.SetFunc(`Prefix`, func() string {
		return c.Route().Prefix
	})
	c.SetFunc(`Path`, c.Path)
	c.SetFunc(`Queries`, c.Queries)
	c.SetFunc(`Domain`, c.Domain)
	c.SetFunc(`Port`, c.Port)
	c.SetFunc(`Scheme`, c.Scheme)
	c.SetFunc(`Site`, c.Site)
	configs := config.Setting()
	c.SetFunc(`Config`, func(args ...string) echo.H {
		if len(args) > 0 {
			return config.Setting(args...)
		}
		return configs
	})
	var siteURI *url.URL
	siteURL := configs.GetStore(`base`).String(`siteURL`)
	if len(siteURL) > 0 {
		siteURI, _ = url.Parse(siteURL)
	}
	c.Internal().Set(`siteURI`, siteURI)
	c.SetFunc(`SiteURI`, func() *url.URL {
		if siteURI == nil {
			return EmptyURL
		}
		return siteURI
	})
	c.SetFunc(`RequestURI`, c.RequestURI)
	c.SetFunc(`GetNextURL`, func(varNames ...string) string {
		return common.GetNextURL(c, varNames...)
	})
	c.SetFunc(`ReturnToCurrentURL`, func(varNames ...string) string {
		return common.ReturnToCurrentURL(c, varNames...)
	})
	c.SetFunc(`WithNextURL`, func(urlStr string, varNames ...string) string {
		return common.WithNextURL(c, urlStr, varNames...)
	})
	c.SetFunc(`WithURLParams`, common.WithURLParams)
	c.SetFunc(`FullURL`, common.FullURL)
	c.SetFunc(`CaptchaForm`, func(args ...interface{}) template.HTML {
		options := tplfunc.MakeMap(args)
		options.Set("captchaId", common.GetHistoryOrNewCaptchaId(c))
		return tplfunc.CaptchaFormWithURLPrefix(c.Echo().Prefix(), options)
	})
	return nil
}

func FuncMap() echo.MiddlewareFunc {
	return func(h echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			now := com.NewTime(time.Now())
			c.SetFunc(`Now`, func() *com.Time {
				return now
			})
			c.SetFunc(`UnixTime`, now.Local().Unix)
			c.SetFunc(`HasString`, hasString)
			c.SetFunc(`Date`, date)
			c.SetFunc(`Token`, checker.Token)
			c.SetFunc(`BackendUploadURL`, checker.BackendUploadURL)
			c.SetFunc(`FrontendUploadURL`, checker.FrontendUploadURL)
			c.SetFunc(`Modal`, func(data interface{}) template.HTML {
				return modal.Render(c, data)
			})
			ErrorPageFunc(c)
			c.SetFunc(`IndexStrSlice`, indexStrSlice)

			if !config.DefaultConfig.ConnectedDB(false) {
				return h.Handle(c)
			}
			c.SetFunc(`Avatar`, func(avatar string, defaults ...string) string {
				if len(avatar) > 0 {
					return tplfunc.AddSuffix(avatar, `_200_200`)
				}
				if len(defaults) > 0 && len(defaults[0]) > 0 {
					return defaults[0]
				}
				return DefaultAvatarURL
			})
			c.SetFunc(`FileTypeByName`, helper.FileTypeByName)
			c.SetFunc(`FileTypeIcon`, helper.FileTypeIcon)
			c.SetFunc(`Project`, func(ident string) *navigate.ProjectItem {
				return navigate.ProjectGet(ident)
			})

			c.SetFunc(`ProjectSearchIdent`, func(ident string) int {
				return navigate.ProjectSearchIdent(ident)
			})
			c.SetFunc(`Projects`, func() navigate.ProjectList {
				return navigate.ProjectListAll()
			})
			c.SetFunc(`SM2PublicKey`, codec.DefaultPublicKeyHex)
			return h.Handle(c)
		})
	}
}

func BackendFuncMap() echo.MiddlewareFunc {
	return func(h echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {

			//用户相关函数
			user := handler.User(c)
			if user != nil {
				c.Set(`user`, user)
				c.SetFunc(`Username`, func() string { return user.Username })
				c.Set(`roleList`, handler.UserRoles(c))
			}
			c.SetFunc(`ProjectIdent`, func() string {
				return GetProjectIdent(c)
			})
			c.SetFunc(`TopButtons`, func() dashboard.TopButtons {
				buttons := dashboard.TopButtonAll(c)
				buttons.Ready(c)
				return buttons
			})
			c.SetFunc(`GlobalHeads`, func() dashboard.GlobalHeads {
				heads := dashboard.GlobalHeadAll(c)
				heads.Ready(c)
				return heads
			})
			c.SetFunc(`GlobalFooters`, func() dashboard.GlobalFooters {
				footers := dashboard.GlobalFooterAll(c)
				footers.Ready(c)
				return footers
			})
			c.SetFunc(`Navigate`, func(side string) navigate.List {
				return GetBackendNavigate(c, side)
			})
			return h.Handle(c)
		})
	}
}

func UserPermission(c echo.Context) *perm.RolePermission {
	permission, ok := c.Internal().Get(`userPermission`).(*perm.RolePermission)
	if !ok || permission == nil {
		permission = perm.New().Init(handler.UserRoles(c))
		c.Internal().Set(`userPermission`, permission)
	}
	return permission
}

func GetProjectIdent(c echo.Context) string {
	projectIdent := c.Internal().String(`projectIdent`)
	if len(projectIdent) == 0 {
		projectIdent = navigate.ProjectIdent(c.Path())
		if len(projectIdent) == 0 {
			if proj := navigate.ProjectFirst(true); proj != nil {
				projectIdent = proj.Ident
			}
		}
		c.Internal().Set(`projectIdent`, projectIdent)
	}
	return projectIdent
}

func GetBackendNavigate(c echo.Context, side string) navigate.List {
	switch side {
	case `top`:
		navList, ok := c.Internal().Get(`navigate.top`).(navigate.List)
		if ok {
			return navList
		}
		user := handler.User(c)
		if user != nil && user.Id == 1 {
			if navigate.TopNavigate == nil {
				return navigate.EmptyList
			}
			return *navigate.TopNavigate
		}
		permission := UserPermission(c)
		navList = permission.FilterNavigate(navigate.TopNavigate)
		c.Internal().Set(`navigate.top`, navList)
		return navList
	case `left`:
		fallthrough
	default:
		navList, ok := c.Internal().Get(`navigate.left`).(navigate.List)
		if ok {
			return navList
		}
		user := handler.User(c)
		var leftNav *navigate.List
		ident := GetProjectIdent(c)
		if len(ident) > 0 {
			if proj := navigate.ProjectGet(ident); proj != nil {
				leftNav = proj.NavList
			}
		}
		if user != nil && user.Id == 1 {
			if leftNav == nil {
				return navigate.EmptyList
			}
			return *leftNav
		}
		permission := UserPermission(c)
		navList = permission.FilterNavigate(leftNav)
		c.Internal().Set(`navigate.left`, navList)
		return navList
	}
}

func indexStrSlice(slice []string, index int) string {
	if slice == nil {
		return ``
	}
	if index >= len(slice) {
		return ``
	}
	return slice[index]
}

func hasString(slice []string, str string) bool {
	if slice == nil {
		return false
	}
	for _, v := range slice {
		if v == str {
			return true
		}
	}
	return false
}

func date(timestamp interface{}) time.Time {
	v := param.AsInt64(timestamp)
	return time.Unix(v, 0)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"github.com/flynn/go-docopt"
	"github.com/flynn/go-tuf"
)

var dir string

func main() {
	log.SetFlags(0)

	usage := `usage: tuf [-h|--help] [-d|--dir=<dir>] <command> [<args>...]

Options:
  -h, --help
  -d, --dir=<dir>

Commands:
  help         Show usage for a specific command
  gen-key      Generate a new signing key for a specific manifest
  revoke-key   Revoke a signing key
  add          Add a target file
  remove       Remove a target file
  snapshot     Update the snapshot manifest
  timestamp    Update the timestamp manifest
  sign         Sign a manifest
  commit       Commit staged files to the repository
  regenerate   Recreate the targets manifest
  clean        Remove all staged manifests
  root-keys    Output a JSON serialized array of root keys to STDOUT

See "tuf help <command>" for more information on a specific command
`

	args, _ := docopt.Parse(usage, nil, true, "", true)
	cmd := args.String["<command>"]
	cmdArgs := args.All["<args>"].([]string)

	if cmd == "help" {
		if len(cmdArgs) == 0 { // `flynn help`
			fmt.Println(usage)
			return
		} else { // `flynn help <command>`
			cmd = cmdArgs[0]
			cmdArgs = []string{"--help"}
		}
	}

	dir = args.String["--tuf-dir"]
	if dir == "" {
		var err error
		dir, err = os.Getwd()
		if err != nil {
			log.Fatal(err)
		}
	}

	if err := runCommand(cmd, cmdArgs); err != nil {
		log.Fatal(err)
	}
}

type cmdFunc func(*docopt.Args, *tuf.Repo) error

type command struct {
	usage string
	f     cmdFunc
}

var commands = make(map[string]*command)

func register(name string, f cmdFunc, usage string) {
	commands[name] = &command{usage: usage, f: f}
}

func runCommand(name string, args []string) error {
	argv := make([]string, 1, 1+len(args))
	argv[0] = name
	argv = append(argv, args...)

	cmd, ok := commands[name]
	if !ok {
		return fmt.Errorf("%s is not a tuf command. See 'tuf help'", name)
	}

	parsedArgs, err := docopt.Parse(cmd.usage, argv, true, "", true)
	if err != nil {
		return err
	}
	repo, err := tuf.NewRepo(tuf.FileSystemStore(dir))
	if err != nil {
		return err
	}
	return cmd.f(parsedArgs, repo)
}

func parseExpires(arg string) (time.Time, error) {
	days, err := strconv.Atoi(arg)
	if err != nil {
		return time.Time{}, fmt.Errorf("failed to parse --expires arg: %s", err)
	}
	return time.Now().AddDate(0, 0, days).UTC(), nil
}
<commit_msg>tuf: Fix using an explicit repository path<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"github.com/flynn/go-docopt"
	"github.com/flynn/go-tuf"
)

func main() {
	log.SetFlags(0)

	usage := `usage: tuf [-h|--help] [-d|--dir=<dir>] <command> [<args>...]

Options:
  -h, --help
  -d <dir>     The path to the repository (defaults to the current working directory)

Commands:
  help         Show usage for a specific command
  gen-key      Generate a new signing key for a specific manifest
  revoke-key   Revoke a signing key
  add          Add a target file
  remove       Remove a target file
  snapshot     Update the snapshot manifest
  timestamp    Update the timestamp manifest
  sign         Sign a manifest
  commit       Commit staged files to the repository
  regenerate   Recreate the targets manifest
  clean        Remove all staged manifests
  root-keys    Output a JSON serialized array of root keys to STDOUT

See "tuf help <command>" for more information on a specific command
`

	args, _ := docopt.Parse(usage, nil, true, "", true)
	cmd := args.String["<command>"]
	cmdArgs := args.All["<args>"].([]string)

	if cmd == "help" {
		if len(cmdArgs) == 0 { // `flynn help`
			fmt.Println(usage)
			return
		} else { // `flynn help <command>`
			cmd = cmdArgs[0]
			cmdArgs = []string{"--help"}
		}
	}

	dir, ok := args.String["-d"]
	if !ok {
		dir = args.String["--dir"]
	}
	if dir == "" {
		var err error
		dir, err = os.Getwd()
		if err != nil {
			log.Fatal(err)
		}
	}

	if err := runCommand(cmd, cmdArgs, dir); err != nil {
		log.Fatal(err)
	}
}

type cmdFunc func(*docopt.Args, *tuf.Repo) error

type command struct {
	usage string
	f     cmdFunc
}

var commands = make(map[string]*command)

func register(name string, f cmdFunc, usage string) {
	commands[name] = &command{usage: usage, f: f}
}

func runCommand(name string, args []string, dir string) error {
	argv := make([]string, 1, 1+len(args))
	argv[0] = name
	argv = append(argv, args...)

	cmd, ok := commands[name]
	if !ok {
		return fmt.Errorf("%s is not a tuf command. See 'tuf help'", name)
	}

	parsedArgs, err := docopt.Parse(cmd.usage, argv, true, "", true)
	if err != nil {
		return err
	}
	repo, err := tuf.NewRepo(tuf.FileSystemStore(dir))
	if err != nil {
		return err
	}
	return cmd.f(parsedArgs, repo)
}

func parseExpires(arg string) (time.Time, error) {
	days, err := strconv.Atoi(arg)
	if err != nil {
		return time.Time{}, fmt.Errorf("failed to parse --expires arg: %s", err)
	}
	return time.Now().AddDate(0, 0, days).UTC(), nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
//	"github.com/vaughan0/go-zmq"
	"log"
	"log/syslog"
)

func main() {

	// Syslog
		syslogLog, err :=syslog.New(syslog.LOG_INFO, "turtledq/server")
		if nil != err {
			log.Fatal("Big problem, could not open up syslog!")
/////////// RETURN
			return
		}

	//DEBUG
	syslogLog.Notice("=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=")
	syslogLog.Notice("TurtleDQ Server BEGIN.")

	// Config
		turtleConfig, err := NewTurtleConfig(syslogLog)
		if nil != err {
			errMsg := fmt.Sprintf("Error with config file: err = [%v]", err)

			syslogLog.Err(errMsg)
			log.Fatal(errMsg)
/////////////////////// RETURN
			return
		}

	// Go!
		go dequeue(syslogLog, turtleConfig.DequeueMongoHref, turtleConfig.DequeueMongoDatabaseName, turtleConfig.DequeueMongoCollectionName, turtleConfig..DequeueAmqpHref)
		go enqueue(syslogLog)

//	// Create ZeroMQ context.
//		ctx, err := zmq.NewContext()
//		if err != nil {
//			panic(err)
//		}
//		defer ctx.Close()
//
//
//	// Create ZeroMQ socket (from ZeroMQ context).
//		sock, err := ctx.Socket(zmq.Router)
//		if err != nil {
//			panic(err)
//		}
//		defer sock.Close()
//
//		if err = sock.Bind("tcp://*:5555"); err != nil {
//			panic(err)
//		}
//
//
//	// Handle input from ZeroMQ (via channels).
//		chans := sock.Channels()
//		defer chans.Close()
//
//
//
//		for {
//			select {
//
//				case msg := <-chans.In():
//					go func() {
//						resp := handleRequest(syslogLog, msg)
//						chans.Out() <- resp
//					}()
//
//				case err := <-chans.Errors():
//					panic(err)
//
//			} // select
//		}

		select{}

	//DEBUG
	syslogLog.Notice("TurtleDQ Server END.")
}

func handleRequest(syslogLog *syslog.Writer, msg [][]byte) [][]byte {

//@TODO
	syslogLog.Notice( fmt.Sprintf("[handleRequest] msg = [%v]", msg) )

	// Return
//@TODO
		return [][]byte{
			[]byte("apple banana cherry"),
		}
}
<commit_msg>correction<commit_after>package main

import (
	"fmt"
//	"github.com/vaughan0/go-zmq"
	"log"
	"log/syslog"
)

func main() {

	// Syslog
		syslogLog, err :=syslog.New(syslog.LOG_INFO, "turtledq/server")
		if nil != err {
			log.Fatal("Big problem, could not open up syslog!")
/////////// RETURN
			return
		}

	//DEBUG
	syslogLog.Notice("=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=-<>-=")
	syslogLog.Notice("TurtleDQ Server BEGIN.")

	// Config
		turtleConfig, err := NewTurtleConfig(syslogLog)
		if nil != err {
			errMsg := fmt.Sprintf("Error with config file: err = [%v]", err)

			syslogLog.Err(errMsg)
			log.Fatal(errMsg)
/////////////////////// RETURN
			return
		}

	// Go!
		go dequeue(syslogLog, turtleConfig.DequeueMongoHref, turtleConfig.DequeueMongoDatabaseName, turtleConfig.DequeueMongoCollectionName, turtleConfig.DequeueAmqpHref)
		go enqueue(syslogLog)

//	// Create ZeroMQ context.
//		ctx, err := zmq.NewContext()
//		if err != nil {
//			panic(err)
//		}
//		defer ctx.Close()
//
//
//	// Create ZeroMQ socket (from ZeroMQ context).
//		sock, err := ctx.Socket(zmq.Router)
//		if err != nil {
//			panic(err)
//		}
//		defer sock.Close()
//
//		if err = sock.Bind("tcp://*:5555"); err != nil {
//			panic(err)
//		}
//
//
//	// Handle input from ZeroMQ (via channels).
//		chans := sock.Channels()
//		defer chans.Close()
//
//
//
//		for {
//			select {
//
//				case msg := <-chans.In():
//					go func() {
//						resp := handleRequest(syslogLog, msg)
//						chans.Out() <- resp
//					}()
//
//				case err := <-chans.Errors():
//					panic(err)
//
//			} // select
//		}

		select{}

	//DEBUG
	syslogLog.Notice("TurtleDQ Server END.")
}

func handleRequest(syslogLog *syslog.Writer, msg [][]byte) [][]byte {

//@TODO
	syslogLog.Notice( fmt.Sprintf("[handleRequest] msg = [%v]", msg) )

	// Return
//@TODO
		return [][]byte{
			[]byte("apple banana cherry"),
		}
}
<|endoftext|>


<commit_before>package ui

import (
	"bytes"
	"os"

	"github.com/chzyer/readline"
	"github.com/juju/ansiterm"
)

// BoldString returns a bolded copy of the string (ANSI escape sequenced)
func BoldString(s string) string { return defUI.BoldString(s) }

// BoldString checks the EnableColors pref and returns a bolded copy
// of the string (ANSI escape sequenced)
func (u *UI) BoldString(s string) string {
	if !u.EnableColors {
		return s
	}

	ctx := ansiterm.Context{
		Styles: []ansiterm.Style{ansiterm.Bold},
	}

	return createStyledString(ctx, s)
}

// FaintString returns a faint (ANSI DarkGray) copy of the string (ANSI escape sequenced)
func FaintString(s string) string { return defUI.FaintString(s) }

// FaintString checks the EnableColors pref and returns a faint (ANSI DarkGrey)
// copy of the string (ANSI escape sequenced)
func (u *UI) FaintString(s string) string {
	if !u.EnableColors {
		return s
	}

	return ColorString(DarkGray, s)
}

// ColorString returns the original string, converted to the provided ANSI color
func ColorString(c Color, s string) string { return defUI.ColorString(c, s) }

// ColorString checks the EnableColors pref and returns a colored
// copy of the string (ANSI escape sequenced)
func (u *UI) ColorString(c Color, s string) string {
	if !u.EnableColors {
		return s
	}

	ctx := ansiterm.Context{
		Foreground: ansiterm.Color(c),
	}

	return createStyledString(ctx, s)
}

// BoldColorString returns a bolded, colored copy of the string (ANSI escape sequenced)
func BoldColorString(c Color, s string) string { return defUI.BoldColorString(c, s) }

// BoldColorString checks the EnableColors pref and returns a bolded, colored
// copy of the string (ANSI escape sequenced)
func (u *UI) BoldColorString(c Color, s string) string {
	if !u.EnableColors {
		return s
	}

	ctx := ansiterm.Context{
		Foreground: ansiterm.Color(c),
		Styles:     []ansiterm.Style{ansiterm.Bold},
	}

	return createStyledString(ctx, s)
}

func createStyledString(ctx ansiterm.Context, s string) string {
	if !readline.IsTerminal(int(os.Stdout.Fd())) {
		return s
	}

	buf := bytes.Buffer{}
	w := ansiterm.NewWriter(&buf)
	w.SetColorCapable(true)
	ctx.Fprint(w, s)
	return buf.String()
}
<commit_msg>Removed unused BoldColorString function<commit_after>package ui

import (
	"bytes"
	"os"

	"github.com/chzyer/readline"
	"github.com/juju/ansiterm"
)

// BoldString returns a bolded copy of the string (ANSI escape sequenced)
func BoldString(s string) string { return defUI.BoldString(s) }

// BoldString checks the EnableColors pref and returns a bolded copy
// of the string (ANSI escape sequenced)
func (u *UI) BoldString(s string) string {
	if !u.EnableColors {
		return s
	}

	ctx := ansiterm.Context{
		Styles: []ansiterm.Style{ansiterm.Bold},
	}

	return createStyledString(ctx, s)
}

// FaintString returns a faint (ANSI DarkGray) copy of the string (ANSI escape sequenced)
func FaintString(s string) string { return defUI.FaintString(s) }

// FaintString checks the EnableColors pref and returns a faint (ANSI DarkGrey)
// copy of the string (ANSI escape sequenced)
func (u *UI) FaintString(s string) string {
	if !u.EnableColors {
		return s
	}

	return ColorString(DarkGray, s)
}

// ColorString returns the original string, converted to the provided ANSI color
func ColorString(c Color, s string) string { return defUI.ColorString(c, s) }

// ColorString checks the EnableColors pref and returns a colored
// copy of the string (ANSI escape sequenced)
func (u *UI) ColorString(c Color, s string) string {
	if !u.EnableColors {
		return s
	}

	ctx := ansiterm.Context{
		Foreground: ansiterm.Color(c),
	}

	return createStyledString(ctx, s)
}

func createStyledString(ctx ansiterm.Context, s string) string {
	if !readline.IsTerminal(int(os.Stdout.Fd())) {
		return s
	}

	buf := bytes.Buffer{}
	w := ansiterm.NewWriter(&buf)
	w.SetColorCapable(true)
	ctx.Fprint(w, s)
	return buf.String()
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"time"

	"code.google.com/p/gopacket"
	"code.google.com/p/gopacket/layers"
	"code.google.com/p/gopacket/pcap"
	"code.google.com/p/gopacket/tcpassembly"
	"code.google.com/p/gopacket/tcpassembly/tcpreader"
)

var iface = flag.String("i", "eth0", "Interface to get packets from")
var snaplen = flag.Int("l", 1600, "SnapLen for pcap packet capture")
var srcFilter = flag.String("s", "tcp and src port 80", "BPF filter for pcap")
var dstFilter = flag.String("d", "tcp and dst port 80", "BPF filter for pcap")

type inFlightRequest struct {
	req       *http.Request
	startTime time.Time
}

func (i *inFlightRequest) String() string {
	return fmt.Sprintf("[%s] http://%s%s", i.req.Method, i.req.Host, i.req.RequestURI)
}

type PortHash map[uint64]*inFlightRequest
type IpHash map[uint64]PortHash

type requestPool struct {
	inFlight IpHash
	mtx      sync.Mutex
}

func (p *requestPool) RemoveInFlight() {
	p.mtx.Lock()
	defer p.mtx.Unlock()

	removeQueue := []uint64{}

	cutOff := time.Now().Add(-1 * time.Minute)
	for _, flowMap := range p.inFlight {
		for hash, req := range flowMap {
			if req.startTime.Before(cutOff) {
				removeQueue = append(removeQueue, hash)
			}
		}

		for _, hash := range removeQueue {
			delete(flowMap, hash)
		}
	}
}

func (p *requestPool) PutReq(net gopacket.Flow, transport gopacket.Flow, req *http.Request, when time.Time) {
	p.mtx.Lock()
	defer p.mtx.Unlock()
	r := &inFlightRequest{
		req:       req,
		startTime: when,
	}

	netHash := net.FastHash()
	portHash := transport.FastHash()

	_, ok := p.inFlight[netHash]
	if !ok {
		p.inFlight[netHash] = make(PortHash)
	}
	p.inFlight[netHash][portHash] = r
}

func (p *requestPool) PutResp(net gopacket.Flow, transport gopacket.Flow, resp *http.Response, when time.Time) {
	p.mtx.Lock()
	defer p.mtx.Unlock()

	netHash := net.FastHash()
	portHash := transport.FastHash()

	flowMap, ok := p.inFlight[netHash]
	if !ok {
		return
	}

	req, ok := flowMap[portHash]
	if !ok {
		return
	}

	deltaTime := when.Sub(req.startTime).String()
	fmt.Println(req, "->", resp.Status, ":", deltaTime)
	delete(flowMap, portHash)
}

type srcStreamFactory struct {
	pool *requestPool
}

func (s *srcStreamFactory) New(net gopacket.Flow, transport gopacket.Flow) tcpassembly.Stream {
	hstream := &tcpStream{
		net:       net,
		transport: transport,
		r:         tcpreader.NewReaderStream(),
	}
	go s.run(hstream)
	return &hstream.r
}

type dstStreamFactory struct {
	pool *requestPool
}

func (d *dstStreamFactory) New(net gopacket.Flow, transport gopacket.Flow) tcpassembly.Stream {
	hstream := &tcpStream{
		net:       net,
		transport: transport,
		r:         tcpreader.NewReaderStream(),
	}
	go d.run(hstream)
	return &hstream.r
}

type tcpStream struct {
	net       gopacket.Flow
	transport gopacket.Flow
	r         tcpreader.ReaderStream
}

func (d *dstStreamFactory) run(h *tcpStream) {
	startTime := time.Now()
	buf := bufio.NewReader(&h.r)
	req, err := http.ReadRequest(buf)
	if err != nil {
		return
	}
	defer req.Body.Close()
	d.pool.PutReq(h.net, h.transport, req, startTime)
	tcpreader.DiscardBytesToFirstError(buf)
}

func (s *srcStreamFactory) run(h *tcpStream) {
	buf := bufio.NewReader(&h.r)
	req := &http.Request{}
	resp, err := http.ReadResponse(buf, req)
	if err != nil {
		return
	}
	tcpreader.DiscardBytesToFirstError(buf)
	s.pool.PutResp(h.net, h.transport, resp, time.Now())
}

func runFilter(factory tcpassembly.StreamFactory, iface string, snaplen int32, filter string, ch chan os.Signal) {
	handle, err := pcap.OpenLive(iface, snaplen, true, pcap.BlockForever)
	if err != nil {
		panic(err)
	}

	if err := handle.SetBPFFilter(filter); err != nil {
		panic(err)
	}

	streamPool := tcpassembly.NewStreamPool(factory)
	assembler := tcpassembly.NewAssembler(streamPool)

	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
	packets := packetSource.Packets()

	ticker := time.Tick(time.Minute)
	for {
		select {
		case packet := <-packets:
			if packet.NetworkLayer() == nil || packet.TransportLayer() == nil || packet.TransportLayer().LayerType() != layers.LayerTypeTCP {
				continue
			}
			tcp := packet.TransportLayer().(*layers.TCP)
			assembler.AssembleWithTimestamp(packet.NetworkLayer().NetworkFlow(), tcp, packet.Metadata().Timestamp)

		case <-ticker:
			assembler.FlushOlderThan(time.Now().Add(-2 * time.Minute))
		case <-ch:
			return
		}
	}
}

func main() {
	flag.Parse()

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt, os.Kill)

	reqPool := &requestPool{
		inFlight: make(IpHash),
	}

	srcFactory := &srcStreamFactory{pool: reqPool}
	srcCh := make(chan os.Signal)
	go runFilter(srcFactory, *iface, int32(*snaplen), *srcFilter, srcCh)

	dstFactory := &dstStreamFactory{pool: reqPool}
	dstCh := make(chan os.Signal)
	go runFilter(dstFactory, *iface, int32(*snaplen), *dstFilter, dstCh)

	ticker := time.Tick(time.Second)
	for {
		select {
		case <-ticker:
			reqPool.RemoveInFlight()
		case sig := <-ch:
			srcCh <- sig
			dstCh <- sig
			return
		}
	}
}
<commit_msg>gopacket moved<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"time"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"github.com/google/gopacket/tcpassembly"
	"github.com/google/gopacket/tcpassembly/tcpreader"
)

var iface = flag.String("i", "eth0", "Interface to get packets from")
var snaplen = flag.Int("l", 1600, "SnapLen for pcap packet capture")
var srcFilter = flag.String("s", "tcp and src port 80", "BPF filter for pcap")
var dstFilter = flag.String("d", "tcp and dst port 80", "BPF filter for pcap")

type inFlightRequest struct {
	req       *http.Request
	startTime time.Time
}

func (i *inFlightRequest) String() string {
	return fmt.Sprintf("[%s] http://%s%s", i.req.Method, i.req.Host, i.req.RequestURI)
}

type PortHash map[uint64]*inFlightRequest
type IpHash map[uint64]PortHash

type requestPool struct {
	inFlight IpHash
	mtx      sync.Mutex
}

func (p *requestPool) RemoveInFlight() {
	p.mtx.Lock()
	defer p.mtx.Unlock()

	removeQueue := []uint64{}

	cutOff := time.Now().Add(-1 * time.Minute)
	for _, flowMap := range p.inFlight {
		for hash, req := range flowMap {
			if req.startTime.Before(cutOff) {
				removeQueue = append(removeQueue, hash)
			}
		}

		for _, hash := range removeQueue {
			delete(flowMap, hash)
		}
	}
}

func (p *requestPool) PutReq(net gopacket.Flow, transport gopacket.Flow, req *http.Request, when time.Time) {
	p.mtx.Lock()
	defer p.mtx.Unlock()
	r := &inFlightRequest{
		req:       req,
		startTime: when,
	}

	netHash := net.FastHash()
	portHash := transport.FastHash()

	_, ok := p.inFlight[netHash]
	if !ok {
		p.inFlight[netHash] = make(PortHash)
	}
	p.inFlight[netHash][portHash] = r
}

func (p *requestPool) PutResp(net gopacket.Flow, transport gopacket.Flow, resp *http.Response, when time.Time) {
	p.mtx.Lock()
	defer p.mtx.Unlock()

	netHash := net.FastHash()
	portHash := transport.FastHash()

	flowMap, ok := p.inFlight[netHash]
	if !ok {
		return
	}

	req, ok := flowMap[portHash]
	if !ok {
		return
	}

	deltaTime := when.Sub(req.startTime).String()
	fmt.Println(req, "->", resp.Status, ":", deltaTime)
	delete(flowMap, portHash)
}

type srcStreamFactory struct {
	pool *requestPool
}

func (s *srcStreamFactory) New(net gopacket.Flow, transport gopacket.Flow) tcpassembly.Stream {
	hstream := &tcpStream{
		net:       net,
		transport: transport,
		r:         tcpreader.NewReaderStream(),
	}
	go s.run(hstream)
	return &hstream.r
}

type dstStreamFactory struct {
	pool *requestPool
}

func (d *dstStreamFactory) New(net gopacket.Flow, transport gopacket.Flow) tcpassembly.Stream {
	hstream := &tcpStream{
		net:       net,
		transport: transport,
		r:         tcpreader.NewReaderStream(),
	}
	go d.run(hstream)
	return &hstream.r
}

type tcpStream struct {
	net       gopacket.Flow
	transport gopacket.Flow
	r         tcpreader.ReaderStream
}

func (d *dstStreamFactory) run(h *tcpStream) {
	startTime := time.Now()
	buf := bufio.NewReader(&h.r)
	req, err := http.ReadRequest(buf)
	if err != nil {
		return
	}
	defer req.Body.Close()
	d.pool.PutReq(h.net, h.transport, req, startTime)
	tcpreader.DiscardBytesToFirstError(buf)
}

func (s *srcStreamFactory) run(h *tcpStream) {
	buf := bufio.NewReader(&h.r)
	req := &http.Request{}
	resp, err := http.ReadResponse(buf, req)
	if err != nil {
		return
	}
	tcpreader.DiscardBytesToFirstError(buf)
	s.pool.PutResp(h.net, h.transport, resp, time.Now())
}

func runFilter(factory tcpassembly.StreamFactory, iface string, snaplen int32, filter string, ch chan os.Signal) {
	handle, err := pcap.OpenLive(iface, snaplen, true, pcap.BlockForever)
	if err != nil {
		panic(err)
	}

	if err := handle.SetBPFFilter(filter); err != nil {
		panic(err)
	}

	streamPool := tcpassembly.NewStreamPool(factory)
	assembler := tcpassembly.NewAssembler(streamPool)

	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
	packets := packetSource.Packets()

	ticker := time.Tick(time.Minute)
	for {
		select {
		case packet := <-packets:
			if packet.NetworkLayer() == nil || packet.TransportLayer() == nil || packet.TransportLayer().LayerType() != layers.LayerTypeTCP {
				continue
			}
			tcp := packet.TransportLayer().(*layers.TCP)
			assembler.AssembleWithTimestamp(packet.NetworkLayer().NetworkFlow(), tcp, packet.Metadata().Timestamp)

		case <-ticker:
			assembler.FlushOlderThan(time.Now().Add(-2 * time.Minute))
		case <-ch:
			return
		}
	}
}

func main() {
	flag.Parse()

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt, os.Kill)

	reqPool := &requestPool{
		inFlight: make(IpHash),
	}

	srcFactory := &srcStreamFactory{pool: reqPool}
	srcCh := make(chan os.Signal)
	go runFilter(srcFactory, *iface, int32(*snaplen), *srcFilter, srcCh)

	dstFactory := &dstStreamFactory{pool: reqPool}
	dstCh := make(chan os.Signal)
	go runFilter(dstFactory, *iface, int32(*snaplen), *dstFilter, dstCh)

	ticker := time.Tick(time.Second)
	for {
		select {
		case <-ticker:
			reqPool.RemoveInFlight()
		case sig := <-ch:
			srcCh <- sig
			dstCh <- sig
			return
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
)

func logRequest(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Println(r.Method, r.URL.Path)
		h.ServeHTTP(w, r)
	})
}

func main() {
	address := ":8080"
	log.Println("Listening on", address)
	panic(http.ListenAndServe(address, logRequest(http.FileServer(http.Dir(".")))))
}
<commit_msg>listen on port 80<commit_after>package main

import (
	"log"
	"net/http"
)

func logRequest(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Println(r.Method, r.URL.Path)
		h.ServeHTTP(w, r)
	})
}

func main() {
	address := ":80"
	log.Println("Listening on", address)
	panic(http.ListenAndServe(address, logRequest(http.FileServer(http.Dir(".")))))
}
<|endoftext|>