<commit_before>// This file contains functions for transpiling common branching and control
// flow, such as "if", "while", "do" and "for". The more complicated control
// flows like "switch" will be put into their own file of the same or sensible
// name.

package transpiler

import (
	"fmt"
	"go/token"

	"github.com/elliotchance/c2go/ast"
	"github.com/elliotchance/c2go/program"
	"github.com/elliotchance/c2go/types"
	"github.com/elliotchance/c2go/util"

	goast "go/ast"
)

func transpileIfStmt(n *ast.IfStmt, p *program.Program) (
	*goast.IfStmt, []goast.Stmt, []goast.Stmt, error) {
	preStmts := []goast.Stmt{}
	postStmts := []goast.Stmt{}
	children := n.Children

	// There is always 4 or 5 children in an IfStmt. For example:
	//
	//     if (i == 0) {
	//         return 0;
	//     } else {
	//         return 1;
	//     }
	//
	// 1. Not sure what this is for. This gets removed.
	// 2. Not sure what this is for.
	// 3. conditional = BinaryOperator: i == 0
	// 4. body = CompoundStmt: { return 0; }
	// 5. elseBody = CompoundStmt: { return 1; }
	//
	// elseBody will be nil if there is no else clause.

	// On linux I have seen only 4 children for an IfStmt with the same
	// definitions above, but missing the first argument. Since we don't
	// know what the first argument is for anyway we will just remove it on
	// Mac if necessary.
	if len(children) == 5 && children[0] != nil {
		panic("non-nil child 0 in IfStmt")
	}
	if len(children) == 5 {
		children = children[1:]
	}

	// From here on there must be 4 children.
	if len(children) != 4 {
		panic(fmt.Sprintf("Expected 4 children in IfStmt, got %#v", children))
	}

	// Maybe we will discover what the nil value is?
	if children[0] != nil {
		panic("non-nil child 0 in IfStmt")
	}

	conditional, conditionalType, newPre, newPost, err := transpileToExpr(children[1], p)
	if err != nil {
		return nil, nil, nil, err
	}

	// The condition in Go must always be a bool.
	boolCondition, err := types.CastExpr(p, conditional, conditionalType, "bool")
	p.AddMessage(ast.GenerateWarningOrErrorMessage(err, n, boolCondition == nil))

	if boolCondition == nil {
		boolCondition = util.NewNil()
	}

	body, newPre, newPost, err := transpileToBlockStmt(children[2], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	r := &goast.IfStmt{
		Cond: boolCondition,
		Body: body,
	}

	if children[3] != nil {
		elseBody, newPre, newPost, err := transpileToBlockStmt(children[3], p)
		if err != nil {
			return nil, nil, nil, err
		}

		preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

		r.Else = elseBody
	}

	return r, newPre, newPost, nil
}

func transpileForStmt(n *ast.ForStmt, p *program.Program) (
	*goast.ForStmt, []goast.Stmt, []goast.Stmt, error) {
	preStmts := []goast.Stmt{}
	postStmts := []goast.Stmt{}

	children := n.Children

	// There are always 5 children in a ForStmt, for example:
	//
	//     for ( c = 0 ; c < n ; c++ ) {
	//         doSomething();
	//     }
	//
	// 1. initExpression = BinaryStmt: c = 0
	// 2. Not sure what this is for, but it's always nil. There is a panic
	//    below in case we discover what it is used for (pun intended).
	// 3. conditionalExpression = BinaryStmt: c < n
	// 4. stepExpression = BinaryStmt: c++
	// 5. body = CompoundStmt: { CallExpr }

	if len(children) != 5 {
		panic(fmt.Sprintf("Expected 5 children in ForStmt, got %#v", children))
	}

	// TODO: The second child of a ForStmt appears to always be null.
	// Are there any cases where it is used?
	if children[1] != nil {
		panic("non-nil child 1 in ForStmt")
	}

	// If we have 2 and more initializations like
	// in operator for
	// for( a = 0, b = 0, c = 0; a < 5; a ++)
	switch c := children[0].(type) {
	case *ast.BinaryOperator:
		if c.Operator == "," {
			// recursive action to code like that:
			// a = 0;
			// b = 0;
			// for(c = 0 ; a < 5 ; a++)
			before, newPre, newPost, err := transpileToStmt(c.Children[0], p)
			if err != nil {
				return nil, nil, nil, err
			}
			preStmts = append(preStmts, newPre...)
			preStmts = append(preStmts, before)
			preStmts = append(preStmts, newPost...)
			children[0] = c.Children[1]
		}
	}

	init, newPre, newPost, err := transpileToStmt(children[0], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	// If we have 2 and more increments
	// in operator for
	// for( a = 0; a < 5; a ++, b++, c+=2)
	switch c := children[3].(type) {
	case *ast.BinaryOperator:
		if c.Operator == "," {
			// recursive action to code like that:
			// a = 0;
			// b = 0;
			// for(a = 0 ; a < 5 ; ){
			// 		body
			// 		a++;
			// 		b++;
			//		c+=2;
			// }
			//
			var compound ast.CompoundStmt
			if children[4] != nil {
				compound = *children[4].(*ast.CompoundStmt)
			}
			compound.Children = append(compound.Children, c.Children[0:len(c.Children)]...)
			children[4] = &compound
			children[3] = nil
		}
	}

	post, newPre, newPost, err := transpileToStmt(children[3], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	// If we have 2 and more conditions
	// in operator for
	// for( a = 0; b = c, b++, a < 5; a ++)
	switch c := children[2].(type) {
	case *ast.BinaryOperator:
		if c.Operator == "," {
			// recursive action to code like that:
			// a = 0;
			// b = 0;
			// for(a = 0 ; ; c+=2){
			// 		b = c;
			// 		b++;
			//		if (!(a < 5))
			// 			break;
			// 		body
			// }
			tempSlice := c.Children[0 : len(c.Children)-1]

			var condition ast.IfStmt
			condition.AddChild(nil)
			var par ast.ParenExpr
			par.AddChild(c.Children[len(c.Children)-1])
			var unitary ast.UnaryOperator
			unitary.AddChild(&par)
			unitary.Operator = "!"
			condition.AddChild(&unitary)
			var c ast.CompoundStmt
			c.AddChild(&ast.BreakStmt{})
			condition.AddChild(&c)
			condition.AddChild(nil)

			tempSlice = append(tempSlice, &condition)

			compound := children[4].(*ast.CompoundStmt)
			compound.Children = append(tempSlice, compound.Children...)
			children[4] = compound

			children[2] = nil
		}
	}

	// The condition can be nil. This means an infinite loop and will be
	// rendered in Go as "for {".
	var condition goast.Expr
	if children[2] != nil {
		var conditionType string
		var newPre, newPost []goast.Stmt
		condition, conditionType, newPre, newPost, err = transpileToExpr(children[2], p)
		if err != nil {
			return nil, nil, nil, err
		}

		preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

		condition, err = types.CastExpr(p, condition, conditionType, "bool")
		p.AddMessage(ast.GenerateWarningOrErrorMessage(err, n, condition == nil))

		if condition == nil {
			condition = util.NewNil()
		}
	}

	body, newPre, newPost, err := transpileToBlockStmt(children[4], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	return &goast.ForStmt{
		Init: init,
		Cond: condition,
		Post: post,
		Body: body,
	}, preStmts, postStmts, nil
}

func transpileWhileStmt(n *ast.WhileStmt, p *program.Program) (
	*goast.ForStmt, []goast.Stmt, []goast.Stmt, error) {
	preStmts := []goast.Stmt{}
	postStmts := []goast.Stmt{}

	// TODO: The first child of a WhileStmt appears to always be null.
	// Are there any cases where it is used?
	children := n.Children[1:]

	body, newPre, newPost, err := transpileToBlockStmt(children[1], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	condition, conditionType, newPre, newPost, err := transpileToExpr(children[0], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	cond, err := types.CastExpr(p, condition, conditionType, "bool")
	p.AddMessage(ast.GenerateWarningOrErrorMessage(err, n, cond == nil))

	if cond == nil {
		cond = util.NewNil()
	}

	return &goast.ForStmt{
		Cond: cond,
		Body: body,
	}, preStmts, postStmts, nil
}

func transpileDoStmt(n *ast.DoStmt, p *program.Program) (
	*goast.ForStmt, []goast.Stmt, []goast.Stmt, error) {
	preStmts := []goast.Stmt{}
	postStmts := []goast.Stmt{}
	children := n.Children

	body, newPre, newPost, err := transpileToBlockStmt(children[0], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	condition, conditionType, newPre, newPost, err := transpileToExpr(children[1], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	// Add IfStmt to the end of the loop to check the condition.
	x, err := types.CastExpr(p, condition, conditionType, "bool")
	p.AddMessage(ast.GenerateWarningOrErrorMessage(err, n, x == nil))

	if x == nil {
		x = util.NewNil()
	}

	body.List = append(body.List, &goast.IfStmt{
		Cond: &goast.UnaryExpr{
			Op: token.NOT,
			X:  x,
		},
		Body: &goast.BlockStmt{
			List: []goast.Stmt{&goast.BranchStmt{Tok: token.BREAK}},
		},
	})

	return &goast.ForStmt{
		Body: body,
	}, preStmts, postStmts, nil
}

func transpileContinueStmt(n *ast.ContinueStmt, p *program.Program) (*goast.BranchStmt, error) {
	return &goast.BranchStmt{
		Tok: token.CONTINUE,
	}, nil
}
<commit_msg>Bug fix for operator FOR without body<commit_after>// This file contains functions for transpiling common branching and control
// flow, such as "if", "while", "do" and "for". The more complicated control
// flows like "switch" will be put into their own file of the same or sensible
// name.

package transpiler

import (
	"fmt"
	"go/token"

	"github.com/elliotchance/c2go/ast"
	"github.com/elliotchance/c2go/program"
	"github.com/elliotchance/c2go/types"
	"github.com/elliotchance/c2go/util"

	goast "go/ast"
)

func transpileIfStmt(n *ast.IfStmt, p *program.Program) (
	*goast.IfStmt, []goast.Stmt, []goast.Stmt, error) {
	preStmts := []goast.Stmt{}
	postStmts := []goast.Stmt{}
	children := n.Children

	// There is always 4 or 5 children in an IfStmt. For example:
	//
	//     if (i == 0) {
	//         return 0;
	//     } else {
	//         return 1;
	//     }
	//
	// 1. Not sure what this is for. This gets removed.
	// 2. Not sure what this is for.
	// 3. conditional = BinaryOperator: i == 0
	// 4. body = CompoundStmt: { return 0; }
	// 5. elseBody = CompoundStmt: { return 1; }
	//
	// elseBody will be nil if there is no else clause.

	// On linux I have seen only 4 children for an IfStmt with the same
	// definitions above, but missing the first argument. Since we don't
	// know what the first argument is for anyway we will just remove it on
	// Mac if necessary.
	if len(children) == 5 && children[0] != nil {
		panic("non-nil child 0 in IfStmt")
	}
	if len(children) == 5 {
		children = children[1:]
	}

	// From here on there must be 4 children.
	if len(children) != 4 {
		panic(fmt.Sprintf("Expected 4 children in IfStmt, got %#v", children))
	}

	// Maybe we will discover what the nil value is?
	if children[0] != nil {
		panic("non-nil child 0 in IfStmt")
	}

	conditional, conditionalType, newPre, newPost, err := transpileToExpr(children[1], p)
	if err != nil {
		return nil, nil, nil, err
	}

	// The condition in Go must always be a bool.
	boolCondition, err := types.CastExpr(p, conditional, conditionalType, "bool")
	p.AddMessage(ast.GenerateWarningOrErrorMessage(err, n, boolCondition == nil))

	if boolCondition == nil {
		boolCondition = util.NewNil()
	}

	body, newPre, newPost, err := transpileToBlockStmt(children[2], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	r := &goast.IfStmt{
		Cond: boolCondition,
		Body: body,
	}

	if children[3] != nil {
		elseBody, newPre, newPost, err := transpileToBlockStmt(children[3], p)
		if err != nil {
			return nil, nil, nil, err
		}

		preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

		r.Else = elseBody
	}

	return r, newPre, newPost, nil
}

func transpileForStmt(n *ast.ForStmt, p *program.Program) (
	*goast.ForStmt, []goast.Stmt, []goast.Stmt, error) {
	preStmts := []goast.Stmt{}
	postStmts := []goast.Stmt{}

	children := n.Children

	// There are always 5 children in a ForStmt, for example:
	//
	//     for ( c = 0 ; c < n ; c++ ) {
	//         doSomething();
	//     }
	//
	// 1. initExpression = BinaryStmt: c = 0
	// 2. Not sure what this is for, but it's always nil. There is a panic
	//    below in case we discover what it is used for (pun intended).
	// 3. conditionalExpression = BinaryStmt: c < n
	// 4. stepExpression = BinaryStmt: c++
	// 5. body = CompoundStmt: { CallExpr }

	if len(children) != 5 {
		panic(fmt.Sprintf("Expected 5 children in ForStmt, got %#v", children))
	}

	// TODO: The second child of a ForStmt appears to always be null.
	// Are there any cases where it is used?
	if children[1] != nil {
		panic("non-nil child 1 in ForStmt")
	}

	// If we have 2 and more initializations like
	// in operator for
	// for( a = 0, b = 0, c = 0; a < 5; a ++)
	switch c := children[0].(type) {
	case *ast.BinaryOperator:
		if c.Operator == "," {
			// recursive action to code like that:
			// a = 0;
			// b = 0;
			// for(c = 0 ; a < 5 ; a++)
			before, newPre, newPost, err := transpileToStmt(c.Children[0], p)
			if err != nil {
				return nil, nil, nil, err
			}
			preStmts = append(preStmts, newPre...)
			preStmts = append(preStmts, before)
			preStmts = append(preStmts, newPost...)
			children[0] = c.Children[1]
		}
	}

	init, newPre, newPost, err := transpileToStmt(children[0], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	// If we have 2 and more increments
	// in operator for
	// for( a = 0; a < 5; a ++, b++, c+=2)
	switch c := children[3].(type) {
	case *ast.BinaryOperator:
		if c.Operator == "," {
			// recursive action to code like that:
			// a = 0;
			// b = 0;
			// for(a = 0 ; a < 5 ; ){
			// 		body
			// 		a++;
			// 		b++;
			//		c+=2;
			// }
			//
			var compound ast.CompoundStmt
			// if body is exist
			if children[4] != nil {
				compound = *children[4].(*ast.CompoundStmt)
			}
			compound.Children = append(compound.Children, c.Children[0:len(c.Children)]...)
			children[4] = &compound
			children[3] = nil
		}
	}

	post, newPre, newPost, err := transpileToStmt(children[3], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	// If we have 2 and more conditions
	// in operator for
	// for( a = 0; b = c, b++, a < 5; a ++)
	switch c := children[2].(type) {
	case *ast.BinaryOperator:
		if c.Operator == "," {
			// recursive action to code like that:
			// a = 0;
			// b = 0;
			// for(a = 0 ; ; c+=2){
			// 		b = c;
			// 		b++;
			//		if (!(a < 5))
			// 			break;
			// 		body
			// }
			tempSlice := c.Children[0 : len(c.Children)-1]

			var condition ast.IfStmt
			condition.AddChild(nil)
			var par ast.ParenExpr
			par.AddChild(c.Children[len(c.Children)-1])
			var unitary ast.UnaryOperator
			unitary.AddChild(&par)
			unitary.Operator = "!"
			condition.AddChild(&unitary)
			var c ast.CompoundStmt
			c.AddChild(&ast.BreakStmt{})
			condition.AddChild(&c)
			condition.AddChild(nil)

			tempSlice = append(tempSlice, &condition)

			compound := children[4].(*ast.CompoundStmt)
			compound.Children = append(tempSlice, compound.Children...)
			children[4] = compound

			children[2] = nil
		}
	}

	// The condition can be nil. This means an infinite loop and will be
	// rendered in Go as "for {".
	var condition goast.Expr
	if children[2] != nil {
		var conditionType string
		var newPre, newPost []goast.Stmt
		condition, conditionType, newPre, newPost, err = transpileToExpr(children[2], p)
		if err != nil {
			return nil, nil, nil, err
		}

		preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

		condition, err = types.CastExpr(p, condition, conditionType, "bool")
		p.AddMessage(ast.GenerateWarningOrErrorMessage(err, n, condition == nil))

		if condition == nil {
			condition = util.NewNil()
		}
	}

	body, newPre, newPost, err := transpileToBlockStmt(children[4], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	return &goast.ForStmt{
		Init: init,
		Cond: condition,
		Post: post,
		Body: body,
	}, preStmts, postStmts, nil
}

func transpileWhileStmt(n *ast.WhileStmt, p *program.Program) (
	*goast.ForStmt, []goast.Stmt, []goast.Stmt, error) {
	preStmts := []goast.Stmt{}
	postStmts := []goast.Stmt{}

	// TODO: The first child of a WhileStmt appears to always be null.
	// Are there any cases where it is used?
	children := n.Children[1:]

	body, newPre, newPost, err := transpileToBlockStmt(children[1], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	condition, conditionType, newPre, newPost, err := transpileToExpr(children[0], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	cond, err := types.CastExpr(p, condition, conditionType, "bool")
	p.AddMessage(ast.GenerateWarningOrErrorMessage(err, n, cond == nil))

	if cond == nil {
		cond = util.NewNil()
	}

	return &goast.ForStmt{
		Cond: cond,
		Body: body,
	}, preStmts, postStmts, nil
}

func transpileDoStmt(n *ast.DoStmt, p *program.Program) (
	*goast.ForStmt, []goast.Stmt, []goast.Stmt, error) {
	preStmts := []goast.Stmt{}
	postStmts := []goast.Stmt{}
	children := n.Children

	body, newPre, newPost, err := transpileToBlockStmt(children[0], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	condition, conditionType, newPre, newPost, err := transpileToExpr(children[1], p)
	if err != nil {
		return nil, nil, nil, err
	}

	preStmts, postStmts = combinePreAndPostStmts(preStmts, postStmts, newPre, newPost)

	// Add IfStmt to the end of the loop to check the condition.
	x, err := types.CastExpr(p, condition, conditionType, "bool")
	p.AddMessage(ast.GenerateWarningOrErrorMessage(err, n, x == nil))

	if x == nil {
		x = util.NewNil()
	}

	body.List = append(body.List, &goast.IfStmt{
		Cond: &goast.UnaryExpr{
			Op: token.NOT,
			X:  x,
		},
		Body: &goast.BlockStmt{
			List: []goast.Stmt{&goast.BranchStmt{Tok: token.BREAK}},
		},
	})

	return &goast.ForStmt{
		Body: body,
	}, preStmts, postStmts, nil
}

func transpileContinueStmt(n *ast.ContinueStmt, p *program.Program) (*goast.BranchStmt, error) {
	return &goast.BranchStmt{
		Tok: token.CONTINUE,
	}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"runtime"
	"runtime/debug"
	"strings"

	"github.com/anaminus/cobra"
	"github.com/anaminus/pflag"
)

func init() {
	var c VersionCommand
	var cmd = &cobra.Command{
		Use:  "version",
		RunE: c.Run,
	}
	c.SetFlags(cmd.PersistentFlags())
	Program.AddCommand(cmd)
}

func VersionString() string {
	s := Version
	if Prerelease != "" {
		s += "-" + Prerelease
	}
	if Build != "" {
		s += "+" + Build
	}
	return s
}

type VersionInfo struct {
	Version    string
	Prerelease string      `json:",omitempty"`
	Build      string      `json:",omitempty"`
	Config     *ConfigInfo `json:",omitempty"`
	Go         *GoInfo     `json:",omitempty"`
}

type ConfigInfo struct {
	SSLLogVar string `json:",omitempty"`
}

type GoInfo struct {
	Version    string
	Compiler   string
	TargetOS   string
	TargetArch string
	Build      *BuildInfo `json:",omitempty"`
}

type BuildInfo struct {
	GoVersion string
	Path      string          `json:",omitempty"`
	Main      debug.Module    `json:",omitempty"`
	Deps      []*debug.Module `json:",omitempty"`
	Settings  []debug.BuildSetting
}

func writeModuleString(s *strings.Builder, mod debug.Module, prefix string) {
	s.WriteString("\t")
	s.WriteString(prefix)
	s.WriteString("\t")
	s.WriteString(mod.Path)
	if mod.Version != "" {
		s.WriteString("\t")
		s.WriteString(mod.Version)
	}
	if mod.Sum != "" {
		s.WriteString("\t")
		s.WriteString(mod.Sum)
	}
	s.WriteString("\n")
	if mod.Replace != nil {
		writeModuleString(s, *mod.Replace, "=>")
	}
}

func (v VersionInfo) String() string {
	if v.Go == nil {
		return VersionString()
	}
	var s strings.Builder
	fmt.Fprintf(&s, "rbxmk version: %s\n", v.Version)
	if v.Prerelease != "" {
		fmt.Fprintf(&s, "rbxmk prerelease: %s\n", v.Prerelease)
	}
	if v.Build != "" {
		fmt.Fprintf(&s, "rbxmk build: %s\n", v.Build)
	}
	if v.Config != nil {
		if v.Config.SSLLogVar != "" {
			fmt.Fprintf(&s, "ssl log var: %s\n", v.Config.SSLLogVar)
		}
	}
	if v.Go != nil {
		fmt.Fprintf(&s, "go version: %s\n", v.Go.Version)
		fmt.Fprintf(&s, "go compiler: %s\n", v.Go.Compiler)
		fmt.Fprintf(&s, "go target: %s/%s\n", v.Go.TargetOS, v.Go.TargetArch)
		if v.Go.Build != nil {
			fmt.Fprintf(&s, "settings:\n")
			if len(v.Go.Build.Settings) > 0 {
				for _, setting := range v.Go.Build.Settings {
					fmt.Fprintf(&s, "\t%s=%s\n", setting.Key, setting.Value)
				}
			}
			if v.Go.Build.Path != "" {
				fmt.Fprintf(&s, "path: %s\n", v.Go.Build.Path)
			}
			if v.Go.Build.Main != (debug.Module{}) || len(v.Go.Build.Deps) > 0 {
				fmt.Fprintf(&s, "modules:\n")
				writeModuleString(&s, v.Go.Build.Main, "mod")
				for _, dep := range v.Go.Build.Deps {
					writeModuleString(&s, *dep, "dep")
				}
			}
		}
	}
	return s.String()
}

type VersionCommand struct {
	Format  string
	Verbose int
	Error   bool
}

func (c *VersionCommand) SetFlags(flags *pflag.FlagSet) {
	flags.StringVarP(&c.Format, "format", "f", "text", DocFlag("Commands/version:Flags/format"))
	flags.CountVarP(&c.Verbose, "verbose", "v", DocFlag("Commands/version:Flags/verbose"))
}

func (c *VersionCommand) WriteInfo(w io.Writer) error {
	info := VersionInfo{
		Version:    Version,
		Prerelease: Prerelease,
		Build:      Build,
	}
	if c.Verbose > 0 {
		info.Config = &ConfigInfo{
			SSLLogVar: sslKeyLogFileEnvVar,
		}
		info.Go = &GoInfo{
			Version:    runtime.Version(),
			Compiler:   runtime.Compiler,
			TargetOS:   runtime.GOOS,
			TargetArch: runtime.GOARCH,
		}
		if c.Verbose > 1 {
			binfo, _ := debug.ReadBuildInfo()
			info.Go.Build = (*BuildInfo)(binfo)
			if c.Verbose < 2 {
				info.Go.Build.Settings = nil
			}
			if c.Verbose < 3 {
				info.Go.Build.Path = ""
				info.Go.Build.Main = debug.Module{}
				info.Go.Build.Deps = nil
			}
		}
	}
	switch c.Format {
	case "json":
		je := json.NewEncoder(w)
		je.SetEscapeHTML(false)
		je.SetIndent("", "\t")
		return je.Encode(info)
	case "text":
		_, err := fmt.Fprintln(w, info.String())
		return err
	default:
		return fmt.Errorf("unknown format %q", c.Format)
	}
}

func (c *VersionCommand) Run(cmd *cobra.Command, args []string) error {
	var w io.Writer
	if c.Error {
		w = cmd.ErrOrStderr()
	} else {
		w = cmd.OutOrStdout()
	}
	return c.WriteInfo(w)
}
<commit_msg>Remove redundant ConfigInfo.<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"runtime"
	"runtime/debug"
	"strings"

	"github.com/anaminus/cobra"
	"github.com/anaminus/pflag"
)

func init() {
	var c VersionCommand
	var cmd = &cobra.Command{
		Use:  "version",
		RunE: c.Run,
	}
	c.SetFlags(cmd.PersistentFlags())
	Program.AddCommand(cmd)
}

func VersionString() string {
	s := Version
	if Prerelease != "" {
		s += "-" + Prerelease
	}
	if Build != "" {
		s += "+" + Build
	}
	return s
}

type VersionInfo struct {
	Version    string
	Prerelease string  `json:",omitempty"`
	Build      string  `json:",omitempty"`
	Go         *GoInfo `json:",omitempty"`
}

type GoInfo struct {
	Version    string
	Compiler   string
	TargetOS   string
	TargetArch string
	Build      *BuildInfo `json:",omitempty"`
}

type BuildInfo struct {
	GoVersion string
	Path      string          `json:",omitempty"`
	Main      debug.Module    `json:",omitempty"`
	Deps      []*debug.Module `json:",omitempty"`
	Settings  []debug.BuildSetting
}

func writeModuleString(s *strings.Builder, mod debug.Module, prefix string) {
	s.WriteString("\t")
	s.WriteString(prefix)
	s.WriteString("\t")
	s.WriteString(mod.Path)
	if mod.Version != "" {
		s.WriteString("\t")
		s.WriteString(mod.Version)
	}
	if mod.Sum != "" {
		s.WriteString("\t")
		s.WriteString(mod.Sum)
	}
	s.WriteString("\n")
	if mod.Replace != nil {
		writeModuleString(s, *mod.Replace, "=>")
	}
}

func (v VersionInfo) String() string {
	if v.Go == nil {
		return VersionString()
	}
	var s strings.Builder
	fmt.Fprintf(&s, "rbxmk version: %s\n", v.Version)
	if v.Prerelease != "" {
		fmt.Fprintf(&s, "rbxmk prerelease: %s\n", v.Prerelease)
	}
	if v.Build != "" {
		fmt.Fprintf(&s, "rbxmk build: %s\n", v.Build)
	}
	if v.Go != nil {
		fmt.Fprintf(&s, "go version: %s\n", v.Go.Version)
		fmt.Fprintf(&s, "go compiler: %s\n", v.Go.Compiler)
		fmt.Fprintf(&s, "go target: %s/%s\n", v.Go.TargetOS, v.Go.TargetArch)
		if v.Go.Build != nil {
			fmt.Fprintf(&s, "settings:\n")
			if len(v.Go.Build.Settings) > 0 {
				for _, setting := range v.Go.Build.Settings {
					fmt.Fprintf(&s, "\t%s=%s\n", setting.Key, setting.Value)
				}
			}
			if v.Go.Build.Path != "" {
				fmt.Fprintf(&s, "path: %s\n", v.Go.Build.Path)
			}
			if v.Go.Build.Main != (debug.Module{}) || len(v.Go.Build.Deps) > 0 {
				fmt.Fprintf(&s, "modules:\n")
				writeModuleString(&s, v.Go.Build.Main, "mod")
				for _, dep := range v.Go.Build.Deps {
					writeModuleString(&s, *dep, "dep")
				}
			}
		}
	}
	return s.String()
}

type VersionCommand struct {
	Format  string
	Verbose int
	Error   bool
}

func (c *VersionCommand) SetFlags(flags *pflag.FlagSet) {
	flags.StringVarP(&c.Format, "format", "f", "text", DocFlag("Commands/version:Flags/format"))
	flags.CountVarP(&c.Verbose, "verbose", "v", DocFlag("Commands/version:Flags/verbose"))
}

func (c *VersionCommand) WriteInfo(w io.Writer) error {
	info := VersionInfo{
		Version:    Version,
		Prerelease: Prerelease,
		Build:      Build,
	}
	if c.Verbose > 0 {
		info.Go = &GoInfo{
			Version:    runtime.Version(),
			Compiler:   runtime.Compiler,
			TargetOS:   runtime.GOOS,
			TargetArch: runtime.GOARCH,
		}
		if c.Verbose > 1 {
			binfo, _ := debug.ReadBuildInfo()
			info.Go.Build = (*BuildInfo)(binfo)
			if c.Verbose < 2 {
				info.Go.Build.Settings = nil
			}
			if c.Verbose < 3 {
				info.Go.Build.Path = ""
				info.Go.Build.Main = debug.Module{}
				info.Go.Build.Deps = nil
			}
		}
	}
	switch c.Format {
	case "json":
		je := json.NewEncoder(w)
		je.SetEscapeHTML(false)
		je.SetIndent("", "\t")
		return je.Encode(info)
	case "text":
		_, err := fmt.Fprintln(w, info.String())
		return err
	default:
		return fmt.Errorf("unknown format %q", c.Format)
	}
}

func (c *VersionCommand) Run(cmd *cobra.Command, args []string) error {
	var w io.Writer
	if c.Error {
		w = cmd.ErrOrStderr()
	} else {
		w = cmd.OutOrStdout()
	}
	return c.WriteInfo(w)
}
<|endoftext|>


<commit_before>package shark

import (
	"strings"
	"os"
	"libxml"
	"fmt"
	xml "libxml/tree"
	tp "athena/proto"
	"libxml/xpath"
	"rubex"
)

func (ctx *Ctx) runBuiltIn(fun *Function, scope *Scope, ins *tp.Instruction, args []interface{}) (returnValue interface{}) {
	returnValue = ""
	switch fun.Name {
	case "this":
		returnValue = scope.Value
	case "yield":
		myYieldBlock := ctx.yieldBlock()
		ctx.Yields = ctx.Yields[:(len(ctx.Yields) - 1)]
		if ctx.yieldBlock() != nil {
			returnValue = ctx.runChildren(scope, myYieldBlock.Ins)
			if returnValue == nil {
				returnValue = "false"
			}
		} else {
			ctx.Log.Error("yield() failure")
		}
		ctx.Yields = append(ctx.Yields, myYieldBlock)

	case "var.Text":
		val := ctx.Env[args[0].(string)]
		returnValue = val
		if len(ins.Children) > 0 {
			ts := &Scope{Value: val}
			ctx.runChildren(ts, ins)
			returnValue = ts.Value
			ctx.Env[args[0].(string)] = returnValue.(string)
		}
	case "var.Text.Text":
		ctx.Env[args[0].(string)] = args[1].(string)
		returnValue = args[1].(string)
	case "deprecated.Text":
		ctx.Log.Info(args[0].(string))
	case "match.Text":
		// Setup stacks
		against, ok := args[0].(string)
		if !ok {
			ctx.Log.Error("AH!")
		}
		ctx.MatchStack = append(ctx.MatchStack, against)
		ctx.MatchShouldContinue = append(ctx.MatchShouldContinue, true)

		// Run children
		ctx.runChildren(scope, ins)

		if ctx.matchShouldContinue() {
			returnValue = "false"
		} else {
			returnValue = "true"
		}

		// Clear
		ctx.MatchShouldContinue = ctx.MatchShouldContinue[:len(ctx.MatchShouldContinue)-1]
		ctx.MatchStack = ctx.MatchStack[:len(ctx.MatchStack)-1]
	case "with.Text":
		returnValue = "false"
		if ctx.matchShouldContinue() {
			if args[0].(string) == ctx.matchTarget() {
				ctx.MatchShouldContinue[len(ctx.MatchShouldContinue)-1] = false
				ctx.runChildren(scope, ins)
				returnValue = "true"
			}
		}
	case "with.Regexp":
		returnValue = "false"
		if ctx.matchShouldContinue() {
			//println(matcher.MatchAgainst, matchWith)
			if (args[0].(*rubex.Regexp)).Match([]uint8(ctx.matchTarget())) {
				ctx.MatchShouldContinue[len(ctx.MatchShouldContinue)-1] = false
				ctx.runChildren(scope, ins)
				returnValue = "true"
			}
		}
	case "not.Text":
		returnValue = "false"
		if ctx.matchShouldContinue() {
			if args[0].(string) != ctx.matchTarget() {
				ctx.MatchShouldContinue[len(ctx.MatchShouldContinue)-1] = false
				ctx.runChildren(scope, ins)
				returnValue = "true"
			}
		}
	case "not.Regexp":
		returnValue = "false"
		if ctx.matchShouldContinue() {
			//println(matcher.MatchAgainst, matchWith)
			if !(args[0].(*rubex.Regexp)).Match([]uint8(ctx.matchTarget())) {
				ctx.MatchShouldContinue[len(ctx.MatchShouldContinue)-1] = false
				ctx.runChildren(scope, ins)
				returnValue = "true"
			}
		}
	case "regexp.Text.Text":
		mode := rubex.ONIG_OPTION_DEFAULT
		if strings.Index(args[1].(string), "i") >= 0 {
			mode = rubex.ONIG_OPTION_IGNORECASE
		}
		if strings.Index(args[1].(string), "m") >= 0 {
			mode = rubex.ONIG_OPTION_MULTILINE
		}
		var err os.Error
		returnValue, err = rubex.NewRegexp(args[0].(string), mode)
		if err != nil {
			ctx.Log.Error("Invalid regexp")
		}
	case "export.Text":
		val := make([]string, 2)
		val[0] = args[0].(string)
		ts := &Scope{Value: ""}
		ctx.runChildren(ts, ins)
		val[1] = ts.Value.(string)
		ctx.Exports = append(ctx.Exports, val)
	case "log.Text":
		ctx.Logs = append(ctx.Logs, args[0].(string))

	// ATOMIC FUNCTIONS
	case "concat.Text.Text":
		//println("Concat:", args[0].(string), "+", args[1].(string))
		returnValue = args[0].(string) + args[1].(string)
	case "concat.Text.Text.Text": //REMOVE
		returnValue = args[0].(string) + args[1].(string) + args[2].(string)
	case "downcase.Text":
		returnValue = strings.ToLower(args[0].(string))
		return
	case "upcase.Text":
		returnValue = strings.ToUpper(args[0].(string))
		return
	case "index.XMLNode", "index.Node":
		returnValue = fmt.Sprintf("%d", scope.Index+1)

	// TEXT FUNCTIONS
	case "set.Text":
		scope.Value = args[0]
	case "append.Text":
		scope.Value = scope.Value.(string) + args[0].(string)
	case "prepend.Text":
		scope.Value = args[0].(string) + scope.Value.(string)
	case "replace.Text":
		ts := &Scope{Value: ""}
		ctx.runChildren(ts, ins)
		scope.Value = strings.Replace(scope.Value.(string), args[0].(string), ts.Value.(string), -1)
	case "replace.Regexp":
		regexp := args[0].(*rubex.Regexp)
		scope.Value = regexp.GsubFunc(scope.Value.(string), func(match string, captures map[string]string) string {
			usesGlobal := (ctx.Env["use_global_replace_vars"] == "true")

			for name, capture := range captures {
				if usesGlobal {
					//println("setting $", name, "to", capture)
					ctx.Env[name] = capture
				}
				ctx.vars()[name] = capture
			}

			replacementScope := &Scope{Value: match}
			ctx.runChildren(replacementScope, ins)
			//println(ins.String())

			//println("Replacement:", replacementScope.Value.(string))
			innerReplacer := rubex.MustCompile(`[\\$](\d)`)
			return innerReplacer.GsubFunc(replacementScope.Value.(string), func(_ string, numeric_captures map[string]string) string {
				capture := numeric_captures["1"]
				var val string
				if usesGlobal {
					val = ctx.Env[capture]
				} else {
					val = ctx.vars()[capture].(string)
				}
				return val
			})
		})
		returnValue = scope.Value

	// XML FUNCTIONS
	case "xml":
		doc := libxml.XmlParseString(scope.Value.(string))
		ns := &Scope{Value: doc}
		ctx.runChildren(ns, ins)
		scope.Value = doc.String()
		returnValue = scope.Value
		doc.Free()
	case "html":
		doc := libxml.HtmlParseString(scope.Value.(string))
		ns := &Scope{Value: doc}
		ctx.runChildren(ns, ins)
		scope.Value = doc.DumpHTML()
		returnValue = scope.Value
		doc.Free()
	case "html_fragment":
		doc := libxml.HtmlParseFragment(scope.Value.(string))
		ns := &Scope{Value: doc.RootElement()}
		ctx.runChildren(ns, ins)
		scope.Value = ns.Value.(xml.Node).Content()
		returnValue = scope.Value
		doc.Free()
	case "select.Text":
		// TODO reuse XPath object
		node := scope.Value.(xml.Node)
		xpCtx := xpath.NewXPath(node.Doc())
		xpath := xpath.CompileXPath(args[0].(string))
		nodeSet := xpCtx.SearchByCompiledXPath(node, xpath).Slice()
		defer xpCtx.Free()
		if len(nodeSet) == 0 {
			returnValue = "false"
		} else {
			returnValue = "true"
		}

		for index, node := range nodeSet {
			if (node != nil) && node.IsLinked() {
				ns := &Scope{Value: node, Index: index}
				ctx.runChildren(ns, ins)
			}
		}
	case "position.Text":
		returnValue = Positions[args[0].(string)]

	// SHARED NODE FUNCTIONS
	case "remove":
		scope.Value.(xml.Node).Remove()
	case "inner", "inner_text", "text":
		node := scope.Value.(xml.Node)
		ts := &Scope{Value: node.Content()}
		ctx.runChildren(ts, ins)
		val := ts.Value.(string)
		_, ok := node.(*xml.Element)
		if ok && node.IsLinked() {
			node.SetContent(val)
		}
		returnValue = val
	case "value":
		node := scope.Value.(xml.Node)
		ts := &Scope{Value: node.Content()}
		ctx.runChildren(ts, ins)
		val := ts.Value.(string)
		_, ok := node.(*xml.Attribute)
		if ok && node.IsLinked() {
			node.SetContent(val)
		}
		returnValue = val
	case "name":
		node := scope.Value.(xml.Node)
		ts := &Scope{Value: node.Name()}
		ctx.runChildren(ts, ins)
		node.SetName(ts.Value.(string))
		returnValue = ts.Value.(string)
	case "dup":
		node := scope.Value.(xml.Node)
		newNode := node.Duplicate()
		_, isElement := node.(*xml.Element)
		if isElement {
			MoveFunc(newNode, node, AFTER)
		}
		ns := &Scope{Value: newNode}
		ctx.runChildren(ns, ins)
	case "fetch.Text":
		searchNode := scope.Value.(xml.Node)
		xPathObj := xpath.NewXPath(searchNode.Doc())
		nodeSet := xPathObj.Search(searchNode, args[0].(string))
		if nodeSet.Size() > 0 {
			node := nodeSet.First()
			attr, ok := node.(*xml.Attribute)
			if ok {
				returnValue = attr.Content()
			} else {
				returnValue = node.String()
			}
		}
		xPathObj.Free()
	case "path":
		returnValue = scope.Value.(xml.Node).Path()

	// LIBXML FUNCTIONS
	case "insert_at.Position.Text":
		node := scope.Value.(xml.Node)
		position := args[0].(Position)
		tagName := args[1].(string)
		element := node.Doc().NewElement(tagName)
		MoveFunc(element, node, position)
		ns := &Scope{Value: element}
		ctx.runChildren(ns, ins)
	case "inject_at.Position.Text":
		node := scope.Value.(xml.Node)
		position := args[0].(Position)
		nodeSet := node.Doc().ParseHtmlFragment(args[1].(string))
		for _, newNode := range nodeSet {
			MoveFunc(newNode, node, position)
		}
		if len(nodeSet) > 0 {
			element, ok := nodeSet[0].(*xml.Element)
			if ok {
				// successfully ran scope
				returnValue = "true"
				ns := &Scope{Value: element}
				ctx.runChildren(ns, ins)
			}
		} else {
			returnValue = "false"
		}
	case "cdata.Text":
		elem, ok := scope.Value.(*xml.Element)
		if ok {
			elem.SetCDataContent(args[0].(string))
		}
	case "move.XMLNode.XMLNode.Position", "move.Node.Node.Position":
		//for name, value := range(ctx.LocalVar) {
		//	println(name, ":", value)
		//}
		MoveFunc(args[0].(xml.Node), args[1].(xml.Node), args[2].(Position))
	case "wrap_text_children.Text":
		returnValue = "false"
		child := scope.Value.(xml.Node).First()
		index := 0
		tagName := args[0].(string)
		for child != nil {
			text, ok := child.(*xml.Text)
			childNext := child.Next()
			if ok {
				returnValue = "true"
				wrap := text.Wrap(tagName)
				ns := &Scope{wrap, index}
				ctx.runChildren(ns, ins)
				index++
			}
			child = childNext
		}

	// ATTRIBUTE FUNCTIONS
	case "attribute.Text":
		node := scope.Value.(xml.Node)
		name := args[0].(string)
		_, ok := node.(*xml.Element)
		if ok == true {
			attr, _ := node.Attribute(name)

			as := &Scope{Value: attr}
			ctx.runChildren(as, ins)
			if attr.IsLinked() && (attr.Content() == "") {
				attr.Remove()
			}
			if !attr.IsLinked() {
				attr.Free()
			}
			returnValue = "true"
		}
	case "to_text.XMLNode":
		returnValue = scope.Value.(xml.Node).String()
	default:
		ctx.Log.Error("Must implement " + fun.Name)
	}
	return
}
<commit_msg>a quick and happy remedy for the var.2 issue<commit_after>package shark

import (
	"strings"
	"os"
	"libxml"
	"fmt"
	xml "libxml/tree"
	tp "athena/proto"
	"libxml/xpath"
	"rubex"
)

func (ctx *Ctx) runBuiltIn(fun *Function, scope *Scope, ins *tp.Instruction, args []interface{}) (returnValue interface{}) {
	returnValue = ""
	switch fun.Name {
	case "this":
		returnValue = scope.Value
	case "yield":
		myYieldBlock := ctx.yieldBlock()
		ctx.Yields = ctx.Yields[:(len(ctx.Yields) - 1)]
		if ctx.yieldBlock() != nil {
			returnValue = ctx.runChildren(scope, myYieldBlock.Ins)
			if returnValue == nil {
				returnValue = "false"
			}
		} else {
			ctx.Log.Error("yield() failure")
		}
		ctx.Yields = append(ctx.Yields, myYieldBlock)

	case "var.Text", "var.Text.Text":
		val := ctx.Env[args[0].(string)]
		if len(args) == 2 {
			returnValue = args[1].(string)
		} else {
			returnValue = val
		}
		if len(ins.Children) > 0 {
			ts := &Scope{Value: returnValue}
			ctx.runChildren(ts, ins)
			returnValue = ts.Value
			ctx.Env[args[0].(string)] = returnValue.(string)
		}
	case "deprecated.Text":
		ctx.Log.Info(args[0].(string))
	case "match.Text":
		// Setup stacks
		against, ok := args[0].(string)
		if !ok {
			ctx.Log.Error("AH!")
		}
		ctx.MatchStack = append(ctx.MatchStack, against)
		ctx.MatchShouldContinue = append(ctx.MatchShouldContinue, true)

		// Run children
		ctx.runChildren(scope, ins)

		if ctx.matchShouldContinue() {
			returnValue = "false"
		} else {
			returnValue = "true"
		}

		// Clear
		ctx.MatchShouldContinue = ctx.MatchShouldContinue[:len(ctx.MatchShouldContinue)-1]
		ctx.MatchStack = ctx.MatchStack[:len(ctx.MatchStack)-1]
	case "with.Text":
		returnValue = "false"
		if ctx.matchShouldContinue() {
			if args[0].(string) == ctx.matchTarget() {
				ctx.MatchShouldContinue[len(ctx.MatchShouldContinue)-1] = false
				ctx.runChildren(scope, ins)
				returnValue = "true"
			}
		}
	case "with.Regexp":
		returnValue = "false"
		if ctx.matchShouldContinue() {
			//println(matcher.MatchAgainst, matchWith)
			if (args[0].(*rubex.Regexp)).Match([]uint8(ctx.matchTarget())) {
				ctx.MatchShouldContinue[len(ctx.MatchShouldContinue)-1] = false
				ctx.runChildren(scope, ins)
				returnValue = "true"
			}
		}
	case "not.Text":
		returnValue = "false"
		if ctx.matchShouldContinue() {
			if args[0].(string) != ctx.matchTarget() {
				ctx.MatchShouldContinue[len(ctx.MatchShouldContinue)-1] = false
				ctx.runChildren(scope, ins)
				returnValue = "true"
			}
		}
	case "not.Regexp":
		returnValue = "false"
		if ctx.matchShouldContinue() {
			//println(matcher.MatchAgainst, matchWith)
			if !(args[0].(*rubex.Regexp)).Match([]uint8(ctx.matchTarget())) {
				ctx.MatchShouldContinue[len(ctx.MatchShouldContinue)-1] = false
				ctx.runChildren(scope, ins)
				returnValue = "true"
			}
		}
	case "regexp.Text.Text":
		mode := rubex.ONIG_OPTION_DEFAULT
		if strings.Index(args[1].(string), "i") >= 0 {
			mode = rubex.ONIG_OPTION_IGNORECASE
		}
		if strings.Index(args[1].(string), "m") >= 0 {
			mode = rubex.ONIG_OPTION_MULTILINE
		}
		var err os.Error
		returnValue, err = rubex.NewRegexp(args[0].(string), mode)
		if err != nil {
			ctx.Log.Error("Invalid regexp")
		}
	case "export.Text":
		val := make([]string, 2)
		val[0] = args[0].(string)
		ts := &Scope{Value: ""}
		ctx.runChildren(ts, ins)
		val[1] = ts.Value.(string)
		ctx.Exports = append(ctx.Exports, val)
	case "log.Text":
		ctx.Logs = append(ctx.Logs, args[0].(string))

	// ATOMIC FUNCTIONS
	case "concat.Text.Text":
		//println("Concat:", args[0].(string), "+", args[1].(string))
		returnValue = args[0].(string) + args[1].(string)
	case "concat.Text.Text.Text": //REMOVE
		returnValue = args[0].(string) + args[1].(string) + args[2].(string)
	case "downcase.Text":
		returnValue = strings.ToLower(args[0].(string))
		return
	case "upcase.Text":
		returnValue = strings.ToUpper(args[0].(string))
		return
	case "index.XMLNode", "index.Node":
		returnValue = fmt.Sprintf("%d", scope.Index+1)

	// TEXT FUNCTIONS
	case "set.Text":
		scope.Value = args[0]
	case "append.Text":
		scope.Value = scope.Value.(string) + args[0].(string)
	case "prepend.Text":
		scope.Value = args[0].(string) + scope.Value.(string)
	case "replace.Text":
		ts := &Scope{Value: ""}
		ctx.runChildren(ts, ins)
		scope.Value = strings.Replace(scope.Value.(string), args[0].(string), ts.Value.(string), -1)
	case "replace.Regexp":
		regexp := args[0].(*rubex.Regexp)
		scope.Value = regexp.GsubFunc(scope.Value.(string), func(match string, captures map[string]string) string {
			usesGlobal := (ctx.Env["use_global_replace_vars"] == "true")

			for name, capture := range captures {
				if usesGlobal {
					//println("setting $", name, "to", capture)
					ctx.Env[name] = capture
				}
				ctx.vars()[name] = capture
			}

			replacementScope := &Scope{Value: match}
			ctx.runChildren(replacementScope, ins)
			//println(ins.String())

			//println("Replacement:", replacementScope.Value.(string))
			innerReplacer := rubex.MustCompile(`[\\$](\d)`)
			return innerReplacer.GsubFunc(replacementScope.Value.(string), func(_ string, numeric_captures map[string]string) string {
				capture := numeric_captures["1"]
				var val string
				if usesGlobal {
					val = ctx.Env[capture]
				} else {
					val = ctx.vars()[capture].(string)
				}
				return val
			})
		})
		returnValue = scope.Value

	// XML FUNCTIONS
	case "xml":
		doc := libxml.XmlParseString(scope.Value.(string))
		ns := &Scope{Value: doc}
		ctx.runChildren(ns, ins)
		scope.Value = doc.String()
		returnValue = scope.Value
		doc.Free()
	case "html":
		doc := libxml.HtmlParseString(scope.Value.(string))
		ns := &Scope{Value: doc}
		ctx.runChildren(ns, ins)
		scope.Value = doc.DumpHTML()
		returnValue = scope.Value
		doc.Free()
	case "html_fragment":
		doc := libxml.HtmlParseFragment(scope.Value.(string))
		ns := &Scope{Value: doc.RootElement()}
		ctx.runChildren(ns, ins)
		scope.Value = ns.Value.(xml.Node).Content()
		returnValue = scope.Value
		doc.Free()
	case "select.Text":
		// TODO reuse XPath object
		node := scope.Value.(xml.Node)
		xpCtx := xpath.NewXPath(node.Doc())
		xpath := xpath.CompileXPath(args[0].(string))
		nodeSet := xpCtx.SearchByCompiledXPath(node, xpath).Slice()
		defer xpCtx.Free()
		if len(nodeSet) == 0 {
			returnValue = "false"
		} else {
			returnValue = "true"
		}

		for index, node := range nodeSet {
			if (node != nil) && node.IsLinked() {
				ns := &Scope{Value: node, Index: index}
				ctx.runChildren(ns, ins)
			}
		}
	case "position.Text":
		returnValue = Positions[args[0].(string)]

	// SHARED NODE FUNCTIONS
	case "remove":
		scope.Value.(xml.Node).Remove()
	case "inner", "inner_text", "text":
		node := scope.Value.(xml.Node)
		ts := &Scope{Value: node.Content()}
		ctx.runChildren(ts, ins)
		val := ts.Value.(string)
		_, ok := node.(*xml.Element)
		if ok && node.IsLinked() {
			node.SetContent(val)
		}
		returnValue = val
	case "value":
		node := scope.Value.(xml.Node)
		ts := &Scope{Value: node.Content()}
		ctx.runChildren(ts, ins)
		val := ts.Value.(string)
		_, ok := node.(*xml.Attribute)
		if ok && node.IsLinked() {
			node.SetContent(val)
		}
		returnValue = val
	case "name":
		node := scope.Value.(xml.Node)
		ts := &Scope{Value: node.Name()}
		ctx.runChildren(ts, ins)
		node.SetName(ts.Value.(string))
		returnValue = ts.Value.(string)
	case "dup":
		node := scope.Value.(xml.Node)
		newNode := node.Duplicate()
		_, isElement := node.(*xml.Element)
		if isElement {
			MoveFunc(newNode, node, AFTER)
		}
		ns := &Scope{Value: newNode}
		ctx.runChildren(ns, ins)
	case "fetch.Text":
		searchNode := scope.Value.(xml.Node)
		xPathObj := xpath.NewXPath(searchNode.Doc())
		nodeSet := xPathObj.Search(searchNode, args[0].(string))
		if nodeSet.Size() > 0 {
			node := nodeSet.First()
			attr, ok := node.(*xml.Attribute)
			if ok {
				returnValue = attr.Content()
			} else {
				returnValue = node.String()
			}
		}
		xPathObj.Free()
	case "path":
		returnValue = scope.Value.(xml.Node).Path()

	// LIBXML FUNCTIONS
	case "insert_at.Position.Text":
		node := scope.Value.(xml.Node)
		position := args[0].(Position)
		tagName := args[1].(string)
		element := node.Doc().NewElement(tagName)
		MoveFunc(element, node, position)
		ns := &Scope{Value: element}
		ctx.runChildren(ns, ins)
	case "inject_at.Position.Text":
		node := scope.Value.(xml.Node)
		position := args[0].(Position)
		nodeSet := node.Doc().ParseHtmlFragment(args[1].(string))
		for _, newNode := range nodeSet {
			MoveFunc(newNode, node, position)
		}
		if len(nodeSet) > 0 {
			element, ok := nodeSet[0].(*xml.Element)
			if ok {
				// successfully ran scope
				returnValue = "true"
				ns := &Scope{Value: element}
				ctx.runChildren(ns, ins)
			}
		} else {
			returnValue = "false"
		}
	case "cdata.Text":
		elem, ok := scope.Value.(*xml.Element)
		if ok {
			elem.SetCDataContent(args[0].(string))
		}
	case "move.XMLNode.XMLNode.Position", "move.Node.Node.Position":
		//for name, value := range(ctx.LocalVar) {
		//	println(name, ":", value)
		//}
		MoveFunc(args[0].(xml.Node), args[1].(xml.Node), args[2].(Position))
	case "wrap_text_children.Text":
		returnValue = "false"
		child := scope.Value.(xml.Node).First()
		index := 0
		tagName := args[0].(string)
		for child != nil {
			text, ok := child.(*xml.Text)
			childNext := child.Next()
			if ok {
				returnValue = "true"
				wrap := text.Wrap(tagName)
				ns := &Scope{wrap, index}
				ctx.runChildren(ns, ins)
				index++
			}
			child = childNext
		}

	// ATTRIBUTE FUNCTIONS
	case "attribute.Text":
		node := scope.Value.(xml.Node)
		name := args[0].(string)
		_, ok := node.(*xml.Element)
		if ok == true {
			attr, _ := node.Attribute(name)

			as := &Scope{Value: attr}
			ctx.runChildren(as, ins)
			if attr.IsLinked() && (attr.Content() == "") {
				attr.Remove()
			}
			if !attr.IsLinked() {
				attr.Free()
			}
			returnValue = "true"
		}
	case "to_text.XMLNode":
		returnValue = scope.Value.(xml.Node).String()
	default:
		ctx.Log.Error("Must implement " + fun.Name)
	}
	return
}
<|endoftext|>


<commit_before>package tsdbexec

import (
	"errors"
	"fmt"
	"github.com/Symantec/scotty/datastructs"
	"github.com/Symantec/scotty/lib/apiutil"
	"github.com/Symantec/scotty/suggest"
	"github.com/Symantec/scotty/tsdb"
	"github.com/Symantec/scotty/tsdbimpl"
	"github.com/Symantec/scotty/tsdbjson"
	"github.com/Symantec/tricorder/go/tricorder/duration"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

var (
	kOptions = &apiutil.Options{
		ErrorGenerator: func(status int, err error) interface{} {
			return newHTTPError(status, err)
		},
	}
)

func newTagFilter(spec *tsdbjson.FilterSpec) (
	tsdb.TagFilter, error) {
	if spec == nil {
		return nil, nil
	}
	return tsdbjson.NewTagFilter(spec.Type, spec.Value)
}

func _suggest(
	params url.Values,
	suggesterMap map[string]suggest.Suggester) (
	result []string, err error) {
	maxStr := params.Get("max")
	var max int
	if maxStr == "" {
		max = 25
	} else {
		max, err = strconv.Atoi(params.Get("max"))
		if err != nil {
			return
		}
	}
	qtype := params.Get("type")
	suggester := suggesterMap[qtype]
	if suggester != nil {
		result = suggester.Suggest(max, params.Get("q"))
		if result == nil {
			result = []string{}
		}
		return
	} else {
		return nil, errors.New(
			fmt.Sprintf("Invalid 'type' parameter:%s", qtype))
	}
}

func ensureDurationAtLeast(
	minDurationInSeconds float64,
	spec **tsdbjson.DownSampleSpec) {
	if (*spec).DurationInSeconds >= minDurationInSeconds {
		return
	}
	newSpec := **spec
	newSpec.DurationInSeconds = minDurationInSeconds
	*spec = &newSpec
}

func query(
	request *tsdbjson.QueryRequest,
	endpoints *datastructs.ApplicationStatuses,
	minDownSampleTime time.Duration) (
	result []tsdbjson.TimeSeries, err error) {
	parsedQueries, err := tsdbjson.ParseQueryRequest(request)
	if err != nil {
		return
	}
	var allSeries []tsdbjson.TimeSeries
	for i := range parsedQueries {
		var series *tsdb.TaggedTimeSeriesSet
		series, err = runSingleParsedQuery(
			parsedQueries[i], endpoints, minDownSampleTime)
		if err != nil {
			return
		}
		allSeries = append(allSeries, tsdbjson.NewTimeSeriesSlice(series)...)
	}
	if allSeries == nil {
		return make([]tsdbjson.TimeSeries, 0), nil
	}
	return allSeries, nil
}

func runParsedQueries(
	requests []tsdbjson.ParsedQuery,
	endpoints *datastructs.ApplicationStatuses,
	minDownSampleTime time.Duration) (
	[]*tsdb.TaggedTimeSeriesSet, error) {
	results := make([]*tsdb.TaggedTimeSeriesSet, len(requests))
	for i, request := range requests {
		result, err := runSingleParsedQuery(
			request, endpoints, minDownSampleTime)
		if err == tsdbimpl.ErrNoSuchMetric {
			results[i] = nil
			continue
		}
		if err != nil {
			return nil, err
		}
		results[i] = result
	}
	return results, nil
}

func runSingleParsedQuery(
	request tsdbjson.ParsedQuery,
	endpoints *datastructs.ApplicationStatuses,
	minDownSampleTime time.Duration) (
	result *tsdb.TaggedTimeSeriesSet, err error) {
	var options tsdbimpl.QueryOptions
	options.HostNameFilter, err = newTagFilter(
		request.Options.HostNameFilter)
	if err != nil {
		return
	}
	options.AppNameFilter, err = newTagFilter(
		request.Options.AppNameFilter)
	if err != nil {
		return
	}
	options.GroupByAppName = request.Options.GroupByAppName
	options.GroupByHostName = request.Options.GroupByHostName
	if request.Aggregator.DownSample == nil {
		return nil, tsdbjson.ErrUnsupportedAggregator
	}
	ensureDurationAtLeast(
		duration.ToFloat(minDownSampleTime),
		&request.Aggregator.DownSample)
	var aggregatorGen tsdb.AggregatorGenerator
	aggregatorGen, err = tsdbjson.NewAggregatorGenerator(
		request.Aggregator.Type,
		request.Aggregator.DownSample,
		request.Aggregator.RateOptions,
	)
	if err != nil {
		return
	}
	return tsdbimpl.Query(
		endpoints,
		request.Metric,
		aggregatorGen,
		request.Start,
		request.End,
		&options)
}

func newHandler(handler interface{}) http.Handler {
	return apiutil.NewHandler(handler, kOptions)
}

func newHTTPError(status int, err error) apiutil.HTTPError {
	var anError httpErrorType
	anError.E.Code = status
	anError.E.Message = err.Error()
	return &anError
}

type errorCodeType struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

type httpErrorType struct {
	E errorCodeType `json:"error"`
}

func (h *httpErrorType) Error() string {
	return h.E.Message
}

func (h *httpErrorType) Status() int {
	return h.E.Code
}
<commit_msg>Simplify code ensuring rollup interval at least collection interval.<commit_after>package tsdbexec

import (
	"errors"
	"fmt"
	"github.com/Symantec/scotty/datastructs"
	"github.com/Symantec/scotty/lib/apiutil"
	"github.com/Symantec/scotty/suggest"
	"github.com/Symantec/scotty/tsdb"
	"github.com/Symantec/scotty/tsdbimpl"
	"github.com/Symantec/scotty/tsdbjson"
	"github.com/Symantec/tricorder/go/tricorder/duration"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

var (
	kOptions = &apiutil.Options{
		ErrorGenerator: func(status int, err error) interface{} {
			return newHTTPError(status, err)
		},
	}
)

func newTagFilter(spec *tsdbjson.FilterSpec) (
	tsdb.TagFilter, error) {
	if spec == nil {
		return nil, nil
	}
	return tsdbjson.NewTagFilter(spec.Type, spec.Value)
}

func _suggest(
	params url.Values,
	suggesterMap map[string]suggest.Suggester) (
	result []string, err error) {
	maxStr := params.Get("max")
	var max int
	if maxStr == "" {
		max = 25
	} else {
		max, err = strconv.Atoi(params.Get("max"))
		if err != nil {
			return
		}
	}
	qtype := params.Get("type")
	suggester := suggesterMap[qtype]
	if suggester != nil {
		result = suggester.Suggest(max, params.Get("q"))
		if result == nil {
			result = []string{}
		}
		return
	} else {
		return nil, errors.New(
			fmt.Sprintf("Invalid 'type' parameter:%s", qtype))
	}
}

func ensureDurationAtLeast(
	spec *tsdbjson.DownSampleSpec,
	minDurationInSeconds float64) *tsdbjson.DownSampleSpec {
	newSpec := *spec
	if newSpec.DurationInSeconds < minDurationInSeconds {
		newSpec.DurationInSeconds = minDurationInSeconds
	}
	return &newSpec
}

func query(
	request *tsdbjson.QueryRequest,
	endpoints *datastructs.ApplicationStatuses,
	minDownSampleTime time.Duration) (
	result []tsdbjson.TimeSeries, err error) {
	parsedQueries, err := tsdbjson.ParseQueryRequest(request)
	if err != nil {
		return
	}
	var allSeries []tsdbjson.TimeSeries
	for i := range parsedQueries {
		var series *tsdb.TaggedTimeSeriesSet
		series, err = runSingleParsedQuery(
			parsedQueries[i], endpoints, minDownSampleTime)
		if err != nil {
			return
		}
		allSeries = append(allSeries, tsdbjson.NewTimeSeriesSlice(series)...)
	}
	if allSeries == nil {
		return make([]tsdbjson.TimeSeries, 0), nil
	}
	return allSeries, nil
}

func runParsedQueries(
	requests []tsdbjson.ParsedQuery,
	endpoints *datastructs.ApplicationStatuses,
	minDownSampleTime time.Duration) (
	[]*tsdb.TaggedTimeSeriesSet, error) {
	results := make([]*tsdb.TaggedTimeSeriesSet, len(requests))
	for i, request := range requests {
		result, err := runSingleParsedQuery(
			request, endpoints, minDownSampleTime)
		if err == tsdbimpl.ErrNoSuchMetric {
			results[i] = nil
			continue
		}
		if err != nil {
			return nil, err
		}
		results[i] = result
	}
	return results, nil
}

func runSingleParsedQuery(
	request tsdbjson.ParsedQuery,
	endpoints *datastructs.ApplicationStatuses,
	minDownSampleTime time.Duration) (
	result *tsdb.TaggedTimeSeriesSet, err error) {
	var options tsdbimpl.QueryOptions
	options.HostNameFilter, err = newTagFilter(
		request.Options.HostNameFilter)
	if err != nil {
		return
	}
	options.AppNameFilter, err = newTagFilter(
		request.Options.AppNameFilter)
	if err != nil {
		return
	}
	options.GroupByAppName = request.Options.GroupByAppName
	options.GroupByHostName = request.Options.GroupByHostName
	if request.Aggregator.DownSample == nil {
		return nil, tsdbjson.ErrUnsupportedAggregator
	}
	request.Aggregator.DownSample = ensureDurationAtLeast(
		request.Aggregator.DownSample,
		duration.ToFloat(minDownSampleTime))
	var aggregatorGen tsdb.AggregatorGenerator
	aggregatorGen, err = tsdbjson.NewAggregatorGenerator(
		request.Aggregator.Type,
		request.Aggregator.DownSample,
		request.Aggregator.RateOptions,
	)
	if err != nil {
		return
	}
	return tsdbimpl.Query(
		endpoints,
		request.Metric,
		aggregatorGen,
		request.Start,
		request.End,
		&options)
}

func newHandler(handler interface{}) http.Handler {
	return apiutil.NewHandler(handler, kOptions)
}

func newHTTPError(status int, err error) apiutil.HTTPError {
	var anError httpErrorType
	anError.E.Code = status
	anError.E.Message = err.Error()
	return &anError
}

type errorCodeType struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

type httpErrorType struct {
	E errorCodeType `json:"error"`
}

func (h *httpErrorType) Error() string {
	return h.E.Message
}

func (h *httpErrorType) Status() int {
	return h.E.Code
}
<|endoftext|>


<commit_before>//  Copyright (c) 2014 Couchbase, 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 upside_down

import (
	"bytes"
	"sort"

	"github.com/blevesearch/bleve/index/store"
)

// the functions in this file are only intended to be used by
// the bleve_dump utility and the debug http handlers
// if your application relies on them, you're doing something wrong
// they may change or be removed at any time

func (udc *UpsideDownCouch) dumpPrefix(kvreader store.KVReader, rv chan interface{}, prefix []byte) {
	start := prefix
	if start == nil {
		start = []byte{0}
	}
	it := kvreader.PrefixIterator(start)
	defer func() {
		cerr := it.Close()
		if cerr != nil {
			rv <- cerr
		}
	}()
	key, val, valid := it.Current()
	for valid {

		row, err := ParseFromKeyValue(key, val)
		if err != nil {
			rv <- err
			return
		}
		rv <- row

		it.Next()
		key, val, valid = it.Current()
	}
}

func (udc *UpsideDownCouch) dumpRange(kvreader store.KVReader, rv chan interface{}, start, end []byte) {
	it := kvreader.RangeIterator(start, end)
	defer func() {
		cerr := it.Close()
		if cerr != nil {
			rv <- cerr
		}
	}()
	key, val, valid := it.Current()
	for valid {

		row, err := ParseFromKeyValue(key, val)
		if err != nil {
			rv <- err
			return
		}
		rv <- row

		it.Next()
		key, val, valid = it.Current()
	}
}

func (udc *UpsideDownCouch) DumpAll() chan interface{} {
	rv := make(chan interface{})
	go func() {
		defer close(rv)

		// start an isolated reader for use during the dump
		kvreader, err := udc.store.Reader()
		if err != nil {
			rv <- err
			return
		}
		defer func() {
			cerr := kvreader.Close()
			if cerr != nil {
				rv <- cerr
			}
		}()

		udc.dumpRange(kvreader, rv, nil, nil)
	}()
	return rv
}

func (udc *UpsideDownCouch) DumpFields() chan interface{} {
	rv := make(chan interface{})
	go func() {
		defer close(rv)

		// start an isolated reader for use during the dump
		kvreader, err := udc.store.Reader()
		if err != nil {
			rv <- err
			return
		}
		defer func() {
			cerr := kvreader.Close()
			if cerr != nil {
				rv <- cerr
			}
		}()

		udc.dumpPrefix(kvreader, rv, []byte{'f'})
	}()
	return rv
}

type keyset [][]byte

func (k keyset) Len() int           { return len(k) }
func (k keyset) Swap(i, j int)      { k[i], k[j] = k[j], k[i] }
func (k keyset) Less(i, j int) bool { return bytes.Compare(k[i], k[j]) < 0 }

// DumpDoc returns all rows in the index related to this doc id
func (udc *UpsideDownCouch) DumpDoc(id string) chan interface{} {
	rv := make(chan interface{})

	go func() {
		defer close(rv)

		// start an isolated reader for use during the dump
		kvreader, err := udc.store.Reader()
		if err != nil {
			rv <- err
			return
		}
		defer func() {
			cerr := kvreader.Close()
			if cerr != nil {
				rv <- cerr
			}
		}()

		back, err := udc.backIndexRowForDoc(kvreader, id)
		if err != nil {
			rv <- err
			return
		}

		// no such doc
		if back == nil {
			return
		}
		// build sorted list of term keys
		keys := make(keyset, 0)
		for _, entry := range back.termEntries {
			tfr := NewTermFrequencyRow([]byte(*entry.Term), uint16(*entry.Field), id, 0, 0)
			key := tfr.Key()
			keys = append(keys, key)
		}
		sort.Sort(keys)

		// first add all the stored rows
		storedRowPrefix := NewStoredRow(id, 0, []uint64{}, 'x', []byte{}).ScanPrefixForDoc()
		udc.dumpPrefix(kvreader, rv, storedRowPrefix)

		// now walk term keys in order and add them as well
		if len(keys) > 0 {
			it := kvreader.RangeIterator(keys[0], nil)
			defer func() {
				cerr := it.Close()
				if cerr != nil {
					rv <- cerr
				}
			}()

			for _, key := range keys {
				it.Seek(key)
				rkey, rval, valid := it.Current()
				if !valid {
					break
				}
				row, err := ParseFromKeyValue(rkey, rval)
				if err != nil {
					rv <- err
					return
				}
				rv <- row
			}
		}
	}()

	return rv
}
<commit_msg>fix dump methods to properly copy keys and values<commit_after>//  Copyright (c) 2014 Couchbase, 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 upside_down

import (
	"bytes"
	"sort"

	"github.com/blevesearch/bleve/index/store"
)

// the functions in this file are only intended to be used by
// the bleve_dump utility and the debug http handlers
// if your application relies on them, you're doing something wrong
// they may change or be removed at any time

func (udc *UpsideDownCouch) dumpPrefix(kvreader store.KVReader, rv chan interface{}, prefix []byte) {
	start := prefix
	if start == nil {
		start = []byte{0}
	}
	it := kvreader.PrefixIterator(start)
	defer func() {
		cerr := it.Close()
		if cerr != nil {
			rv <- cerr
		}
	}()
	key, val, valid := it.Current()
	for valid {
		ck := make([]byte, len(key))
		copy(ck, key)
		cv := make([]byte, len(val))
		copy(cv, val)
		row, err := ParseFromKeyValue(ck, cv)
		if err != nil {
			rv <- err
			return
		}
		rv <- row

		it.Next()
		key, val, valid = it.Current()
	}
}

func (udc *UpsideDownCouch) dumpRange(kvreader store.KVReader, rv chan interface{}, start, end []byte) {
	it := kvreader.RangeIterator(start, end)
	defer func() {
		cerr := it.Close()
		if cerr != nil {
			rv <- cerr
		}
	}()
	key, val, valid := it.Current()
	for valid {
		ck := make([]byte, len(key))
		copy(ck, key)
		cv := make([]byte, len(val))
		copy(cv, val)
		row, err := ParseFromKeyValue(ck, cv)
		if err != nil {
			rv <- err
			return
		}
		rv <- row

		it.Next()
		key, val, valid = it.Current()
	}
}

func (udc *UpsideDownCouch) DumpAll() chan interface{} {
	rv := make(chan interface{})
	go func() {
		defer close(rv)

		// start an isolated reader for use during the dump
		kvreader, err := udc.store.Reader()
		if err != nil {
			rv <- err
			return
		}
		defer func() {
			cerr := kvreader.Close()
			if cerr != nil {
				rv <- cerr
			}
		}()

		udc.dumpRange(kvreader, rv, nil, nil)
	}()
	return rv
}

func (udc *UpsideDownCouch) DumpFields() chan interface{} {
	rv := make(chan interface{})
	go func() {
		defer close(rv)

		// start an isolated reader for use during the dump
		kvreader, err := udc.store.Reader()
		if err != nil {
			rv <- err
			return
		}
		defer func() {
			cerr := kvreader.Close()
			if cerr != nil {
				rv <- cerr
			}
		}()

		udc.dumpPrefix(kvreader, rv, []byte{'f'})
	}()
	return rv
}

type keyset [][]byte

func (k keyset) Len() int           { return len(k) }
func (k keyset) Swap(i, j int)      { k[i], k[j] = k[j], k[i] }
func (k keyset) Less(i, j int) bool { return bytes.Compare(k[i], k[j]) < 0 }

// DumpDoc returns all rows in the index related to this doc id
func (udc *UpsideDownCouch) DumpDoc(id string) chan interface{} {
	rv := make(chan interface{})

	go func() {
		defer close(rv)

		// start an isolated reader for use during the dump
		kvreader, err := udc.store.Reader()
		if err != nil {
			rv <- err
			return
		}
		defer func() {
			cerr := kvreader.Close()
			if cerr != nil {
				rv <- cerr
			}
		}()

		back, err := udc.backIndexRowForDoc(kvreader, id)
		if err != nil {
			rv <- err
			return
		}

		// no such doc
		if back == nil {
			return
		}
		// build sorted list of term keys
		keys := make(keyset, 0)
		for _, entry := range back.termEntries {
			tfr := NewTermFrequencyRow([]byte(*entry.Term), uint16(*entry.Field), id, 0, 0)
			key := tfr.Key()
			keys = append(keys, key)
		}
		sort.Sort(keys)

		// first add all the stored rows
		storedRowPrefix := NewStoredRow(id, 0, []uint64{}, 'x', []byte{}).ScanPrefixForDoc()
		udc.dumpPrefix(kvreader, rv, storedRowPrefix)

		// now walk term keys in order and add them as well
		if len(keys) > 0 {
			it := kvreader.RangeIterator(keys[0], nil)
			defer func() {
				cerr := it.Close()
				if cerr != nil {
					rv <- cerr
				}
			}()

			for _, key := range keys {
				it.Seek(key)
				rkey, rval, valid := it.Current()
				if !valid {
					break
				}
				rck := make([]byte, len(rkey))
				copy(rck, key)
				rcv := make([]byte, len(rval))
				copy(rcv, rval)
				row, err := ParseFromKeyValue(rck, rcv)
				if err != nil {
					rv <- err
					return
				}
				rv <- row
			}
		}
	}()

	return rv
}
<|endoftext|>


<commit_before>package redirect

import (
	"net/http"
	"strings"

	"github.com/ant0ine/go-json-rest/rest"
)

// SecureRedirectMiddleware redirects the client to the identical URL served via HTTPS
type SecureRedirectMiddleware struct {
	WhiteListedPaths map[string]struct{}
}

func NewSecureRedirectMiddleware(paths ...string) SecureRedirectMiddleware {
	uniquePaths := make(map[string]struct{})
	for _, p := range paths {
		uniquePaths[p] = struct{}{}
	}
	return SecureRedirectMiddleware{
		WhiteListedPaths: uniquePaths,
	}
}

func (srm SecureRedirectMiddleware) MiddlewareFunc(h rest.HandlerFunc) rest.HandlerFunc {
	return func(w rest.ResponseWriter, r *rest.Request) {
		_, whiteListed := srm.WhiteListedPaths[r.URL.Path]
		if strings.ToLower(r.Header.Get("X-Forwarded-Proto")) == "http" && !whiteListed {
			redirectURL := r.URL
			redirectURL.Host = r.Host
			redirectURL.Scheme = "https"
			http.Redirect(w.(http.ResponseWriter), r.Request, redirectURL.String(), http.StatusMovedPermanently)
			return
		}
		h(w, r)
	}
}
<commit_msg>better names to indicate use of these paths<commit_after>package redirect

import (
	"net/http"
	"strings"

	"github.com/ant0ine/go-json-rest/rest"
)

// SecureRedirectMiddleware redirects the client to the identical URL served via HTTPS
type SecureRedirectMiddleware struct {
	AllowedInsecurePaths map[string]struct{}
}

func NewSecureRedirectMiddleware(allowedInsecurepaths ...string) SecureRedirectMiddleware {
	uniquePaths := make(map[string]struct{})
	for _, p := range allowedInsecurepaths {
		uniquePaths[p] = struct{}{}
	}
	return SecureRedirectMiddleware{
		AllowedInsecurePaths: uniquePaths,
	}
}

func (srm SecureRedirectMiddleware) MiddlewareFunc(h rest.HandlerFunc) rest.HandlerFunc {
	return func(w rest.ResponseWriter, r *rest.Request) {
		_, whiteListed := srm.AllowedInsecurePaths[r.URL.Path]
		if strings.ToLower(r.Header.Get("X-Forwarded-Proto")) == "http" && !whiteListed {
			redirectURL := r.URL
			redirectURL.Host = r.Host
			redirectURL.Scheme = "https"
			http.Redirect(w.(http.ResponseWriter), r.Request, redirectURL.String(), http.StatusMovedPermanently)
			return
		}
		h(w, r)
	}
}
<|endoftext|>


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

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

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

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

package user

import (
	"strings"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/code"

	"github.com/admpub/nging/v4/application/handler"
	"github.com/admpub/nging/v4/application/handler/term"
	"github.com/admpub/nging/v4/application/library/codec"
	"github.com/admpub/nging/v4/application/library/filemanager"
	"github.com/admpub/nging/v4/application/model"
)

func Edit(ctx echo.Context) error {
	var err error
	user := handler.User(ctx)
	if user == nil {
		return ctx.E(`登录信息获取失败，请重新登录`)
	}
	m := model.NewUser(ctx)
	err = m.Get(nil, `id`, user.Id)
	if err != nil {
		return err
	}
	needCheckU2F := m.NeedCheckU2F(user.Id)
	if ctx.IsPost() {
		email := strings.TrimSpace(ctx.Form(`email`))
		mobile := strings.TrimSpace(ctx.Form(`mobile`))
		modifyPass := ctx.Form(`modifyPass`) == `1`

		//新密码
		newPass := strings.TrimSpace(ctx.Form(`newPass`))
		confirmPass := strings.TrimSpace(ctx.Form(`confirmPass`))

		//旧密码
		passwd := strings.TrimSpace(ctx.Form(`pass`))

		passwd, err = codec.DefaultSM2DecryptHex(passwd)
		if err != nil {
			return ctx.NewError(code.InvalidParameter, ctx.T(`旧密码解密失败: %v`, err)).SetZone(`pass`)
		}
		if modifyPass {
			newPass, err = codec.DefaultSM2DecryptHex(newPass)
			if err != nil {
				return ctx.NewError(code.InvalidParameter, ctx.T(`新密码解密失败: %v`, err)).SetZone(`newPass`)
			}
			confirmPass, err = codec.DefaultSM2DecryptHex(confirmPass)
			if err != nil {
				return ctx.NewError(code.InvalidParameter, ctx.T(`您输入的确认密码解密失败: %v`, err)).SetZone(`confirmPass`)
			}
		}

		gender := strings.TrimSpace(ctx.Form(`gender`))

		if len(email) == 0 {
			err = ctx.E(`Email不能为空`)
		} else if modifyPass && len(newPass) < 8 {
			err = ctx.E(`新密码不能少于8个字符`)
		} else if modifyPass && newPass != confirmPass {
			err = ctx.E(`新密码与确认新密码不一致`)
		} else if !ctx.Validate(`email`, email, `email`).Ok() {
			err = ctx.E(`Email地址"%s"格式不正确`, email)
		} else if len(mobile) > 0 && !ctx.Validate(`mobile`, mobile, `mobile`).Ok() {
			err = ctx.E(`手机号"%s"格式不正确`, mobile)
		} else if m.NgingUser.Password != com.MakePassword(passwd, m.NgingUser.Salt) {
			err = ctx.E(`旧密码输入不正确`)
		} else if needCheckU2F {
			//两步验证码
			err = GAuthVerify(ctx, `u2fCode`)
		}
		if err == nil && !ctx.Validate(`email`, email, `email`).Ok() {
			err = ctx.E(`Email地址格式不正确`)
		}
		if err == nil {
			set := map[string]interface{}{
				`email`:  email,
				`mobile`: mobile,
				`avatar`: ctx.Form(`avatar`),
				`gender`: gender,
			}
			if modifyPass {
				set[`password`] = com.MakePassword(newPass, m.NgingUser.Salt)
			}
			err = m.SetFields(nil, set, `id`, user.Id)
		}
		if err == nil {
			handler.SendOk(ctx, ctx.T(`修改成功`))
			m.Get(nil, `id`, user.Id)
			m.SetSession()
			return ctx.Redirect(handler.URLFor(`/user/edit`))
		}
	}
	ctx.Set(`needCheckU2F`, needCheckU2F)
	return ctx.Render(`user/edit`, handler.Err(ctx, err))
}

func AutoCompletePath(ctx echo.Context) error {
	sshAccountID := ctx.Formx(`sshAccountId`).Uint()
	if sshAccountID > 0 {
		check, _ := ctx.Funcs()[`CheckPerm`].(func(string) error)
		data := ctx.Data()
		if check == nil {
			data.SetData([]string{})
			return ctx.JSON(data)
		}
		if err := check(`manager/command_add`); err != nil {
			return err
		}
		if err := check(`manager/command_edit`); err != nil {
			return err
		}
		return term.SftpSearch(ctx, sshAccountID)
	}
	data := ctx.Data()
	prefix := ctx.Form(`query`)
	size := ctx.Formx(`size`, `10`).Int()
	var paths []string
	switch ctx.Form(`type`) {
	case `dir`:
		paths = filemanager.SearchDir(prefix, size)
	case `file`:
		paths = filemanager.SearchFile(prefix, size)
	default:
		paths = filemanager.Search(prefix, size)
	}
	data.SetData(paths)
	return ctx.JSON(data)
}
<commit_msg>update<commit_after>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

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

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

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

package user

import (
	"strings"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/code"

	"github.com/admpub/nging/v4/application/handler"
	"github.com/admpub/nging/v4/application/library/codec"
	"github.com/admpub/nging/v4/application/library/filemanager"
	"github.com/admpub/nging/v4/application/model"
)

func Edit(ctx echo.Context) error {
	var err error
	user := handler.User(ctx)
	if user == nil {
		return ctx.E(`登录信息获取失败，请重新登录`)
	}
	m := model.NewUser(ctx)
	err = m.Get(nil, `id`, user.Id)
	if err != nil {
		return err
	}
	needCheckU2F := m.NeedCheckU2F(user.Id)
	if ctx.IsPost() {
		email := strings.TrimSpace(ctx.Form(`email`))
		mobile := strings.TrimSpace(ctx.Form(`mobile`))
		modifyPass := ctx.Form(`modifyPass`) == `1`

		//新密码
		newPass := strings.TrimSpace(ctx.Form(`newPass`))
		confirmPass := strings.TrimSpace(ctx.Form(`confirmPass`))

		//旧密码
		passwd := strings.TrimSpace(ctx.Form(`pass`))

		passwd, err = codec.DefaultSM2DecryptHex(passwd)
		if err != nil {
			return ctx.NewError(code.InvalidParameter, ctx.T(`旧密码解密失败: %v`, err)).SetZone(`pass`)
		}
		if modifyPass {
			newPass, err = codec.DefaultSM2DecryptHex(newPass)
			if err != nil {
				return ctx.NewError(code.InvalidParameter, ctx.T(`新密码解密失败: %v`, err)).SetZone(`newPass`)
			}
			confirmPass, err = codec.DefaultSM2DecryptHex(confirmPass)
			if err != nil {
				return ctx.NewError(code.InvalidParameter, ctx.T(`您输入的确认密码解密失败: %v`, err)).SetZone(`confirmPass`)
			}
		}

		gender := strings.TrimSpace(ctx.Form(`gender`))

		if len(email) == 0 {
			err = ctx.E(`Email不能为空`)
		} else if modifyPass && len(newPass) < 8 {
			err = ctx.E(`新密码不能少于8个字符`)
		} else if modifyPass && newPass != confirmPass {
			err = ctx.E(`新密码与确认新密码不一致`)
		} else if !ctx.Validate(`email`, email, `email`).Ok() {
			err = ctx.E(`Email地址"%s"格式不正确`, email)
		} else if len(mobile) > 0 && !ctx.Validate(`mobile`, mobile, `mobile`).Ok() {
			err = ctx.E(`手机号"%s"格式不正确`, mobile)
		} else if m.NgingUser.Password != com.MakePassword(passwd, m.NgingUser.Salt) {
			err = ctx.E(`旧密码输入不正确`)
		} else if needCheckU2F {
			//两步验证码
			err = GAuthVerify(ctx, `u2fCode`)
		}
		if err == nil && !ctx.Validate(`email`, email, `email`).Ok() {
			err = ctx.E(`Email地址格式不正确`)
		}
		if err == nil {
			set := map[string]interface{}{
				`email`:  email,
				`mobile`: mobile,
				`avatar`: ctx.Form(`avatar`),
				`gender`: gender,
			}
			if modifyPass {
				set[`password`] = com.MakePassword(newPass, m.NgingUser.Salt)
			}
			err = m.SetFields(nil, set, `id`, user.Id)
		}
		if err == nil {
			handler.SendOk(ctx, ctx.T(`修改成功`))
			m.Get(nil, `id`, user.Id)
			m.SetSession()
			return ctx.Redirect(handler.URLFor(`/user/edit`))
		}
	}
	ctx.Set(`needCheckU2F`, needCheckU2F)
	return ctx.Render(`user/edit`, handler.Err(ctx, err))
}

var onAutoCompletePath = []func(echo.Context) (bool, error){}

func OnAutoCompletePath(fn func(echo.Context) (bool, error)) {
	onAutoCompletePath = append(onAutoCompletePath, fn)
}

func FireAutoCompletePath(c echo.Context) (bool, error) {
	for _, fn := range onAutoCompletePath {
		ok, err := fn(c)
		if ok || err != nil {
			return true, err
		}
	}
	return false, nil
}

func AutoCompletePath(ctx echo.Context) error {
	if ok, err := FireAutoCompletePath(ctx); ok || err != nil {
		return err
	}
	data := ctx.Data()
	prefix := ctx.Form(`query`)
	size := ctx.Formx(`size`, `10`).Int()
	var paths []string
	switch ctx.Form(`type`) {
	case `dir`:
		paths = filemanager.SearchDir(prefix, size)
	case `file`:
		paths = filemanager.SearchFile(prefix, size)
	default:
		paths = filemanager.Search(prefix, size)
	}
	data.SetData(paths)
	return ctx.JSON(data)
}
<|endoftext|>


<commit_before>// Copyright © 2017 Circonus, Inc. <support@circonus.com>
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

package check

import (
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/pkg/errors"
)

func (c *Check) setReverseConfigs() error {
	c.revConfigs = nil
	if c.broker == nil {
		return errors.New("broker is uninitialized")
	}
	if c.checkConfig == nil {
		return errors.New("check is uninitialized")
	}

	if len(c.checkConfig.ReverseURLs) == 0 {
		return errors.New("no reverse URLs found in check")
	}

	cfgs := make(ReverseConfigs)

	for _, rURL := range c.checkConfig.ReverseURLs {
		// Replace protocol, url.Parse does not understand 'mtev_reverse'.
		// Important part is validating what's after 'proto://'.
		// Using raw tls connections, the url protocol is not germane.
		reverseURL, err := url.Parse(strings.Replace(rURL, "mtev_reverse", "https", -1))
		if err != nil {
			return errors.Wrapf(err, "parsing check reverse URL (%s)", rURL)
		}

		brokerAddr, err := net.ResolveTCPAddr("tcp", reverseURL.Host)
		if err != nil {
			return errors.Wrapf(err, "invalid reverse service address (%s)", rURL)
		}

		tlsConfig, cn, err := c.brokerTLSConfig(reverseURL)
		if err != nil {
			return errors.Wrapf(err, "creating TLS config for (%s - %s)", c.broker.CID, rURL)
		}

		cfgs[cn] = ReverseConfig{
			CN:         cn,
			ReverseURL: reverseURL,
			BrokerID:   c.broker.CID,
			BrokerAddr: brokerAddr,
			TLSConfig:  tlsConfig,
		}
	}

	c.revConfigs = &cfgs
	return nil
}

// FindPrimaryBrokerInstance will walk through reverse urls to locate the instance
// in a broker cluster which is the current check owner. Returns the instance cn or error.
func (c *Check) FindPrimaryBrokerInstance(cfgs *ReverseConfigs) (string, error) {
	c.Lock()
	defer c.Unlock()

	primaryCN := ""

	// there is only one reverse url, broker is not clustered
	if len(*cfgs) == 1 {
		c.logger.Debug().Msg("non-clustered broker identified")
		for name := range *cfgs {
			return name, nil
		}
	}

	c.logger.Debug().Msg("clustered broker identified, determining which owns check")
	// clustered brokers, need to identify which broker is the primary for the check
	for name, cfg := range *cfgs {
		client := &http.Client{
			Transport: &http.Transport{
				Proxy: http.ProxyFromEnvironment,
				Dial: (&net.Dialer{
					Timeout: 5 * time.Second,
				}).Dial,
				TLSHandshakeTimeout: 3 * time.Second,
				TLSClientConfig:     cfg.TLSConfig, // all reverse brokers use HTTPS/TLS
				DisableKeepAlives:   true,
				MaxIdleConnsPerHost: -1,
				DisableCompression:  false,
			},
		}

		ownerReqURL := strings.Replace(cfg.ReverseURL.String(), "/check/", "/checks/owner/", 1)
		c.logger.Debug().Str("trying", ownerReqURL).Msg("checking")

		req, err := http.NewRequest("GET", ownerReqURL, nil)
		if err != nil {
			c.logger.Warn().Err(err).Str("url", ownerReqURL).Msg("creating check owner request")
			return "", err
		}
		req.Header.Add("Accept", "application/json")

		resp, err := client.Do(req)
		if err != nil {
			c.logger.Warn().Err(err).Str("url", cfg.ReverseURL.String()).Msg("executing check owner request")
			if nerr, ok := err.(net.Error); ok {
				if nerr.Timeout() {
					continue
				}
			}
			return "", err
		}
		resp.Body.Close() // we only care about headers

		switch resp.StatusCode {
		case http.StatusNoContent:
			primaryCN = name
			c.logger.Debug().Str("cn", primaryCN).Msg("found owner")
		case http.StatusFound:
			location := resp.Header.Get("Location")
			if location == "" {
				c.logger.Warn().Str("req_url", ownerReqURL).Msg("received 302 but 'Location' header missing/blank")
				continue
			}
			c.logger.Debug().Str("location", location).Msg("received Location header")
			// NOTE: this isn't actually a URL, the 'host' portion is actually the CN of
			//       the broker detail which should be used for the reverse connection.
			pu, err := url.Parse(strings.Replace(location, "mtev_reverse", "https", 1))
			if err != nil {
				c.logger.Warn().Err(err).Str("location", location).Msg("unable to parse location")
				continue
			}
			primaryCN = pu.Host
			c.logger.Debug().Str("cn", primaryCN).Msg("using owner from location header")
		default:
			// try next reverse url host (e.g. if there was an error connecting to this one)
		}
	}

	if primaryCN == "" {
		return "", &ErrNoOwnerFound{
			Err:     "unable to locate check owner broker instance",
			CheckID: c.checkConfig.CID,
		}
	}

	if _, ok := (*cfgs)[primaryCN]; !ok {
		return "", &ErrInvalidOwner{
			Err:      "broker owner identified with invalid CN",
			CheckID:  c.checkConfig.CID,
			BrokerCN: primaryCN,
		}
	}

	c.logger.Debug().Str("cn", primaryCN).Msg("check owner broker instance")
	return primaryCN, nil
}
<commit_msg>fix: ensure Location header is not followed upd: debug broker configs as they are added<commit_after>// Copyright © 2017 Circonus, Inc. <support@circonus.com>
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

package check

import (
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/pkg/errors"
)

func (c *Check) setReverseConfigs() error {
	c.revConfigs = nil
	if c.broker == nil {
		return errors.New("broker is uninitialized")
	}
	if c.checkConfig == nil {
		return errors.New("check is uninitialized")
	}

	if len(c.checkConfig.ReverseURLs) == 0 {
		return errors.New("no reverse URLs found in check")
	}

	cfgs := make(ReverseConfigs)

	for _, rURL := range c.checkConfig.ReverseURLs {
		// Replace protocol, url.Parse does not understand 'mtev_reverse'.
		// Important part is validating what's after 'proto://'.
		// Using raw tls connections, the url protocol is not germane.
		reverseURL, err := url.Parse(strings.Replace(rURL, "mtev_reverse", "https", -1))
		if err != nil {
			return errors.Wrapf(err, "parsing check reverse URL (%s)", rURL)
		}

		brokerAddr, err := net.ResolveTCPAddr("tcp", reverseURL.Host)
		if err != nil {
			return errors.Wrapf(err, "invalid reverse service address (%s)", rURL)
		}

		tlsConfig, cn, err := c.brokerTLSConfig(reverseURL)
		if err != nil {
			return errors.Wrapf(err, "creating TLS config for (%s - %s)", c.broker.CID, rURL)
		}

		cfgs[cn] = ReverseConfig{
			CN:         cn,
			ReverseURL: reverseURL,
			BrokerID:   c.broker.CID,
			BrokerAddr: brokerAddr,
			TLSConfig:  tlsConfig,
		}
		c.logger.Debug().
			Str("CN", cn).
			Str("reverse_url", reverseURL.String()).
			Str("broker_id", c.broker.CID).
			Bool("tls", tlsConfig != nil).
			Msg("added reverse config")
	}

	c.revConfigs = &cfgs

	return nil
}

// FindPrimaryBrokerInstance will walk through reverse urls to locate the instance
// in a broker cluster which is the current check owner. Returns the instance cn or error.
func (c *Check) FindPrimaryBrokerInstance(cfgs *ReverseConfigs) (string, error) {
	c.Lock()
	defer c.Unlock()

	primaryCN := ""

	// there is only one reverse url, broker is not clustered
	if len(*cfgs) == 1 {
		c.logger.Debug().Msg("non-clustered broker identified")
		for name := range *cfgs {
			return name, nil
		}
	}

	c.logger.Debug().Msg("clustered broker identified, determining which owns check")
	// clustered brokers, need to identify which broker is the primary for the check
	for name, cfg := range *cfgs {
		client := &http.Client{
			CheckRedirect: func(req *http.Request, via []*http.Request) error {
				// NOTE: so client doesn't automatically try to connect to the
				// 'Location' returned in the response header. Need to process
				// it not "go" to it.
				return http.ErrUseLastResponse
			},
			Transport: &http.Transport{
				Proxy: http.ProxyFromEnvironment,
				Dial: (&net.Dialer{
					Timeout: 5 * time.Second,
				}).Dial,
				TLSHandshakeTimeout: 3 * time.Second,
				TLSClientConfig:     cfg.TLSConfig, // all reverse brokers use HTTPS/TLS
				DisableKeepAlives:   true,
				MaxIdleConnsPerHost: -1,
				DisableCompression:  false,
			},
		}

		ownerReqURL := strings.Replace(cfg.ReverseURL.String(), "/check/", "/checks/owner/", 1)
		c.logger.Debug().Str("trying", ownerReqURL).Msg("checking")

		req, err := http.NewRequest("GET", ownerReqURL, nil)
		if err != nil {
			c.logger.Warn().Err(err).Str("url", ownerReqURL).Msg("creating check owner request")
			return "", err
		}
		req.Header.Add("Accept", "application/json")

		resp, err := client.Do(req)
		if err != nil {
			c.logger.Warn().Err(err).Str("url", ownerReqURL).Msg("executing check owner request")
			if nerr, ok := err.(net.Error); ok {
				if nerr.Timeout() {
					continue
				}
			}
			return "", err
		}
		resp.Body.Close() // we only care about headers

		switch resp.StatusCode {
		case http.StatusNoContent:
			primaryCN = name
			c.logger.Debug().Str("cn", primaryCN).Msg("found owner")
		case http.StatusFound:
			location := resp.Header.Get("Location")
			if location == "" {
				c.logger.Warn().Str("req_url", ownerReqURL).Msg("received 302 but 'Location' header missing/blank")
				continue
			}
			c.logger.Debug().Str("location", location).Msg("received Location header")
			// NOTE: this isn't actually a URL, the 'host' portion is actually the CN of
			//       the broker detail which should be used for the reverse connection.
			pu, err := url.Parse(strings.Replace(location, "mtev_reverse", "https", 1))
			if err != nil {
				c.logger.Warn().Err(err).Str("location", location).Msg("unable to parse location")
				continue
			}
			primaryCN = pu.Host
			c.logger.Debug().Str("cn", primaryCN).Msg("using owner from location header")
		default:
			// try next reverse url host (e.g. if there was an error connecting to this one)
		}
	}

	if primaryCN == "" {
		return "", &ErrNoOwnerFound{
			Err:     "unable to locate check owner broker instance",
			CheckID: c.checkConfig.CID,
		}
	}

	if _, ok := (*cfgs)[primaryCN]; !ok {
		return "", &ErrInvalidOwner{
			Err:      "broker owner identified with invalid CN",
			CheckID:  c.checkConfig.CID,
			BrokerCN: primaryCN,
		}
	}

	c.logger.Debug().Str("cn", primaryCN).Msg("check owner broker instance")
	return primaryCN, nil
}
<|endoftext|>