<commit_before>package rest

import (
	"fmt"
	"log"
	"reflect"
)

// TODO:
//  - Consider using/writing a more robust data validation library.
//    E.g. https://github.com/Assembli/beautiful-validity
//    This would allow for semantic validation and custom validation logic.
//    For now, we are only providing type validation.
//
//  - Make type coercion pluggable (i.e. conversion/validation of custom types).

// Rules is a collection of Rules and a reflect.Type which they correspond to.
type Rules interface {
	// Contents returns the contained Rules.
	Contents() []*Rule

	// ResourceType returns the reflect.Type these Rules correspond to.
	ResourceType() reflect.Type

	// Validate verifies that the Rules are valid, meaning they specify fields that exist
	// and correct types. If a Rule is invalid, an error is returned. If the Rules are
	// valid, nil is returned. This will recursively validate nested Rules.
	Validate() error

	// Filter filters the slice of Rules based on the specified bool. True means to
	// filter out outbound Rules such that the returned slice contains only inbound Rules.
	// False means to filter out inbound Rules such that the returned slice contains only
	// outbound Rules.
	Filter(bool) Rules

	// Size returns the number of contained Rules.
	Size() int
}

type rules struct {
	contents     []*Rule
	resourceType reflect.Type
}

// Contents returns the contained Rules.
func (r rules) Contents() []*Rule {
	return r.contents
}

// ResourceType returns the reflect.Type these Rules correspond to.
func (r rules) ResourceType() reflect.Type {
	return r.resourceType
}

// Validate verifies that the Rules are valid, meaning they specify fields that exist
// and correct types. If a Rule is invalid, an error is returned. If the Rules are
// valid, nil is returned. This will recursively validate nested Rules.
func (r rules) Validate() error {
	for _, rule := range r.contents {
		resourceType := r.resourceType
		if resourceType.Kind() != reflect.Struct && resourceType.Kind() != reflect.Map {
			return fmt.Errorf(
				"Invalid resource type: must be struct or map, got %s",
				resourceType)
		}

		field, found := resourceType.FieldByName(rule.Field)
		if !found {
			return fmt.Errorf(
				"Invalid Rule for %s: field '%s' does not exist",
				resourceType, rule.Field)
		}

		if !rule.validType(field.Type) {
			return fmt.Errorf(
				"Invalid Rule for %s: field '%s' is type %s, not %s",
				resourceType, rule.Field, field.Type, typeToName[rule.Type])
		}

		// Validate nested Rules.
		if rule.Rules != nil {
			if err := rule.Rules.Validate(); err != nil {
				return err
			}
		}
	}

	return nil
}

// Filter filters the slice of Rules based on the specified bool. True means to
// filter out outbound Rules such that the returned slice contains only inbound Rules.
// False means to filter out inbound Rules such that the returned slice contains only
// outbound Rules.
func (r rules) Filter(inbound bool) Rules {
	filtered := make([]*Rule, 0, len(r.contents))
	for _, rule := range r.contents {
		if inbound && rule.OutputOnly {
			// Filter out outbound Rules.
			continue
		} else if !inbound && rule.InputOnly {
			// Filter out inbound Rules.
			continue
		}
		filtered = append(filtered, rule)
	}

	return rules{contents: filtered, resourceType: r.resourceType}
}

// Size returns the number of contained Rules.
func (r rules) Size() int {
	return len(r.contents)
}

// NewRules returns a set of Rules for use by a ResourceHandler. The first argument
// must be a resource pointer (and can be nil) used to associate the Rules with a
// resource type. If it isn't a pointer, this will panic.
func NewRules(ptr interface{}, r ...*Rule) Rules {
	resourceType := reflect.TypeOf(ptr)
	if resourceType.Kind() != reflect.Ptr {
		panic(fmt.Sprintf("Must provide resource pointer to NewRules, got %s",
			resourceType.Kind()))
	}

	return rules{
		resourceType: resourceType.Elem(),
		contents:     r,
	}
}

// Rule provides schema validation and type coercion for request input and fine-grained
// control over response output. If a ResourceHandler provides input Rules which
// specify types, input fields will attempt to be coerced to those types. If coercion
// fails, an error will be returned in the response. If a ResourceHandler provides
// output Rules, only the fields corresponding to those Rules will be sent back. This
// prevents new fields from leaking into old API versions.
type Rule struct {
	// Name of the resource field. This is the name as it appears in the struct
	// definition.
	Field string

	// Name of the input/output field. Use Name() to retrieve the field alias while
	// falling back to the field name if it's not specified.
	FieldAlias string

	// Type to coerce field value to. If the value cannot be coerced, an error will be
	// returned in the response. Defaults to Unspecified, which is the equivalent of
	// an interface{} value.
	Type Type

	// Indicates if the field must have a value. Defaults to false.
	Required bool

	// Versions is a list of the API versions this Rule applies to. If empty, it will
	// be applied to all versions.
	Versions []string

	// Indicates if the Rule should only be applied to requests.
	InputOnly bool

	// Indicates if the Rule should only be applied to responses.
	OutputOnly bool

	// Function which produces the field value to receive.
	InputHandler func(interface{}) interface{}

	// Function which produces the field value to send.
	OutputHandler func(interface{}) interface{}

	// Nested Rules to apply to field value.
	Rules Rules
}

// Name returns the name of the input/output field alias. It defaults to the field
// name if the alias was not specified.
func (r Rule) Name() string {
	alias := r.FieldAlias
	if alias == "" {
		alias = r.Field
	}
	return alias
}

// Applies returns whether or not the Rule applies to the given version.
func (r Rule) Applies(version string) bool {
	if r.Versions == nil {
		return true
	}

	for _, v := range r.Versions {
		if v == version {
			return true
		}
	}

	return false
}

// validType returns whether or not the Rule is valid for the given reflect.Type.
func (r Rule) validType(fieldType reflect.Type) bool {
	if r.Type == Unspecified {
		return true
	}

	kind := typeToKind[r.Type]
	return fieldType.Kind() == kind
}

// applyInboundRules applies Rules which are not specified as output only to the
// provided Payload. If the Payload is nil, an empty Payload will be returned. If no
// Rules are provided, this acts as an identity function. If Rules are provided, any
// incoming fields which are not specified will be discarded. If Rules specify types,
// incoming values will attempted to be coerced. If coercion fails, an error will be
// returned. If Rules specify nested Rules, they will be recursively applied to the
// field value, taking precedence over a type coercion.
func applyInboundRules(payload Payload, rules Rules) (Payload, error) {
	if payload == nil {
		return Payload{}, nil
	}

	// Apply only inbound Rules.
	rules = rules.Filter(true)

	if rules.Size() == 0 {
		return payload, nil
	}

	newPayload := Payload{}

fieldLoop:
	for field, value := range payload {
		for _, rule := range rules.Contents() {
			if rule.Name() == field {
				if nestedInboundRulesApply(value, rule.Rules) {
					// Nested Rules take precedence over type coercion.
					v, err := applyNestedInboundRules(value, rule.Rules)
					if err != nil {
						return nil, err
					}
					value = v
				} else if rule.Type != Unspecified {
					// Coerce to specified type.
					coerced, err := coerceType(value, rule.Type)
					if err != nil {
						return nil, err
					}
					value = coerced
				}

				if rule.InputHandler != nil {
					value = rule.InputHandler(value)
				}

				newPayload[field] = value
				continue fieldLoop
			}
		}

		log.Printf("Discarding field '%s'", field)
	}

	// Ensure no required fields are missing.
	if err := enforceRequiredFields(rules, newPayload); err != nil {
		log.Println(err)
		return nil, err
	}

	return newPayload, nil
}

// applyNestedInboundRules recursively applies nested Rules which are not specified as
// output only to the provided value.
func applyNestedInboundRules(value interface{}, rules Rules) (interface{}, error) {
	var fieldValue interface{}
	valueType := reflect.TypeOf(value).Kind()
	if valueType == reflect.Slice {
		// Apply nested Rules to each item in the slice.
		s := reflect.ValueOf(value)
		nestedValues := make([]interface{}, s.Len())
		for i := 0; i < s.Len(); i++ {
			var payload map[string]interface{}
			payload, err := applyInboundRules(
				s.Index(i).Interface().(map[string]interface{}), rules)
			if err != nil {
				return nil, err
			}
			nestedValues[i] = payload
		}
		fieldValue = nestedValues
	} else {
		var payload map[string]interface{}
		payload, err := applyInboundRules(value.(map[string]interface{}), rules)
		if err != nil {
			return nil, err
		}
		fieldValue = payload
	}

	return fieldValue, nil
}

// nestedInboundRulesApply returns true if the Rules contain inbound Rules and
// the value is a map or slice.
func nestedInboundRulesApply(value interface{}, rules Rules) bool {
	if rules == nil || rules.Size() == 0 {
		return false
	}

	valueType := reflect.TypeOf(value).Kind()
	if valueType != reflect.Map && valueType != reflect.Slice {
		// Only apply nested Rules to maps and slices.
		return false
	}

	return rules.Filter(true).Size() > 0
}

// applyOutboundRules applies Rules which are not specified as input only to the
// provided Resource. If the Resource is nil, not a struct or
// map[string]interface{}, or no Rules are provided, this acts as an identity
// function. If Rules are provided, only the fields specified by them will be
// included in the returned Resource. This is to prevent new fields from leaking
// into old API versions. If Rules specify nested Rules, they will be recursively
// applied to field values.
func applyOutboundRules(resource Resource, rules Rules) Resource {
	// Apply only outbound Rules.
	rules = rules.Filter(false)

	if isNil(resource) || rules.Size() == 0 {
		// Return resource as-is if no Rules are provided.
		return resource
	}

	// Get the underlying value by dereferencing the pointer if there is one.
	resourceValue := reflect.Indirect(reflect.ValueOf(resource))
	resource = resourceValue.Interface()
	resourceType := reflect.TypeOf(resource)
	var payload Resource

	if resourceType.Kind() == reflect.Map {
		if resourceMap, ok := resource.(map[string]interface{}); ok {
			payload = applyOutboundRulesForMap(resourceMap, rules)
		} else {
			// Nothing we can do if the keys aren't strings.
			payload = resource
		}
	} else if resourceType.Kind() == reflect.Struct {
		payload = applyOutboundRulesForStruct(resourceValue, rules)
	} else {
		// Only apply Rules to resource structs and maps.
		payload = resource
	}

	return payload
}

// applyOutboundRulesForMap applies Rules which are not specified as input only to the
// provided map. If a Rule specifies a field which is not in the map, it will be skipped.
// If a Rule specifies nested Rules, they will be recursively applied to the corresponding
// value.
func applyOutboundRulesForMap(resource map[string]interface{}, rules Rules) Payload {
	payload := Payload{}
	for _, rule := range rules.Contents() {
		fieldValue, ok := resource[rule.Field]
		if !ok {
			log.Printf("Map resource missing field '%s'", rule.Field)
			continue
		}

		if rule.Rules != nil {
			fieldValue = applyNestedOutboundRules(fieldValue, rule)
		}

		if rule.OutputHandler != nil {
			fieldValue = rule.OutputHandler(fieldValue)
		}
		payload[rule.Name()] = fieldValue
	}

	return payload
}

// applyOutboundRulesForStruct applies Rules which are not specified as input only to the
// provided reflect.Value. The precondition for this function is that the value is an
// instance of the type specified on the Rules. If a Rule specifies nested Rules, they
// will be recursively applied to the corresponding value.
func applyOutboundRulesForStruct(resourceValue reflect.Value, rules Rules) Payload {
	payload := Payload{}
	for _, rule := range rules.Contents() {
		// Rule validation occurs at server start. No need to check for field existence.
		field := resourceValue.FieldByName(rule.Field)
		fieldValue := field.Interface()

		if rule.Rules != nil {
			fieldValue = applyNestedOutboundRules(fieldValue, rule)
		}

		if rule.OutputHandler != nil {
			fieldValue = rule.OutputHandler(fieldValue)
		}
		payload[rule.Name()] = fieldValue
	}

	return payload
}

// applyNestedOutboundRules recursively applies nested Rules which are not specified as
// input only to the provided Resource.
func applyNestedOutboundRules(resource Resource, rule *Rule) Resource {
	var fieldValue Resource

	if reflect.TypeOf(resource).Kind() == reflect.Slice {
		// Apply nested Rules to each item in the slice.
		s := reflect.ValueOf(resource)
		nestedValues := make([]interface{}, s.Len())
		for i := 0; i < s.Len(); i++ {
			nestedValues[i] = applyOutboundRules(s.Index(i).Interface(), rule.Rules)
		}
		fieldValue = nestedValues
	} else {
		fieldValue = applyOutboundRules(resource, rule.Rules)
	}

	return fieldValue
}

