<commit_before>package main

import (
	"errors"
	"github.com/abcum/lcp"
	"go/ast"
	"go/parser"
	"go/token"
	"regexp"
	"strings"
	"text/template"
)

var displayNameRegExp = regexp.MustCompile(`display:\"(.*)\"`)
var transformUpperRegExp = regexp.MustCompile(`(?i)transform:\s*upper`)
var transformLowerRegExp = regexp.MustCompile(`(?i)transform:\s*lower`)
var transformNoneRegExp = regexp.MustCompile(`(?i)transform:\s*none`)

var enumHeaderTemplate *template.Template
var enumItemTemplate *template.Template

func firstLetter(in string) string {

	if in == "" {
		return ""
	}

	return strings.ToLower(in[:1])
}

func init() {

	funcMap := template.FuncMap{
		"firstLetter": firstLetter,
	}

	enumHeaderTemplate = template.Must(template.New("enumheader").Funcs(funcMap).Parse(enumHeaderTemplateText))
	enumItemTemplate = template.Must(template.New("enumitem").Parse(enumItemTemplateText))

}

type transform int

const (
	transformNone transform = iota
	transformUpper
	transformLower
)

type enum struct {
	PackageName string
	Values      []string
	//OverrideDisplayName contains a map of the Value string to override
	//value, if it exists. If it is in the map with value "" then it has been
	//overridden to have that value. If it is not in the map then it should be
	//default.
	OverrideDisplayName map[string]string
	Transform           map[string]transform
	DefaultTransform    transform
}

//findDelegateName looks through the given package to find the name of the
//struct that appears to represent the gameDelegate type, and returns its name.
func findDelegateName(packageASTs map[string]*ast.Package) ([]string, error) {

	var result []string

	for _, theAST := range packageASTs {
		for _, file := range theAST.Files {
			for _, decl := range file.Decls {

				//We're looking for function declarations like func (g
				//*gameDelegate) ConfigureMoves()
				//*boardgame.MoveTypeConfigBundle.

				funDecl, ok := decl.(*ast.FuncDecl)

				//Guess this decl wasn't a fun.
				if !ok {
					continue
				}

				if funDecl.Name.Name != "ConfigureMoves" {
					continue
				}

				if funDecl.Type.Params.NumFields() != 0 {
					continue
				}

				if funDecl.Type.Results.NumFields() != 1 {
					continue
				}

				returnFieldStar, ok := funDecl.Type.Results.List[0].Type.(*ast.StarExpr)

				if !ok {
					//OK, doesn't return a pointer, can't be a match.
					continue
				}

				returnFieldSelector, ok := returnFieldStar.X.(*ast.SelectorExpr)

				if !ok {
					//OK, there's no boardgame...
					continue
				}

				if returnFieldSelector.Sel.Name != "MoveTypeConfigBundle" {
					continue
				}

				returnFieldSelectorPackage, ok := returnFieldSelector.X.(*ast.Ident)

				if !ok {
					continue
				}

				if returnFieldSelectorPackage.Name != "boardgame" {
					continue
				}

				//TODO: verify the one return type is boardgame.MoveTypeConfigBundle

				if funDecl.Recv == nil || funDecl.Recv.NumFields() != 1 {
					//Verify i
					continue
				}

				//OK, it appears to be the right method. Extract out information about it.

				starExp, ok := funDecl.Recv.List[0].Type.(*ast.StarExpr)

				if !ok {
					return nil, errors.New("Couldn't cast candidate to star exp")
				}

				ident, ok := starExp.X.(*ast.Ident)

				if !ok {
					return nil, errors.New("Rest of star expression wasn't an ident")
				}

				result = append(result, ident.Name)

			}
		}
	}

	return result, nil
}

//filterDelegateNames takes delegate names we may want to export, and filters
//out any that already have a ConfigureEnums outputted.
func filterDelegateNames(candidates []string, packageASTs map[string]*ast.Package) []string {

	candidateMap := make(map[string]bool, len(candidates))

	for _, candidate := range candidates {
		candidateMap[candidate] = true
	}

	//Look through packageASTs and set to false any that we find a ConfigureEnums for.

	for _, theAST := range packageASTs {
		for _, file := range theAST.Files {

			//If the file was auto-generated by auto-enum (which by default is
			//at auto_enum.go but could be anywhere) then those definitions
			//don't count as manual definitions.
			if len(file.Comments) > 0 && strings.Contains(file.Comments[0].Text(), "It was generated by autoreader.") {
				continue
			}

			for _, decl := range file.Decls {

				//We're looking for function declarations like func (g
				//*gameDelegate) ConfigureMoves()
				//*boardgame.MoveTypeConfigBundle.

				funDecl, ok := decl.(*ast.FuncDecl)

				//Guess this decl wasn't a fun.
				if !ok {
					continue
				}

				if funDecl.Name.Name != "ConfigureEnums" {
					continue
				}

				if funDecl.Type.Params.NumFields() != 0 {
					continue
				}

				if funDecl.Type.Results.NumFields() != 1 {
					continue
				}

				returnFieldStar, ok := funDecl.Type.Results.List[0].Type.(*ast.StarExpr)

				if !ok {
					//OK, doesn't return a pointer, can't be a match.
					continue
				}

				returnFieldSelector, ok := returnFieldStar.X.(*ast.SelectorExpr)

				if !ok {
					//OK, there's no boardgame...
					continue
				}

				if returnFieldSelector.Sel.Name != "Set" {
					continue
				}

				returnFieldSelectorPackage, ok := returnFieldSelector.X.(*ast.Ident)

				if !ok {
					continue
				}

				if returnFieldSelectorPackage.Name != "enum" {
					continue
				}

				if funDecl.Recv == nil || funDecl.Recv.NumFields() != 1 {
					//Verify i
					continue
				}

				//OK, it appears to be the right method. Extract out information about it.

				starExp, ok := funDecl.Recv.List[0].Type.(*ast.StarExpr)

				if !ok {
					//Not expected, but whatever, it's safe to just include it
					continue
				}

				ident, ok := starExp.X.(*ast.Ident)

				if !ok {
					//Not expected, but whatever, it's safe to just include it
					continue
				}

				//If that struct type were one of the things we would export,
				//then note not to export it. If it wasn't already in, it
				//doesn't hurt to affirmatively say not to export it.
				candidateMap[ident.Name] = false

			}
		}
	}

	var result []string

	for name, include := range candidateMap {
		if !include {
			continue
		}
		result = append(result, name)
	}

	return result

}

//findEnums processes the package at packageName and returns a list of enums
//that should be processed (that is, they have the magic comment)
func findEnums(packageASTs map[string]*ast.Package) (enums []*enum, err error) {

	for packageName, theAST := range packageASTs {
		for _, file := range theAST.Files {
			for _, decl := range file.Decls {
				genDecl, ok := decl.(*ast.GenDecl)

				if !ok {
					//Guess it wasn't a genDecl at all.
					continue
				}

				if genDecl.Tok != token.CONST {
					//We're only interested in Const decls.
					continue
				}

				if !enumConfig(genDecl.Doc.Text()) {
					//Must not have found the magic comment in the docs.
					continue
				}

				defaultTransform := configTransform(genDecl.Doc.Text(), transformNone)

				theEnum := &enum{
					PackageName:         packageName,
					OverrideDisplayName: make(map[string]string),
					Transform:           make(map[string]transform),
					DefaultTransform:    defaultTransform,
				}

				for _, spec := range genDecl.Specs {

					valueSpec, ok := spec.(*ast.ValueSpec)

					if !ok {
						//Guess it wasn't a valueSpec after all!
						continue
					}

					if len(valueSpec.Names) != 1 {
						return nil, errors.New("Found an enum that had more than one name on a line. That's not allowed for now.")
					}

					valueName := valueSpec.Names[0].Name

					theEnum.Values = append(theEnum.Values, valueName)

					if hasOverride, displayName := overrideDisplayname(valueSpec.Doc.Text()); hasOverride {
						theEnum.OverrideDisplayName[valueName] = displayName
					}

					theEnum.Transform[valueName] = configTransform(valueSpec.Doc.Text(), defaultTransform)

				}

				if len(theEnum.Values) > 0 {
					enums = append(enums, theEnum)
				}

			}
		}
	}

	return enums, nil
}

//outputForEnums takes the found enums and produces the output string
//representing the un-formatted go code to generate for those enums.
func outputForEnums(enums []*enum, delegateNames []string) (enumOutput string, err error) {
	for _, enum := range enums {

		if enumOutput == "" {
			enumOutput = enumHeaderForPackage(enum.PackageName, delegateNames)
		}

		var literals [][]byte

		for _, literal := range enum.Values {
			if !fieldNamePublic(literal) {
				continue
			}
			literals = append(literals, []byte(literal))
		}

		if len(literals) == 0 {
			return "", errors.New("No public literals in enum")
		}

		prefix := string(lcp.LCP(literals...))

		if len(prefix) == 0 {
			return "", errors.New("Enum with autoreader configured didn't have a common prefix.")
		}

		values := make(map[string]string, len(literals))

		i := 0

		for _, literal := range enum.Values {
			if !strings.HasPrefix(literal, prefix) {
				return "", errors.New("enum literal didn't have prefix we thought it did")
			}

			//If there's an override deisplay name, use that
			displayName, ok := enum.OverrideDisplayName[literal]

			//If there wasn't an override, do the default. Note that an
			//override "" that is in the map is legal.
			if !ok {
				displayName = titleCaseToWords(strings.Replace(literal, prefix, "", -1))

				switch enum.Transform[literal] {
				case transformLower:
					displayName = strings.ToLower(displayName)
				case transformUpper:
					displayName = strings.ToUpper(displayName)
				}
			}

			values[literal] = displayName
			i++
		}

		enumOutput += enumItem(prefix, values)

	}

	return enumOutput, nil
}

var titleCaseReplacer *strings.Replacer

//titleCaseToWords writes "ATitleCaseString" to "A Title Case String"
func titleCaseToWords(in string) string {

	//substantially recreated in moves/base.go

	if titleCaseReplacer == nil {

		var replacements []string

		for r := 'A'; r <= 'Z'; r++ {
			str := string(r)
			replacements = append(replacements, str)
			replacements = append(replacements, " "+str)
		}

		titleCaseReplacer = strings.NewReplacer(replacements...)

	}

	return strings.TrimSpace(titleCaseReplacer.Replace(in))

}