// enforceRequiredFields verifies that the provided Payload has values for any Rules
// with the Required flag set to true. If any required fields are missing, an error
// will be returned. Otherwise nil is returned.
func enforceRequiredFields(rules Rules, payload Payload) error {
ruleLoop:
	for _, rule := range rules.Contents() {
		if !rule.Required {
			continue
		}

		for field := range payload {
			if rule.Name() == field {
				continue ruleLoop
			}
		}

		return fmt.Errorf("Missing required field '%s'", rule.Name())
	}

	return nil
}

// isNil returns true if the given Resource is a nil value or pointer, false if
// not.
func isNil(resource Resource) bool {
	value := reflect.ValueOf(resource)
	t := value.Kind()
	switch t {
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map,
		reflect.Ptr, reflect.Slice:
		return value.IsNil()
	default:
		return resource == nil
	}
}
<commit_msg>Add TODO note for apply versions to nested Rules<commit_after>package rest

import (
	"fmt"
	"log"
	"reflect"
)

// TODO:
//  - Consider using/writing a more robust data validation library.
//    E.g. https://github.com/Assembli/beautiful-validity
//    This would allow for semantic validation and custom validation logic.
//    For now, we are only providing type validation.
//
//  - Make type coercion pluggable (i.e. conversion/validation of custom types).
//
//  - Apply versioning to nested Rules. Currently, nested Rules are applied if the parent
//    is applied, even if they specify versions which do not include the requested
//    version.

// Rules is a collection of Rules and a reflect.Type which they correspond to.
type Rules interface {
	// Contents returns the contained Rules.
	Contents() []*Rule

	// ResourceType returns the reflect.Type these Rules correspond to.
	ResourceType() reflect.Type

	// Validate verifies that the Rules are valid, meaning they specify fields that exist
	// and correct types. If a Rule is invalid, an error is returned. If the Rules are
	// valid, nil is returned. This will recursively validate nested Rules.
	Validate() error

	// Filter filters the slice of Rules based on the specified bool. True means to
	// filter out outbound Rules such that the returned slice contains only inbound Rules.
	// False means to filter out inbound Rules such that the returned slice contains only
	// outbound Rules.
	Filter(bool) Rules

	// Size returns the number of contained Rules.
	Size() int
}

type rules struct {
	contents     []*Rule
	resourceType reflect.Type
}

// Contents returns the contained Rules.
func (r rules) Contents() []*Rule {
	return r.contents
}

// ResourceType returns the reflect.Type these Rules correspond to.
func (r rules) ResourceType() reflect.Type {
	return r.resourceType
}

// Validate verifies that the Rules are valid, meaning they specify fields that exist
// and correct types. If a Rule is invalid, an error is returned. If the Rules are
// valid, nil is returned. This will recursively validate nested Rules.
func (r rules) Validate() error {
	for _, rule := range r.contents {
		resourceType := r.resourceType
		if resourceType.Kind() != reflect.Struct && resourceType.Kind() != reflect.Map {
			return fmt.Errorf(
				"Invalid resource type: must be struct or map, got %s",
				resourceType)
		}

		field, found := resourceType.FieldByName(rule.Field)
		if !found {
			return fmt.Errorf(
				"Invalid Rule for %s: field '%s' does not exist",
				resourceType, rule.Field)
		}

		if !rule.validType(field.Type) {
			return fmt.Errorf(
				"Invalid Rule for %s: field '%s' is type %s, not %s",
				resourceType, rule.Field, field.Type, typeToName[rule.Type])
		}

		// Validate nested Rules.
		if rule.Rules != nil {
			if err := rule.Rules.Validate(); err != nil {
				return err
			}
		}
	}

	return nil
}

// Filter filters the slice of Rules based on the specified bool. True means to
// filter out outbound Rules such that the returned slice contains only inbound Rules.
// False means to filter out inbound Rules such that the returned slice contains only
// outbound Rules.
func (r rules) Filter(inbound bool) Rules {
	filtered := make([]*Rule, 0, len(r.contents))
	for _, rule := range r.contents {
		if inbound && rule.OutputOnly {
			// Filter out outbound Rules.
			continue
		} else if !inbound && rule.InputOnly {
			// Filter out inbound Rules.
			continue
		}
		filtered = append(filtered, rule)
	}

	return rules{contents: filtered, resourceType: r.resourceType}
}

// Size returns the number of contained Rules.
func (r rules) Size() int {
	return len(r.contents)
}

// NewRules returns a set of Rules for use by a ResourceHandler. The first argument
// must be a resource pointer (and can be nil) used to associate the Rules with a
// resource type. If it isn't a pointer, this will panic.
func NewRules(ptr interface{}, r ...*Rule) Rules {
	resourceType := reflect.TypeOf(ptr)
	if resourceType.Kind() != reflect.Ptr {
		panic(fmt.Sprintf("Must provide resource pointer to NewRules, got %s",
			resourceType.Kind()))
	}

	return rules{
		resourceType: resourceType.Elem(),
		contents:     r,
	}
}

// Rule provides schema validation and type coercion for request input and fine-grained
// control over response output. If a ResourceHandler provides input Rules which
// specify types, input fields will attempt to be coerced to those types. If coercion
// fails, an error will be returned in the response. If a ResourceHandler provides
// output Rules, only the fields corresponding to those Rules will be sent back. This
// prevents new fields from leaking into old API versions.
type Rule struct {
	// Name of the resource field. This is the name as it appears in the struct
	// definition.
	Field string

	// Name of the input/output field. Use Name() to retrieve the field alias while
	// falling back to the field name if it's not specified.
	FieldAlias string

	// Type to coerce field value to. If the value cannot be coerced, an error will be
	// returned in the response. Defaults to Unspecified, which is the equivalent of
	// an interface{} value.
	Type Type

	// Indicates if the field must have a value. Defaults to false.
	Required bool

	// Versions is a list of the API versions this Rule applies to. If empty, it will
	// be applied to all versions.
	Versions []string

	// Indicates if the Rule should only be applied to requests.
	InputOnly bool

	// Indicates if the Rule should only be applied to responses.
	OutputOnly bool

	// Function which produces the field value to receive.
	InputHandler func(interface{}) interface{}

	// Function which produces the field value to send.
	OutputHandler func(interface{}) interface{}

	// Nested Rules to apply to field value.
	Rules Rules
}

// Name returns the name of the input/output field alias. It defaults to the field
// name if the alias was not specified.
func (r Rule) Name() string {
	alias := r.FieldAlias
	if alias == "" {
		alias = r.Field
	}
	return alias
}

// Applies returns whether or not the Rule applies to the given version.
func (r Rule) Applies(version string) bool {
	if r.Versions == nil {
		return true
	}

	for _, v := range r.Versions {
		if v == version {
			return true
		}
	}

	return false
}

// validType returns whether or not the Rule is valid for the given reflect.Type.
func (r Rule) validType(fieldType reflect.Type) bool {
	if r.Type == Unspecified {
		return true
	}

	kind := typeToKind[r.Type]
	return fieldType.Kind() == kind
}

// applyInboundRules applies Rules which are not specified as output only to the
// provided Payload. If the Payload is nil, an empty Payload will be returned. If no
// Rules are provided, this acts as an identity function. If Rules are provided, any
// incoming fields which are not specified will be discarded. If Rules specify types,
// incoming values will attempted to be coerced. If coercion fails, an error will be
// returned. If Rules specify nested Rules, they will be recursively applied to the
// field value, taking precedence over a type coercion.
func applyInboundRules(payload Payload, rules Rules) (Payload, error) {
	if payload == nil {
		return Payload{}, nil
	}

	// Apply only inbound Rules.
	rules = rules.Filter(true)

	if rules.Size() == 0 {
		return payload, nil
	}

	newPayload := Payload{}

fieldLoop:
	for field, value := range payload {
		for _, rule := range rules.Contents() {
			if rule.Name() == field {
				if nestedInboundRulesApply(value, rule.Rules) {
					// Nested Rules take precedence over type coercion.
					v, err := applyNestedInboundRules(value, rule.Rules)
					if err != nil {
						return nil, err
					}
					value = v
				} else if rule.Type != Unspecified {
					// Coerce to specified type.
					coerced, err := coerceType(value, rule.Type)
					if err != nil {
						return nil, err
					}
					value = coerced
				}

				if rule.InputHandler != nil {
					value = rule.InputHandler(value)
				}

				newPayload[field] = value
				continue fieldLoop
			}
		}

		log.Printf("Discarding field '%s'", field)
	}

	// Ensure no required fields are missing.
	if err := enforceRequiredFields(rules, newPayload); err != nil {
		log.Println(err)
		return nil, err
	}

	return newPayload, nil
}

// applyNestedInboundRules recursively applies nested Rules which are not specified as
// output only to the provided value.
func applyNestedInboundRules(value interface{}, rules Rules) (interface{}, error) {
	var fieldValue interface{}
	valueType := reflect.TypeOf(value).Kind()
	if valueType == reflect.Slice {
		// Apply nested Rules to each item in the slice.
		s := reflect.ValueOf(value)
		nestedValues := make([]interface{}, s.Len())
		for i := 0; i < s.Len(); i++ {
			var payload map[string]interface{}
			payload, err := applyInboundRules(
				s.Index(i).Interface().(map[string]interface{}), rules)
			if err != nil {
				return nil, err
			}
			nestedValues[i] = payload
		}
		fieldValue = nestedValues
	} else {
		var payload map[string]interface{}
		payload, err := applyInboundRules(value.(map[string]interface{}), rules)
		if err != nil {
			return nil, err
		}
		fieldValue = payload
	}

	return fieldValue, nil
}

// nestedInboundRulesApply returns true if the Rules contain inbound Rules and
// the value is a map or slice.
func nestedInboundRulesApply(value interface{}, rules Rules) bool {
	if rules == nil || rules.Size() == 0 {
		return false
	}

	valueType := reflect.TypeOf(value).Kind()
	if valueType != reflect.Map && valueType != reflect.Slice {
		// Only apply nested Rules to maps and slices.
		return false
	}

	return rules.Filter(true).Size() > 0
}

// applyOutboundRules applies Rules which are not specified as input only to the
// provided Resource. If the Resource is nil, not a struct or
// map[string]interface{}, or no Rules are provided, this acts as an identity
// function. If Rules are provided, only the fields specified by them will be
// included in the returned Resource. This is to prevent new fields from leaking
// into old API versions. If Rules specify nested Rules, they will be recursively
// applied to field values.
func applyOutboundRules(resource Resource, rules Rules) Resource {
	// Apply only outbound Rules.
	rules = rules.Filter(false)

	if isNil(resource) || rules.Size() == 0 {
		// Return resource as-is if no Rules are provided.
		return resource
	}

	// Get the underlying value by dereferencing the pointer if there is one.
	resourceValue := reflect.Indirect(reflect.ValueOf(resource))
	resource = resourceValue.Interface()
	resourceType := reflect.TypeOf(resource)
	var payload Resource

	if resourceType.Kind() == reflect.Map {
		if resourceMap, ok := resource.(map[string]interface{}); ok {
			payload = applyOutboundRulesForMap(resourceMap, rules)
		} else {
			// Nothing we can do if the keys aren't strings.
			payload = resource
		}
	} else if resourceType.Kind() == reflect.Struct {
		payload = applyOutboundRulesForStruct(resourceValue, rules)
	} else {
		// Only apply Rules to resource structs and maps.
		payload = resource
	}

	return payload
}

// applyOutboundRulesForMap applies Rules which are not specified as input only to the
// provided map. If a Rule specifies a field which is not in the map, it will be skipped.
// If a Rule specifies nested Rules, they will be recursively applied to the corresponding
// value.
func applyOutboundRulesForMap(resource map[string]interface{}, rules Rules) Payload {
	payload := Payload{}
	for _, rule := range rules.Contents() {
		fieldValue, ok := resource[rule.Field]
		if !ok {
			log.Printf("Map resource missing field '%s'", rule.Field)
			continue
		}

		if rule.Rules != nil {
			fieldValue = applyNestedOutboundRules(fieldValue, rule)
		}

		if rule.OutputHandler != nil {
			fieldValue = rule.OutputHandler(fieldValue)
		}
		payload[rule.Name()] = fieldValue
	}

	return payload
}

// applyOutboundRulesForStruct applies Rules which are not specified as input only to the
// provided reflect.Value. The precondition for this function is that the value is an
// instance of the type specified on the Rules. If a Rule specifies nested Rules, they
// will be recursively applied to the corresponding value.
func applyOutboundRulesForStruct(resourceValue reflect.Value, rules Rules) Payload {
	payload := Payload{}
	for _, rule := range rules.Contents() {
		// Rule validation occurs at server start. No need to check for field existence.
		field := resourceValue.FieldByName(rule.Field)
		fieldValue := field.Interface()

		if rule.Rules != nil {
			fieldValue = applyNestedOutboundRules(fieldValue, rule)
		}

		if rule.OutputHandler != nil {
			fieldValue = rule.OutputHandler(fieldValue)
		}
		payload[rule.Name()] = fieldValue
	}

	return payload
}