func processEnums(packageName string) (enumOutput string, err error) {

	packageASTs, err := parser.ParseDir(token.NewFileSet(), packageName, nil, parser.ParseComments)

	if err != nil {
		return "", errors.New("Parse error: " + err.Error())
	}

	enums, err := findEnums(packageASTs)

	if err != nil {
		return "", errors.New("Couldn't parse for enums: " + err.Error())
	}

	if len(enums) == 0 {
		//No enums. That's totally legit.
		return "", nil
	}

	delegateNames, err := findDelegateName(packageASTs)

	if err != nil {
		return "", errors.New("Failed to find delegate name: " + err.Error())
	}

	filteredDelegateNames := filterDelegateNames(delegateNames, packageASTs)

	output, err := outputForEnums(enums, filteredDelegateNames)

	if err != nil {
		return "", errors.New("Couldn't generate output for enums: " + err.Error())
	}

	return output, nil

}

func enumConfig(docLines string) bool {

	for _, docLine := range strings.Split(docLines, "\n") {
		docLine = strings.ToLower(docLine)
		docLine = strings.TrimPrefix(docLine, "//")
		docLine = strings.TrimSpace(docLine)
		if strings.HasPrefix(docLine, magicDocLinePrefix) {
			return true
		}
	}

	return false
}

func configTransform(docLines string, defaultTransform transform) transform {
	for _, line := range strings.Split(docLines, "\n") {
		if transformLowerRegExp.MatchString(line) {
			return transformLower
		}
		if transformUpperRegExp.MatchString(line) {
			return transformUpper
		}
		if transformNoneRegExp.MatchString(line) {
			return transformNone
		}
	}

	return defaultTransform
}

func overrideDisplayname(docLines string) (hasOverride bool, displayName string) {
	for _, line := range strings.Split(docLines, "\n") {
		result := displayNameRegExp.FindStringSubmatch(line)

		if len(result) == 0 {
			continue
		}

		if len(result[0]) == 0 {
			continue
		}
		if len(result) != 2 {
			continue
		}

		//Found it! Even if the matched expression is "", that's fine. if
		//there are quoted strings that's fine, because that's exactly how
		//they should be output at the end.
		return true, result[1]

	}

	return false, ""
}

func enumHeaderForPackage(packageName string, delegateNames []string) string {

	return templateOutput(enumHeaderTemplate, map[string]interface{}{
		"packageName":   packageName,
		"delegateNames": delegateNames,
	})
}

func enumItem(prefix string, values map[string]string) string {
	return templateOutput(enumItemTemplate, map[string]interface{}{
		"prefix": prefix,
		"values": values,
	})
}

const enumHeaderTemplateText = `/************************************
 *
 * This file contains auto-generated methods to help configure enums.
 * It was generated by autoreader.
 *
 * DO NOT EDIT by hand.
 *
 ************************************/

package {{.packageName}}

import (
	"github.com/jkomoros/boardgame/enum"
)

var Enums = enum.NewSet()

{{range $delegateName := .delegateNames -}}
//ConfigureEnums simply returns Enums, the auto-generated Enums variable. This
//is output because {{$delegateName}} appears to be a struct that implements
//boardgame.GameDelegate, and does not already have a ConfigureEnums
//explicitly defined.
func ({{firstLetter $delegateName}} *{{$delegateName}}) ConfigureEnums() *enum.Set {
	return Enums
}

{{end}}

`

const enumItemTemplateText = `var {{.prefix}}Enum = Enums.MustAdd("{{.prefix}}", map[int]string{
	{{ $prefix := .prefix -}}
	{{range $name, $value := .values -}}
	{{$name}}: "{{$value}}",
	{{end}}
})

`
<commit_msg>Ensure a deterministic output for multiple delegates. Fixes #593.<commit_after>package main

import (
	"errors"
	"github.com/abcum/lcp"
	"go/ast"
	"go/parser"
	"go/token"
	"regexp"
	"sort"
	"strings"
	"text/template"
)

var displayNameRegExp = regexp.MustCompile(`display:\"(.*)\"`)
var transformUpperRegExp = regexp.MustCompile(`(?i)transform:\s*upper`)
var transformLowerRegExp = regexp.MustCompile(`(?i)transform:\s*lower`)
var transformNoneRegExp = regexp.MustCompile(`(?i)transform:\s*none`)

var enumHeaderTemplate *template.Template
var enumDelegateTemplate *template.Template
var enumItemTemplate *template.Template

func firstLetter(in string) string {

	if in == "" {
		return ""
	}

	return strings.ToLower(in[:1])
}

func init() {

	funcMap := template.FuncMap{
		"firstLetter": firstLetter,
	}

	enumHeaderTemplate = template.Must(template.New("enumheader").Funcs(funcMap).Parse(enumHeaderTemplateText))
	enumDelegateTemplate = template.Must(template.New("enumdelegate").Funcs(funcMap).Parse(enumDelegateTemplateText))
	enumItemTemplate = template.Must(template.New("enumitem").Parse(enumItemTemplateText))

}

type transform int

const (
	transformNone transform = iota
	transformUpper
	transformLower
)

type enum struct {
	PackageName string
	Values      []string
	//OverrideDisplayName contains a map of the Value string to override
	//value, if it exists. If it is in the map with value "" then it has been
	//overridden to have that value. If it is not in the map then it should be
	//default.
	OverrideDisplayName map[string]string
	Transform           map[string]transform
	DefaultTransform    transform
}

//findDelegateName looks through the given package to find the name of the
//struct that appears to represent the gameDelegate type, and returns its name.
func findDelegateName(packageASTs map[string]*ast.Package) ([]string, error) {

	var result []string

	for _, theAST := range packageASTs {
		for _, file := range theAST.Files {
			for _, decl := range file.Decls {

				//We're looking for function declarations like func (g
				//*gameDelegate) ConfigureMoves()
				//*boardgame.MoveTypeConfigBundle.

				funDecl, ok := decl.(*ast.FuncDecl)

				//Guess this decl wasn't a fun.
				if !ok {
					continue
				}

				if funDecl.Name.Name != "ConfigureMoves" {
					continue
				}

				if funDecl.Type.Params.NumFields() != 0 {
					continue
				}

				if funDecl.Type.Results.NumFields() != 1 {
					continue
				}

				returnFieldStar, ok := funDecl.Type.Results.List[0].Type.(*ast.StarExpr)

				if !ok {
					//OK, doesn't return a pointer, can't be a match.
					continue
				}

				returnFieldSelector, ok := returnFieldStar.X.(*ast.SelectorExpr)

				if !ok {
					//OK, there's no boardgame...
					continue
				}

				if returnFieldSelector.Sel.Name != "MoveTypeConfigBundle" {
					continue
				}

				returnFieldSelectorPackage, ok := returnFieldSelector.X.(*ast.Ident)

				if !ok {
					continue
				}

				if returnFieldSelectorPackage.Name != "boardgame" {
					continue
				}

				//TODO: verify the one return type is boardgame.MoveTypeConfigBundle

				if funDecl.Recv == nil || funDecl.Recv.NumFields() != 1 {
					//Verify i
					continue
				}

				//OK, it appears to be the right method. Extract out information about it.

				starExp, ok := funDecl.Recv.List[0].Type.(*ast.StarExpr)

				if !ok {
					return nil, errors.New("Couldn't cast candidate to star exp")
				}

				ident, ok := starExp.X.(*ast.Ident)

				if !ok {
					return nil, errors.New("Rest of star expression wasn't an ident")
				}

				result = append(result, ident.Name)

			}
		}
	}

	return result, nil
}

//filterDelegateNames takes delegate names we may want to export, and filters
//out any that already have a ConfigureEnums outputted.
func filterDelegateNames(candidates []string, packageASTs map[string]*ast.Package) []string {

	candidateMap := make(map[string]bool, len(candidates))

	for _, candidate := range candidates {
		candidateMap[candidate] = true
	}

	//Look through packageASTs and set to false any that we find a ConfigureEnums for.

	for _, theAST := range packageASTs {
		for _, file := range theAST.Files {

			//If the file was auto-generated by auto-enum (which by default is
			//at auto_enum.go but could be anywhere) then those definitions
			//don't count as manual definitions.
			if len(file.Comments) > 0 && strings.Contains(file.Comments[0].Text(), "It was generated by autoreader.") {
				continue
			}

			for _, decl := range file.Decls {

				//We're looking for function declarations like func (g
				//*gameDelegate) ConfigureMoves()
				//*boardgame.MoveTypeConfigBundle.

				funDecl, ok := decl.(*ast.FuncDecl)

				//Guess this decl wasn't a fun.
				if !ok {
					continue
				}

				if funDecl.Name.Name != "ConfigureEnums" {
					continue
				}

				if funDecl.Type.Params.NumFields() != 0 {
					continue
				}

				if funDecl.Type.Results.NumFields() != 1 {
					continue
				}

				returnFieldStar, ok := funDecl.Type.Results.List[0].Type.(*ast.StarExpr)

				if !ok {
					//OK, doesn't return a pointer, can't be a match.
					continue
				}

				returnFieldSelector, ok := returnFieldStar.X.(*ast.SelectorExpr)

				if !ok {
					//OK, there's no boardgame...
					continue
				}

				if returnFieldSelector.Sel.Name != "Set" {
					continue
				}

				returnFieldSelectorPackage, ok := returnFieldSelector.X.(*ast.Ident)

				if !ok {
					continue
				}

				if returnFieldSelectorPackage.Name != "enum" {
					continue
				}

				if funDecl.Recv == nil || funDecl.Recv.NumFields() != 1 {
					//Verify i
					continue
				}

				//OK, it appears to be the right method. Extract out information about it.

				starExp, ok := funDecl.Recv.List[0].Type.(*ast.StarExpr)

				if !ok {
					//Not expected, but whatever, it's safe to just include it
					continue
				}

				ident, ok := starExp.X.(*ast.Ident)

				if !ok {
					//Not expected, but whatever, it's safe to just include it
					continue
				}

				//If that struct type were one of the things we would export,
				//then note not to export it. If it wasn't already in, it
				//doesn't hurt to affirmatively say not to export it.
				candidateMap[ident.Name] = false

			}
		}
	}

	var result []string

	for name, include := range candidateMap {
		if !include {
			continue
		}
		result = append(result, name)
	}

	return result

}