// applyNestedOutboundRules recursively applies nested Rules which are not specified as
// input only to the provided Resource.
func applyNestedOutboundRules(resource Resource, rule *Rule) Resource {
	var fieldValue Resource

	if reflect.TypeOf(resource).Kind() == reflect.Slice {
		// Apply nested Rules to each item in the slice.
		s := reflect.ValueOf(resource)
		nestedValues := make([]interface{}, s.Len())
		for i := 0; i < s.Len(); i++ {
			nestedValues[i] = applyOutboundRules(s.Index(i).Interface(), rule.Rules)
		}
		fieldValue = nestedValues
	} else {
		fieldValue = applyOutboundRules(resource, rule.Rules)
	}

	return fieldValue
}

// enforceRequiredFields verifies that the provided Payload has values for any Rules
// with the Required flag set to true. If any required fields are missing, an error
// will be returned. Otherwise nil is returned.
func enforceRequiredFields(rules Rules, payload Payload) error {
ruleLoop:
	for _, rule := range rules.Contents() {
		if !rule.Required {
			continue
		}

		for field := range payload {
			if rule.Name() == field {
				continue ruleLoop
			}
		}

		return fmt.Errorf("Missing required field '%s'", rule.Name())
	}

	return nil
}

// isNil returns true if the given Resource is a nil value or pointer, false if
// not.
func isNil(resource Resource) bool {
	value := reflect.ValueOf(resource)
	t := value.Kind()
	switch t {
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map,
		reflect.Ptr, reflect.Slice:
		return value.IsNil()
	default:
		return resource == nil
	}
}
<|endoftext|>


<commit_before>// Package linewriter implements a write filter that expect "\n" or "\r" ended
// lines as its input.
package linewriter

import (
	"bufio"
	"io"
)

// Writer allows to write input text line by line to provided output io.Writer.
// If no conversion is enabled it calls output.Write method once per line. In
// conversion mode it converts any newline to provided nl string but in this
// case it can use more than one write for one line. If bufio.Writer is used as
// output it calls its Flush method after each line written.
type Writer struct {
	out   io.Writer
	newnl []byte
}

var (
	CRLF = []byte{'\r', '\n'}
	CR   = CRLF[0:1]
	LF   = CRLF[1:2]
)

func Make(output io.Writer, newnl []byte) Writer {
	return Writer{out: output, newnl: newnl}
}

func New(output io.Writer, newnl []byte) *Writer {
	b := new(Writer)
	*b = Make(output, newnl)
	return b
}

func indexNL(buf []byte) int {
	for n, b := range buf {
		if b == '\n' || b == '\r' {
			return n
		}
	}
	return -1
}

func (w *Writer) Write(buf []byte) (n int, err error) {
	b, _ := w.out.(*bufio.Writer)
	for {
		var m int
		i := indexNL(buf)
		if i == -1 {
			m, err = w.out.Write(buf)
			n += m
			return n, err
		}
		if w.newnl != nil {
			m, err = w.out.Write(buf[:i])
			n += m
			if err == nil {
				m, err = w.out.Write(w.newnl)
				n += m
			}
		} else {
			m, err = w.out.Write(buf[:i+1])
			n += m
		}
		if b != nil && err == nil {
			err = b.Flush()
		}
		if err != nil {
			return n, err
		}
		buf = buf[i+1:]
	}
}
<commit_msg>text/linewriter: Add Flush method.<commit_after>// Package linewriter implements a write filter that expect "\n" or "\r" ended
// lines as its input.
package linewriter

import (
	"bufio"
	"io"
)

// Writer allows to write input text line by line to provided output io.Writer.
// If no conversion is enabled it calls output.Write method once per line. In
// conversion mode it converts any newline to provided nl string but in this
// case it can use more than one write for one line. If bufio.Writer is used as
// output it calls its Flush method after each line written.
type Writer struct {
	out   io.Writer
	newnl []byte
}

var (
	CRLF = []byte{'\r', '\n'}
	CR   = CRLF[0:1]
	LF   = CRLF[1:2]
)

func Make(output io.Writer, newnl []byte) Writer {
	return Writer{out: output, newnl: newnl}
}

func New(output io.Writer, newnl []byte) *Writer {
	b := new(Writer)
	*b = Make(output, newnl)
	return b
}

func indexNL(buf []byte) int {
	for n, b := range buf {
		if b == '\n' || b == '\r' {
			return n
		}
	}
	return -1
}

func (w *Writer) Write(buf []byte) (n int, err error) {
	b, _ := w.out.(*bufio.Writer)
	for {
		var m int
		i := indexNL(buf)
		if i == -1 {
			m, err = w.out.Write(buf)
			n += m
			return n, err
		}
		if w.newnl != nil {
			m, err = w.out.Write(buf[:i])
			n += m
			if err == nil {
				m, err = w.out.Write(w.newnl)
				n += m
			}
		} else {
			m, err = w.out.Write(buf[:i+1])
			n += m
		}
		if b != nil && err == nil {
			err = b.Flush()
		}
		if err != nil {
			return n, err
		}
		buf = buf[i+1:]
	}
}

func (w *Writer) Flush() error {
	if b, ok := w.out.(*bufio.Writer); ok {
		return b.Flush()
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package runtime

import (
	"unsafe"
)

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

// An notInHeapSlice is a slice backed by go:notinheap memory.
type notInHeapSlice struct {
	array *notInHeap
	len   int
	cap   int
}

// maxElems is a lookup table containing the maximum capacity for a slice.
// The index is the size of the slice element.
var maxElems = [...]uintptr{
	^uintptr(0),
	_MaxMem / 1, _MaxMem / 2, _MaxMem / 3, _MaxMem / 4,
	_MaxMem / 5, _MaxMem / 6, _MaxMem / 7, _MaxMem / 8,
	_MaxMem / 9, _MaxMem / 10, _MaxMem / 11, _MaxMem / 12,
	_MaxMem / 13, _MaxMem / 14, _MaxMem / 15, _MaxMem / 16,
	_MaxMem / 17, _MaxMem / 18, _MaxMem / 19, _MaxMem / 20,
	_MaxMem / 21, _MaxMem / 22, _MaxMem / 23, _MaxMem / 24,
	_MaxMem / 25, _MaxMem / 26, _MaxMem / 27, _MaxMem / 28,
	_MaxMem / 29, _MaxMem / 30, _MaxMem / 31, _MaxMem / 32,
}

// maxSliceCap returns the maximum capacity for a slice.
func maxSliceCap(elemsize uintptr) uintptr {
	if elemsize < uintptr(len(maxElems)) {
		return maxElems[elemsize]
	}
	return _MaxMem / elemsize
}

func makeslice(et *_type, len, cap int) slice {
	// NOTE: The len > maxElements check here is not strictly necessary,
	// but it produces a 'len out of range' error instead of a 'cap out of range' error
	// when someone does make([]T, bignumber). 'cap out of range' is true too,
	// but since the cap is only being supplied implicitly, saying len is clearer.
	// See issue 4085.
	maxElements := maxSliceCap(et.size)
	if len < 0 || uintptr(len) > maxElements {
		panic(errorString("makeslice: len out of range"))
	}

	if cap < len || uintptr(cap) > maxElements {
		panic(errorString("makeslice: cap out of range"))
	}

	p := mallocgc(et.size*uintptr(cap), et, true)
	return slice{p, len, cap}
}

func makeslice64(et *_type, len64, cap64 int64) slice {
	len := int(len64)
	if int64(len) != len64 {
		panic(errorString("makeslice: len out of range"))
	}

	cap := int(cap64)
	if int64(cap) != cap64 {
		panic(errorString("makeslice: cap out of range"))
	}

	return makeslice(et, len, cap)
}

// growslice handles slice growth during append.
// It is passed the slice element type, the old slice, and the desired new minimum capacity,
// and it returns a new slice with at least that capacity, with the old data
// copied into it.
// The new slice's length is set to the old slice's length,
// NOT to the new requested capacity.
// This is for codegen convenience. The old slice's length is used immediately
// to calculate where to write new values during an append.
// TODO: When the old backend is gone, reconsider this decision.
// The SSA backend might prefer the new length or to return only ptr/cap and save stack space.
func growslice(et *_type, old slice, cap int) slice {
	if raceenabled {
		callerpc := getcallerpc()
		racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
	}
	if msanenabled {
		msanread(old.array, uintptr(old.len*int(et.size)))
	}

	if et.size == 0 {
		if cap < old.cap {
			panic(errorString("growslice: cap out of range"))
		}
		// append should not create a slice with nil pointer but non-zero len.
		// We assume that append doesn't need to preserve old.array in this case.
		return slice{unsafe.Pointer(&zerobase), old.len, cap}
	}

	newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
		if old.len < 1024 {
			newcap = doublecap
		} else {
			// Check 0 < newcap to detect overflow
			// and prevent an infinite loop.
			for 0 < newcap && newcap < cap {
				newcap += newcap / 4
			}
			// Set newcap to the requested cap when
			// the newcap calculation overflowed.
			if newcap <= 0 {
				newcap = cap
			}
		}
	}

	var lenmem, newlenmem, capmem uintptr
	const ptrSize = unsafe.Sizeof((*byte)(nil))
	switch et.size {
	case 1:
		lenmem = uintptr(old.len)
		newlenmem = uintptr(cap)
		capmem = roundupsize(uintptr(newcap))
		newcap = int(capmem)
	case ptrSize:
		lenmem = uintptr(old.len) * ptrSize
		newlenmem = uintptr(cap) * ptrSize
		capmem = roundupsize(uintptr(newcap) * ptrSize)
		newcap = int(capmem / ptrSize)
	default:
		lenmem = uintptr(old.len) * et.size
		newlenmem = uintptr(cap) * et.size
		capmem = roundupsize(uintptr(newcap) * et.size)
		newcap = int(capmem / et.size)
	}

	if cap < old.cap || capmem > _MaxMem {
		panic(errorString("growslice: cap out of range"))
	}

	var p unsafe.Pointer
	if et.kind&kindNoPointers != 0 {
		p = mallocgc(capmem, nil, false)
		memmove(p, old.array, lenmem)
		// The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length).
		// Only clear the part that will not be overwritten.
		memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
	} else {
		// Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
		p = mallocgc(capmem, et, true)
		if !writeBarrier.enabled {
			memmove(p, old.array, lenmem)
		} else {
			for i := uintptr(0); i < lenmem; i += et.size {
				typedmemmove(et, add(p, i), add(old.array, i))
			}
		}
	}

	return slice{p, old.len, newcap}
}

func slicecopy(to, fm slice, width uintptr) int {
	if fm.len == 0 || to.len == 0 {
		return 0
	}

	n := fm.len
	if to.len < n {
		n = to.len
	}

	if width == 0 {
		return n
	}

	if raceenabled {
		callerpc := getcallerpc()
		pc := funcPC(slicecopy)
		racewriterangepc(to.array, uintptr(n*int(width)), callerpc, pc)
		racereadrangepc(fm.array, uintptr(n*int(width)), callerpc, pc)
	}
	if msanenabled {
		msanwrite(to.array, uintptr(n*int(width)))
		msanread(fm.array, uintptr(n*int(width)))
	}

	size := uintptr(n) * width
	if size == 1 { // common case worth about 2x to do here
		// TODO: is this still worth it with new memmove impl?
		*(*byte)(to.array) = *(*byte)(fm.array) // known to be a byte pointer
	} else {
		memmove(to.array, fm.array, size)
	}
	return n
}

func slicestringcopy(to []byte, fm string) int {
	if len(fm) == 0 || len(to) == 0 {
		return 0
	}

	n := len(fm)
	if len(to) < n {
		n = len(to)
	}

	if raceenabled {
		callerpc := getcallerpc()
		pc := funcPC(slicestringcopy)
		racewriterangepc(unsafe.Pointer(&to[0]), uintptr(n), callerpc, pc)
	}
	if msanenabled {
		msanwrite(unsafe.Pointer(&to[0]), uintptr(n))
	}

	memmove(unsafe.Pointer(&to[0]), stringStructOf(&fm).str, uintptr(n))
	return n
}
<commit_msg>runtime: protect growslice against newcap*et.size overflow<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package runtime

import (
	"unsafe"
)

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

// An notInHeapSlice is a slice backed by go:notinheap memory.
type notInHeapSlice struct {
	array *notInHeap
	len   int
	cap   int
}

// maxElems is a lookup table containing the maximum capacity for a slice.
// The index is the size of the slice element.
var maxElems = [...]uintptr{
	^uintptr(0),
	_MaxMem / 1, _MaxMem / 2, _MaxMem / 3, _MaxMem / 4,
	_MaxMem / 5, _MaxMem / 6, _MaxMem / 7, _MaxMem / 8,
	_MaxMem / 9, _MaxMem / 10, _MaxMem / 11, _MaxMem / 12,
	_MaxMem / 13, _MaxMem / 14, _MaxMem / 15, _MaxMem / 16,
	_MaxMem / 17, _MaxMem / 18, _MaxMem / 19, _MaxMem / 20,
	_MaxMem / 21, _MaxMem / 22, _MaxMem / 23, _MaxMem / 24,
	_MaxMem / 25, _MaxMem / 26, _MaxMem / 27, _MaxMem / 28,
	_MaxMem / 29, _MaxMem / 30, _MaxMem / 31, _MaxMem / 32,
}

// maxSliceCap returns the maximum capacity for a slice.
func maxSliceCap(elemsize uintptr) uintptr {
	if elemsize < uintptr(len(maxElems)) {
		return maxElems[elemsize]
	}
	return _MaxMem / elemsize
}

func makeslice(et *_type, len, cap int) slice {
	// NOTE: The len > maxElements check here is not strictly necessary,
	// but it produces a 'len out of range' error instead of a 'cap out of range' error
	// when someone does make([]T, bignumber). 'cap out of range' is true too,
	// but since the cap is only being supplied implicitly, saying len is clearer.
	// See issue 4085.
	maxElements := maxSliceCap(et.size)
	if len < 0 || uintptr(len) > maxElements {
		panic(errorString("makeslice: len out of range"))
	}

	if cap < len || uintptr(cap) > maxElements {
		panic(errorString("makeslice: cap out of range"))
	}

	p := mallocgc(et.size*uintptr(cap), et, true)
	return slice{p, len, cap}
}

func makeslice64(et *_type, len64, cap64 int64) slice {
	len := int(len64)
	if int64(len) != len64 {
		panic(errorString("makeslice: len out of range"))
	}

	cap := int(cap64)
	if int64(cap) != cap64 {
		panic(errorString("makeslice: cap out of range"))
	}

	return makeslice(et, len, cap)
}

// growslice handles slice growth during append.
// It is passed the slice element type, the old slice, and the desired new minimum capacity,
// and it returns a new slice with at least that capacity, with the old data
// copied into it.
// The new slice's length is set to the old slice's length,
// NOT to the new requested capacity.
// This is for codegen convenience. The old slice's length is used immediately
// to calculate where to write new values during an append.
// TODO: When the old backend is gone, reconsider this decision.
// The SSA backend might prefer the new length or to return only ptr/cap and save stack space.
func growslice(et *_type, old slice, cap int) slice {
	if raceenabled {
		callerpc := getcallerpc()
		racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
	}
	if msanenabled {
		msanread(old.array, uintptr(old.len*int(et.size)))
	}

	if et.size == 0 {
		if cap < old.cap {
			panic(errorString("growslice: cap out of range"))
		}
		// append should not create a slice with nil pointer but non-zero len.
		// We assume that append doesn't need to preserve old.array in this case.
		return slice{unsafe.Pointer(&zerobase), old.len, cap}
	}

	newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
		if old.len < 1024 {
			newcap = doublecap
		} else {
			// Check 0 < newcap to detect overflow
			// and prevent an infinite loop.
			for 0 < newcap && newcap < cap {
				newcap += newcap / 4
			}
			// Set newcap to the requested cap when
			// the newcap calculation overflowed.
			if newcap <= 0 {
				newcap = cap
			}
		}
	}

	var overflow bool
	var lenmem, newlenmem, capmem uintptr
	const ptrSize = unsafe.Sizeof((*byte)(nil))
	switch et.size {
	case 1:
		lenmem = uintptr(old.len)
		newlenmem = uintptr(cap)
		capmem = roundupsize(uintptr(newcap))
		overflow = uintptr(newcap) > _MaxMem
		newcap = int(capmem)
	case ptrSize:
		lenmem = uintptr(old.len) * ptrSize
		newlenmem = uintptr(cap) * ptrSize
		capmem = roundupsize(uintptr(newcap) * ptrSize)
		overflow = uintptr(newcap) > _MaxMem/ptrSize
		newcap = int(capmem / ptrSize)
	default:
		lenmem = uintptr(old.len) * et.size
		newlenmem = uintptr(cap) * et.size
		capmem = roundupsize(uintptr(newcap) * et.size)
		overflow = uintptr(newcap) > maxSliceCap(et.size)
		newcap = int(capmem / et.size)
	}

	// The check of overflow (uintptr(newcap) > maxSliceCap(et.size))
	// in addition to capmem > _MaxMem is needed to prevent an overflow
	// which can be used to trigger a segfault on 32bit architectures
	// with this example program:
	//
	// type T [1<<27 + 1]int64
	//
	// var d T
	// var s []T
	//
	// func main() {
	//   s = append(s, d, d, d, d)
	//   print(len(s), "\n")
	// }
	if cap < old.cap || overflow || capmem > _MaxMem {
		panic(errorString("growslice: cap out of range"))
	}

	var p unsafe.Pointer
	if et.kind&kindNoPointers != 0 {
		p = mallocgc(capmem, nil, false)
		memmove(p, old.array, lenmem)
		// The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length).
		// Only clear the part that will not be overwritten.
		memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
	} else {
		// Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
		p = mallocgc(capmem, et, true)
		if !writeBarrier.enabled {
			memmove(p, old.array, lenmem)
		} else {
			for i := uintptr(0); i < lenmem; i += et.size {
				typedmemmove(et, add(p, i), add(old.array, i))
			}
		}
	}

	return slice{p, old.len, newcap}
}