//findEnums processes the package at packageName and returns a list of enums
//that should be processed (that is, they have the magic comment)
func findEnums(packageASTs map[string]*ast.Package) (enums []*enum, err error) {

	for packageName, theAST := range packageASTs {
		for _, file := range theAST.Files {
			for _, decl := range file.Decls {
				genDecl, ok := decl.(*ast.GenDecl)

				if !ok {
					//Guess it wasn't a genDecl at all.
					continue
				}

				if genDecl.Tok != token.CONST {
					//We're only interested in Const decls.
					continue
				}

				if !enumConfig(genDecl.Doc.Text()) {
					//Must not have found the magic comment in the docs.
					continue
				}

				defaultTransform := configTransform(genDecl.Doc.Text(), transformNone)

				theEnum := &enum{
					PackageName:         packageName,
					OverrideDisplayName: make(map[string]string),
					Transform:           make(map[string]transform),
					DefaultTransform:    defaultTransform,
				}

				for _, spec := range genDecl.Specs {

					valueSpec, ok := spec.(*ast.ValueSpec)

					if !ok {
						//Guess it wasn't a valueSpec after all!
						continue
					}

					if len(valueSpec.Names) != 1 {
						return nil, errors.New("Found an enum that had more than one name on a line. That's not allowed for now.")
					}

					valueName := valueSpec.Names[0].Name

					theEnum.Values = append(theEnum.Values, valueName)

					if hasOverride, displayName := overrideDisplayname(valueSpec.Doc.Text()); hasOverride {
						theEnum.OverrideDisplayName[valueName] = displayName
					}

					theEnum.Transform[valueName] = configTransform(valueSpec.Doc.Text(), defaultTransform)

				}

				if len(theEnum.Values) > 0 {
					enums = append(enums, theEnum)
				}

			}
		}
	}

	return enums, nil
}

//outputForEnums takes the found enums and produces the output string
//representing the un-formatted go code to generate for those enums.
func outputForEnums(enums []*enum, delegateNames []string) (enumOutput string, err error) {
	for _, enum := range enums {

		if enumOutput == "" {
			enumOutput = enumHeaderForPackage(enum.PackageName, delegateNames)
		}

		var literals [][]byte

		for _, literal := range enum.Values {
			if !fieldNamePublic(literal) {
				continue
			}
			literals = append(literals, []byte(literal))
		}

		if len(literals) == 0 {
			return "", errors.New("No public literals in enum")
		}

		prefix := string(lcp.LCP(literals...))

		if len(prefix) == 0 {
			return "", errors.New("Enum with autoreader configured didn't have a common prefix.")
		}

		values := make(map[string]string, len(literals))

		i := 0

		for _, literal := range enum.Values {
			if !strings.HasPrefix(literal, prefix) {
				return "", errors.New("enum literal didn't have prefix we thought it did")
			}

			//If there's an override deisplay name, use that
			displayName, ok := enum.OverrideDisplayName[literal]

			//If there wasn't an override, do the default. Note that an
			//override "" that is in the map is legal.
			if !ok {
				displayName = titleCaseToWords(strings.Replace(literal, prefix, "", -1))

				switch enum.Transform[literal] {
				case transformLower:
					displayName = strings.ToLower(displayName)
				case transformUpper:
					displayName = strings.ToUpper(displayName)
				}
			}

			values[literal] = displayName
			i++
		}

		enumOutput += enumItem(prefix, values)

	}

	return enumOutput, nil
}

var titleCaseReplacer *strings.Replacer

//titleCaseToWords writes "ATitleCaseString" to "A Title Case String"
func titleCaseToWords(in string) string {

	//substantially recreated in moves/base.go

	if titleCaseReplacer == nil {

		var replacements []string

		for r := 'A'; r <= 'Z'; r++ {
			str := string(r)
			replacements = append(replacements, str)
			replacements = append(replacements, " "+str)
		}

		titleCaseReplacer = strings.NewReplacer(replacements...)

	}

	return strings.TrimSpace(titleCaseReplacer.Replace(in))

}

func processEnums(packageName string) (enumOutput string, err error) {

	packageASTs, err := parser.ParseDir(token.NewFileSet(), packageName, nil, parser.ParseComments)

	if err != nil {
		return "", errors.New("Parse error: " + err.Error())
	}

	enums, err := findEnums(packageASTs)

	if err != nil {
		return "", errors.New("Couldn't parse for enums: " + err.Error())
	}

	if len(enums) == 0 {
		//No enums. That's totally legit.
		return "", nil
	}

	delegateNames, err := findDelegateName(packageASTs)

	if err != nil {
		return "", errors.New("Failed to find delegate name: " + err.Error())
	}

	filteredDelegateNames := filterDelegateNames(delegateNames, packageASTs)

	output, err := outputForEnums(enums, filteredDelegateNames)

	if err != nil {
		return "", errors.New("Couldn't generate output for enums: " + err.Error())
	}

	return output, nil

}

func enumConfig(docLines string) bool {

	for _, docLine := range strings.Split(docLines, "\n") {
		docLine = strings.ToLower(docLine)
		docLine = strings.TrimPrefix(docLine, "//")
		docLine = strings.TrimSpace(docLine)
		if strings.HasPrefix(docLine, magicDocLinePrefix) {
			return true
		}
	}

	return false
}

func configTransform(docLines string, defaultTransform transform) transform {
	for _, line := range strings.Split(docLines, "\n") {
		if transformLowerRegExp.MatchString(line) {
			return transformLower
		}
		if transformUpperRegExp.MatchString(line) {
			return transformUpper
		}
		if transformNoneRegExp.MatchString(line) {
			return transformNone
		}
	}

	return defaultTransform
}

func overrideDisplayname(docLines string) (hasOverride bool, displayName string) {
	for _, line := range strings.Split(docLines, "\n") {
		result := displayNameRegExp.FindStringSubmatch(line)

		if len(result) == 0 {
			continue
		}

		if len(result[0]) == 0 {
			continue
		}
		if len(result) != 2 {
			continue
		}

		//Found it! Even if the matched expression is "", that's fine. if
		//there are quoted strings that's fine, because that's exactly how
		//they should be output at the end.
		return true, result[1]

	}

	return false, ""
}

func enumHeaderForPackage(packageName string, delegateNames []string) string {

	output := templateOutput(enumHeaderTemplate, map[string]interface{}{
		"packageName": packageName,
	})

	//Ensure  a consistent ordering.
	sort.Strings(delegateNames)

	for _, delegateName := range delegateNames {
		output += templateOutput(enumDelegateTemplate, map[string]interface{}{
			"delegateName": delegateName,
		})
	}

	return output
}

func enumItem(prefix string, values map[string]string) string {
	return templateOutput(enumItemTemplate, map[string]interface{}{
		"prefix": prefix,
		"values": values,
	})
}

const enumHeaderTemplateText = `/************************************
 *
 * This file contains auto-generated methods to help configure enums.
 * It was generated by autoreader.
 *
 * DO NOT EDIT by hand.
 *
 ************************************/

package {{.packageName}}

import (
	"github.com/jkomoros/boardgame/enum"
)

var Enums = enum.NewSet()

`

const enumDelegateTemplateText = `//ConfigureEnums simply returns Enums, the auto-generated Enums variable. This
//is output because {{.delegateName}} appears to be a struct that implements
//boardgame.GameDelegate, and does not already have a ConfigureEnums
//explicitly defined.
func ({{firstLetter .delegateName}} *{{.delegateName}}) ConfigureEnums() *enum.Set {
	return Enums
}

`

const enumItemTemplateText = `var {{.prefix}}Enum = Enums.MustAdd("{{.prefix}}", map[int]string{
	{{ $prefix := .prefix -}}
	{{range $name, $value := .values -}}
	{{$name}}: "{{$value}}",
	{{end}}
})

`
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"syscall"

	"github.com/opencontainers/runc/libcontainer/system"
)

func main() {
	var logFile string
	flag.StringVar(&logFile, "log", "dadoo.log", "dadoo log file path")
	flag.Parse()

	command := flag.Args()[0] // e.g. run
	runtime := flag.Args()[1] // e.g. runc
	bundlePath := flag.Args()[2]
	containerId := flag.Args()[3]

	if command != "run" {
		fmt.Fprintf(os.Stderr, "unknown command: %s", command)
		os.Exit(127)
	}

	fd3 := os.NewFile(3, "/proc/self/fd/3")

	signals := make(chan os.Signal, 100)
	signal.Notify(signals, syscall.SIGCHLD)

	pidFilePath := filepath.Join(bundlePath, "pidfile")

	// we need to be the subreaper so we can wait on the detached container process
	system.SetSubreaper(os.Getpid())

	runcStartCmd := exec.Command(runtime, "-debug", "-log", logFile, "start", "-d", "-pid-file", pidFilePath, containerId)
	runcStartCmd.Dir = bundlePath

	if err := runcStartCmd.Start(); err != nil {
		fd3.Write([]byte{2})
		os.Exit(2)
	}

	pid := -2
	for range signals {

		exits := make(map[int]int)
		for {
			var status syscall.WaitStatus
			var rusage syscall.Rusage
			wpid, err := syscall.Wait4(-1, &status, syscall.WNOHANG, &rusage)

			if err != nil || wpid < 0 {
				break // wait for next SIGCHLD
			}

			if wpid == runcStartCmd.Process.Pid {
				fd3.Write([]byte{byte(status.ExitStatus())})

				if status.ExitStatus() != 0 {
					os.Exit(3) // nothing to wait for, container didn't launch
				}

				pid, err = readPid(pidFilePath)
				check(err)
			}

			if wpid == pid || pid < 0 {
				exits[wpid] = status.ExitStatus()
			}

			if status, ok := exits[pid]; ok {
				check(exec.Command(runtime, "delete", containerId).Run())
				os.Exit(status)
			}
		}
	}
}

func check(err error) {
	if err != nil {
		panic(err)
	}
}

func readPid(pidFile string) (int, error) {
	b, err := ioutil.ReadFile(pidFile)
	if err != nil {
		return -1, err
	}

	var pid int
	if _, err := fmt.Sscanf(string(b), "%d", &pid); err != nil {
		return -1, err
	}

	return pid, nil
}
<commit_msg>Prevent unnecessary looping in dadoo bin<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"syscall"

	"github.com/opencontainers/runc/libcontainer/system"
)

func main() {
	var logFile string
	flag.StringVar(&logFile, "log", "dadoo.log", "dadoo log file path")
	flag.Parse()

	command := flag.Args()[0] // e.g. run
	runtime := flag.Args()[1] // e.g. runc
	bundlePath := flag.Args()[2]
	containerId := flag.Args()[3]

	if command != "run" {
		fmt.Fprintf(os.Stderr, "unknown command: %s", command)
		os.Exit(127)
	}

	fd3 := os.NewFile(3, "/proc/self/fd/3")

	signals := make(chan os.Signal, 100)
	signal.Notify(signals, syscall.SIGCHLD)

	pidFilePath := filepath.Join(bundlePath, "pidfile")

	// we need to be the subreaper so we can wait on the detached container process
	system.SetSubreaper(os.Getpid())

	runcStartCmd := exec.Command(runtime, "-debug", "-log", logFile, "start", "-d", "-pid-file", pidFilePath, containerId)
	runcStartCmd.Dir = bundlePath

	if err := runcStartCmd.Start(); err != nil {
		fd3.Write([]byte{2})
		os.Exit(2)
	}

	pid := -2
	for range signals {

		exits := make(map[int]int)
		for {
			var status syscall.WaitStatus
			var rusage syscall.Rusage
			wpid, err := syscall.Wait4(-1, &status, syscall.WNOHANG, &rusage)

			if err != nil || wpid <= 0 {
				break // wait for next SIGCHLD
			}

			if wpid == runcStartCmd.Process.Pid {
				fd3.Write([]byte{byte(status.ExitStatus())})

				if status.ExitStatus() != 0 {
					os.Exit(3) // nothing to wait for, container didn't launch
				}

				pid, err = readPid(pidFilePath)
				check(err)
			}

			if wpid == pid || pid < 0 {
				exits[wpid] = status.ExitStatus()
			}

			if status, ok := exits[pid]; ok {
				check(exec.Command(runtime, "delete", containerId).Run())
				os.Exit(status)
			}
		}
	}
}