func slicecopy(to, fm slice, width uintptr) int {
	if fm.len == 0 || to.len == 0 {
		return 0
	}

	n := fm.len
	if to.len < n {
		n = to.len
	}

	if width == 0 {
		return n
	}

	if raceenabled {
		callerpc := getcallerpc()
		pc := funcPC(slicecopy)
		racewriterangepc(to.array, uintptr(n*int(width)), callerpc, pc)
		racereadrangepc(fm.array, uintptr(n*int(width)), callerpc, pc)
	}
	if msanenabled {
		msanwrite(to.array, uintptr(n*int(width)))
		msanread(fm.array, uintptr(n*int(width)))
	}

	size := uintptr(n) * width
	if size == 1 { // common case worth about 2x to do here
		// TODO: is this still worth it with new memmove impl?
		*(*byte)(to.array) = *(*byte)(fm.array) // known to be a byte pointer
	} else {
		memmove(to.array, fm.array, size)
	}
	return n
}

func slicestringcopy(to []byte, fm string) int {
	if len(fm) == 0 || len(to) == 0 {
		return 0
	}

	n := len(fm)
	if len(to) < n {
		n = len(to)
	}

	if raceenabled {
		callerpc := getcallerpc()
		pc := funcPC(slicestringcopy)
		racewriterangepc(unsafe.Pointer(&to[0]), uintptr(n), callerpc, pc)
	}
	if msanenabled {
		msanwrite(unsafe.Pointer(&to[0]), uintptr(n))
	}

	memmove(unsafe.Pointer(&to[0]), stringStructOf(&fm).str, uintptr(n))
	return n
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"reflect"

	"github.com/cosmos72/gomacro/fast"
	"github.com/cosmos72/gomacro/imports"
)

func fail(format string, args ...interface{}) {
	panic(errors.New(fmt.Sprintf(format, args...)))
}

// example from Earl J Wagner use case: load a file in the interpreter,
// then interactively replace a (bugged) interpreted function
// with another (corrected) interpreted function,
// without losing the packages already loaded in the intepreter
func main() {
	// 1. allocate the fast interpreter. Reason: the fast.Interp.EvalFile() does not have yet a second argument 'pkgpath'
	ir := fast.New()

	// 2. switch to package "github.com/cosmos72/gomacro/example/earljwagner2"
	ir.ChangePackage("earljwagner2", "github.com/cosmos72/gomacro/example/earljwagner2")

	// 3. tell the interpreter to load the file "cube.go" into the current package
	ir.EvalFile("cube.go")

	// 4. switch back to package "main"
	ir.ChangePackage("main", "main")

	// 5. tell the interpreter to import the package containing the interpreted function Cube() loaded from file
	ir.Eval(`import "github.com/cosmos72/gomacro/example/earljwagner2"`)

	// 6. execute interpreted Cube() loaded from file - and realise it's bugged
	xcube, _ := ir.Eval1("earljwagner2.Cube(3.0)")
	fmt.Printf("interpreted earljwagner2.Cube(3.0) = %f\n", xcube.Interface().(float64))

	// 7. tell the interpreter to switch to package "github.com/cosmos72/gomacro/example/earljwagner2"
	//    at REPL, one would instead type the following (note the quotes):
	//      package "github.com/cosmos72/gomacro/example/earljwagner2"
	ir.ChangePackage("earljwagner2", "github.com/cosmos72/gomacro/example/earljwagner2")

	// 8. the interpreted function Cube() can now be invoked without package prefix
	xcube, _ = ir.Eval1("Cube(4.0)")
	fmt.Printf("interpreted Cube(4.0) = %f\n", xcube.Interface().(float64))

	// 9. redefine the interpreted function Cube(), replacing the loaded one
	ir.Eval("func Cube(x float64) float64 { return x*x*x }")

	// 10. invoke the redefined function Cube() - the bug is solved :)
	xcube, _ = ir.Eval1("Cube(4.0)")
	fmt.Printf("interpreted Cube(4.0) = %f\n", xcube.Interface().(float64))

	// 11. note: compiled code will *NOT* automatically know about the bug-fixed Cube() living inside the interpreter.
	//    One solution is to stay inside the interpreter REPL and use interpreted functions.
	//    Another solution is to extract the bug-fixed function from the interpreter and use it,
	//    for example by storing it inside imports.Packages
	imports.Packages["github.com/cosmos72/gomacro/example/earljwagner2"] = imports.Package{
		Binds: map[string]reflect.Value{
			"Cube": reflect.ValueOf(Cube),
		},
	}
}
<commit_msg>fix comment in example/earljwagner2/earljwagner2.go<commit_after>package main

import (
	"errors"
	"fmt"
	"reflect"

	"github.com/cosmos72/gomacro/fast"
	"github.com/cosmos72/gomacro/imports"
)

func fail(format string, args ...interface{}) {
	panic(errors.New(fmt.Sprintf(format, args...)))
}

// example from Earl J Wagner use case: load a file in the interpreter,
// then interactively replace a (bugged) interpreted function
// with another (corrected) interpreted function,
// without losing the packages already loaded in the intepreter
func main() {
	// 1. create the fast interpreter.
	ir := fast.New()

	// 2. switch to package "github.com/cosmos72/gomacro/example/earljwagner2"
	ir.ChangePackage("earljwagner2", "github.com/cosmos72/gomacro/example/earljwagner2")

	// 3. tell the interpreter to load the file "cube.go" into the current package
	ir.EvalFile("cube.go")

	// 4. switch back to package "main"
	ir.ChangePackage("main", "main")

	// 5. tell the interpreter to import the package containing the interpreted function Cube() loaded from file
	ir.Eval(`import "github.com/cosmos72/gomacro/example/earljwagner2"`)

	// 6. execute interpreted Cube() loaded from file - and realise it's bugged
	xcube, _ := ir.Eval1("earljwagner2.Cube(3.0)")
	fmt.Printf("interpreted earljwagner2.Cube(3.0) = %f\n", xcube.Interface().(float64))

	// 7. tell the interpreter to switch to package "github.com/cosmos72/gomacro/example/earljwagner2"
	//    at REPL, one would instead type the following (note the quotes):
	//      package "github.com/cosmos72/gomacro/example/earljwagner2"
	ir.ChangePackage("earljwagner2", "github.com/cosmos72/gomacro/example/earljwagner2")

	// 8. the interpreted function Cube() can now be invoked without package prefix
	xcube, _ = ir.Eval1("Cube(4.0)")
	fmt.Printf("interpreted Cube(4.0) = %f\n", xcube.Interface().(float64))

	// 9. redefine the interpreted function Cube(), replacing the loaded one
	ir.Eval("func Cube(x float64) float64 { return x*x*x }")

	// 10. invoke the redefined function Cube() - the bug is solved :)
	xcube, _ = ir.Eval1("Cube(4.0)")
	fmt.Printf("interpreted Cube(4.0) = %f\n", xcube.Interface().(float64))

	// 11. note: compiled code will *NOT* automatically know about the bug-fixed Cube() living inside the interpreter.
	//    One solution is to stay inside the interpreter REPL and use interpreted functions.
	//    Another solution is to extract the bug-fixed function from the interpreter and use it,
	//    for example by storing it inside imports.Packages
	imports.Packages["github.com/cosmos72/gomacro/example/earljwagner2"] = imports.Package{
		Binds: map[string]reflect.Value{
			"Cube": reflect.ValueOf(Cube),
		},
	}
}
<|endoftext|>