func check(err error) {
	if err != nil {
		panic(err)
	}
}

func readPid(pidFile string) (int, error) {
	b, err := ioutil.ReadFile(pidFile)
	if err != nil {
		return -1, err
	}

	var pid int
	if _, err := fmt.Sscanf(string(b), "%d", &pid); err != nil {
		return -1, err
	}

	return pid, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strings"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"

	"github.com/weaveworks/flux/api"
	transport "github.com/weaveworks/flux/http"
	"github.com/weaveworks/flux/http/client"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type rootOpts struct {
	URL       string
	Token     string
	Namespace string
	API       api.Server
}

func newRoot() *rootOpts {
	return &rootOpts{}
}

var rootLongHelp = strings.TrimSpace(`
fluxctl helps you deploy your code.

Connecting:

  # To a fluxd running in namespace "default" in your current kubectl context
  fluxctl list-controllers

  # To a fluxd running in namespace "weave" in your current kubectl context
  fluxctl --k8s-fwd-ns=weave list-controllers

  # To a Weave Cloud instance, with your instance token in $TOKEN
  fluxctl --token $TOKEN list-controllers

Workflow:
  fluxctl list-controllers                                                   # Which controllers are running?
  fluxctl list-images --controller=default:deployment/foo                    # Which images are running/available?
  fluxctl release --controller=default:deployment/foo --update-image=bar:v2  # Release new version.
`)

const (
	envVariableURL        = "FLUX_URL"
	envVariableNamespace  = "FLUX_FORWARD_NAMESPACE"
	envVariableToken      = "FLUX_SERVICE_TOKEN"
	envVariableCloudToken = "WEAVE_CLOUD_TOKEN"
	defaultURLGivenToken  = "https://cloud.weave.works/api/flux"
)

func (opts *rootOpts) Command() *cobra.Command {
	cmd := &cobra.Command{
		Use:               "fluxctl",
		Long:              rootLongHelp,
		SilenceUsage:      true,
		SilenceErrors:     true,
		PersistentPreRunE: opts.PersistentPreRunE,
	}

	cmd.PersistentFlags().StringVar(&opts.Namespace, "k8s-fwd-ns", "default",
		fmt.Sprintf("Namespace in which fluxd is running, for creating a port forward to access the API. No port forward will be created if a URL or token is given. You can also set the environment variable %s", envVariableNamespace))
	cmd.PersistentFlags().StringVarP(&opts.URL, "url", "u", "",
		fmt.Sprintf("Base URL of the flux API (defaults to %q if a token is provided); you can also set the environment variable %s", defaultURLGivenToken, envVariableURL))
	cmd.PersistentFlags().StringVarP(&opts.Token, "token", "t", "",
		fmt.Sprintf("Weave Cloud authentication token; you can also set the environment variable %s or %s", envVariableCloudToken, envVariableToken))

	cmd.AddCommand(
		newVersionCommand(),
		newServiceList(opts).Command(),
		newControllerShow(opts).Command(),
		newControllerList(opts).Command(),
		newControllerRelease(opts).Command(),
		newServiceAutomate(opts).Command(),
		newControllerDeautomate(opts).Command(),
		newControllerLock(opts).Command(),
		newControllerUnlock(opts).Command(),
		newControllerPolicy(opts).Command(),
		newSave(opts).Command(),
		newIdentity(opts).Command(),
		newSync(opts).Command(),
	)

	return cmd
}

func (opts *rootOpts) PersistentPreRunE(cmd *cobra.Command, _ []string) error {
	opts.Namespace = getFromEnvIfNotSet(cmd.Flags(), "k8s-fwd-ns", opts.Namespace, envVariableNamespace)
	opts.Token = getFromEnvIfNotSet(cmd.Flags(), "token", opts.Token, envVariableToken, envVariableCloudToken)
	opts.URL = getFromEnvIfNotSet(cmd.Flags(), "url", opts.URL, envVariableURL)

	if opts.Token != "" && opts.URL == "" {
		opts.URL = defaultURLGivenToken
	}

	if opts.URL == "" {
		portforwarder, err := tryPortforwards(opts.Namespace, metav1.LabelSelector{
			MatchExpressions: []metav1.LabelSelectorRequirement{
				metav1.LabelSelectorRequirement{
					Key:      "name",
					Operator: metav1.LabelSelectorOpIn,
					Values:   []string{"flux", "fluxd", "weave-flux-agent"},
				},
			},
		}, metav1.LabelSelector{
			MatchLabels: map[string]string{
				"app": "flux",
			},
		})
		if err != nil {
			return err
		}

		opts.URL = fmt.Sprintf("http://127.0.0.1:%d/api/flux", portforwarder.ListenPort)
	}

	if _, err := url.Parse(opts.URL); err != nil {
		return errors.Wrapf(err, "parsing URL")
	}

	opts.API = client.New(http.DefaultClient, transport.NewAPIRouter(), opts.URL, client.Token(opts.Token))
	return nil
}

func getFromEnvIfNotSet(flags *pflag.FlagSet, flagName, value string, envNames ...string) string {
	if flags.Changed(flagName) {
		return value
	}
	for _, envName := range envNames {
		if env := os.Getenv(envName); env != "" {
			return env
		}
	}
	return value // not changed, so presumably the default
}
<commit_msg>Skip port forwarding for version command<commit_after>package main

import (
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strings"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"

	"github.com/weaveworks/flux/api"
	transport "github.com/weaveworks/flux/http"
	"github.com/weaveworks/flux/http/client"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type rootOpts struct {
	URL       string
	Token     string
	Namespace string
	API       api.Server
}

func newRoot() *rootOpts {
	return &rootOpts{}
}

var rootLongHelp = strings.TrimSpace(`
fluxctl helps you deploy your code.

Connecting:

  # To a fluxd running in namespace "default" in your current kubectl context
  fluxctl list-controllers

  # To a fluxd running in namespace "weave" in your current kubectl context
  fluxctl --k8s-fwd-ns=weave list-controllers

  # To a Weave Cloud instance, with your instance token in $TOKEN
  fluxctl --token $TOKEN list-controllers

Workflow:
  fluxctl list-controllers                                                   # Which controllers are running?
  fluxctl list-images --controller=default:deployment/foo                    # Which images are running/available?
  fluxctl release --controller=default:deployment/foo --update-image=bar:v2  # Release new version.
`)

const (
	envVariableURL        = "FLUX_URL"
	envVariableNamespace  = "FLUX_FORWARD_NAMESPACE"
	envVariableToken      = "FLUX_SERVICE_TOKEN"
	envVariableCloudToken = "WEAVE_CLOUD_TOKEN"
	defaultURLGivenToken  = "https://cloud.weave.works/api/flux"
)

func (opts *rootOpts) Command() *cobra.Command {
	cmd := &cobra.Command{
		Use:               "fluxctl",
		Long:              rootLongHelp,
		SilenceUsage:      true,
		SilenceErrors:     true,
		PersistentPreRunE: opts.PersistentPreRunE,
	}

	cmd.PersistentFlags().StringVar(&opts.Namespace, "k8s-fwd-ns", "default",
		fmt.Sprintf("Namespace in which fluxd is running, for creating a port forward to access the API. No port forward will be created if a URL or token is given. You can also set the environment variable %s", envVariableNamespace))
	cmd.PersistentFlags().StringVarP(&opts.URL, "url", "u", "",
		fmt.Sprintf("Base URL of the flux API (defaults to %q if a token is provided); you can also set the environment variable %s", defaultURLGivenToken, envVariableURL))
	cmd.PersistentFlags().StringVarP(&opts.Token, "token", "t", "",
		fmt.Sprintf("Weave Cloud authentication token; you can also set the environment variable %s or %s", envVariableCloudToken, envVariableToken))

	cmd.AddCommand(
		newVersionCommand(),
		newServiceList(opts).Command(),
		newControllerShow(opts).Command(),
		newControllerList(opts).Command(),
		newControllerRelease(opts).Command(),
		newServiceAutomate(opts).Command(),
		newControllerDeautomate(opts).Command(),
		newControllerLock(opts).Command(),
		newControllerUnlock(opts).Command(),
		newControllerPolicy(opts).Command(),
		newSave(opts).Command(),
		newIdentity(opts).Command(),
		newSync(opts).Command(),
	)

	return cmd
}

func (opts *rootOpts) PersistentPreRunE(cmd *cobra.Command, _ []string) error {
	// skip port forward for version command
	switch cmd.Use {
	case "version":
		return nil
	}

	opts.Namespace = getFromEnvIfNotSet(cmd.Flags(), "k8s-fwd-ns", opts.Namespace, envVariableNamespace)
	opts.Token = getFromEnvIfNotSet(cmd.Flags(), "token", opts.Token, envVariableToken, envVariableCloudToken)
	opts.URL = getFromEnvIfNotSet(cmd.Flags(), "url", opts.URL, envVariableURL)

	if opts.Token != "" && opts.URL == "" {
		opts.URL = defaultURLGivenToken
	}

	if opts.URL == "" {
		portforwarder, err := tryPortforwards(opts.Namespace, metav1.LabelSelector{
			MatchExpressions: []metav1.LabelSelectorRequirement{
				metav1.LabelSelectorRequirement{
					Key:      "name",
					Operator: metav1.LabelSelectorOpIn,
					Values:   []string{"flux", "fluxd", "weave-flux-agent"},
				},
			},
		}, metav1.LabelSelector{
			MatchLabels: map[string]string{
				"app": "flux",
			},
		})
		if err != nil {
			return err
		}

		opts.URL = fmt.Sprintf("http://127.0.0.1:%d/api/flux", portforwarder.ListenPort)
	}

	if _, err := url.Parse(opts.URL); err != nil {
		return errors.Wrapf(err, "parsing URL")
	}

	opts.API = client.New(http.DefaultClient, transport.NewAPIRouter(), opts.URL, client.Token(opts.Token))
	return nil
}

func getFromEnvIfNotSet(flags *pflag.FlagSet, flagName, value string, envNames ...string) string {
	if flags.Changed(flagName) {
		return value
	}
	for _, envName := range envNames {
		if env := os.Getenv(envName); env != "" {
			return env
		}
	}
	return value // not changed, so presumably the default
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"path/filepath"

	"github.com/constabulary/gb"
	"github.com/constabulary/gb/cmd"
	"github.com/constabulary/gb/cmd/gb-vendor/vendor"
)

var (
	// gb vendor update flags

	// update all dependencies
	updateAll bool
)

func init() {
	registerCommand("update", UpdateCmd)
}

func addUpdateFlags(fs *flag.FlagSet) {
	fs.BoolVar(&updateAll, "all", false, "update all dependencies")
}

var UpdateCmd = &cmd.Command{
	ShortDesc: "updates a local dependency",
	Run: func(ctx *gb.Context, args []string) error {
		if len(args) != 1 && !updateAll {
			return fmt.Errorf("update: import path or --all flag is missing")
		} else if len(args) == 1 && updateAll {
			return fmt.Errorf("update: you cannot specify path and --all flag at once")
		}

		m, err := vendor.ReadManifest(manifestFile(ctx))
		if err != nil {
			return fmt.Errorf("could not load manifest: %v", err)
		}

		var dependencies []vendor.Dependency
		if updateAll {
			dependencies = make([]vendor.Dependency, len(m.Dependencies))
			copy(dependencies, m.Dependencies)
		} else {
			p := args[0]
			dependency, err := m.GetDependencyForImportpath(p)
			if err != nil {
				return fmt.Errorf("could not get dependency: %v", err)
			}
			dependencies = append(dependencies, dependency)
		}

		for _, d := range dependencies {
			url := d.Repository
			path := d.Importpath

			err = m.RemoveDependency(d)
			if err != nil {
				return fmt.Errorf("dependency could not be deleted from manifest: %v", err)
			}

			localClone := vendor.GitClone{
				Path: filepath.Join(ctx.Projectdir(), "vendor", "src", path),
			}
			err = localClone.Destroy()
			if err != nil {
				return fmt.Errorf("dependency could not be deleted: %v", err)
			}

			repo := vendor.GitRepo{
				URL: url,
			}

			wc, err := repo.Clone()
			if err != nil {
				return err
			}

			rev, err := wc.Revision()
			if err != nil {
				return err
			}

			branch, err := wc.Branch()
			if err != nil {
				return err
			}

			dep := vendor.Dependency{
				Importpath: path,
				Repository: url,
				Revision:   rev,
				Branch:     branch,
				Path:       "",
			}

			if err := m.AddDependency(dep); err != nil {
				return err
			}

			dst := filepath.Join(ctx.Projectdir(), "vendor", "src", dep.Importpath)
			src := filepath.Join(wc.Dir(), dep.Path)

			if err := copypath(dst, src); err != nil {
				return err
			}

			if err := wc.Destroy(); err != nil {
				return err
			}
			fmt.Println(dependencies)
		}

		return vendor.WriteManifest(manifestFile(ctx), m)
	},
	AddFlags: addUpdateFlags,
}
<commit_msg>Removed debugging leftover<commit_after>package main

import (
	"flag"
	"fmt"
	"path/filepath"

	"github.com/constabulary/gb"
	"github.com/constabulary/gb/cmd"
	"github.com/constabulary/gb/cmd/gb-vendor/vendor"
)

var (
	// gb vendor update flags

	// update all dependencies
	updateAll bool
)

func init() {
	registerCommand("update", UpdateCmd)
}

func addUpdateFlags(fs *flag.FlagSet) {
	fs.BoolVar(&updateAll, "all", false, "update all dependencies")
}

var UpdateCmd = &cmd.Command{
	ShortDesc: "updates a local dependency",
	Run: func(ctx *gb.Context, args []string) error {
		if len(args) != 1 && !updateAll {
			return fmt.Errorf("update: import path or --all flag is missing")
		} else if len(args) == 1 && updateAll {
			return fmt.Errorf("update: you cannot specify path and --all flag at once")
		}

		m, err := vendor.ReadManifest(manifestFile(ctx))
		if err != nil {
			return fmt.Errorf("could not load manifest: %v", err)
		}

		var dependencies []vendor.Dependency
		if updateAll {
			dependencies = make([]vendor.Dependency, len(m.Dependencies))
			copy(dependencies, m.Dependencies)
		} else {
			p := args[0]
			dependency, err := m.GetDependencyForImportpath(p)
			if err != nil {
				return fmt.Errorf("could not get dependency: %v", err)
			}
			dependencies = append(dependencies, dependency)
		}

		for _, d := range dependencies {
			url := d.Repository
			path := d.Importpath

			err = m.RemoveDependency(d)
			if err != nil {
				return fmt.Errorf("dependency could not be deleted from manifest: %v", err)
			}

			localClone := vendor.GitClone{
				Path: filepath.Join(ctx.Projectdir(), "vendor", "src", path),
			}
			err = localClone.Destroy()
			if err != nil {
				return fmt.Errorf("dependency could not be deleted: %v", err)
			}

			repo := vendor.GitRepo{
				URL: url,
			}

			wc, err := repo.Clone()
			if err != nil {
				return err
			}

			rev, err := wc.Revision()
			if err != nil {
				return err
			}

			branch, err := wc.Branch()
			if err != nil {
				return err
			}

			dep := vendor.Dependency{
				Importpath: path,
				Repository: url,
				Revision:   rev,
				Branch:     branch,
				Path:       "",
			}

			if err := m.AddDependency(dep); err != nil {
				return err
			}

			dst := filepath.Join(ctx.Projectdir(), "vendor", "src", dep.Importpath)
			src := filepath.Join(wc.Dir(), dep.Path)

			if err := copypath(dst, src); err != nil {
				return err
			}

			if err := wc.Destroy(); err != nil {
				return err
			}
		}

		return vendor.WriteManifest(manifestFile(ctx), m)
	},
	AddFlags: addUpdateFlags,
}
<|endoftext|>


<commit_before>/*
 * Minio Cloud Storage, (C) 2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cmd

import (
	"net/http"
	"os"
	"path"
	"regexp"
	"strings"
	"time"

	router "github.com/gorilla/mux"
	"github.com/rs/cors"
)

// HandlerFunc - useful to chain different middleware http.Handler
type HandlerFunc func(http.Handler) http.Handler

func registerHandlers(mux *router.Router, handlerFns ...HandlerFunc) http.Handler {
	var f http.Handler
	f = mux
	for _, hFn := range handlerFns {
		f = hFn(f)
	}
	return f
}

// Adds limiting body size middleware

// Set the body size limit to 6 Gb = Maximum object size + other possible data
// in the same request
const requestMaxBodySize = 1024 * 1024 * 1024 * (5 + 1)

type requestSizeLimitHandler struct {
	handler     http.Handler
	maxBodySize int64
}

func setRequestSizeLimitHandler(h http.Handler) http.Handler {
	return requestSizeLimitHandler{handler: h, maxBodySize: requestMaxBodySize}
}

func (h requestSizeLimitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Restricting read data to a given maximum length
	r.Body = http.MaxBytesReader(w, r.Body, h.maxBodySize)
	h.handler.ServeHTTP(w, r)
}

// Adds redirect rules for incoming requests.
type redirectHandler struct {
	handler        http.Handler
	locationPrefix string
}

// Reserved bucket.
const (
	reservedBucket = "/minio"
)

func setBrowserRedirectHandler(h http.Handler) http.Handler {
	return redirectHandler{handler: h, locationPrefix: reservedBucket}
}

func (h redirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if !strings.EqualFold(os.Getenv("MINIO_BROWSER"), "off") {
		// Re-direction handled specifically for browsers.
		if strings.Contains(r.Header.Get("User-Agent"), "Mozilla") && !isRequestSignatureV4(r) {
			// '/' is redirected to 'locationPrefix/'
			// '/webrpc' is redirected to 'locationPrefix/webrpc'
			// '/login' is redirected to 'locationPrefix/login'
			switch r.URL.Path {
			case "/", "/webrpc", "/login", "/favicon.ico":
				location := h.locationPrefix + r.URL.Path
				// Redirect to new location.
				http.Redirect(w, r, location, http.StatusTemporaryRedirect)
				return
			}
		}
	}

	h.handler.ServeHTTP(w, r)
}

// Adds Cache-Control header
type cacheControlHandler struct {
	handler http.Handler
}

func setBrowserCacheControlHandler(h http.Handler) http.Handler {
	return cacheControlHandler{h}
}

func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" && strings.Contains(r.Header.Get("User-Agent"), "Mozilla") {
		// For all browser requests set appropriate Cache-Control policies
		match, e := regexp.MatchString(reservedBucket+`/([^/]+\.js|favicon.ico)`, r.URL.Path)
		if e != nil {
			writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
			return
		}
		if match {
			// For assets set cache expiry of one year. For each release, the name
			// of the asset name will change and hence it can not be served from cache.
			w.Header().Set("Cache-Control", "max-age=31536000")
		} else if strings.HasPrefix(r.URL.Path, reservedBucket+"/") {
			// For non asset requests we serve index.html which will never be cached.
			w.Header().Set("Cache-Control", "no-store")
		}
	}
	h.handler.ServeHTTP(w, r)
}

// Adds verification for incoming paths.
type minioPrivateBucketHandler struct {
	handler        http.Handler
	reservedBucket string
}

func setPrivateBucketHandler(h http.Handler) http.Handler {
	return minioPrivateBucketHandler{handler: h, reservedBucket: reservedBucket}
}

func (h minioPrivateBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// For all non browser requests, reject access to 'reservedBucket'.
	if !strings.Contains(r.Header.Get("User-Agent"), "Mozilla") && path.Clean(r.URL.Path) == reservedBucket {
		writeErrorResponse(w, r, ErrAllAccessDisabled, r.URL.Path)
		return
	}
	h.handler.ServeHTTP(w, r)
}

// Supported Amz date formats.
var amzDateFormats = []string{
	time.RFC1123,
	time.RFC1123Z,
	iso8601Format,
	// Add new AMZ date formats here.
}

// parseAmzDate - parses date string into supported amz date formats.
func parseAmzDate(amzDateStr string) (amzDate time.Time, apiErr APIErrorCode) {
	for _, dateFormat := range amzDateFormats {
		amzDate, e := time.Parse(dateFormat, amzDateStr)
		if e == nil {
			return amzDate, ErrNone
		}
	}
	return time.Time{}, ErrMalformedDate
}

// Supported Amz date headers.
var amzDateHeaders = []string{
	"x-amz-date",
	"date",
}

// parseAmzDateHeader - parses supported amz date headers, in
// supported amz date formats.
func parseAmzDateHeader(req *http.Request) (time.Time, APIErrorCode) {
	for _, amzDateHeader := range amzDateHeaders {
		amzDateStr := req.Header.Get(http.CanonicalHeaderKey(amzDateHeader))
		if amzDateStr != "" {
			return parseAmzDate(amzDateStr)
		}
	}
	// Date header missing.
	return time.Time{}, ErrMissingDateHeader
}

type timeValidityHandler struct {
}

func (h timeValidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Verify if date headers are set, if not reject the request
	if _, ok := r.Header["Authorization"]; ok {
		amzDate, apiErr := parseAmzDateHeader(r)
		if apiErr != ErrNone {
			// All our internal APIs are sensitive towards Date
			// header, for all requests where Date header is not
			// present we will reject such clients.
			writeErrorResponse(w, r, apiErr, r.URL.Path)
			return
		}
		// Verify if the request date header is shifted by less than globalMaxSkewTime parameter in the past
		// or in the future, reject request otherwise.
		curTime := time.Now().UTC()
		if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
			writeErrorResponse(w, r, ErrRequestTimeTooSkewed, r.URL.Path)
			return
		}
	}
}

type resourceHandler struct {
	handler http.Handler
}

// setCorsHandler handler for CORS (Cross Origin Resource Sharing)
func setCorsHandler(h http.Handler) http.Handler {
	c := cors.New(cors.Options{
		AllowedOrigins: []string{"*"},
		AllowedMethods: []string{"GET", "HEAD", "POST", "PUT"},
		AllowedHeaders: []string{"*"},
		ExposedHeaders: []string{"ETag"},
	})
	return c.Handler(h)
}

// setIgnoreResourcesHandler -
// Ignore resources handler is wrapper handler used for API request resource validation
// Since we do not support all the S3 queries, it is necessary for us to throw back a
// valid error message indicating that requested feature is not implemented.
func setIgnoreResourcesHandler(h http.Handler) http.Handler {
	return resourceHandler{h}
}

// Resource handler ServeHTTP() wrapper
func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Skip the first element which is usually '/' and split the rest.
	splits := strings.SplitN(r.URL.Path[1:], "/", 2)

	// Save bucketName and objectName extracted from url Path.
	var bucketName, objectName string
	if len(splits) == 1 {
		bucketName = splits[0]
	}
	if len(splits) == 2 {
		bucketName = splits[0]
		objectName = splits[1]
	}

	// If bucketName is present and not objectName check for bucket level resource queries.
	if bucketName != "" && objectName == "" {
		if ignoreNotImplementedBucketResources(r) {
			writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
			return
		}
	}
	// If bucketName and objectName are present check for its resource queries.
	if bucketName != "" && objectName != "" {
		if ignoreNotImplementedObjectResources(r) {
			writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
			return
		}
	}
	// A put method on path "/" doesn't make sense, ignore it.
	if r.Method == "PUT" && r.URL.Path == "/" {
		writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
		return
	}

	// Serve HTTP.
	h.handler.ServeHTTP(w, r)
}

//// helpers

// Checks requests for not implemented Bucket resources
func ignoreNotImplementedBucketResources(req *http.Request) bool {
	for name := range req.URL.Query() {
		if notimplementedBucketResourceNames[name] {
			return true
		}
	}
	return false
}

// Checks requests for not implemented Object resources
func ignoreNotImplementedObjectResources(req *http.Request) bool {
	for name := range req.URL.Query() {
		if notimplementedObjectResourceNames[name] {
			return true
		}
	}
	return false
}

// List of not implemented bucket queries
var notimplementedBucketResourceNames = map[string]bool{
	"acl":            true,
	"cors":           true,
	"lifecycle":      true,
	"logging":        true,
	"replication":    true,
	"tagging":        true,
	"versions":       true,
	"requestPayment": true,
	"versioning":     true,
	"website":        true,
}

// List of not implemented object queries
var notimplementedObjectResourceNames = map[string]bool{
	"torrent": true,
	"acl":     true,
	"policy":  true,
}
<commit_msg>Redirect /minio to /minio/ when requests come from browsers (#2937)<commit_after>/*
 * Minio Cloud Storage, (C) 2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cmd

import (
	"net/http"
	"os"
	"path"
	"regexp"
	"strings"
	"time"

	router "github.com/gorilla/mux"
	"github.com/rs/cors"
)

// HandlerFunc - useful to chain different middleware http.Handler
type HandlerFunc func(http.Handler) http.Handler

func registerHandlers(mux *router.Router, handlerFns ...HandlerFunc) http.Handler {
	var f http.Handler
	f = mux
	for _, hFn := range handlerFns {
		f = hFn(f)
	}
	return f
}

// Adds limiting body size middleware

// Set the body size limit to 6 Gb = Maximum object size + other possible data
// in the same request
const requestMaxBodySize = 1024 * 1024 * 1024 * (5 + 1)

type requestSizeLimitHandler struct {
	handler     http.Handler
	maxBodySize int64
}

func setRequestSizeLimitHandler(h http.Handler) http.Handler {
	return requestSizeLimitHandler{handler: h, maxBodySize: requestMaxBodySize}
}

func (h requestSizeLimitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Restricting read data to a given maximum length
	r.Body = http.MaxBytesReader(w, r.Body, h.maxBodySize)
	h.handler.ServeHTTP(w, r)
}

// Adds redirect rules for incoming requests.
type redirectHandler struct {
	handler        http.Handler
	locationPrefix string
}

// Reserved bucket.
const (
	reservedBucket = "/minio"
)

func setBrowserRedirectHandler(h http.Handler) http.Handler {
	return redirectHandler{handler: h, locationPrefix: reservedBucket}
}

func (h redirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if !strings.EqualFold(os.Getenv("MINIO_BROWSER"), "off") {
		// Re-direction handled specifically for browsers.
		if strings.Contains(r.Header.Get("User-Agent"), "Mozilla") && !isRequestSignatureV4(r) {
			switch r.URL.Path {
			case "/", "/webrpc", "/login", "/favicon.ico":
				// '/' is redirected to 'locationPrefix/'
				// '/webrpc' is redirected to 'locationPrefix/webrpc'
				// '/login' is redirected to 'locationPrefix/login'
				location := h.locationPrefix + r.URL.Path
				// Redirect to new location.
				http.Redirect(w, r, location, http.StatusTemporaryRedirect)
				return
			case h.locationPrefix:
				// locationPrefix is redirected to 'locationPrefix/'
				location := h.locationPrefix + "/"
				http.Redirect(w, r, location, http.StatusTemporaryRedirect)
				return
			}
		}
	}

	h.handler.ServeHTTP(w, r)
}

// Adds Cache-Control header
type cacheControlHandler struct {
	handler http.Handler
}

func setBrowserCacheControlHandler(h http.Handler) http.Handler {
	return cacheControlHandler{h}
}

func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" && strings.Contains(r.Header.Get("User-Agent"), "Mozilla") {
		// For all browser requests set appropriate Cache-Control policies
		match, e := regexp.MatchString(reservedBucket+`/([^/]+\.js|favicon.ico)`, r.URL.Path)
		if e != nil {
			writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
			return
		}
		if match {
			// For assets set cache expiry of one year. For each release, the name
			// of the asset name will change and hence it can not be served from cache.
			w.Header().Set("Cache-Control", "max-age=31536000")
		} else if strings.HasPrefix(r.URL.Path, reservedBucket+"/") {
			// For non asset requests we serve index.html which will never be cached.
			w.Header().Set("Cache-Control", "no-store")
		}
	}
	h.handler.ServeHTTP(w, r)
}

// Adds verification for incoming paths.
type minioPrivateBucketHandler struct {
	handler        http.Handler
	reservedBucket string
}

func setPrivateBucketHandler(h http.Handler) http.Handler {
	return minioPrivateBucketHandler{handler: h, reservedBucket: reservedBucket}
}

func (h minioPrivateBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// For all non browser requests, reject access to 'reservedBucket'.
	if !strings.Contains(r.Header.Get("User-Agent"), "Mozilla") && path.Clean(r.URL.Path) == reservedBucket {
		writeErrorResponse(w, r, ErrAllAccessDisabled, r.URL.Path)
		return
	}
	h.handler.ServeHTTP(w, r)
}

// Supported Amz date formats.
var amzDateFormats = []string{
	time.RFC1123,
	time.RFC1123Z,
	iso8601Format,
	// Add new AMZ date formats here.
}

// parseAmzDate - parses date string into supported amz date formats.
func parseAmzDate(amzDateStr string) (amzDate time.Time, apiErr APIErrorCode) {
	for _, dateFormat := range amzDateFormats {
		amzDate, e := time.Parse(dateFormat, amzDateStr)
		if e == nil {
			return amzDate, ErrNone
		}
	}
	return time.Time{}, ErrMalformedDate
}

// Supported Amz date headers.
var amzDateHeaders = []string{
	"x-amz-date",
	"date",
}

// parseAmzDateHeader - parses supported amz date headers, in
// supported amz date formats.
func parseAmzDateHeader(req *http.Request) (time.Time, APIErrorCode) {
	for _, amzDateHeader := range amzDateHeaders {
		amzDateStr := req.Header.Get(http.CanonicalHeaderKey(amzDateHeader))
		if amzDateStr != "" {
			return parseAmzDate(amzDateStr)
		}
	}
	// Date header missing.
	return time.Time{}, ErrMissingDateHeader
}

type timeValidityHandler struct {
}

func (h timeValidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Verify if date headers are set, if not reject the request
	if _, ok := r.Header["Authorization"]; ok {
		amzDate, apiErr := parseAmzDateHeader(r)
		if apiErr != ErrNone {
			// All our internal APIs are sensitive towards Date
			// header, for all requests where Date header is not
			// present we will reject such clients.
			writeErrorResponse(w, r, apiErr, r.URL.Path)
			return
		}
		// Verify if the request date header is shifted by less than globalMaxSkewTime parameter in the past
		// or in the future, reject request otherwise.
		curTime := time.Now().UTC()
		if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
			writeErrorResponse(w, r, ErrRequestTimeTooSkewed, r.URL.Path)
			return
		}
	}
}

type resourceHandler struct {
	handler http.Handler
}

// setCorsHandler handler for CORS (Cross Origin Resource Sharing)
func setCorsHandler(h http.Handler) http.Handler {
	c := cors.New(cors.Options{
		AllowedOrigins: []string{"*"},
		AllowedMethods: []string{"GET", "HEAD", "POST", "PUT"},
		AllowedHeaders: []string{"*"},
		ExposedHeaders: []string{"ETag"},
	})
	return c.Handler(h)
}

// setIgnoreResourcesHandler -
// Ignore resources handler is wrapper handler used for API request resource validation
// Since we do not support all the S3 queries, it is necessary for us to throw back a
// valid error message indicating that requested feature is not implemented.
func setIgnoreResourcesHandler(h http.Handler) http.Handler {
	return resourceHandler{h}
}

// Resource handler ServeHTTP() wrapper
func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Skip the first element which is usually '/' and split the rest.
	splits := strings.SplitN(r.URL.Path[1:], "/", 2)

	// Save bucketName and objectName extracted from url Path.
	var bucketName, objectName string
	if len(splits) == 1 {
		bucketName = splits[0]
	}
	if len(splits) == 2 {
		bucketName = splits[0]
		objectName = splits[1]
	}

	// If bucketName is present and not objectName check for bucket level resource queries.
	if bucketName != "" && objectName == "" {
		if ignoreNotImplementedBucketResources(r) {
			writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
			return
		}
	}
	// If bucketName and objectName are present check for its resource queries.
	if bucketName != "" && objectName != "" {
		if ignoreNotImplementedObjectResources(r) {
			writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
			return
		}
	}
	// A put method on path "/" doesn't make sense, ignore it.
	if r.Method == "PUT" && r.URL.Path == "/" {
		writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
		return
	}

	// Serve HTTP.
	h.handler.ServeHTTP(w, r)
}

//// helpers

// Checks requests for not implemented Bucket resources
func ignoreNotImplementedBucketResources(req *http.Request) bool {
	for name := range req.URL.Query() {
		if notimplementedBucketResourceNames[name] {
			return true
		}
	}
	return false
}

// Checks requests for not implemented Object resources
func ignoreNotImplementedObjectResources(req *http.Request) bool {
	for name := range req.URL.Query() {
		if notimplementedObjectResourceNames[name] {
			return true
		}
	}
	return false
}

// List of not implemented bucket queries
var notimplementedBucketResourceNames = map[string]bool{
	"acl":            true,
	"cors":           true,
	"lifecycle":      true,
	"logging":        true,
	"replication":    true,
	"tagging":        true,
	"versions":       true,
	"requestPayment": true,
	"versioning":     true,
	"website":        true,
}

// List of not implemented object queries
var notimplementedObjectResourceNames = map[string]bool{
	"torrent": true,
	"acl":     true,
	"policy":  true,
}
<|endoftext|>


<commit_before>/*
 * Minio Cloud Storage, (C) 2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cmd

import (
	"net/http"
	"path"
	"regexp"
	"strings"
	"time"

	humanize "github.com/dustin/go-humanize"
	router "github.com/gorilla/mux"
	"github.com/rs/cors"
)

// HandlerFunc - useful to chain different middleware http.Handler
type HandlerFunc func(http.Handler) http.Handler

func registerHandlers(mux *router.Router, handlerFns ...HandlerFunc) http.Handler {
	var f http.Handler
	f = mux
	for _, hFn := range handlerFns {
		f = hFn(f)
	}
	return f
}

// Adds limiting body size middleware

// Maximum allowed form data field values. 64MiB is a guessed practical value
// which is more than enough to accommodate any form data fields and headers.
const requestFormDataSize = 64 * humanize.MiByte

// For any HTTP request, request body should be not more than 5GiB + requestFormDataSize
// where, 5GiB is the maximum allowed object size for object upload.
const requestMaxBodySize = 5*humanize.GiByte + requestFormDataSize

type requestSizeLimitHandler struct {
	handler     http.Handler
	maxBodySize int64
}

func setRequestSizeLimitHandler(h http.Handler) http.Handler {
	return requestSizeLimitHandler{handler: h, maxBodySize: requestMaxBodySize}
}

func (h requestSizeLimitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Restricting read data to a given maximum length
	r.Body = http.MaxBytesReader(w, r.Body, h.maxBodySize)
	h.handler.ServeHTTP(w, r)
}

// Adds redirect rules for incoming requests.
type redirectHandler struct {
	handler        http.Handler
	locationPrefix string
}

// Reserved bucket.
const (
	reservedBucket = "/minio"
)

func setBrowserRedirectHandler(h http.Handler) http.Handler {
	return redirectHandler{handler: h, locationPrefix: reservedBucket}
}

func (h redirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Re-direction handled specifically for browsers.
	if strings.Contains(r.Header.Get("User-Agent"), "Mozilla") && !isRequestSignatureV4(r) {
		switch r.URL.Path {
		case "/", "/webrpc", "/login", "/favicon.ico":
			// '/' is redirected to 'locationPrefix/'
			// '/webrpc' is redirected to 'locationPrefix/webrpc'
			// '/login' is redirected to 'locationPrefix/login'
			location := h.locationPrefix + r.URL.Path
			// Redirect to new location.
			http.Redirect(w, r, location, http.StatusTemporaryRedirect)
			return
		case h.locationPrefix:
			// locationPrefix is redirected to 'locationPrefix/'
			location := h.locationPrefix + "/"
			http.Redirect(w, r, location, http.StatusTemporaryRedirect)
			return
		}
	}
	h.handler.ServeHTTP(w, r)
}

// Adds Cache-Control header
type cacheControlHandler struct {
	handler http.Handler
}

func setBrowserCacheControlHandler(h http.Handler) http.Handler {
	return cacheControlHandler{h}
}

func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" && strings.Contains(r.Header.Get("User-Agent"), "Mozilla") {
		// For all browser requests set appropriate Cache-Control policies
		match, e := regexp.MatchString(reservedBucket+`/([^/]+\.js|favicon.ico)`, r.URL.Path)
		if e != nil {
			writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
			return
		}
		if match {
			// For assets set cache expiry of one year. For each release, the name
			// of the asset name will change and hence it can not be served from cache.
			w.Header().Set("Cache-Control", "max-age=31536000")
		} else if strings.HasPrefix(r.URL.Path, reservedBucket+"/") {
			// For non asset requests we serve index.html which will never be cached.
			w.Header().Set("Cache-Control", "no-store")
		}
	}
	h.handler.ServeHTTP(w, r)
}

// Adds verification for incoming paths.
type minioPrivateBucketHandler struct {
	handler        http.Handler
	reservedBucket string
}

func setPrivateBucketHandler(h http.Handler) http.Handler {
	return minioPrivateBucketHandler{handler: h, reservedBucket: reservedBucket}
}

func (h minioPrivateBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// For all non browser requests, reject access to 'reservedBucket'.
	if !strings.Contains(r.Header.Get("User-Agent"), "Mozilla") && path.Clean(r.URL.Path) == reservedBucket {
		writeErrorResponse(w, r, ErrAllAccessDisabled, r.URL.Path)
		return
	}
	h.handler.ServeHTTP(w, r)
}

// Supported Amz date formats.
var amzDateFormats = []string{
	time.RFC1123,
	time.RFC1123Z,
	iso8601Format,
	// Add new AMZ date formats here.
}

// parseAmzDate - parses date string into supported amz date formats.
func parseAmzDate(amzDateStr string) (amzDate time.Time, apiErr APIErrorCode) {
	for _, dateFormat := range amzDateFormats {
		amzDate, e := time.Parse(dateFormat, amzDateStr)
		if e == nil {
			return amzDate, ErrNone
		}
	}
	return time.Time{}, ErrMalformedDate
}

// Supported Amz date headers.
var amzDateHeaders = []string{
	"x-amz-date",
	"date",
}

// parseAmzDateHeader - parses supported amz date headers, in
// supported amz date formats.
func parseAmzDateHeader(req *http.Request) (time.Time, APIErrorCode) {
	for _, amzDateHeader := range amzDateHeaders {
		amzDateStr := req.Header.Get(http.CanonicalHeaderKey(amzDateHeader))
		if amzDateStr != "" {
			return parseAmzDate(amzDateStr)
		}
	}
	// Date header missing.
	return time.Time{}, ErrMissingDateHeader
}

type timeValidityHandler struct {
	handler http.Handler
}

// setTimeValidityHandler to validate parsable time over http header
func setTimeValidityHandler(h http.Handler) http.Handler {
	return timeValidityHandler{h}
}

func (h timeValidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	aType := getRequestAuthType(r)
	if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned {
		// Verify if date headers are set, if not reject the request
		amzDate, apiErr := parseAmzDateHeader(r)
		if apiErr != ErrNone {
			// All our internal APIs are sensitive towards Date
			// header, for all requests where Date header is not
			// present we will reject such clients.
			writeErrorResponse(w, r, apiErr, r.URL.Path)
			return
		}
		// Verify if the request date header is shifted by less than globalMaxSkewTime parameter in the past
		// or in the future, reject request otherwise.
		curTime := time.Now().UTC()
		if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
			writeErrorResponse(w, r, ErrRequestTimeTooSkewed, r.URL.Path)
			return
		}
	}
	h.handler.ServeHTTP(w, r)
}

type resourceHandler struct {
	handler http.Handler
}

// setCorsHandler handler for CORS (Cross Origin Resource Sharing)
func setCorsHandler(h http.Handler) http.Handler {
	c := cors.New(cors.Options{
		AllowedOrigins: []string{"*"},
		AllowedMethods: []string{"GET", "HEAD", "POST", "PUT"},
		AllowedHeaders: []string{"*"},
		ExposedHeaders: []string{"ETag"},
	})
	return c.Handler(h)
}

// setIgnoreResourcesHandler -
// Ignore resources handler is wrapper handler used for API request resource validation
// Since we do not support all the S3 queries, it is necessary for us to throw back a
// valid error message indicating that requested feature is not implemented.
func setIgnoreResourcesHandler(h http.Handler) http.Handler {
	return resourceHandler{h}
}

// Resource handler ServeHTTP() wrapper
func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Skip the first element which is usually '/' and split the rest.
	splits := strings.SplitN(r.URL.Path[1:], "/", 2)

	// Save bucketName and objectName extracted from url Path.
	var bucketName, objectName string
	if len(splits) == 1 {
		bucketName = splits[0]
	}
	if len(splits) == 2 {
		bucketName = splits[0]
		objectName = splits[1]
	}

	// If bucketName is present and not objectName check for bucket level resource queries.
	if bucketName != "" && objectName == "" {
		if ignoreNotImplementedBucketResources(r) {
			writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
			return
		}
	}
	// If bucketName and objectName are present check for its resource queries.
	if bucketName != "" && objectName != "" {
		if ignoreNotImplementedObjectResources(r) {
			writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
			return
		}
	}
	// A put method on path "/" doesn't make sense, ignore it.
	if r.Method == "PUT" && r.URL.Path == "/" {
		writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
		return
	}

	// Serve HTTP.
	h.handler.ServeHTTP(w, r)
}

//// helpers

// Checks requests for not implemented Bucket resources
func ignoreNotImplementedBucketResources(req *http.Request) bool {
	for name := range req.URL.Query() {
		if notimplementedBucketResourceNames[name] {
			return true
		}
	}
	return false
}

// Checks requests for not implemented Object resources
func ignoreNotImplementedObjectResources(req *http.Request) bool {
	for name := range req.URL.Query() {
		if notimplementedObjectResourceNames[name] {
			return true
		}
	}
	return false
}

// List of not implemented bucket queries
var notimplementedBucketResourceNames = map[string]bool{
	"acl":            true,
	"cors":           true,
	"lifecycle":      true,
	"logging":        true,
	"replication":    true,
	"tagging":        true,
	"versions":       true,
	"requestPayment": true,
	"versioning":     true,
	"website":        true,
}

// List of not implemented object queries
var notimplementedObjectResourceNames = map[string]bool{
	"torrent": true,
	"acl":     true,
	"policy":  true,
}
<commit_msg>handlers: Handle re-direction properly for S3 requests. (#3355)<commit_after>/*
 * Minio Cloud Storage, (C) 2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cmd

import (
	"net/http"
	"path"
	"regexp"
	"strings"
	"time"

	humanize "github.com/dustin/go-humanize"
	router "github.com/gorilla/mux"
	"github.com/rs/cors"
)

// HandlerFunc - useful to chain different middleware http.Handler
type HandlerFunc func(http.Handler) http.Handler

func registerHandlers(mux *router.Router, handlerFns ...HandlerFunc) http.Handler {
	var f http.Handler
	f = mux
	for _, hFn := range handlerFns {
		f = hFn(f)
	}
	return f
}

// Adds limiting body size middleware

// Maximum allowed form data field values. 64MiB is a guessed practical value
// which is more than enough to accommodate any form data fields and headers.
const requestFormDataSize = 64 * humanize.MiByte

// For any HTTP request, request body should be not more than 5GiB + requestFormDataSize
// where, 5GiB is the maximum allowed object size for object upload.
const requestMaxBodySize = 5*humanize.GiByte + requestFormDataSize

type requestSizeLimitHandler struct {
	handler     http.Handler
	maxBodySize int64
}

func setRequestSizeLimitHandler(h http.Handler) http.Handler {
	return requestSizeLimitHandler{handler: h, maxBodySize: requestMaxBodySize}
}

func (h requestSizeLimitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Restricting read data to a given maximum length
	r.Body = http.MaxBytesReader(w, r.Body, h.maxBodySize)
	h.handler.ServeHTTP(w, r)
}

// Adds redirect rules for incoming requests.
type redirectHandler struct {
	handler        http.Handler
	locationPrefix string
}

// Reserved bucket.
const (
	reservedBucket = "/minio"
)

func setBrowserRedirectHandler(h http.Handler) http.Handler {
	return redirectHandler{handler: h, locationPrefix: reservedBucket}
}

func (h redirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	aType := getRequestAuthType(r)
	// Re-direct only for JWT and anonymous requests coming from web-browser.
	if aType == authTypeJWT || aType == authTypeAnonymous {
		// Re-direction handled specifically for browsers.
		if strings.Contains(r.Header.Get("User-Agent"), "Mozilla") {
			switch r.URL.Path {
			case "/", "/webrpc", "/login", "/favicon.ico":
				// '/' is redirected to 'locationPrefix/'
				// '/webrpc' is redirected to 'locationPrefix/webrpc'
				// '/login' is redirected to 'locationPrefix/login'
				location := h.locationPrefix + r.URL.Path
				// Redirect to new location.
				http.Redirect(w, r, location, http.StatusTemporaryRedirect)
				return
			case h.locationPrefix:
				// locationPrefix is redirected to 'locationPrefix/'
				location := h.locationPrefix + "/"
				http.Redirect(w, r, location, http.StatusTemporaryRedirect)
				return
			}
		}
	}
	h.handler.ServeHTTP(w, r)
}

// Adds Cache-Control header
type cacheControlHandler struct {
	handler http.Handler
}

func setBrowserCacheControlHandler(h http.Handler) http.Handler {
	return cacheControlHandler{h}
}

func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" && strings.Contains(r.Header.Get("User-Agent"), "Mozilla") {
		// For all browser requests set appropriate Cache-Control policies
		match, e := regexp.MatchString(reservedBucket+`/([^/]+\.js|favicon.ico)`, r.URL.Path)
		if e != nil {
			writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
			return
		}
		if match {
			// For assets set cache expiry of one year. For each release, the name
			// of the asset name will change and hence it can not be served from cache.
			w.Header().Set("Cache-Control", "max-age=31536000")
		} else if strings.HasPrefix(r.URL.Path, reservedBucket+"/") {
			// For non asset requests we serve index.html which will never be cached.
			w.Header().Set("Cache-Control", "no-store")
		}
	}
	h.handler.ServeHTTP(w, r)
}

// Adds verification for incoming paths.
type minioPrivateBucketHandler struct {
	handler        http.Handler
	reservedBucket string
}

func setPrivateBucketHandler(h http.Handler) http.Handler {
	return minioPrivateBucketHandler{handler: h, reservedBucket: reservedBucket}
}

func (h minioPrivateBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// For all non browser requests, reject access to 'reservedBucket'.
	if !strings.Contains(r.Header.Get("User-Agent"), "Mozilla") && path.Clean(r.URL.Path) == reservedBucket {
		writeErrorResponse(w, r, ErrAllAccessDisabled, r.URL.Path)
		return
	}
	h.handler.ServeHTTP(w, r)
}

// Supported Amz date formats.
var amzDateFormats = []string{
	time.RFC1123,
	time.RFC1123Z,
	iso8601Format,
	// Add new AMZ date formats here.
}

// parseAmzDate - parses date string into supported amz date formats.
func parseAmzDate(amzDateStr string) (amzDate time.Time, apiErr APIErrorCode) {
	for _, dateFormat := range amzDateFormats {
		amzDate, e := time.Parse(dateFormat, amzDateStr)
		if e == nil {
			return amzDate, ErrNone
		}
	}
	return time.Time{}, ErrMalformedDate
}

// Supported Amz date headers.
var amzDateHeaders = []string{
	"x-amz-date",
	"date",
}

// parseAmzDateHeader - parses supported amz date headers, in
// supported amz date formats.
func parseAmzDateHeader(req *http.Request) (time.Time, APIErrorCode) {
	for _, amzDateHeader := range amzDateHeaders {
		amzDateStr := req.Header.Get(http.CanonicalHeaderKey(amzDateHeader))
		if amzDateStr != "" {
			return parseAmzDate(amzDateStr)
		}
	}
	// Date header missing.
	return time.Time{}, ErrMissingDateHeader
}

type timeValidityHandler struct {
	handler http.Handler
}

// setTimeValidityHandler to validate parsable time over http header
func setTimeValidityHandler(h http.Handler) http.Handler {
	return timeValidityHandler{h}
}

func (h timeValidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	aType := getRequestAuthType(r)
	if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned {
		// Verify if date headers are set, if not reject the request
		amzDate, apiErr := parseAmzDateHeader(r)
		if apiErr != ErrNone {
			// All our internal APIs are sensitive towards Date
			// header, for all requests where Date header is not
			// present we will reject such clients.
			writeErrorResponse(w, r, apiErr, r.URL.Path)
			return
		}
		// Verify if the request date header is shifted by less than globalMaxSkewTime parameter in the past
		// or in the future, reject request otherwise.
		curTime := time.Now().UTC()
		if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
			writeErrorResponse(w, r, ErrRequestTimeTooSkewed, r.URL.Path)
			return
		}
	}
	h.handler.ServeHTTP(w, r)
}

type resourceHandler struct {
	handler http.Handler
}

// setCorsHandler handler for CORS (Cross Origin Resource Sharing)
func setCorsHandler(h http.Handler) http.Handler {
	c := cors.New(cors.Options{
		AllowedOrigins: []string{"*"},
		AllowedMethods: []string{"GET", "HEAD", "POST", "PUT"},
		AllowedHeaders: []string{"*"},
		ExposedHeaders: []string{"ETag"},
	})
	return c.Handler(h)
}

// setIgnoreResourcesHandler -
// Ignore resources handler is wrapper handler used for API request resource validation
// Since we do not support all the S3 queries, it is necessary for us to throw back a
// valid error message indicating that requested feature is not implemented.
func setIgnoreResourcesHandler(h http.Handler) http.Handler {
	return resourceHandler{h}
}

// Resource handler ServeHTTP() wrapper
func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Skip the first element which is usually '/' and split the rest.
	splits := strings.SplitN(r.URL.Path[1:], "/", 2)

	// Save bucketName and objectName extracted from url Path.
	var bucketName, objectName string
	if len(splits) == 1 {
		bucketName = splits[0]
	}
	if len(splits) == 2 {
		bucketName = splits[0]
		objectName = splits[1]
	}

	// If bucketName is present and not objectName check for bucket level resource queries.
	if bucketName != "" && objectName == "" {
		if ignoreNotImplementedBucketResources(r) {
			writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
			return
		}
	}
	// If bucketName and objectName are present check for its resource queries.
	if bucketName != "" && objectName != "" {
		if ignoreNotImplementedObjectResources(r) {
			writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
			return
		}
	}
	// A put method on path "/" doesn't make sense, ignore it.
	if r.Method == "PUT" && r.URL.Path == "/" {
		writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
		return
	}

	// Serve HTTP.
	h.handler.ServeHTTP(w, r)
}

//// helpers

// Checks requests for not implemented Bucket resources
func ignoreNotImplementedBucketResources(req *http.Request) bool {
	for name := range req.URL.Query() {
		if notimplementedBucketResourceNames[name] {
			return true
		}
	}
	return false
}

// Checks requests for not implemented Object resources
func ignoreNotImplementedObjectResources(req *http.Request) bool {
	for name := range req.URL.Query() {
		if notimplementedObjectResourceNames[name] {
			return true
		}
	}
	return false
}

// List of not implemented bucket queries
var notimplementedBucketResourceNames = map[string]bool{
	"acl":            true,
	"cors":           true,
	"lifecycle":      true,
	"logging":        true,
	"replication":    true,
	"tagging":        true,
	"versions":       true,
	"requestPayment": true,
	"versioning":     true,
	"website":        true,
}

// List of not implemented object queries
var notimplementedObjectResourceNames = map[string]bool{
	"torrent": true,
	"acl":     true,
	"policy":  true,
}
<|endoftext|>