<commit_before>package rncryptor

import(
  "bytes"
  "errors"
  "crypto/sha1"
  "crypto/sha256"
  "crypto/hmac"
  "crypto/aes"
  "crypto/cipher"
  "golang.org/x/crypto/pbkdf2"
)

func Decrypt(password string, data []byte) ([]byte, error) {
  version         := data[:1]
  options         := data[1:2]
  encSalt         := data[2:10]
  hmacSalt        := data[10:18]
  iv              := data[18:34]
  cipherText      := data[34:(len(data)-66+34)]
  expectedHmac    := data[len(data)-32:len(data)]

  msg := make([]byte, 0)
  msg = append(msg, version...)
  msg = append(msg, options...)
  msg = append(msg, encSalt...)
  msg = append(msg, hmacSalt...)
  msg = append(msg, iv...)
  msg = append(msg, cipherText...)

  hmacKey := pbkdf2.Key([]byte(password), hmacSalt, 10000, 32, sha1.New)
  testHmac := hmac.New(sha256.New, hmacKey)
  testHmac.Write(msg)
  testHmacVal := testHmac.Sum(nil)

  verified := bytes.Equal(testHmacVal, expectedHmac)

  if !verified {
    return nil, errors.New("Password may be incorrect, or the data has been corrupted. (HMAC could not be verified)")
  }

  cipherKey := pbkdf2.Key([]byte(password), encSalt, 10000, 32, sha1.New)
  cipherBlock, err := aes.NewCipher(cipherKey)
  if err != nil {
    return nil, err
  }

  decrypted := make([]byte, len(cipherText))
  copy(decrypted, cipherText)
  decrypter := cipher.NewCBCDecrypter(cipherBlock, iv)
  decrypter.CryptBlocks(decrypted, decrypted)

  length := len(decrypted)
  unpadding := int(decrypted[length-1])

  return decrypted[:(length - unpadding)], nil
}
<commit_msg>add Encrypt method<commit_after>package rncryptor

import(
  "bytes"
  "errors"
  "crypto/rand"
  "crypto/sha1"
  "crypto/sha256"
  "crypto/hmac"
  "crypto/aes"
  "crypto/cipher"
  "golang.org/x/crypto/pbkdf2"
)

func Decrypt(password string, data []byte) ([]byte, error) {
  version         := data[:1]
  options         := data[1:2]
  encSalt         := data[2:10]
  hmacSalt        := data[10:18]
  iv              := data[18:34]
  cipherText      := data[34:(len(data)-66+34)]
  expectedHmac    := data[len(data)-32:len(data)]

  msg := make([]byte, 0)
  msg = append(msg, version...)
  msg = append(msg, options...)
  msg = append(msg, encSalt...)
  msg = append(msg, hmacSalt...)
  msg = append(msg, iv...)
  msg = append(msg, cipherText...)

  hmacKey := pbkdf2.Key([]byte(password), hmacSalt, 10000, 32, sha1.New)
  testHmac := hmac.New(sha256.New, hmacKey)
  testHmac.Write(msg)
  testHmacVal := testHmac.Sum(nil)

  verified := bytes.Equal(testHmacVal, expectedHmac)

  if !verified {
    return nil, errors.New("Password may be incorrect, or the data has been corrupted. (HMAC could not be verified)")
  }

  cipherKey := pbkdf2.Key([]byte(password), encSalt, 10000, 32, sha1.New)
  cipherBlock, err := aes.NewCipher(cipherKey)
  if err != nil {
    return nil, err
  }

  decrypted := make([]byte, len(cipherText))
  copy(decrypted, cipherText)
  decrypter := cipher.NewCBCDecrypter(cipherBlock, iv)
  decrypter.CryptBlocks(decrypted, decrypted)

  length := len(decrypted)
  unpadding := int(decrypted[length-1])

  return decrypted[:(length - unpadding)], nil
}

func Encrypt(password string, data []byte) ([]byte, error) {
  encSalt, encSaltErr := RandBytes(8)
  if encSaltErr != nil {
    return nil, encSaltErr
  }

  hmacSalt, hmacSaltErr := RandBytes(8)
  if hmacSaltErr != nil {
    return nil, hmacSaltErr
  }

  iv, ivErr := RandBytes(16)
  if ivErr != nil {
    return nil, ivErr
  }

  encrypted, encErr := EncryptWithOptions(password, data, encSalt, hmacSalt, iv)
  if encErr != nil {
    return nil, encErr
  }
  return encrypted, nil
}

func EncryptWithOptions(password string, data, encSalt, hmacSalt, iv []byte) ([]byte, error) {
  if len(password) < 1 {
    return nil, errors.New("Password cannot be empty")
  }

  encKey := pbkdf2.Key([]byte(password), encSalt, 10000, 32, sha1.New)
  hmacKey := pbkdf2.Key([]byte(password), hmacSalt, 10000, 32, sha1.New)

  cipherText := make([]byte, len(data))
  copy(cipherText, data)

  version := byte(3)
  options := byte(1)

  msg := make([]byte, 0)
  msg = append(msg, version)
  msg = append(msg, options)
  msg = append(msg, encSalt...)
  msg = append(msg, hmacSalt...)
  msg = append(msg, iv...)

  cipherBlock, cipherBlockErr := aes.NewCipher(encKey)
  if cipherBlockErr != nil {
    return nil, cipherBlockErr
  }

  // padd text for encryption
  blockSize := cipherBlock.BlockSize()
  padding := blockSize - len(cipherText)%blockSize
  padText := bytes.Repeat([]byte{byte(padding)}, padding)
  cipherText = append(cipherText, padText...)

  encrypter := cipher.NewCBCEncrypter(cipherBlock, iv)
  encrypter.CryptBlocks(cipherText, cipherText)

  msg = append(msg, cipherText...)

  hmacSrc := hmac.New(sha256.New, hmacKey)
  hmacSrc.Write(msg)
  hmacVal := hmacSrc.Sum(nil)

  msg = append(msg, hmacVal...)

  return msg, nil
}

func RandBytes(num int64) ([]byte, error) {
  bits := make([]byte, num)
  _, err := rand.Read(bits)
  if err != nil {
    return nil, err
  }
  return bits, nil
}
<|endoftext|>


<commit_before>package route

import (
	"strings"

	"github.com/labstack/echo"
	"github.com/node4j/node-repo-proxy/npm"
)

func buildNpmUrl(c echo.Context) string {
	name := c.Param("name")
	version := c.Param("version")
	len := len(name + "-" + version)

	rest := c.Param("file")[len:]

	ext := strings.Join(strings.Split(rest, ".")[1:], ".")
	url := "https://registry.npmjs.org/" + name + "/-/" + name + "-" + version + "." + ext

	return url
}

/*
func npmFile(c echo.Context) error {
	url := buildNpmUrl(c)

	resp, err := http.Get(url)
	if err != nil {
		return err
	}

	return serveResp(c, resp)
}
*/

func npmFileRedirect(c echo.Context) error {
	url := buildNpmUrl(c)
	return serveRedirect(c, url)
}

/*
func npmFileHead(c echo.Context) error {
	url := buildNpmUrl(c)

	resp, err := http.Head(url)
	if err != nil {
		return err
	}

	return serveResp(c, resp)
}
*/

func npmMetaData(c echo.Context) error {
	name := c.Param("name")
	data, err := npm.GetMetaData(name)

	return serveMetaData(c, data, err)
}
<commit_msg>Alias for tar.gz => tgz in NPM<commit_after>package route

import (
	"strings"

	"github.com/labstack/echo"
	"github.com/node4j/node-repo-proxy/npm"
)

func buildNpmUrl(c echo.Context) string {
	name := c.Param("name")
	version := c.Param("version")
	len := len(name + "-" + version)

	rest := c.Param("file")[len:]

	ext := strings.Join(strings.Split(rest, ".")[1:], ".")
	if ext == "tar.gz" {
		ext = "tgz"
	}

	url := "https://registry.npmjs.org/" + name + "/-/" + name + "-" + version + "." + ext

	return url
}

/*
func npmFile(c echo.Context) error {
	url := buildNpmUrl(c)

	resp, err := http.Get(url)
	if err != nil {
		return err
	}

	return serveResp(c, resp)
}
*/

func npmFileRedirect(c echo.Context) error {
	url := buildNpmUrl(c)
	return serveRedirect(c, url)
}

/*
func npmFileHead(c echo.Context) error {
	url := buildNpmUrl(c)

	resp, err := http.Head(url)
	if err != nil {
		return err
	}

	return serveResp(c, resp)
}
*/

func npmMetaData(c echo.Context) error {
	name := c.Param("name")
	data, err := npm.GetMetaData(name)

	return serveMetaData(c, data, err)
}
<|endoftext|>


<commit_before>package main

import (
  "flag"
  "fmt"
  "os"
  "strings"
  "bufio"
  "path/filepath"

  "golang.org/x/crypto/ssh"
  "sprinter/sshlib"
)

var commandFlag = flag.String("command", "", "  Command")
var hostsFlag = flag.String("hosts", "", " Hosts")
var keyFlag = flag.String("key", "", " Key")
var userFlag = flag.String("user", "root", " User")
var portFlag = flag.Int("port", 22, " Port")

func init() {
  Hostsfile, err := filepath.Abs(filepath.Dir(os.Args[0]) + "/Hostsfile")
  if err != nil {
    fmt.Println(err)
  }
  flag.StringVar(commandFlag, "c", "", "  Command")
  flag.StringVar(hostsFlag, "h", Hostsfile, "  Hosts")
  flag.StringVar(keyFlag, "k", "", "  Key")
  flag.StringVar(userFlag, "u", "root", "  User")
  flag.IntVar(portFlag, "p", 22, "  Port")
}

var usage = `Usage: sprinter [options] <args>

Sprinter executes commands on systems by reading a file

-c, -command             Run command or commands: 'df-h','uname -a'
-h, -hosts, optional     Hosts file location, default is ./Hostsfile
-k, -key                 PEM key file location: ~/.ssh/key.pem
-u, -user, optional      Username to SSH as, default is root
-p, -port, optional      Port to SSH as, default is 22

Documentation:  https://github.com/marshyski/sprinter/blob/master/README.md

`

func main() {

  flag.Usage = func() {
    fmt.Println(usage)
  }

  flag.Parse()

  if *commandFlag == "" {
    fmt.Println(usage)
    os.Exit(1)
  }

  if *keyFlag == "" {
    fmt.Println(usage)
    os.Exit(1)
  }

  argsStr := strings.Split(*commandFlag, ",")
  argsList := make([]string, 0)

  for _, item := range argsStr {
    argsList = append(argsList, item)
  }

  if file, err := os.Open(*hostsFlag); err == nil {

    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {

      for _, item := range argsList {

        if err != nil {
          fmt.Println(item)
          fmt.Println(err)
          } else {

            sshConfig := &ssh.ClientConfig{
              User: *userFlag,
              Auth: []ssh.AuthMethod{
                sshlib.PublicKeyFile(*keyFlag),
              },
            }

            client := &sshlib.SSHClient{
              Config: sshConfig,
              Host:   scanner.Text(),
              Port:   *portFlag,
            }

            cmd := &sshlib.SSHCommand{
              Path:   item,
              Env:    []string{},
              Stdin:  os.Stdin,
              Stdout: os.Stdout,
              Stderr: os.Stderr,
            }

            fmt.Println()
            fmt.Println(scanner.Text(), cmd.Path)
            if err := client.RunCommand(cmd); err != nil {
              fmt.Println(os.Stderr, "command run error: %s\n", err)
              os.Exit(1)
            }
          }
        }
      }

      if err = scanner.Err(); err != nil {
        fmt.Println(err)
      }

      } else {
        fmt.Println(err)
      }
}
<commit_msg>Updated title usage<commit_after>package main

import (
  "flag"
  "fmt"
  "os"
  "strings"
  "bufio"
  "path/filepath"

  "golang.org/x/crypto/ssh"
  "sprinter/sshlib"
)

var commandFlag = flag.String("command", "", "  Command")
var hostsFlag = flag.String("hosts", "", " Hosts")
var keyFlag = flag.String("key", "", " Key")
var userFlag = flag.String("user", "root", " User")
var portFlag = flag.Int("port", 22, " Port")

func init() {
  Hostsfile, err := filepath.Abs(filepath.Dir(os.Args[0]) + "/Hostsfile")
  if err != nil {
    fmt.Println(err)
  }
  flag.StringVar(commandFlag, "c", "", "  Command")
  flag.StringVar(hostsFlag, "h", Hostsfile, "  Hosts")
  flag.StringVar(keyFlag, "k", "", "  Key")
  flag.StringVar(userFlag, "u", "root", "  User")
  flag.IntVar(portFlag, "p", 22, "  Port")
}

var usage = `Usage: sprinter [options] <args>

Sprinter executes SSH commands on systems by reading a file line by line

-c, -command             Run command or commands: 'df-h','uname -a'
-h, -hosts, optional     Hosts file location, default is ./Hostsfile
-k, -key                 PEM key file location: ~/.ssh/key.pem
-u, -user, optional      Username to SSH as, default is root
-p, -port, optional      Port to SSH as, default is 22

Documentation:  https://github.com/marshyski/sprinter/blob/master/README.md

`

func main() {

  flag.Usage = func() {
    fmt.Println(usage)
  }

  flag.Parse()

  if *commandFlag == "" {
    fmt.Println(usage)
    os.Exit(1)
  }

  if *keyFlag == "" {
    fmt.Println(usage)
    os.Exit(1)
  }

  argsStr := strings.Split(*commandFlag, ",")
  argsList := make([]string, 0)

  for _, item := range argsStr {
    argsList = append(argsList, item)
  }

  if file, err := os.Open(*hostsFlag); err == nil {

    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {

      for _, item := range argsList {

        if err != nil {
          fmt.Println(item)
          fmt.Println(err)
          } else {

            sshConfig := &ssh.ClientConfig{
              User: *userFlag,
              Auth: []ssh.AuthMethod{
                sshlib.PublicKeyFile(*keyFlag),
              },
            }

            client := &sshlib.SSHClient{
              Config: sshConfig,
              Host:   scanner.Text(),
              Port:   *portFlag,
            }

            cmd := &sshlib.SSHCommand{
              Path:   item,
              Env:    []string{},
              Stdin:  os.Stdin,
              Stdout: os.Stdout,
              Stderr: os.Stderr,
            }

            fmt.Println()
            fmt.Println(scanner.Text(), cmd.Path)
            if err := client.RunCommand(cmd); err != nil {
              fmt.Println(os.Stderr, "command run error: %s\n", err)
              os.Exit(1)
            }
          }
        }
      }

      if err = scanner.Err(); err != nil {
        fmt.Println(err)
      }

      } else {
        fmt.Println(err)
      }
}
<|endoftext|>


<commit_before>package sprite

import "container/list"
import "github.com/pnegre/gogame"

type glist []*Group

var infoSprites = make(map[Sprite]glist)

func registerSprite(sp Sprite, g *Group) {
	if gl, ok := infoSprites[sp]; ok {
		gl = append(gl, g)
		infoSprites[sp] = gl
		return
	}

	var gl glist
	gl = append(gl, g)
	infoSprites[sp] = gl
}

func KillFromAllGroups(s Sprite) {
	if gl, ok := infoSprites[s]; ok {
		for _, g := range gl {
			g.Remove(s)
		}
		delete(infoSprites, s)
	}

}

// Check if two sprites are colliding
func Collide(c1, c2 Sprite) bool {
	r1 := c1.GetRect()
	r2 := c2.GetRect()
	// TODO: check for pixel perfect collision. Check alpha components of the two textures
	return r1.Intersects(r2)
}

type Sprite interface {
	GetRect() *gogame.Rect
	Update()
	Draw()
}

type Group struct {
	sprList *list.List
}

func NewGroup() *Group {
	g := new(Group)
	g.sprList = list.New()
	return g
}

func (self *Group) Add(s Sprite) {
	self.sprList.PushFront(s)
	registerSprite(s, self)
}

func (self *Group) Len() int {
	return self.sprList.Len()
}

func (self *Group) GetElement(n int) Sprite {
	e := self.sprList.Front()
	i := 0
	for e != nil {
		if i == n {
			return e.Value.(Sprite)
		}
		e = e.Next()
		i++
	}
	return nil
}

func (self *Group) Update() {
	e := self.sprList.Front()
	for e != nil {
		s := e.Value.(Sprite)
		e = e.Next()
		s.Update()
	}
}

func (self *Group) Draw() {
	for e := self.sprList.Front(); e != nil; e = e.Next() {
		s := e.Value.(Sprite)
		s.Draw()
	}
}

func (self *Group) Remove(sp Sprite) {
	var le *list.Element
	for e := self.sprList.Front(); e != nil; e = e.Next() {
		s := e.Value.(Sprite)
		if s == sp {
			le = e
			break
		}
	}
	if le != nil {
		self.sprList.Remove(le)

	}

}

func (self *Group) CollideSpr(sp Sprite) (Sprite, bool) {
	for e := self.sprList.Front(); e != nil; e = e.Next() {
		s := e.Value.(Sprite)
		if Collide(s, sp) {
			return s, true
		}
	}
	return nil, false
}

// Check if any two of sprites of the two groups collide
// returns the sprites colliding (group1, group2)
func (self *Group) CollideGroup(g *Group) (Sprite, Sprite, bool) {
	for e := g.sprList.Front(); e != nil; e = e.Next() {
		s := e.Value.(Sprite)
		if ss, ok := self.CollideSpr(s); ok {
			return ss, s, true
		}
	}
	return nil, nil, false
}
<commit_msg>Adding commentaries...<commit_after>package sprite

import "container/list"
import "github.com/pnegre/gogame"

type glist []*Group

var infoSprites = make(map[Sprite]glist)

func registerSprite(sp Sprite, g *Group) {
	if gl, ok := infoSprites[sp]; ok {
		gl = append(gl, g)
		infoSprites[sp] = gl
		return
	}

	var gl glist
	gl = append(gl, g)
	infoSprites[sp] = gl
}

// Remove the sprite from all groups
func KillFromAllGroups(s Sprite) {
	if gl, ok := infoSprites[s]; ok {
		for _, g := range gl {
			g.Remove(s)
		}
		delete(infoSprites, s)
	}

}

// Check if two sprites are colliding, using rects
func Collide(c1, c2 Sprite) bool {
	r1 := c1.GetRect()
	r2 := c2.GetRect()
	// TODO: check for pixel perfect collision. Check alpha components of the two textures
	return r1.Intersects(r2)
}

type Sprite interface {
	GetRect() *gogame.Rect
	Update()
	Draw()
}

// Container to hold and manage multiple sprite objects
type Group struct {
	sprList *list.List
}

// Creates new container
func NewGroup() *Group {
	g := new(Group)
	g.sprList = list.New()
	return g
}

// Add new sprite to group
func (self *Group) Add(s Sprite) {
	self.sprList.PushFront(s)
	registerSprite(s, self)
}

// Get number of sprites of this group
func (self *Group) Len() int {
	return self.sprList.Len()
}

// Get sprite number "n" from this group
func (self *Group) GetElement(n int) Sprite {
	e := self.sprList.Front()
	i := 0
	for e != nil {
		if i == n {
			return e.Value.(Sprite)
		}
		e = e.Next()
		i++
	}
	return nil
}

// Calls the Update() method on all sprites in this group
func (self *Group) Update() {
	e := self.sprList.Front()
	for e != nil {
		s := e.Value.(Sprite)
		e = e.Next()
		s.Update()
	}
}

// Calls the Draw() method on all sprites in this group
func (self *Group) Draw() {
	for e := self.sprList.Front(); e != nil; e = e.Next() {
		s := e.Value.(Sprite)
		s.Draw()
	}
}

// Remove sprite from group
func (self *Group) Remove(sp Sprite) {
	var le *list.Element
	for e := self.sprList.Front(); e != nil; e = e.Next() {
		s := e.Value.(Sprite)
		if s == sp {
			le = e
			break
		}
	}
	if le != nil {
		self.sprList.Remove(le)

	}

}

// Find the sprite in this group that collides with the provided sprite.
func (self *Group) CollideSpr(sp Sprite) (Sprite, bool) {
	for e := self.sprList.Front(); e != nil; e = e.Next() {
		s := e.Value.(Sprite)
		if Collide(s, sp) {
			return s, true
		}
	}
	return nil, false
}

// Check if any two of sprites of the two groups collide
// returns the colliding sprites (group1, group2)
func (self *Group) CollideGroup(g *Group) (Sprite, Sprite, bool) {
	for e := g.sprList.Front(); e != nil; e = e.Next() {
		s := e.Value.(Sprite)
		if ss, ok := self.CollideSpr(s); ok {
			return ss, s, true
		}
	}
	return nil, nil, false
}
<|endoftext|>


<commit_before>//
// mod_http.go
//
// http mod panel
//

package srnd

import (
  "github.com/majestrate/srndv2/src/nacl"
  "github.com/gorilla/sessions"
  "errors"
  "encoding/hex"
  "encoding/json"
  "fmt"
  "io"
  "log"
  "net/http"
  "strings"
)

type httpModUI struct {
  regen func (ArticleEntry)
  delete func (string)
  modMessageChan chan NNTPMessage
  database Database
  articles ArticleStore
  store *sessions.CookieStore
  prefix string
  mod_prefix string
}

func createHttpModUI(frontend httpFrontend) httpModUI {
  return httpModUI{frontend.Regen, frontend.deleteThreadMarkup, make(chan NNTPMessage), frontend.daemon.database, frontend.daemon.store, frontend.store, frontend.prefix, frontend.prefix + "mod/"}

}
func (self httpModUI) getAdminFunc(funcname string) AdminFunc {
  if funcname == "template.reload" {
    return func(param map[string]interface{}) (string, error) {
      tname, ok := param["template"]
      t := ""
      switch tname.(type) {
      case string:
        t = tname.(string)
      default:
        return "failed to reload templates", errors.New("invalid parameters")
      }
      if ok {
        template.reloadTemplate(t)
        return "reloaded " + t, nil
      }
      template.reloadAllTemplates()
      return "reloaded all templates", nil
    }
  }
  return nil
}

// handle an admin action
func (self httpModUI) HandleAdminCommand(wr http.ResponseWriter, r *http.Request) {
  self.asAuthed(func(url string) {
    action := strings.Split(url, "/admin/")[1]
    f := self.getAdminFunc(action)
    if f == nil {
      wr.WriteHeader(404)
    } else {
      var msg string
      req := make(map[string]interface{})
      dec := json.NewDecoder(r.Body)
      err := dec.Decode(req)
      if err == nil {
        msg, err = f(req)
      }
      resp := make(map[string]interface{})
      resp["error"] = err.Error()
      resp["result"] = msg
      enc := json.NewEncoder(wr)
      enc.Encode(resp)
    }

  }, wr, r)
}

// TODO: check for different levels of permissions
func (self httpModUI) CheckKey(privkey string) (bool, error) {
  privkey_bytes, err := hex.DecodeString(privkey)
  if err == nil {
    pubkey_bytes := nacl.GetSignPubkey(privkey_bytes)
    if pubkey_bytes != nil {
      pubkey := hex.EncodeToString(pubkey_bytes)
      if self.database.CheckModPubkeyGlobal(pubkey) {
        // this user is an admin
        return true, nil
      } else {
        return false, nil
      }
    }
  }
  log.Println("invalid key format for key", privkey)
  return false, err
}

func (self httpModUI) MessageChan() chan NNTPMessage {
  return self.modMessageChan
}

func (self httpModUI) getSession(r *http.Request) *sessions.Session {
  s, _ := self.store.Get(r, "nntpchan-mod")
  return s
}

// get the session's private key as bytes or nil if we don't have it
func (self httpModUI) getSessionPrivkeyBytes(r *http.Request) []byte {
  s := self.getSession(r)
  k, ok := s.Values["privkey"]
  if ok {
    privkey_bytes, err := hex.DecodeString(k.(string))
    if err == nil {
      return privkey_bytes
    }
    log.Println("failed to decode private key bytes from session", err)
  } else {
    log.Println("failed to get private key from session, no private key in session?")
  }
  return nil
}

// returns true if the session is okay
// otherwise redirect to login page
func (self httpModUI) checkSession(r *http.Request) bool {
  s := self.getSession(r)
  k, ok := s.Values["privkey"]
  if ok {
    ok, err := self.CheckKey(k.(string))
    if err != nil {
      return false
    }
    return ok
  }
  return false
}

func (self httpModUI) writeTemplate(wr http.ResponseWriter, name string) {
  self.writeTemplateParam(wr, name, nil)
}

func (self httpModUI) writeTemplateParam(wr http.ResponseWriter, name string, param map[string]string) {
  if param == nil {
    param = make(map[string]string)
  }
  param["prefix"] = self.prefix
  param["mod_prefix"] = self.mod_prefix
  io.WriteString(wr, template.renderTemplate(name, param))
}

// do a function as authenticated
// pass in the request path to the handler
func (self httpModUI) asAuthed(handler func(string), wr http.ResponseWriter, r *http.Request) {
  if self.checkSession(r) {
    handler(r.URL.Path)
  } else {
    wr.WriteHeader(403)
  }
}

// do stuff to a certain message if with have it and are authed
func (self httpModUI) asAuthedWithMessage(handler func(ArticleEntry, *http.Request) map[string]interface{}, wr http.ResponseWriter, req *http.Request) {
  self.asAuthed(func(path string) {
    // get the long hash
    if strings.Count(path, "/") > 2 {
      // TOOD: prefix detection
      longhash := strings.Split(path, "/")[3]
      // get the message id
      msg, err := self.database.GetMessageIDByHash(longhash)
      resp := make(map[string]interface{})
      if err == nil {
        resp = handler(msg, req)
      } else {
        resp["error"] = fmt.Sprintf("don't have message with hash %s, %s", longhash, err.Error())
      }
      enc := json.NewEncoder(wr)
      enc.Encode(resp)
    } else {
      wr.WriteHeader(404)
    }
  }, wr, req)
}

func (self httpModUI) HandleAddPubkey(wr http.ResponseWriter, r *http.Request) {
}

func (self httpModUI) HandleDelPubkey(wr http.ResponseWriter, r *http.Request) {
}

func (self httpModUI) HandleUnbanAddress(wr http.ResponseWriter, r *http.Request) {
  self.asAuthed(func(path string) {
    // extract the ip address
    // TODO: ip ranges and prefix detection
    if strings.Count(path, "/") > 2 {
      addr := strings.Split(path, "/")[3]
      resp := make(map[string]interface{})
      banned, err := self.database.CheckIPBanned(addr)
      if err != nil {
        resp["error"] = fmt.Sprintf("cannot tell if %s is banned: %s", addr, err.Error())
      } else if banned {
        // TODO: rangebans
        err = self.database.UnbanAddr(addr)
        if err == nil {
          resp["result"] = fmt.Sprintf("%s was unbanned", addr)
        } else {
          resp["error"] = err.Error()
        }
      } else {
        resp["error"] = fmt.Sprintf("%s was not banned", addr)
      }
      enc := json.NewEncoder(wr)
      enc.Encode(resp)
    } else {
      wr.WriteHeader(404)
    }
  }, wr, r)
}

// handle ban logic
func (self httpModUI) handleBanAddress(msg ArticleEntry, r *http.Request) map[string]interface{} {
  // get the article headers
  resp := make(map[string]interface{})
  msgid := msg.MessageID()
  hdr := self.articles.GetHeaders(msgid)
  if hdr == nil {
    // we don't got it?!
    resp["error"] = fmt.Sprintf("message %s not on the filesystem wtf?", msgid)
  } else {
    // get the associated encrypted ip
    encip := hdr.Get("X-Encrypted-Ip", hdr.Get("X-Encrypted-IP", ""))
    encip = strings.Trim(encip, "\t ")

    if len(encip) == 0 {
      // no ip header detected
      resp["error"] = fmt.Sprintf("%s has no IP, ban Tor instead", msgid)
    } else {
      // get the ip address if we have it
      ip, err := self.database.GetIPAddress(encip)
      if len(ip) > 0 {
        // we have it
        // ban the address
        err = self.database.BanAddr(ip)
        // then we tell everyone about it
        var key string
        // TODO: we SHOULD have the key, but what if we do not?
        key, err = self.database.GetEncKey(encip)
        // create mod message
        // TODO: hardcoded ban period
        mm := ModMessage{overchanInetBan(encip, key, -1),}
        privkey_bytes := self.getSessionPrivkeyBytes(r)
        if privkey_bytes == nil {
          // this should not happen
          log.Println("failed to get privkey bytes from session")
          resp["error"] = "failed to get private key from session. wtf?"
        } else {
          // wrap and sign
          nntp := wrapModMessage(mm)
          nntp, err = signArticle(nntp, privkey_bytes)
          if err == nil {
            // federate
            self.modMessageChan <- nntp
          }
        }
      } else {
        // we don't have it
        // ban the encrypted version
        err = self.database.BanEncAddr(encip)
      }
      if err == nil {
        result_msg :=  fmt.Sprintf("We banned %s", encip)
        if len(ip) > 0 {
          result_msg += fmt.Sprintf(" (%s)", ip)
        }
        resp["banned"] = result_msg
      } else {
        resp["error"] = err.Error()
      }

    }
  }
  return resp
}

func (self httpModUI) handleDeletePost(msg ArticleEntry, r *http.Request) map[string]interface{} {
  var mm ModMessage
  resp := make(map[string]interface{})
  msgid := msg.MessageID()
  delmsgs := []string{}
  // get headers
  hdr := self.articles.GetHeaders(msgid)
  if hdr == nil {
    resp["error"] = fmt.Sprintf("message %s is not on the filesystem? wtf!", msgid)
  } else {
    ref := hdr.Get("References", hdr.Get("Reference", ""))
    ref = strings.Trim(ref, "\t ")
    // is it a root post?
    if ref == "" {
      // load replies
      replies := self.database.GetThreadReplies(msgid, 0)
      if replies != nil {
        for _, repl := range(replies) {
          // append mod line to mod message for reply
          mm = append(mm, overchanDelete(repl))
          // add to delete queue
          delmsgs = append(delmsgs, repl)
        }
      }
    }
    delmsgs = append(delmsgs, msgid)
    // append mod line to mod message
    mm = append(mm, overchanDelete(msgid))

    resp["deleted"] = delmsgs
    // only regen threads when we delete a non root port
    if ref != "" {
      group := hdr.Get("Newsgroups", "")
      self.regen(ArticleEntry{
        ref, group,
      })
    }
    privkey_bytes := self.getSessionPrivkeyBytes(r)
    if privkey_bytes == nil {
      // crap this should never happen
      log.Println("failed to get private keys from session, not federating")
    } else {
      // wrap and sign mod message
      nntp, err := signArticle(wrapModMessage(mm), privkey_bytes)
      if err == nil {
        // send it off to federate
        self.modMessageChan <- nntp
      } else {
        resp["error"] = fmt.Sprintf("signing error: %s", err.Error())
      }
    }
  }
  return resp
}

// ban the address of a poster
func (self httpModUI) HandleBanAddress(wr http.ResponseWriter, r *http.Request) {
  self.asAuthedWithMessage(self.handleBanAddress, wr, r)
}

// delete a post
func (self httpModUI) HandleDeletePost(wr http.ResponseWriter, r *http.Request) {
  self.asAuthedWithMessage(self.handleDeletePost, wr, r)
}

func (self httpModUI) HandleLogin(wr http.ResponseWriter, r *http.Request) {
  privkey := r.FormValue("privkey")
  msg := "failed login: "
  if len(privkey) == 0 {
    msg += "no key"
  } else {
    ok, err := self.CheckKey(privkey)
    if err != nil {
      msg += fmt.Sprintf("%s", err)
    } else if ok {
      msg = "login okay"
      sess := self.getSession(r)
      sess.Values["privkey"] = privkey
      sess.Save(r, wr)
    } else {
      msg += "invalid key"
    }
  }
  self.writeTemplateParam(wr, "modlogin_result.mustache", map[string]string { "message" : msg })
}

func (self httpModUI) HandleKeyGen(wr http.ResponseWriter, r *http.Request) {
  pk, sk := newSignKeypair()
  tripcode := makeTripcode(pk)
  self.writeTemplateParam(wr, "keygen.mustache", map[string]string {"public" : pk, "secret" : sk, "tripcode" : tripcode})
}

func (self httpModUI) ServeModPage(wr http.ResponseWriter, r *http.Request) {
  if self.checkSession(r) {
    // we are logged in
    // serve mod page
    self.writeTemplate(wr, "modpage.mustache")
  } else {
    // we are not logged in
    // serve login page
    self.writeTemplate(wr, "modlogin.mustache")
  }

}
<commit_msg>only decode request body when doing a POST<commit_after>//
// mod_http.go
//
// http mod panel
//

package srnd

import (
  "github.com/majestrate/srndv2/src/nacl"
  "github.com/gorilla/sessions"
  "errors"
  "encoding/hex"
  "encoding/json"
  "fmt"
  "io"
  "log"
  "net/http"
  "strings"
)

type httpModUI struct {
  regen func (ArticleEntry)
  delete func (string)
  modMessageChan chan NNTPMessage
  database Database
  articles ArticleStore
  store *sessions.CookieStore
  prefix string
  mod_prefix string
}

func createHttpModUI(frontend httpFrontend) httpModUI {
  return httpModUI{frontend.Regen, frontend.deleteThreadMarkup, make(chan NNTPMessage), frontend.daemon.database, frontend.daemon.store, frontend.store, frontend.prefix, frontend.prefix + "mod/"}

}
func (self httpModUI) getAdminFunc(funcname string) AdminFunc {
  if funcname == "template.reload" {
    return func(param map[string]interface{}) (string, error) {
      tname, ok := param["template"]
      t := ""
      switch tname.(type) {
      case string:
        t = tname.(string)
      default:
        return "failed to reload templates", errors.New("invalid parameters")
      }
      if ok {
        template.reloadTemplate(t)
        return "reloaded " + t, nil
      }
      template.reloadAllTemplates()
      return "reloaded all templates", nil
    }
  }
  return nil
}

// handle an admin action
func (self httpModUI) HandleAdminCommand(wr http.ResponseWriter, r *http.Request) {
  self.asAuthed(func(url string) {
    action := strings.Split(url, "/admin/")[1]
    f := self.getAdminFunc(action)
    if f == nil {
      wr.WriteHeader(404)
    } else {
      var msg string
      var err error
      req := make(map[string]interface{})
      if r.Method == "POST" {
        dec := json.NewDecoder(r.Body)
        err = dec.Decode(req)
      }
      if err == nil {
        msg, err = f(req)
      }
      resp := make(map[string]interface{})
      resp["error"] = err.Error()
      resp["result"] = msg
      enc := json.NewEncoder(wr)
      enc.Encode(resp)
    }

  }, wr, r)
}

// TODO: check for different levels of permissions
func (self httpModUI) CheckKey(privkey string) (bool, error) {
  privkey_bytes, err := hex.DecodeString(privkey)
  if err == nil {
    pubkey_bytes := nacl.GetSignPubkey(privkey_bytes)
    if pubkey_bytes != nil {
      pubkey := hex.EncodeToString(pubkey_bytes)
      if self.database.CheckModPubkeyGlobal(pubkey) {
        // this user is an admin
        return true, nil
      } else {
        return false, nil
      }
    }
  }
  log.Println("invalid key format for key", privkey)
  return false, err
}

func (self httpModUI) MessageChan() chan NNTPMessage {
  return self.modMessageChan
}

func (self httpModUI) getSession(r *http.Request) *sessions.Session {
  s, _ := self.store.Get(r, "nntpchan-mod")
  return s
}

// get the session's private key as bytes or nil if we don't have it
func (self httpModUI) getSessionPrivkeyBytes(r *http.Request) []byte {
  s := self.getSession(r)
  k, ok := s.Values["privkey"]
  if ok {
    privkey_bytes, err := hex.DecodeString(k.(string))
    if err == nil {
      return privkey_bytes
    }
    log.Println("failed to decode private key bytes from session", err)
  } else {
    log.Println("failed to get private key from session, no private key in session?")
  }
  return nil
}

// returns true if the session is okay
// otherwise redirect to login page
func (self httpModUI) checkSession(r *http.Request) bool {
  s := self.getSession(r)
  k, ok := s.Values["privkey"]
  if ok {
    ok, err := self.CheckKey(k.(string))
    if err != nil {
      return false
    }
    return ok
  }
  return false
}

func (self httpModUI) writeTemplate(wr http.ResponseWriter, name string) {
  self.writeTemplateParam(wr, name, nil)
}

func (self httpModUI) writeTemplateParam(wr http.ResponseWriter, name string, param map[string]string) {
  if param == nil {
    param = make(map[string]string)
  }
  param["prefix"] = self.prefix
  param["mod_prefix"] = self.mod_prefix
  io.WriteString(wr, template.renderTemplate(name, param))
}

// do a function as authenticated
// pass in the request path to the handler
func (self httpModUI) asAuthed(handler func(string), wr http.ResponseWriter, r *http.Request) {
  if self.checkSession(r) {
    handler(r.URL.Path)
  } else {
    wr.WriteHeader(403)
  }
}

// do stuff to a certain message if with have it and are authed
func (self httpModUI) asAuthedWithMessage(handler func(ArticleEntry, *http.Request) map[string]interface{}, wr http.ResponseWriter, req *http.Request) {
  self.asAuthed(func(path string) {
    // get the long hash
    if strings.Count(path, "/") > 2 {
      // TOOD: prefix detection
      longhash := strings.Split(path, "/")[3]
      // get the message id
      msg, err := self.database.GetMessageIDByHash(longhash)
      resp := make(map[string]interface{})
      if err == nil {
        resp = handler(msg, req)
      } else {
        resp["error"] = fmt.Sprintf("don't have message with hash %s, %s", longhash, err.Error())
      }
      enc := json.NewEncoder(wr)
      enc.Encode(resp)
    } else {
      wr.WriteHeader(404)
    }
  }, wr, req)
}

func (self httpModUI) HandleAddPubkey(wr http.ResponseWriter, r *http.Request) {
}

func (self httpModUI) HandleDelPubkey(wr http.ResponseWriter, r *http.Request) {
}

func (self httpModUI) HandleUnbanAddress(wr http.ResponseWriter, r *http.Request) {
  self.asAuthed(func(path string) {
    // extract the ip address
    // TODO: ip ranges and prefix detection
    if strings.Count(path, "/") > 2 {
      addr := strings.Split(path, "/")[3]
      resp := make(map[string]interface{})
      banned, err := self.database.CheckIPBanned(addr)
      if err != nil {
        resp["error"] = fmt.Sprintf("cannot tell if %s is banned: %s", addr, err.Error())
      } else if banned {
        // TODO: rangebans
        err = self.database.UnbanAddr(addr)
        if err == nil {
          resp["result"] = fmt.Sprintf("%s was unbanned", addr)
        } else {
          resp["error"] = err.Error()
        }
      } else {
        resp["error"] = fmt.Sprintf("%s was not banned", addr)
      }
      enc := json.NewEncoder(wr)
      enc.Encode(resp)
    } else {
      wr.WriteHeader(404)
    }
  }, wr, r)
}

// handle ban logic
func (self httpModUI) handleBanAddress(msg ArticleEntry, r *http.Request) map[string]interface{} {
  // get the article headers
  resp := make(map[string]interface{})
  msgid := msg.MessageID()
  hdr := self.articles.GetHeaders(msgid)
  if hdr == nil {
    // we don't got it?!
    resp["error"] = fmt.Sprintf("message %s not on the filesystem wtf?", msgid)
  } else {
    // get the associated encrypted ip
    encip := hdr.Get("X-Encrypted-Ip", hdr.Get("X-Encrypted-IP", ""))
    encip = strings.Trim(encip, "\t ")

    if len(encip) == 0 {
      // no ip header detected
      resp["error"] = fmt.Sprintf("%s has no IP, ban Tor instead", msgid)
    } else {
      // get the ip address if we have it
      ip, err := self.database.GetIPAddress(encip)
      if len(ip) > 0 {
        // we have it
        // ban the address
        err = self.database.BanAddr(ip)
        // then we tell everyone about it
        var key string
        // TODO: we SHOULD have the key, but what if we do not?
        key, err = self.database.GetEncKey(encip)
        // create mod message
        // TODO: hardcoded ban period
        mm := ModMessage{overchanInetBan(encip, key, -1),}
        privkey_bytes := self.getSessionPrivkeyBytes(r)
        if privkey_bytes == nil {
          // this should not happen
          log.Println("failed to get privkey bytes from session")
          resp["error"] = "failed to get private key from session. wtf?"
        } else {
          // wrap and sign
          nntp := wrapModMessage(mm)
          nntp, err = signArticle(nntp, privkey_bytes)
          if err == nil {
            // federate
            self.modMessageChan <- nntp
          }
        }
      } else {
        // we don't have it
        // ban the encrypted version
        err = self.database.BanEncAddr(encip)
      }
      if err == nil {
        result_msg :=  fmt.Sprintf("We banned %s", encip)
        if len(ip) > 0 {
          result_msg += fmt.Sprintf(" (%s)", ip)
        }
        resp["banned"] = result_msg
      } else {
        resp["error"] = err.Error()
      }

    }
  }
  return resp
}

func (self httpModUI) handleDeletePost(msg ArticleEntry, r *http.Request) map[string]interface{} {
  var mm ModMessage
  resp := make(map[string]interface{})
  msgid := msg.MessageID()
  delmsgs := []string{}
  // get headers
  hdr := self.articles.GetHeaders(msgid)
  if hdr == nil {
    resp["error"] = fmt.Sprintf("message %s is not on the filesystem? wtf!", msgid)
  } else {
    ref := hdr.Get("References", hdr.Get("Reference", ""))
    ref = strings.Trim(ref, "\t ")
    // is it a root post?
    if ref == "" {
      // load replies
      replies := self.database.GetThreadReplies(msgid, 0)
      if replies != nil {
        for _, repl := range(replies) {
          // append mod line to mod message for reply
          mm = append(mm, overchanDelete(repl))
          // add to delete queue
          delmsgs = append(delmsgs, repl)
        }
      }
    }
    delmsgs = append(delmsgs, msgid)
    // append mod line to mod message
    mm = append(mm, overchanDelete(msgid))

    resp["deleted"] = delmsgs
    // only regen threads when we delete a non root port
    if ref != "" {
      group := hdr.Get("Newsgroups", "")
      self.regen(ArticleEntry{
        ref, group,
      })
    }
    privkey_bytes := self.getSessionPrivkeyBytes(r)
    if privkey_bytes == nil {
      // crap this should never happen
      log.Println("failed to get private keys from session, not federating")
    } else {
      // wrap and sign mod message
      nntp, err := signArticle(wrapModMessage(mm), privkey_bytes)
      if err == nil {
        // send it off to federate
        self.modMessageChan <- nntp
      } else {
        resp["error"] = fmt.Sprintf("signing error: %s", err.Error())
      }
    }
  }
  return resp
}

// ban the address of a poster
func (self httpModUI) HandleBanAddress(wr http.ResponseWriter, r *http.Request) {
  self.asAuthedWithMessage(self.handleBanAddress, wr, r)
}

// delete a post
func (self httpModUI) HandleDeletePost(wr http.ResponseWriter, r *http.Request) {
  self.asAuthedWithMessage(self.handleDeletePost, wr, r)
}

func (self httpModUI) HandleLogin(wr http.ResponseWriter, r *http.Request) {
  privkey := r.FormValue("privkey")
  msg := "failed login: "
  if len(privkey) == 0 {
    msg += "no key"
  } else {
    ok, err := self.CheckKey(privkey)
    if err != nil {
      msg += fmt.Sprintf("%s", err)
    } else if ok {
      msg = "login okay"
      sess := self.getSession(r)
      sess.Values["privkey"] = privkey
      sess.Save(r, wr)
    } else {
      msg += "invalid key"
    }
  }
  self.writeTemplateParam(wr, "modlogin_result.mustache", map[string]string { "message" : msg })
}

func (self httpModUI) HandleKeyGen(wr http.ResponseWriter, r *http.Request) {
  pk, sk := newSignKeypair()
  tripcode := makeTripcode(pk)
  self.writeTemplateParam(wr, "keygen.mustache", map[string]string {"public" : pk, "secret" : sk, "tripcode" : tripcode})
}

func (self httpModUI) ServeModPage(wr http.ResponseWriter, r *http.Request) {
  if self.checkSession(r) {
    // we are logged in
    // serve mod page
    self.writeTemplate(wr, "modpage.mustache")
  } else {
    // we are not logged in
    // serve login page
    self.writeTemplate(wr, "modlogin.mustache")
  }

}
<|endoftext|>


<commit_before>package registry

import (
	"fmt"
	"net/http"
	"net/url"
)

// HubService gives acess to the Docker Hub API used to retrieve authentication tokens.
type HubService struct {
	client *Client
}

// Retrieves a read-only token from the Docker Hub for the specified repo.
func (h *HubService) GetReadToken(repo string) (*TokenAuth, error) {
	path := fmt.Sprintf("repositories/%s/images", repo)
	req, err := h.newRequest("GET", path, NilAuth{})
	if err != nil {
		return nil, err
	}

	return h.do(req)
}

// Retrieves a write-only token from the Docker Hub for the specified repo. The
// auth argument should be the user's basic auth credentials.
func (h *HubService) GetWriteToken(repo string, auth Authenticator) (*TokenAuth, error) {
	path := fmt.Sprintf("repositories/%s/", repo)
	req, err := h.newRequest("PUT", path, auth)
	if err != nil {
		return nil, err
	}

	return h.do(req)
}

// Retrieves a write-only token from the Docker Hub for the specified repo. The
// auth argument should be the user's basic auth credentials.
func (h *HubService) GetDeleteToken(repo string, auth Authenticator) (*TokenAuth, error) {
	path := fmt.Sprintf("repositories/%s/", repo)
	req, err := h.newRequest("DELETE", path, auth)
	if err != nil {
		return nil, err
	}

	return h.do(req)
}

func (h *HubService) newRequest(method, urlStr string, auth Authenticator) (*http.Request, error) {
	req, err := h.client.newRequest(method, urlStr, auth, []string{})
	if err != nil {
		return nil, err
	}

	req.Header.Add("X-Docker-Token", "true")

	return req, nil
}

func (h *HubService) do(req *http.Request) (*TokenAuth, error) {
	res, err := h.client.do(req, nil)
	if err != nil {
		return nil, err
	}

	token := &TokenAuth{Token: res.Header.Get("X-Docker-Token")}

	switch req.Method {
	case "GET":
		token.Access = Read
	case "PUT":
		token.Access = Write
	case "DELETE":
		token.Access = Delete
	}

	// Need to parse the value cause different requests return the endpoints
	// in different formats
	url, _ := url.Parse(res.Header.Get("X-Docker-Endpoints"))
	if url.Host != "" {
		token.Host = url.Host
	} else {
		token.Host = url.Path
	}

	return token, nil
}
<commit_msg>adds a Hub.GetReadTokenWithAuth<commit_after>package registry

import (
	"fmt"
	"net/http"
	"net/url"
)

// HubService gives acess to the Docker Hub API used to retrieve authentication tokens.
type HubService struct {
	client *Client
}

// Retrieves a read-only token from the Docker Hub for the specified repo.
func (h *HubService) GetReadToken(repo string) (*TokenAuth, error) {
	path := fmt.Sprintf("repositories/%s/images", repo)
	req, err := h.newRequest("GET", path, NilAuth{})
	if err != nil {
		return nil, err
	}

	return h.do(req)
}

// Retrieves a read-only token from the Docker Hub for the specified private repo.
func (h *HubService) GetReadTokenWithAuth(repo string, auth Authenticator) (*TokenAuth, error) {
	path := fmt.Sprintf("repositories/%s/images", repo)
	req, err := h.newRequest("GET", path, auth)
	if err != nil {
		return nil, err
	}

	return h.do(req)
}

// Retrieves a write-only token from the Docker Hub for the specified repo. The
// auth argument should be the user's basic auth credentials.
func (h *HubService) GetWriteToken(repo string, auth Authenticator) (*TokenAuth, error) {
	path := fmt.Sprintf("repositories/%s/", repo)
	req, err := h.newRequest("PUT", path, auth)
	if err != nil {
		return nil, err
	}

	return h.do(req)
}

// Retrieves a write-only token from the Docker Hub for the specified repo. The
// auth argument should be the user's basic auth credentials.
func (h *HubService) GetDeleteToken(repo string, auth Authenticator) (*TokenAuth, error) {
	path := fmt.Sprintf("repositories/%s/", repo)
	req, err := h.newRequest("DELETE", path, auth)
	if err != nil {
		return nil, err
	}

	return h.do(req)
}

func (h *HubService) newRequest(method, urlStr string, auth Authenticator) (*http.Request, error) {
	req, err := h.client.newRequest(method, urlStr, auth, []string{})
	if err != nil {
		return nil, err
	}

	req.Header.Add("X-Docker-Token", "true")

	return req, nil
}

func (h *HubService) do(req *http.Request) (*TokenAuth, error) {
	res, err := h.client.do(req, nil)
	if err != nil {
		return nil, err
	}

	token := &TokenAuth{Token: res.Header.Get("X-Docker-Token")}

	switch req.Method {
	case "GET":
		token.Access = Read
	case "PUT":
		token.Access = Write
	case "DELETE":
		token.Access = Delete
	}

	// Need to parse the value cause different requests return the endpoints
	// in different formats
	url, _ := url.Parse(res.Header.Get("X-Docker-Endpoints"))
	if url.Host != "" {
		token.Host = url.Host
	} else {
		token.Host = url.Path
	}

	return token, nil
}
<|endoftext|>