<commit_before>// This file is subject to a 1-clause BSD license.
// Its contents can be found in the enclosed LICENSE file.

package reputation

import (
	"github.com/garyburd/redigo/redis"
	"github.com/jteeuwen/ircb/plugin"
	"github.com/jteeuwen/ircb/proto"
	"log"
	"os"
	"regexp"
	"strconv"
)

type RepChange struct {
	entity string
	delta  int
}

var reputation map[string]int

var reputationChanges chan RepChange

var red redis.Conn

func init() { plugin.Register(New) }

type Plugin struct {
	*plugin.Base

	// Each entry holds a regex pattern which should be excluded
	// from the url-title-lookup.
	exclude []*regexp.Regexp

	// Pattern which recognizes s-expressions.
	// TODO use proper parser
	sexpr *regexp.Regexp
}

func New(profile string) plugin.Plugin {
	p := new(Plugin)
	p.Base = plugin.New(profile, "url")
	p.sexpr = regexp.MustCompile(`\((\+\+|--|rep) (.*?)\)`)
	return p
}

// Init initializes the plugin. it loads configuration data and binds
// commands and protocol handlers.
func (p *Plugin) Load(c *proto.Client) (err error) {
	err = p.Base.Load(c)
	if err != nil {
		return
	}

	c.Bind(proto.CmdPrivMsg, func(c *proto.Client, m *proto.Message) {
		p.parseSexpr(c, m)
	})

	ini := p.LoadConfig()
	if ini == nil {
		return
	}

	s := ini.Section("exclude")
	list := s.List("url")
	p.exclude = make([]*regexp.Regexp, len(list))

	for i := range list {
		p.exclude[i], err = regexp.Compile(list[i])

		if err != nil {
			return
		}
	}

	return
}

// parseSexpr looks for s-expressions embedded in incoming messages.
// If they are valid s-expressions and not in the exclude list,
// we use them to fetch page titles from the internet.
func (p *Plugin) parseSexpr(c *proto.Client, m *proto.Message) {
	list := p.sexpr.FindAllStringSubmatch(m.Data, -1)
	if len(list) == 0 {
		return
	}

	for _, sexpr := range list {
		if !p.excluded(sexpr[0]) {
			go scoreReputation(c, m, sexpr)
		}
	}
}

// excluded returns true if the given url is part of the exclusion list.
func (p *Plugin) excluded(url string) bool {
	for _, excl := range p.exclude {
		if excl.MatchString(url) {
			return true
		}
	}

	return false
}

// fetchTitle attempts to retrieve the title element for a given url.
func scoreReputation(c *proto.Client, m *proto.Message, match []string) {
	entity := match[2]
	action := match[1]
	action_words := " is in limbo"
	switch action {
	case "++":
		log.Printf("incrementing %s", entity)
		_, err := red.Do("INCR", entity)
		if err != nil {
			log.Print(err)
		}
		action_words = "gained 1 rep"
	case "--":
		log.Printf("decrementing %s", entity)
		_, err := red.Do("DECR", entity)
		if err != nil {
			log.Print(err)
		}
		action_words = "lost 1 rep"
	case "rep":
		action_words = "has rep"

	default:
		log.Printf("action %s not supported", action)
		return
	}

	rep, err := red.Do("GET", entity)
	if err != nil {
		log.Printf("ERROR: %s", err)
		return
	}
	rep_b, ok := rep.([]byte)
	if !ok {
		log.Printf("ERROR: not a byte slice type: %v", rep)
		return
	}
	log.Printf("Fetched %s", string(rep_b))
	rep_i, err := strconv.Atoi(string(rep_b))
	if err != nil {
		log.Printf("Error converting to integer %s", err)
		return
	}

	c.PrivMsg(m.Receiver, "%s %s! rep: %d", entity, action_words, rep_i)
}

func init() {
	var err error
	red, err = redis.Dial(os.Getenv("REDIS_NETWORK"), os.Getenv("REDIS_ADDRESS"))
	if err != nil {
		log.Printf("ERROR: Failed to connect to redis database - reputation plugin will NOT load")
		panic(err)
	}
	_, err = red.Do("AUTH", os.Getenv("REDIS_PASSWORD"))
	if err != nil {
		panic(err)
	}
}
<commit_msg>Make reputation case-insensitive<commit_after>// This file is subject to a 1-clause BSD license.
// Its contents can be found in the enclosed LICENSE file.

package reputation

import (
	"github.com/garyburd/redigo/redis"
	"github.com/jteeuwen/ircb/plugin"
    "strings"
	"github.com/jteeuwen/ircb/proto"
	"log"
	"os"
	"regexp"
	"strconv"
)

type RepChange struct {
	entity string
	delta  int
}

var reputation map[string]int

var reputationChanges chan RepChange

var red redis.Conn

func init() { plugin.Register(New) }

type Plugin struct {
	*plugin.Base

	// Each entry holds a regex pattern which should be excluded
	// from the url-title-lookup.
	exclude []*regexp.Regexp

	// Pattern which recognizes s-expressions.
	// TODO use proper parser
	sexpr *regexp.Regexp
}

func New(profile string) plugin.Plugin {
	p := new(Plugin)
	p.Base = plugin.New(profile, "url")
	p.sexpr = regexp.MustCompile(`\((\+\+|--|rep) (.*?)\)`)
	return p
}

// Init initializes the plugin. it loads configuration data and binds
// commands and protocol handlers.
func (p *Plugin) Load(c *proto.Client) (err error) {
	err = p.Base.Load(c)
	if err != nil {
		return
	}

	c.Bind(proto.CmdPrivMsg, func(c *proto.Client, m *proto.Message) {
		p.parseSexpr(c, m)
	})

	ini := p.LoadConfig()
	if ini == nil {
		return
	}

	s := ini.Section("exclude")
	list := s.List("url")
	p.exclude = make([]*regexp.Regexp, len(list))

	for i := range list {
		p.exclude[i], err = regexp.Compile(list[i])

		if err != nil {
			return
		}
	}

	return
}

// parseSexpr looks for s-expressions embedded in incoming messages.
// If they are valid s-expressions and not in the exclude list,
// we use them to fetch page titles from the internet.
func (p *Plugin) parseSexpr(c *proto.Client, m *proto.Message) {
	list := p.sexpr.FindAllStringSubmatch(m.Data, -1)
	if len(list) == 0 {
		return
	}

	for _, sexpr := range list {
		if !p.excluded(sexpr[0]) {
			go scoreReputation(c, m, sexpr)
		}
	}
}

// excluded returns true if the given url is part of the exclusion list.
func (p *Plugin) excluded(url string) bool {
	for _, excl := range p.exclude {
		if excl.MatchString(url) {
			return true
		}
	}

	return false
}

// fetchTitle attempts to retrieve the title element for a given url.
func scoreReputation(c *proto.Client, m *proto.Message, match []string) {
	entity := strings.ToLower(match[2])
	action := match[1]
	action_words := " is in limbo"
	switch action {
	case "++":
		log.Printf("incrementing %s", entity)
		_, err := red.Do("INCR", entity)
		if err != nil {
			log.Print(err)
		}
		action_words = "gained 1 rep"
	case "--":
		log.Printf("decrementing %s", entity)
		_, err := red.Do("DECR", entity)
		if err != nil {
			log.Print(err)
		}
		action_words = "lost 1 rep"
	case "rep":
		action_words = "has rep"

	default:
		log.Printf("action %s not supported", action)
		return
	}

	rep, err := red.Do("GET", entity)
	if err != nil {
		log.Printf("ERROR: %s", err)
		return
	}
	rep_b, ok := rep.([]byte)
	if !ok {
		log.Printf("ERROR: not a byte slice type: %v", rep)
		return
	}
	log.Printf("Fetched %s", string(rep_b))
	rep_i, err := strconv.Atoi(string(rep_b))
	if err != nil {
		log.Printf("Error converting to integer %s", err)
		return
	}

	c.PrivMsg(m.Receiver, "%s %s! rep: %d", entity, action_words, rep_i)
}

func init() {
	var err error
	red, err = redis.Dial(os.Getenv("REDIS_NETWORK"), os.Getenv("REDIS_ADDRESS"))
	if err != nil {
		log.Printf("ERROR: Failed to connect to redis database - reputation plugin will NOT load")
		panic(err)
	}
	_, err = red.Do("AUTH", os.Getenv("REDIS_PASSWORD"))
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>// This file is part of Monsti, a web content management system.
// Copyright 2012-2013 Christian Neumann
//
// Monsti 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.
//
// Monsti 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 Monsti.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"fmt"
	"github.com/gorilla/sessions"
	"github.com/monsti/form"
	"github.com/monsti/service"
	"github.com/monsti/util/l10n"
	"github.com/monsti/util/template"
	"io/ioutil"
	"launchpad.net/goyaml"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
)

// getFooter retrieves the footer.
//
// root is the path to the data directory
//
// Returns an empty string if there is no footer.
func getFooter(root string) string {
	path := filepath.Join(root, "footer.html")
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return ""
	}
	return string(content)
}

// getBelowHeader retrieves the below header content for the given node.
//
// path is the node's path.
// root is the path to the data directory.
//
// Returns an empty string if there is no below header content.
func getBelowHeader(path, root string) string {
	file := filepath.Join(root, path, "below_header.html")
	content, err := ioutil.ReadFile(file)
	if err != nil {
		return ""
	}
	return string(content)
}

// getSidebar retrieves the sidebar content for the given node.
//
// path is the node's path.
// root is the path to the data directory.
//
// It traverses up to the root until it finds a node with defined sidebar
// content.
//
// Returns an empty string if there is no sidebar content.
func getSidebar(path, root string) string {
	for {
		file := filepath.Join(root, path, "sidebar.html")
		content, err := ioutil.ReadFile(file)
		if err != nil {
			if path == filepath.Dir(path) {
				break
			}
			path = filepath.Dir(path)
			continue
		}
		return string(content)
	}
	return ""
}

// navLink represents a link in the navigation.
type navLink struct {
	Name, Target  string
	Active, Child bool
	Order         int
}

type navigation []navLink

// Len is the number of elements in the navigation.
func (n navigation) Len() int {
	return len(n)
}

// Less returns whether the element with index i should sort
// before the element with index j.
func (n navigation) Less(i, j int) bool {
	return n[i].Order < n[j].Order || (n[i].Order == n[j].Order &&
		n[i].Name < n[j].Name)
}

// Swap swaps the elements with indexes i and j.
func (n *navigation) Swap(i, j int) {
	(*n)[i], (*n)[j] = (*n)[j], (*n)[i]
}

// getShortTitle returns the given node's ShortTitle attribute, or, if the
// ShortTitle is of zero length, its Title attribute.
func getShortTitle(node service.NodeInfo) string {
	if len(node.ShortTitle) > 0 {
		return node.ShortTitle
	}
	return node.Title
}

// getNav returns the navigation for the given node.
//
// nodePath is the absolute path of the node for which to get the navigation.
// active is the absolute path to the currently active node.
// root is the path of the data directory.
func getNav(nodePath, active string, root string) (navLinks navigation,
	err error) {
	// Search children
	children, err := ioutil.ReadDir(filepath.Join(root, nodePath))
	if err != nil {
		return nil, fmt.Errorf("Could not read node directory: %v", err)
	}
	anyChild := false
	childrenNavLinks := navLinks[:]
	for _, child := range children {
		if !child.IsDir() {
			continue
		}
		node, err := lookupNode(root, path.Join(nodePath,
			child.Name()))
		if err != nil || node.Hide {
			continue
		}
		anyChild = true
		childrenNavLinks = append(childrenNavLinks, navLink{
			Name:   getShortTitle(node),
			Target: child.Name(), Child: true, Order: node.Order})
	}
	if !anyChild {
		if nodePath == "/" || path.Dir(nodePath) == "/" {
			return nil, nil
		}
		return getNav(path.Dir(nodePath), active, root)
	}
	sort.Sort(&childrenNavLinks)
	siblingsNavLinks := navLinks[:]
	// Search siblings
	if nodePath != "/" && path.Dir(nodePath) == "/" {
		node, err := lookupNode(root, nodePath)
		if err != nil {
			return nil, fmt.Errorf("Could not find node: %v", err)
		}
		siblingsNavLinks = append(siblingsNavLinks, navLink{
			Name:   getShortTitle(node),
			Target: path.Join("..", path.Base(nodePath)), Order: node.Order})
	} else if nodePath != "/" {
		parent := path.Dir(nodePath)
		siblings, err := ioutil.ReadDir(filepath.Join(root, parent))
		if err != nil {
			return nil, fmt.Errorf("Could not read node directory: %v", err)
		}
		for _, sibling := range siblings {
			if !sibling.IsDir() {
				continue
			}
			node, err := lookupNode(root, path.Join(parent,
				sibling.Name()))
			if err != nil || node.Hide {
				continue
			}
			siblingsNavLinks = append(siblingsNavLinks, navLink{
				Name:   getShortTitle(node),
				Target: path.Join("..", sibling.Name()), Order: node.Order})
		}
	}
	sort.Sort(&siblingsNavLinks)
	// Insert children at their parent
	for i, link := range siblingsNavLinks {
		if link.Target == path.Join("..", path.Base(nodePath)) {
			navLinks = append(navLinks, siblingsNavLinks[:i+1]...)
			navLinks = append(navLinks, childrenNavLinks...)
			navLinks = append(navLinks, siblingsNavLinks[i+1:]...)
			break
		}
	}
	if len(navLinks) == 0 {
		navLinks = childrenNavLinks
	}
	// Compute node paths relative to active node and search and set the Active
	// link
	for i, link := range navLinks {
		rel, err := filepath.Rel(active, path.Join(nodePath, link.Target))
		if err != nil {
			panic(fmt.Sprint("Could not comute relative path:", err))
		}
		navLinks[i].Target = rel
		if rel == "." || (len(rel) >= 5 && rel[:2] == ".." && rel[len(rel)-2:] == "..") {
			navLinks[i].Active = true
		}
	}
	return
}

// MakeAbsolute converts relative targets to absolute ones by adding the given
// root path. It also adds a trailing slash.
func (nav *navigation) MakeAbsolute(root string) {
	for i := range *nav {
		if (*nav)[i].Target[0] != '/' {
			(*nav)[i].Target = path.Join(root, (*nav)[i].Target)
		}
		(*nav)[i].Target = (*nav)[i].Target + "/"
	}
}

type addFormData struct {
	Type, Name, Title string
}

// Add handles add requests.
func (h *nodeHandler) Add(w http.ResponseWriter, r *http.Request,
	reqnode service.NodeInfo, session *sessions.Session,
	cSession *service.Session, site site) {
	G := l10n.UseCatalog(cSession.Locale)
	data := addFormData{}
	nodeTypeOptions := []form.Option{}
	/*
		for _, nodeType := range h.Settings.NodeTypes {
			nodeTypeOptions = append(nodeTypeOptions,
				form.Option{nodeType, nodeType})
		}
	*/
	selectWidget := form.SelectWidget{nodeTypeOptions}
	form := form.NewForm(&data, form.Fields{
		"Type": form.Field{G("Content type"), "", form.Required(G("Required.")), selectWidget},
		"Name": form.Field{G("Name"),
			G("The name as it should appear in the URL."),
			form.And(form.Required(G("Required.")), form.Regex(`^[-\w]*$`,
				G("Contains	invalid characters."))), nil},
		"Title": form.Field{G("Title"), "", form.Required(G("Required.")), nil}})
	switch r.Method {
	case "GET":
	case "POST":
		r.ParseForm()
		if form.Fill(r.Form) {
			data.Name = strings.ToLower(data.Name)
			/*
				if !inStringSlice(data.Type, h.Settings.NodeTypes) {
					panic("Can't add this content type.")
				}
			*/
			newPath := filepath.Join(reqnode.Path, data.Name)
			newNode := service.NodeInfo{
				Path:  newPath,
				Type:  data.Type,
				Title: data.Title}
			data, err := h.Info.FindDataService()
			if err != nil {
				panic("Can't find data service: " + err.Error())
			}
			if err := data.UpdateNode(site.Name, newNode); err != nil {
				panic("Can't add node: " + err.Error())
			}
			http.Redirect(w, r, newPath+"/@@edit", http.StatusSeeOther)
			return
		}
	default:
		panic("Request method not supported: " + r.Method)
	}
	body := h.Renderer.Render("daemon/actions/addform", template.Context{
		"Form": form.RenderData()}, cSession.Locale,
		h.Settings.Monsti.GetSiteTemplatesPath(site.Name))
	env := masterTmplEnv{Node: reqnode, Session: cSession,
		Flags: EDIT_VIEW, Title: G("Add content")}
	fmt.Fprint(w, renderInMaster(h.Renderer, []byte(body), env, h.Settings,
		site, cSession.Locale))
}

type removeFormData struct {
	Confirm int
}

// Remove handles remove requests.
func (h *nodeHandler) Remove(w http.ResponseWriter, r *http.Request,
	node service.NodeInfo, session *sessions.Session, cSession *service.Session,
	site site) {
	G := l10n.UseCatalog(cSession.Locale)
	data := removeFormData{}
	form := form.NewForm(&data, form.Fields{
		"Confirm": form.Field{G("Confirm"), "", form.Required(G("Required.")),
			new(form.HiddenWidget)}})
	switch r.Method {
	case "GET":
	case "POST":
		r.ParseForm()
		if form.Fill(r.Form) {
			panic("Not implemented")
			/*
			removeNode(node.Path, site.Directories.Data)
			*/
			http.Redirect(w, r, path.Dir(node.Path), http.StatusSeeOther)
			return
		}
	default:
		panic("Request method not supported: " + r.Method)
	}
	data.Confirm = 1489
	body := h.Renderer.Render("daemon/actions/removeform", template.Context{
		"Form": form.RenderData(), "Node": node},
		cSession.Locale, h.Settings.Monsti.GetSiteTemplatesPath(site.Name))
	env := masterTmplEnv{Node: node, Session: cSession,
		Flags: EDIT_VIEW, Title: fmt.Sprintf(G("Remove \"%v\""), node.Title)}
	fmt.Fprint(w, renderInMaster(h.Renderer, []byte(body), env, h.Settings,
		site, cSession.Locale))
}

// lookupNode look ups a node at the given path.
// If no such node exists, return nil.
func lookupNode(root, path string) (service.NodeInfo, error) {
	node_path := filepath.Join(root, path[1:], "node.yaml")
	content, err := ioutil.ReadFile(node_path)
	if err != nil {
		return service.NodeInfo{}, err
	}
	var reqnode service.NodeInfo
	if err = goyaml.Unmarshal(content, &reqnode); err != nil {
		return service.NodeInfo{}, err
	}
	reqnode.Path = path
	return reqnode, nil
}

// removeNode recursively removes the given node from the data directory located
// at the given root and from the navigation of the parent node.
func removeNode(path, root string) {
	nodePath := filepath.Join(root, path[1:])
	if err := os.RemoveAll(nodePath); err != nil {
		panic("Can't remove node: " + err.Error())
	}
}
<commit_msg>Fix template paths.<commit_after>// This file is part of Monsti, a web content management system.
// Copyright 2012-2013 Christian Neumann
//
// Monsti 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.
//
// Monsti 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 Monsti.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"fmt"
	"github.com/gorilla/sessions"
	"github.com/monsti/form"
	"github.com/monsti/service"
	"github.com/monsti/util/l10n"
	"github.com/monsti/util/template"
	"io/ioutil"
	"launchpad.net/goyaml"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
)

// getFooter retrieves the footer.
//
// root is the path to the data directory
//
// Returns an empty string if there is no footer.
func getFooter(root string) string {
	path := filepath.Join(root, "footer.html")
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return ""
	}
	return string(content)
}

// getBelowHeader retrieves the below header content for the given node.
//
// path is the node's path.
// root is the path to the data directory.
//
// Returns an empty string if there is no below header content.
func getBelowHeader(path, root string) string {
	file := filepath.Join(root, path, "below_header.html")
	content, err := ioutil.ReadFile(file)
	if err != nil {
		return ""
	}
	return string(content)
}

// getSidebar retrieves the sidebar content for the given node.
//
// path is the node's path.
// root is the path to the data directory.
//
// It traverses up to the root until it finds a node with defined sidebar
// content.
//
// Returns an empty string if there is no sidebar content.
func getSidebar(path, root string) string {
	for {
		file := filepath.Join(root, path, "sidebar.html")
		content, err := ioutil.ReadFile(file)
		if err != nil {
			if path == filepath.Dir(path) {
				break
			}
			path = filepath.Dir(path)
			continue
		}
		return string(content)
	}
	return ""
}

// navLink represents a link in the navigation.
type navLink struct {
	Name, Target  string
	Active, Child bool
	Order         int
}

type navigation []navLink

// Len is the number of elements in the navigation.
func (n navigation) Len() int {
	return len(n)
}

// Less returns whether the element with index i should sort
// before the element with index j.
func (n navigation) Less(i, j int) bool {
	return n[i].Order < n[j].Order || (n[i].Order == n[j].Order &&
		n[i].Name < n[j].Name)
}

// Swap swaps the elements with indexes i and j.
func (n *navigation) Swap(i, j int) {
	(*n)[i], (*n)[j] = (*n)[j], (*n)[i]
}

// getShortTitle returns the given node's ShortTitle attribute, or, if the
// ShortTitle is of zero length, its Title attribute.
func getShortTitle(node service.NodeInfo) string {
	if len(node.ShortTitle) > 0 {
		return node.ShortTitle
	}
	return node.Title
}

// getNav returns the navigation for the given node.
//
// nodePath is the absolute path of the node for which to get the navigation.
// active is the absolute path to the currently active node.
// root is the path of the data directory.
func getNav(nodePath, active string, root string) (navLinks navigation,
	err error) {
	// Search children
	children, err := ioutil.ReadDir(filepath.Join(root, nodePath))
	if err != nil {
		return nil, fmt.Errorf("Could not read node directory: %v", err)
	}
	anyChild := false
	childrenNavLinks := navLinks[:]
	for _, child := range children {
		if !child.IsDir() {
			continue
		}
		node, err := lookupNode(root, path.Join(nodePath,
			child.Name()))
		if err != nil || node.Hide {
			continue
		}
		anyChild = true
		childrenNavLinks = append(childrenNavLinks, navLink{
			Name:   getShortTitle(node),
			Target: child.Name(), Child: true, Order: node.Order})
	}
	if !anyChild {
		if nodePath == "/" || path.Dir(nodePath) == "/" {
			return nil, nil
		}
		return getNav(path.Dir(nodePath), active, root)
	}
	sort.Sort(&childrenNavLinks)
	siblingsNavLinks := navLinks[:]
	// Search siblings
	if nodePath != "/" && path.Dir(nodePath) == "/" {
		node, err := lookupNode(root, nodePath)
		if err != nil {
			return nil, fmt.Errorf("Could not find node: %v", err)
		}
		siblingsNavLinks = append(siblingsNavLinks, navLink{
			Name:   getShortTitle(node),
			Target: path.Join("..", path.Base(nodePath)), Order: node.Order})
	} else if nodePath != "/" {
		parent := path.Dir(nodePath)
		siblings, err := ioutil.ReadDir(filepath.Join(root, parent))
		if err != nil {
			return nil, fmt.Errorf("Could not read node directory: %v", err)
		}
		for _, sibling := range siblings {
			if !sibling.IsDir() {
				continue
			}
			node, err := lookupNode(root, path.Join(parent,
				sibling.Name()))
			if err != nil || node.Hide {
				continue
			}
			siblingsNavLinks = append(siblingsNavLinks, navLink{
				Name:   getShortTitle(node),
				Target: path.Join("..", sibling.Name()), Order: node.Order})
		}
	}
	sort.Sort(&siblingsNavLinks)
	// Insert children at their parent
	for i, link := range siblingsNavLinks {
		if link.Target == path.Join("..", path.Base(nodePath)) {
			navLinks = append(navLinks, siblingsNavLinks[:i+1]...)
			navLinks = append(navLinks, childrenNavLinks...)
			navLinks = append(navLinks, siblingsNavLinks[i+1:]...)
			break
		}
	}
	if len(navLinks) == 0 {
		navLinks = childrenNavLinks
	}
	// Compute node paths relative to active node and search and set the Active
	// link
	for i, link := range navLinks {
		rel, err := filepath.Rel(active, path.Join(nodePath, link.Target))
		if err != nil {
			panic(fmt.Sprint("Could not comute relative path:", err))
		}
		navLinks[i].Target = rel
		if rel == "." || (len(rel) >= 5 && rel[:2] == ".." && rel[len(rel)-2:] == "..") {
			navLinks[i].Active = true
		}
	}
	return
}

// MakeAbsolute converts relative targets to absolute ones by adding the given
// root path. It also adds a trailing slash.
func (nav *navigation) MakeAbsolute(root string) {
	for i := range *nav {
		if (*nav)[i].Target[0] != '/' {
			(*nav)[i].Target = path.Join(root, (*nav)[i].Target)
		}
		(*nav)[i].Target = (*nav)[i].Target + "/"
	}
}

type addFormData struct {
	Type, Name, Title string
}

// Add handles add requests.
func (h *nodeHandler) Add(w http.ResponseWriter, r *http.Request,
	reqnode service.NodeInfo, session *sessions.Session,
	cSession *service.Session, site site) {
	G := l10n.UseCatalog(cSession.Locale)
	data := addFormData{}
	nodeTypeOptions := []form.Option{}
	/*
		for _, nodeType := range h.Settings.NodeTypes {
			nodeTypeOptions = append(nodeTypeOptions,
				form.Option{nodeType, nodeType})
		}
	*/
	selectWidget := form.SelectWidget{nodeTypeOptions}
	form := form.NewForm(&data, form.Fields{
		"Type": form.Field{G("Content type"), "", form.Required(G("Required.")), selectWidget},
		"Name": form.Field{G("Name"),
			G("The name as it should appear in the URL."),
			form.And(form.Required(G("Required.")), form.Regex(`^[-\w]*$`,
				G("Contains	invalid characters."))), nil},
		"Title": form.Field{G("Title"), "", form.Required(G("Required.")), nil}})
	switch r.Method {
	case "GET":
	case "POST":
		r.ParseForm()
		if form.Fill(r.Form) {
			data.Name = strings.ToLower(data.Name)
			/*
				if !inStringSlice(data.Type, h.Settings.NodeTypes) {
					panic("Can't add this content type.")
				}
			*/
			newPath := filepath.Join(reqnode.Path, data.Name)
			newNode := service.NodeInfo{
				Path:  newPath,
				Type:  data.Type,
				Title: data.Title}
			data, err := h.Info.FindDataService()
			if err != nil {
				panic("Can't find data service: " + err.Error())
			}
			if err := data.UpdateNode(site.Name, newNode); err != nil {
				panic("Can't add node: " + err.Error())
			}
			http.Redirect(w, r, newPath+"/@@edit", http.StatusSeeOther)
			return
		}
	default:
		panic("Request method not supported: " + r.Method)
	}
	body := h.Renderer.Render("httpd/actions/addform", template.Context{
		"Form": form.RenderData()}, cSession.Locale,
		h.Settings.Monsti.GetSiteTemplatesPath(site.Name))
	env := masterTmplEnv{Node: reqnode, Session: cSession,
		Flags: EDIT_VIEW, Title: G("Add content")}
	fmt.Fprint(w, renderInMaster(h.Renderer, []byte(body), env, h.Settings,
		site, cSession.Locale))
}

type removeFormData struct {
	Confirm int
}

// Remove handles remove requests.
func (h *nodeHandler) Remove(w http.ResponseWriter, r *http.Request,
	node service.NodeInfo, session *sessions.Session, cSession *service.Session,
	site site) {
	G := l10n.UseCatalog(cSession.Locale)
	data := removeFormData{}
	form := form.NewForm(&data, form.Fields{
		"Confirm": form.Field{G("Confirm"), "", form.Required(G("Required.")),
			new(form.HiddenWidget)}})
	switch r.Method {
	case "GET":
	case "POST":
		r.ParseForm()
		if form.Fill(r.Form) {
			panic("Not implemented")
			/*
			removeNode(node.Path, site.Directories.Data)
			*/
			http.Redirect(w, r, path.Dir(node.Path), http.StatusSeeOther)
			return
		}
	default:
		panic("Request method not supported: " + r.Method)
	}
	data.Confirm = 1489
	body := h.Renderer.Render("httpd/actions/removeform", template.Context{
		"Form": form.RenderData(), "Node": node},
		cSession.Locale, h.Settings.Monsti.GetSiteTemplatesPath(site.Name))
	env := masterTmplEnv{Node: node, Session: cSession,
		Flags: EDIT_VIEW, Title: fmt.Sprintf(G("Remove \"%v\""), node.Title)}
	fmt.Fprint(w, renderInMaster(h.Renderer, []byte(body), env, h.Settings,
		site, cSession.Locale))
}

// lookupNode look ups a node at the given path.
// If no such node exists, return nil.
func lookupNode(root, path string) (service.NodeInfo, error) {
	node_path := filepath.Join(root, path[1:], "node.yaml")
	content, err := ioutil.ReadFile(node_path)
	if err != nil {
		return service.NodeInfo{}, err
	}
	var reqnode service.NodeInfo
	if err = goyaml.Unmarshal(content, &reqnode); err != nil {
		return service.NodeInfo{}, err
	}
	reqnode.Path = path
	return reqnode, nil
}

// removeNode recursively removes the given node from the data directory located
// at the given root and from the navigation of the parent node.
func removeNode(path, root string) {
	nodePath := filepath.Join(root, path[1:])
	if err := os.RemoveAll(nodePath); err != nil {
		panic("Can't remove node: " + err.Error())
	}
}
<|endoftext|>


<commit_before>// (c) Copyright 2015-2017 JONNALAGADDA Srinivas
//
// 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 flow

import (
	"database/sql"
	"errors"
	"log"
)

// NodeID is the type of unique identifiers of nodes.
type NodeID int64

// NodeFunc defines the type of functions that generate notification
// messages in workflows.
//
// These functions are triggered by appropriate nodes, when document
// events are applied to documents to possibly transform them.
// Invocation of a `NodeFunc` should result in a message that can then
// be dispatched to applicable mailboxes.
//
// Error should be returned only when an impossible situation arises,
// and processing needs to abort.  Note that returning an error stops
// the workflow.  Manual intervention will be needed to move the
// document further.
//
// N. B. NodeFunc instances must be referentially transparent --
// stateless and not capture their environment in any manner.
// Unexpected bad things could happen otherwise!
type NodeFunc func(*Document, *DocEvent) *Message

// defNodeFunc prepares a simple message that can be posted to
// applicable mailboces.
func defNodeFunc(d *Document, event *DocEvent) *Message {
	return &Message{
		DocType: DocType{
			ID: d.DocType.ID,
		},
		DocID: d.ID,
		Event: event.ID,
		Title: d.Title,
		Data:  event.Text,
	}
}

// Node represents a specific logical unit of processing and routing
// in a workflow.
type Node struct {
	ID       NodeID          `json:"ID"`                      // Unique identifier of this node
	DocType  DocTypeID       `json:"DocType"`                 // Document type which this node's workflow manages
	State    DocStateID      `json:"DocState"`                // A document arriving at this node must be in this state
	AccCtx   AccessContextID `json:"AccessContext,omitempty"` // Specific access context associated with this state, if any
	Wflow    WorkflowID      `json:"Workflow"`                // Containing flow of this node
	Name     string          `json:"Name"`                    // Unique within its workflow
	NodeType NodeType        `json:"NodeType"`                // Topology type of this node
	nfunc    NodeFunc        // Processing function of this node
}

// Transitions answers the possible document states into which a
// document currently in the given state can transition.
func (n *Node) Transitions() (map[DocActionID]DocStateID, error) {
	return DocTypes._Transitions(n.DocType, n.State)
}

// SetFunc registers the given node function with this node.
//
// If `nil` is given, a default node function is registered instead.
// This default function sets the document title as the message
// subject, and the event's data as the message body.
func (n *Node) SetFunc(fn NodeFunc) error {
	if fn == nil {
		n.nfunc = defNodeFunc
		return nil
	}

	n.nfunc = fn
	return nil
}

// Func answers the processing function registered in this node
// definition.
func (n *Node) Func() NodeFunc {
	return n.nfunc
}

// applyEvent checks to see if the given event can be applied
// successfully.  Accordingly, it prepares a message by utilising the
// registered node function, and posts it to applicable mailboxes.
func (n *Node) applyEvent(otx *sql.Tx, event *DocEvent, recipients []GroupID) (DocStateID, error) {
	ts, err := n.Transitions()
	if err != nil {
		return 0, err
	}
	tstate, ok := ts[event.Action]
	if !ok {
		return 0, ErrWorkflowInvalidAction
	}

	// Check document's current state.
	doc, err := Documents.Get(otx, event.DocType, event.DocID)
	if err != nil {
		return 0, err
	}
	if doc.State.ID != event.State {
		return 0, ErrDocEventStateMismatch
	}

	// Document has already transitioned.  So, we note that the event
	// is applied, and return.
	//
	// N.B. This has implications for `NodeTypeJoinAny` below.  Should
	// you alter this logic or its position, verify that the
	// corresponding logic in the switch below is in coherence.
	if doc.State.ID == tstate {
		err = n.recordEvent(otx, event, tstate, true)
		if err != nil {
			return 0, err
		}
		return tstate, ErrDocEventRedundant
	}

	// Transition document state according to the target node type.

	tnode, err := Nodes.GetByState(n.DocType, tstate)
	if err != nil {
		return 0, err
	}

	switch tnode.NodeType {
	case NodeTypeJoinAny:
		// Multiple 'in's, but any one suffices.

		// We have already checked to see if the document has
		// transitioned into the target state.  If we have come this
		// far, the event can be applied.
		fallthrough

	case NodeTypeBegin, NodeTypeEnd, NodeTypeLinear, NodeTypeBranch:
		// Any node type having a single 'in'.

		// Update the document to transition the state.
		err = Documents.setState(otx, event.DocType, event.DocID, tstate, tnode.AccCtx)
		if err != nil {
			return 0, err
		}

		// Record event application.
		err = n.recordEvent(otx, event, tstate, false)
		if err != nil {
			return 0, err
		}

		// Post messages.
		msg := n.nfunc(doc, event)
		if len(recipients) == 0 {
			recipients, err = tnode.determineRecipients(otx, event.Group)
			if err != nil {
				return 0, err
			}
		}
		// It is legal to not have any recipients, too.
		if len(recipients) > 0 {
			err = n.postMessage(otx, msg, recipients)
			if err != nil {
				return 0, err
			}
		}

	case NodeTypeJoinAll:
		// Multiple 'in's, and all are required.

		// TODO(js)

	default:
		log.Panicf("unknown node type encountered : %s\n", tnode.NodeType)
	}

	return tstate, nil
}

// recordEvent writes a record stating that the given event has
// successfully been applied to effect a document state transition.
func (n *Node) recordEvent(otx *sql.Tx, event *DocEvent, tstate DocStateID, statusOnly bool) error {
	if !statusOnly {
		q := `
		INSERT INTO wf_docevent_application(doctype_id, doc_id, from_state_id, docevent_id, to_state_id)
		VALUES(?, ?, ?, ?, ?)
		`
		_, err := otx.Exec(q, event.DocType, event.DocID, event.State, event.ID, tstate)
		if err != nil {
			return err
		}
	}

	q := `UPDATE wf_docevents SET status = 'A' WHERE id = ?`
	_, err := otx.Exec(q, event.ID)
	if err != nil {
		return err
	}

	return nil
}

// determineRecipients takes the document type and access context into
// account, and determines the list of groups to which the
// notification should be posted.
func (n *Node) determineRecipients(otx *sql.Tx, group GroupID) ([]GroupID, error) {
	q := `
	SELECT reports_to
	FROM wf_ac_group_hierarchy
	WHERE ac_id = ?
	AND group_id = ?
	ORDER BY group_id
	LIMIT 1
	`
	rows, err := otx.Query(q, n.AccCtx, group)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	ary := make([]GroupID, 0, 4)
	for rows.Next() {
		var gid int64
		err = rows.Scan(&gid)
		if err != nil {
			return nil, err
		}
		ary = append(ary, GroupID(gid))
	}
	if rows.Err() != nil {
		return nil, err
	}
	return ary, nil
}

// postMessage posts the given message into the mailboxes of the
// specified recipients.
func (n *Node) postMessage(otx *sql.Tx, msg *Message, recipients []GroupID) error {
	// Record the message.

	q := `
	INSERT INTO wf_messages(doctype_id, doc_id, docevent_id, title, data)
	VALUES(?, ?, ?, ?, ?)
	`
	res, err := otx.Exec(q, msg.DocType, msg.DocID, msg.Event, msg.Title, msg.Data)
	if err != nil {
		return err
	}
	var msgid int64
	if msgid, err = res.LastInsertId(); err != nil {
		return err
	}

	// Post it into applicable mailboxes.

	q = `
	INSERT INTO wf_mailboxes(group_id, message_id, unread)
	VALUES(?, ?, 1)
	`
	for _, gid := range recipients {
		res, err = otx.Exec(q, gid, msgid)
		if err != nil {
			return err
		}
	}

	return nil
}

// Unexported type, only for convenience methods.
type _Nodes struct{}

// Nodes provides a resource-like interface to the nodes defined in
// this system.
var Nodes _Nodes

// List answers a list of the nodes comprising the given workflow.
func (_Nodes) List(id WorkflowID) ([]*Node, error) {
	q := `
	SELECT id, doctype_id, docstate_id, workflow_id, name, type
	FROM wf_workflow_nodes
	WHERE workflow_id = ?
	`
	rows, err := db.Query(q, id)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	ary := make([]*Node, 0, 5)
	for rows.Next() {
		var elem Node
		err = rows.Scan(&elem.ID, &elem.DocType, &elem.State, &elem.Wflow, &elem.Name, &elem.NodeType)
		if err != nil {
			return nil, err
		}
		elem.nfunc = defNodeFunc
		ary = append(ary, &elem)
	}
	if err = rows.Err(); err != nil {
		return nil, err
	}

	return ary, nil
}

// Get retrieves the requested node from the database.
func (_Nodes) Get(id NodeID) (*Node, error) {
	if id <= 0 {
		return nil, errors.New("node ID must be a positive integer")
	}

	var elem Node
	var acID sql.NullInt64
	q := `
	SELECT id, doctype_id, docstate_id, ac_id, workflow_id, name, type
	FROM wf_workflow_nodes
	WHERE id = ?
	`
	row := db.QueryRow(q, id)
	err := row.Scan(&elem.ID, &elem.DocType, &elem.State, &acID, &elem.Wflow, &elem.Name, &elem.NodeType)
	if err != nil {
		return nil, err
	}
	if acID.Valid {
		elem.AccCtx = AccessContextID(acID.Int64)
	}

	elem.nfunc = defNodeFunc
	return &elem, nil
}

// GetByState retrieves the requested node from the database, as per
// the document state specification.
func (_Nodes) GetByState(dtype DocTypeID, state DocStateID) (*Node, error) {
	var elem Node
	var acID sql.NullInt64
	q := `
	SELECT id, doctype_id, docstate_id, ac_id, workflow_id, name, type
	FROM wf_workflow_nodes
	WHERE doctype_id = ?
	AND docstate_id = ?
	`
	row := db.QueryRow(q, dtype, state)
	err := row.Scan(&elem.ID, &elem.DocType, &elem.State, &acID, &elem.Wflow, &elem.Name, &elem.NodeType)
	if err != nil {
		return nil, err
	}
	if acID.Valid {
		elem.AccCtx = AccessContextID(acID.Int64)
	}

	elem.nfunc = defNodeFunc
	return &elem, nil
}
<commit_msg>Propagate recent message structure change to message post in node<commit_after>// (c) Copyright 2015-2017 JONNALAGADDA Srinivas
//
// 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 flow

import (
	"database/sql"
	"errors"
	"log"
)

// NodeID is the type of unique identifiers of nodes.
type NodeID int64

// NodeFunc defines the type of functions that generate notification
// messages in workflows.
//
// These functions are triggered by appropriate nodes, when document
// events are applied to documents to possibly transform them.
// Invocation of a `NodeFunc` should result in a message that can then
// be dispatched to applicable mailboxes.
//
// Error should be returned only when an impossible situation arises,
// and processing needs to abort.  Note that returning an error stops
// the workflow.  Manual intervention will be needed to move the
// document further.
//
// N. B. NodeFunc instances must be referentially transparent --
// stateless and not capture their environment in any manner.
// Unexpected bad things could happen otherwise!
type NodeFunc func(*Document, *DocEvent) *Message

// defNodeFunc prepares a simple message that can be posted to
// applicable mailboces.
func defNodeFunc(d *Document, event *DocEvent) *Message {
	return &Message{
		DocType: DocType{
			ID: d.DocType.ID,
		},
		DocID: d.ID,
		Event: event.ID,
		Title: d.Title,
		Data:  event.Text,
	}
}

// Node represents a specific logical unit of processing and routing
// in a workflow.
type Node struct {
	ID       NodeID          `json:"ID"`                      // Unique identifier of this node
	DocType  DocTypeID       `json:"DocType"`                 // Document type which this node's workflow manages
	State    DocStateID      `json:"DocState"`                // A document arriving at this node must be in this state
	AccCtx   AccessContextID `json:"AccessContext,omitempty"` // Specific access context associated with this state, if any
	Wflow    WorkflowID      `json:"Workflow"`                // Containing flow of this node
	Name     string          `json:"Name"`                    // Unique within its workflow
	NodeType NodeType        `json:"NodeType"`                // Topology type of this node
	nfunc    NodeFunc        // Processing function of this node
}

// Transitions answers the possible document states into which a
// document currently in the given state can transition.
func (n *Node) Transitions() (map[DocActionID]DocStateID, error) {
	return DocTypes._Transitions(n.DocType, n.State)
}

// SetFunc registers the given node function with this node.
//
// If `nil` is given, a default node function is registered instead.
// This default function sets the document title as the message
// subject, and the event's data as the message body.
func (n *Node) SetFunc(fn NodeFunc) error {
	if fn == nil {
		n.nfunc = defNodeFunc
		return nil
	}

	n.nfunc = fn
	return nil
}

// Func answers the processing function registered in this node
// definition.
func (n *Node) Func() NodeFunc {
	return n.nfunc
}

// applyEvent checks to see if the given event can be applied
// successfully.  Accordingly, it prepares a message by utilising the
// registered node function, and posts it to applicable mailboxes.
func (n *Node) applyEvent(otx *sql.Tx, event *DocEvent, recipients []GroupID) (DocStateID, error) {
	ts, err := n.Transitions()
	if err != nil {
		return 0, err
	}
	tstate, ok := ts[event.Action]
	if !ok {
		return 0, ErrWorkflowInvalidAction
	}

	// Check document's current state.
	doc, err := Documents.Get(otx, event.DocType, event.DocID)
	if err != nil {
		return 0, err
	}
	if doc.State.ID != event.State {
		return 0, ErrDocEventStateMismatch
	}

	// Document has already transitioned.  So, we note that the event
	// is applied, and return.
	//
	// N.B. This has implications for `NodeTypeJoinAny` below.  Should
	// you alter this logic or its position, verify that the
	// corresponding logic in the switch below is in coherence.
	if doc.State.ID == tstate {
		err = n.recordEvent(otx, event, tstate, true)
		if err != nil {
			return 0, err
		}
		return tstate, ErrDocEventRedundant
	}

	// Transition document state according to the target node type.

	tnode, err := Nodes.GetByState(n.DocType, tstate)
	if err != nil {
		return 0, err
	}

	switch tnode.NodeType {
	case NodeTypeJoinAny:
		// Multiple 'in's, but any one suffices.

		// We have already checked to see if the document has
		// transitioned into the target state.  If we have come this
		// far, the event can be applied.
		fallthrough

	case NodeTypeBegin, NodeTypeEnd, NodeTypeLinear, NodeTypeBranch:
		// Any node type having a single 'in'.

		// Update the document to transition the state.
		err = Documents.setState(otx, event.DocType, event.DocID, tstate, tnode.AccCtx)
		if err != nil {
			return 0, err
		}

		// Record event application.
		err = n.recordEvent(otx, event, tstate, false)
		if err != nil {
			return 0, err
		}

		// Post messages.
		msg := n.nfunc(doc, event)
		if len(recipients) == 0 {
			recipients, err = tnode.determineRecipients(otx, event.Group)
			if err != nil {
				return 0, err
			}
		}
		// It is legal to not have any recipients, too.
		if len(recipients) > 0 {
			err = n.postMessage(otx, msg, recipients)
			if err != nil {
				return 0, err
			}
		}

	case NodeTypeJoinAll:
		// Multiple 'in's, and all are required.

		// TODO(js)

	default:
		log.Panicf("unknown node type encountered : %s\n", tnode.NodeType)
	}

	return tstate, nil
}

// recordEvent writes a record stating that the given event has
// successfully been applied to effect a document state transition.
func (n *Node) recordEvent(otx *sql.Tx, event *DocEvent, tstate DocStateID, statusOnly bool) error {
	if !statusOnly {
		q := `
		INSERT INTO wf_docevent_application(doctype_id, doc_id, from_state_id, docevent_id, to_state_id)
		VALUES(?, ?, ?, ?, ?)
		`
		_, err := otx.Exec(q, event.DocType, event.DocID, event.State, event.ID, tstate)
		if err != nil {
			return err
		}
	}

	q := `UPDATE wf_docevents SET status = 'A' WHERE id = ?`
	_, err := otx.Exec(q, event.ID)
	if err != nil {
		return err
	}

	return nil
}

// determineRecipients takes the document type and access context into
// account, and determines the list of groups to which the
// notification should be posted.
func (n *Node) determineRecipients(otx *sql.Tx, group GroupID) ([]GroupID, error) {
	q := `
	SELECT reports_to
	FROM wf_ac_group_hierarchy
	WHERE ac_id = ?
	AND group_id = ?
	ORDER BY group_id
	LIMIT 1
	`
	rows, err := otx.Query(q, n.AccCtx, group)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	ary := make([]GroupID, 0, 4)
	for rows.Next() {
		var gid int64
		err = rows.Scan(&gid)
		if err != nil {
			return nil, err
		}
		ary = append(ary, GroupID(gid))
	}
	if rows.Err() != nil {
		return nil, err
	}
	return ary, nil
}

// postMessage posts the given message into the mailboxes of the
// specified recipients.
func (n *Node) postMessage(otx *sql.Tx, msg *Message, recipients []GroupID) error {
	// Record the message.

	q := `
	INSERT INTO wf_messages(doctype_id, doc_id, docevent_id, title, data)
	VALUES(?, ?, ?, ?, ?)
	`
	res, err := otx.Exec(q, msg.DocType.ID, msg.DocID, msg.Event, msg.Title, msg.Data)
	if err != nil {
		return err
	}
	var msgid int64
	if msgid, err = res.LastInsertId(); err != nil {
		return err
	}

	// Post it into applicable mailboxes.

	q = `
	INSERT INTO wf_mailboxes(group_id, message_id, unread)
	VALUES(?, ?, 1)
	`
	for _, gid := range recipients {
		res, err = otx.Exec(q, gid, msgid)
		if err != nil {
			return err
		}
	}

	return nil
}

// Unexported type, only for convenience methods.
type _Nodes struct{}

// Nodes provides a resource-like interface to the nodes defined in
// this system.
var Nodes _Nodes

// List answers a list of the nodes comprising the given workflow.
func (_Nodes) List(id WorkflowID) ([]*Node, error) {
	q := `
	SELECT id, doctype_id, docstate_id, workflow_id, name, type
	FROM wf_workflow_nodes
	WHERE workflow_id = ?
	`
	rows, err := db.Query(q, id)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	ary := make([]*Node, 0, 5)
	for rows.Next() {
		var elem Node
		err = rows.Scan(&elem.ID, &elem.DocType, &elem.State, &elem.Wflow, &elem.Name, &elem.NodeType)
		if err != nil {
			return nil, err
		}
		elem.nfunc = defNodeFunc
		ary = append(ary, &elem)
	}
	if err = rows.Err(); err != nil {
		return nil, err
	}

	return ary, nil
}

// Get retrieves the requested node from the database.
func (_Nodes) Get(id NodeID) (*Node, error) {
	if id <= 0 {
		return nil, errors.New("node ID must be a positive integer")
	}

	var elem Node
	var acID sql.NullInt64
	q := `
	SELECT id, doctype_id, docstate_id, ac_id, workflow_id, name, type
	FROM wf_workflow_nodes
	WHERE id = ?
	`
	row := db.QueryRow(q, id)
	err := row.Scan(&elem.ID, &elem.DocType, &elem.State, &acID, &elem.Wflow, &elem.Name, &elem.NodeType)
	if err != nil {
		return nil, err
	}
	if acID.Valid {
		elem.AccCtx = AccessContextID(acID.Int64)
	}

	elem.nfunc = defNodeFunc
	return &elem, nil
}

// GetByState retrieves the requested node from the database, as per
// the document state specification.
func (_Nodes) GetByState(dtype DocTypeID, state DocStateID) (*Node, error) {
	var elem Node
	var acID sql.NullInt64
	q := `
	SELECT id, doctype_id, docstate_id, ac_id, workflow_id, name, type
	FROM wf_workflow_nodes
	WHERE doctype_id = ?
	AND docstate_id = ?
	`
	row := db.QueryRow(q, dtype, state)
	err := row.Scan(&elem.ID, &elem.DocType, &elem.State, &acID, &elem.Wflow, &elem.Name, &elem.NodeType)
	if err != nil {
		return nil, err
	}
	if acID.Valid {
		elem.AccCtx = AccessContextID(acID.Int64)
	}

	elem.nfunc = defNodeFunc
	return &elem, nil
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"net/http"
	"Utils"
	"Flags"
	"path/filepath"
	"strconv"
	"UserManager"
	"SessionManager"
	"regexp"
	"Templates"
	"time"
)

const (
	// Routes
	rootURL = "/"
	docURL = rootURL + "doc/"
	basicAuthURL = rootURL + "download/"
	funcURL = rootURL + "ops/"

	// Paths
	pivatePath = "res/html/"
	publicPath = pivatePath + "/public/"

	// URLs
	mainPageURL = rootURL + "index.html"
	settingsPageURL = rootURL + "settings.html"
	loginPageURL = rootURL + "public/login.html"

	// MISC
	debugging = false; // Disables Login for Debugging
)

func main() {
	requestMultiplexer := http.NewServeMux()

	//Login,Logout
	requestMultiplexer.HandleFunc(funcURL + "login", authHandler)

	//Settings (Change Password)
	requestMultiplexer.HandleFunc(funcURL + "settings", settingsHandler)

	//Index Functions
	//DeleteData
	requestMultiplexer.HandleFunc(funcURL + "delete", Templates.DeleteDataHandler)

	//DownloadData
	requestMultiplexer.HandleFunc(funcURL + "download", Templates.DownloadDataHandler)

	//UploadData
	requestMultiplexer.HandleFunc(funcURL + "upload", Templates.UploadDataDataHandler)

	//NewFolder
	requestMultiplexer.HandleFunc(funcURL + "newFolder", Templates.NewFolderHandler)

	//Basic Auth
	requestMultiplexer.HandleFunc(basicAuthURL, basicAuthHandler(Templates.DownloadBasicAuthDataHandler))

	// General Handlers for Website + Godoc
	requestMultiplexer.HandleFunc(docURL, docHandler)
	requestMultiplexer.HandleFunc(rootURL, sessionCheckHandler)

	cfg := &tls.Config{
		MinVersion:               tls.VersionTLS12,
		CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
		PreferServerCipherSuites: true,
		CipherSuites: []uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		},
	}

	srv := &http.Server{
		Addr:         ":" + strconv.Itoa(Flags.GetPort()),
		Handler:      requestMultiplexer,
		TLSConfig:    cfg,
		TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
	}

	Utils.HandlePanic(srv.ListenAndServeTLS(Flags.GetTLScert(), Flags.GetTLSkey()))
}

func sessionCheckHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")

	r.ParseForm()
	cookie, err := r.Cookie("Session")
	if err != nil {
		Utils.LogDebug(err.Error())
		cookie := http.Cookie{Name: "Session", Value: "empty", Expires: time.Now().Add(365 * 24 * time.Hour), Path: "/"}
		http.SetCookie(w, &cookie)
		http.Redirect(w, r, loginPageURL, 302)
		return
	} else {
		// Public Folder ?
		publicFolderRegex, _ := regexp.Compile("^public")

		if (SessionManager.ValidateSession(cookie.Value) || publicFolderRegex.MatchString(r.URL.EscapedPath()[1:]) || debugging) {
			rootHandler(w, r)
			return
		} else {
			Utils.LogDebug("Access denied for " + r.URL.EscapedPath() + " by Session Check")
			http.Redirect(w, r, loginPageURL, 302)
			return
		}
	}

}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	var url string

	if r.URL.EscapedPath()[1:] == "" {
		url = mainPageURL
	} else {
		url = r.URL.EscapedPath()[1:]
	}

	path, err := filepath.Abs(pivatePath + url)
	Utils.HandlePrint(err)

	if (url[len(url) - 4:] == "html") {
		// Split string at / , switch case files

		switch url {
		case "/index.html":
			Templates.IndexHandler(w, r, path)
			Utils.LogDebug("File Accessed with TemplateEngine:	" + path)
		case "settings.html":
			Templates.SettingHandler(w, r, path)
			Utils.LogDebug("File Accessed with TemplateEngine:	" + path)
		default:
			http.ServeFile(w, r, path)
			Utils.LogDebug("File Accessed with StaticFileServer:	" + path)
		}

	} else {
		http.ServeFile(w, r, path)
		Utils.LogDebug("File Accessed with StaticFileServer:	" + path)
	}

}

func authHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	intent := r.FormValue("intent")
	if (intent == "login") {
		Utils.LogDebug("Intent=Login")
		email := r.FormValue("email")
		password := r.FormValue("password")
		if (UserManager.VerifyUser(email, password)) {

			session := Utils.RandString(128)
			SessionManager.NewSession(SessionManager.SessionRecord{Email:email, Session: session, SessionLast:time.Now()})
			cookie := http.Cookie{Name: "Session", Value: session, Expires: time.Now().Add(365 * 24 * time.Hour), Path: "/"}
			http.SetCookie(w, &cookie)

			http.Redirect(w, r, mainPageURL, 302)

		} else {
			http.Redirect(w, r, loginPageURL + "?status=failed", 302)
		}
	} else if (intent == "register") {
		Utils.LogDebug("Intent=Register")
		name := r.FormValue("name")
		email := r.FormValue("email")
		password := r.FormValue("password")
		password2 := r.FormValue("password2")

		if (password != password2) {
			//Passwords not equal
			Utils.LogDebug("Passwörter stimmen nicht überein. Registrierung fehlgeschlagen.")
			http.Redirect(w, r, loginPageURL + "?status=passwordsNotEqual", 302)
			return
		}

		registerOK := UserManager.RegisterUser(name, email, password)
		if (registerOK) {
			session := Utils.RandString(128)
			SessionManager.NewSession(SessionManager.SessionRecord{Email:email, Session: session, SessionLast:time.Now()})
			cookie := http.Cookie{Name: "Session", Value: session, Expires: time.Now().Add(365 * 24 * time.Hour), Path: "/"}
			http.SetCookie(w, &cookie)

			http.Redirect(w, r, mainPageURL, 302)
			Utils.LogDebug("Registrierung erfolgreich.")
		} else {
			http.Redirect(w, r, loginPageURL + "?status=userAlreadyExists", 302)
			Utils.LogDebug("Benutzer mit angegebener E-Mail-Adresse existiert bereits. Registrierung fehlgeschlagen.")
		}
	} else if (r.URL.Query().Get("intent") == "logout") {
		Utils.LogDebug("Intent=Logout")
		cookie, err := r.Cookie("Session")
		Utils.HandlePrint(err)
		session, present := SessionManager.GetSessionRecord(cookie.Value)
		if present {
			err = SessionManager.InvalidateSession(session.Session)
		}
		if (err != nil) {
			http.Redirect(w, r, loginPageURL + "?status=error", 302)
			return
		}
		http.Redirect(w, r, loginPageURL + "?status=logout", 302)
		return
	} else {
		Utils.LogDebug("Intent=BadRequest")
		http.Redirect(w, r, loginPageURL + "?status=badrequest", 302)
		return
	}
}

func settingsHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	passwordOld := r.FormValue("passwordOld")
	passwordNew := r.FormValue("passwordNew")
	passwordNew2 := r.FormValue("passwordNew2")

	if (passwordNew != passwordNew2) {
		//Passwords not equal
		Utils.LogDebug("Neue Passwörter stimmen nicht überein. Änderung fehlgeschlagen.")
		http.Redirect(w, r, settingsPageURL + "?status=passwordsNotEqual", 302)
		return
	}

	cookie, err := r.Cookie("Session")
	Utils.HandlePrint(err)
	session, present := SessionManager.GetSessionRecord(cookie.Value)
	if(present) {
		email := session.Email

		Utils.LogDebug("EMail: " + email)

		if (UserManager.VerifyUser(email, passwordOld)) {
			UserManager.ChangePassword(email, passwordNew)
			http.Redirect(w, r, settingsPageURL, 302)
			Utils.LogDebug("Kennwort erfolgreich geändert.")
		} else {
			http.Redirect(w, r, settingsPageURL + "?status=oldPasswordNotValid", 302)
			Utils.LogDebug("Altes Kennwort nicht korrekt. Kennwortänderung fehlgeschlagen.")
		}
	}

}

//basicAuth - Checks submitted user credentials and grants access to handler
func basicAuthHandler(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		email, pass, ok := r.BasicAuth()

		if ok && UserManager.VerifyUser(email, pass) {
			handler(w, r)
		} else {
			w.Header().Set("WWW-Authenticate", `Basic realm="Fileserver: Bitte mit E-Mail-Adresse und Kennwort anmelden, um auf Dateien zuzugreifen."`)
			http.Error(w, "Unauthorized.", http.StatusUnauthorized)
		}

	}
}

func docHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")

	path, err := filepath.Abs("res/" + r.URL.EscapedPath()[1:])
	Utils.HandlePrint(err)

	Utils.LogDebug("File Accessed:	" + path)

	if (r.URL.EscapedPath()[1:] == "doc/" || r.URL.EscapedPath()[1:] == "doc") {
		Utils.LogDebug("Redirecting from doc to doc/pkg/fileServer.html")
		http.Redirect(w, r, "pkg/FileServer.html", 302)
	} else {
		http.ServeFile(w, r, path)
	}
}

<commit_msg>email ToLower at login and register<commit_after>package main

import (
	"crypto/tls"
	"net/http"
	"Utils"
	"Flags"
	"path/filepath"
	"strconv"
	"UserManager"
	"SessionManager"
	"regexp"
	"Templates"
	"time"
	"strings"
)

const (
	// Routes
	rootURL = "/"
	docURL = rootURL + "doc/"
	basicAuthURL = rootURL + "download/"
	funcURL = rootURL + "ops/"

	// Paths
	pivatePath = "res/html/"
	publicPath = pivatePath + "/public/"

	// URLs
	mainPageURL = rootURL + "index.html"
	settingsPageURL = rootURL + "settings.html"
	loginPageURL = rootURL + "public/login.html"

	// MISC
	debugging = false; // Disables Login for Debugging
)

func main() {
	requestMultiplexer := http.NewServeMux()

	//Login,Logout
	requestMultiplexer.HandleFunc(funcURL + "login", authHandler)

	//Settings (Change Password)
	requestMultiplexer.HandleFunc(funcURL + "settings", settingsHandler)

	//Index Functions
	//DeleteData
	requestMultiplexer.HandleFunc(funcURL + "delete", Templates.DeleteDataHandler)

	//DownloadData
	requestMultiplexer.HandleFunc(funcURL + "download", Templates.DownloadDataHandler)

	//UploadData
	requestMultiplexer.HandleFunc(funcURL + "upload", Templates.UploadDataDataHandler)

	//NewFolder
	requestMultiplexer.HandleFunc(funcURL + "newFolder", Templates.NewFolderHandler)

	//Basic Auth
	requestMultiplexer.HandleFunc(basicAuthURL, basicAuthHandler(Templates.DownloadBasicAuthDataHandler))

	// General Handlers for Website + Godoc
	requestMultiplexer.HandleFunc(docURL, docHandler)
	requestMultiplexer.HandleFunc(rootURL, sessionCheckHandler)

	cfg := &tls.Config{
		MinVersion:               tls.VersionTLS12,
		CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
		PreferServerCipherSuites: true,
		CipherSuites: []uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		},
	}

	srv := &http.Server{
		Addr:         ":" + strconv.Itoa(Flags.GetPort()),
		Handler:      requestMultiplexer,
		TLSConfig:    cfg,
		TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
	}

	Utils.HandlePanic(srv.ListenAndServeTLS(Flags.GetTLScert(), Flags.GetTLSkey()))
}

func sessionCheckHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")

	r.ParseForm()
	cookie, err := r.Cookie("Session")
	if err != nil {
		Utils.LogDebug(err.Error())
		cookie := http.Cookie{Name: "Session", Value: "empty", Expires: time.Now().Add(365 * 24 * time.Hour), Path: "/"}
		http.SetCookie(w, &cookie)
		http.Redirect(w, r, loginPageURL, 302)
		return
	} else {
		// Public Folder ?
		publicFolderRegex, _ := regexp.Compile("^public")

		if (SessionManager.ValidateSession(cookie.Value) || publicFolderRegex.MatchString(r.URL.EscapedPath()[1:]) || debugging) {
			rootHandler(w, r)
			return
		} else {
			Utils.LogDebug("Access denied for " + r.URL.EscapedPath() + " by Session Check")
			http.Redirect(w, r, loginPageURL, 302)
			return
		}
	}

}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	var url string

	if r.URL.EscapedPath()[1:] == "" {
		url = mainPageURL
	} else {
		url = r.URL.EscapedPath()[1:]
	}

	path, err := filepath.Abs(pivatePath + url)
	Utils.HandlePrint(err)

	if (url[len(url) - 4:] == "html") {
		// Split string at / , switch case files

		switch url {
		case "/index.html":
			Templates.IndexHandler(w, r, path)
			Utils.LogDebug("File Accessed with TemplateEngine:	" + path)
		case "settings.html":
			Templates.SettingHandler(w, r, path)
			Utils.LogDebug("File Accessed with TemplateEngine:	" + path)
		default:
			http.ServeFile(w, r, path)
			Utils.LogDebug("File Accessed with StaticFileServer:	" + path)
		}

	} else {
		http.ServeFile(w, r, path)
		Utils.LogDebug("File Accessed with StaticFileServer:	" + path)
	}

}

func authHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	intent := r.FormValue("intent")
	if (intent == "login") {
		Utils.LogDebug("Intent=Login")
		email := strings.ToLower(r.FormValue("email"))
		password := r.FormValue("password")
		if (UserManager.VerifyUser(email, password)) {

			session := Utils.RandString(128)
			SessionManager.NewSession(SessionManager.SessionRecord{Email:email, Session: session, SessionLast:time.Now()})
			cookie := http.Cookie{Name: "Session", Value: session, Expires: time.Now().Add(365 * 24 * time.Hour), Path: "/"}
			http.SetCookie(w, &cookie)

			http.Redirect(w, r, mainPageURL, 302)

		} else {
			http.Redirect(w, r, loginPageURL + "?status=failed", 302)
		}
	} else if (intent == "register") {
		Utils.LogDebug("Intent=Register")
		name := r.FormValue("name")
		email := strings.ToLower(r.FormValue("email"))
		password := r.FormValue("password")
		password2 := r.FormValue("password2")

		if (password != password2) {
			//Passwords not equal
			Utils.LogDebug("Passwörter stimmen nicht überein. Registrierung fehlgeschlagen.")
			http.Redirect(w, r, loginPageURL + "?status=passwordsNotEqual", 302)
			return
		}

		registerOK := UserManager.RegisterUser(name, email, password)
		if (registerOK) {
			session := Utils.RandString(128)
			SessionManager.NewSession(SessionManager.SessionRecord{Email:email, Session: session, SessionLast:time.Now()})
			cookie := http.Cookie{Name: "Session", Value: session, Expires: time.Now().Add(365 * 24 * time.Hour), Path: "/"}
			http.SetCookie(w, &cookie)

			http.Redirect(w, r, mainPageURL, 302)
			Utils.LogDebug("Registrierung erfolgreich.")
		} else {
			http.Redirect(w, r, loginPageURL + "?status=userAlreadyExists", 302)
			Utils.LogDebug("Benutzer mit angegebener E-Mail-Adresse existiert bereits. Registrierung fehlgeschlagen.")
		}
	} else if (r.URL.Query().Get("intent") == "logout") {
		Utils.LogDebug("Intent=Logout")
		cookie, err := r.Cookie("Session")
		Utils.HandlePrint(err)
		session, present := SessionManager.GetSessionRecord(cookie.Value)
		if present {
			err = SessionManager.InvalidateSession(session.Session)
		}
		if (err != nil) {
			http.Redirect(w, r, loginPageURL + "?status=error", 302)
			return
		}
		http.Redirect(w, r, loginPageURL + "?status=logout", 302)
		return
	} else {
		Utils.LogDebug("Intent=BadRequest")
		http.Redirect(w, r, loginPageURL + "?status=badrequest", 302)
		return
	}
}

func settingsHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	passwordOld := r.FormValue("passwordOld")
	passwordNew := r.FormValue("passwordNew")
	passwordNew2 := r.FormValue("passwordNew2")

	if (passwordNew != passwordNew2) {
		//Passwords not equal
		Utils.LogDebug("Neue Passwörter stimmen nicht überein. Änderung fehlgeschlagen.")
		http.Redirect(w, r, settingsPageURL + "?status=passwordsNotEqual", 302)
		return
	}

	cookie, err := r.Cookie("Session")
	Utils.HandlePrint(err)
	session, present := SessionManager.GetSessionRecord(cookie.Value)
	if(present) {
		email := session.Email

		Utils.LogDebug("EMail: " + email)

		if (UserManager.VerifyUser(email, passwordOld)) {
			UserManager.ChangePassword(email, passwordNew)
			http.Redirect(w, r, settingsPageURL, 302)
			Utils.LogDebug("Kennwort erfolgreich geändert.")
		} else {
			http.Redirect(w, r, settingsPageURL + "?status=oldPasswordNotValid", 302)
			Utils.LogDebug("Altes Kennwort nicht korrekt. Kennwortänderung fehlgeschlagen.")
		}
	}

}

//basicAuth - Checks submitted user credentials and grants access to handler
func basicAuthHandler(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		email, pass, ok := r.BasicAuth()

		if ok && UserManager.VerifyUser(email, pass) {
			handler(w, r)
		} else {
			w.Header().Set("WWW-Authenticate", `Basic realm="Fileserver: Bitte mit E-Mail-Adresse und Kennwort anmelden, um auf Dateien zuzugreifen."`)
			http.Error(w, "Unauthorized.", http.StatusUnauthorized)
		}

	}
}

func docHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")

	path, err := filepath.Abs("res/" + r.URL.EscapedPath()[1:])
	Utils.HandlePrint(err)

	Utils.LogDebug("File Accessed:	" + path)

	if (r.URL.EscapedPath()[1:] == "doc/" || r.URL.EscapedPath()[1:] == "doc") {
		Utils.LogDebug("Redirecting from doc to doc/pkg/fileServer.html")
		http.Redirect(w, r, "pkg/FileServer.html", 302)
	} else {
		http.ServeFile(w, r, path)
	}
}

<|endoftext|>


<commit_before>// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package avm

import (
	"errors"

	"github.com/ava-labs/avalanchego/codec"
	"github.com/ava-labs/avalanchego/database"
	"github.com/ava-labs/avalanchego/ids"
	"github.com/ava-labs/avalanchego/snow"
	"github.com/ava-labs/avalanchego/vms/components/avax"
	"github.com/ava-labs/avalanchego/vms/components/verify"
)

var errNilTx = errors.New("nil tx is not valid")

// BaseTx is the basis of all transactions.
type BaseTx struct {
	avax.BaseTx `serialize:"true"`
}

func (t *BaseTx) InitFx(vm *VM) error {
	// todo genesis is only tx without inputs
	// todo index loop might be better impl
	for i, n := 0, len(t.Ins); i < n; i++ {
		in := t.Ins[i]
		fxIdx, err := vm.getFx(in.In)
		if err != nil {
			return err
		}

		fx := vm.fxs[fxIdx]
		in.FxID = fx.ID
	}

	for i, n := 0, len(t.Outs); i < n; i++ {
		out := t.Outs[i]
		fxIdx, err := vm.getFx(out.Out)
		if err != nil {
			return err
		}

		fx := vm.fxs[fxIdx]
		out.FxID = fx.ID
	}
	return nil
}

// SyntacticVerify that this transaction is well-formed.
func (t *BaseTx) SyntacticVerify(
	ctx *snow.Context,
	c codec.Manager,
	txFeeAssetID ids.ID,
	txFee uint64,
	_ uint64,
	_ int,
) error {
	if t == nil {
		return errNilTx
	}
	if err := t.MetadataVerify(ctx); err != nil {
		return err
	}

	return avax.VerifyTx(
		txFee,
		txFeeAssetID,
		[][]*avax.TransferableInput{t.Ins},
		[][]*avax.TransferableOutput{t.Outs},
		c,
	)
}

// SemanticVerify that this transaction is valid to be spent.
func (t *BaseTx) SemanticVerify(vm *VM, tx UnsignedTx, creds []verify.Verifiable) error {
	for i, in := range t.Ins {
		cred := creds[i]
		if err := vm.verifyTransfer(tx, in, cred); err != nil {
			return err
		}
	}
	for _, out := range t.Outs {
		fxIndex, err := vm.getFx(out.Out)
		if err != nil {
			return err
		}
		if assetID := out.AssetID(); !vm.verifyFxUsage(fxIndex, assetID) {
			return errIncompatibleFx
		}
	}
	return nil
}

// ExecuteWithSideEffects writes the batch with any additional side effects
func (t *BaseTx) ExecuteWithSideEffects(_ *VM, batch database.Batch) error { return batch.Write() }
<commit_msg>remove todo<commit_after>// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package avm

import (
	"errors"

	"github.com/ava-labs/avalanchego/codec"
	"github.com/ava-labs/avalanchego/database"
	"github.com/ava-labs/avalanchego/ids"
	"github.com/ava-labs/avalanchego/snow"
	"github.com/ava-labs/avalanchego/vms/components/avax"
	"github.com/ava-labs/avalanchego/vms/components/verify"
)

var errNilTx = errors.New("nil tx is not valid")

// BaseTx is the basis of all transactions.
type BaseTx struct {
	avax.BaseTx `serialize:"true"`
}

func (t *BaseTx) InitFx(vm *VM) error {
	for i, n := 0, len(t.Ins); i < n; i++ {
		in := t.Ins[i]
		fxIdx, err := vm.getFx(in.In)
		if err != nil {
			return err
		}

		fx := vm.fxs[fxIdx]
		in.FxID = fx.ID
	}

	for i, n := 0, len(t.Outs); i < n; i++ {
		out := t.Outs[i]
		fxIdx, err := vm.getFx(out.Out)
		if err != nil {
			return err
		}

		fx := vm.fxs[fxIdx]
		out.FxID = fx.ID
	}
	return nil
}

// SyntacticVerify that this transaction is well-formed.
func (t *BaseTx) SyntacticVerify(
	ctx *snow.Context,
	c codec.Manager,
	txFeeAssetID ids.ID,
	txFee uint64,
	_ uint64,
	_ int,
) error {
	if t == nil {
		return errNilTx
	}
	if err := t.MetadataVerify(ctx); err != nil {
		return err
	}

	return avax.VerifyTx(
		txFee,
		txFeeAssetID,
		[][]*avax.TransferableInput{t.Ins},
		[][]*avax.TransferableOutput{t.Outs},
		c,
	)
}

// SemanticVerify that this transaction is valid to be spent.
func (t *BaseTx) SemanticVerify(vm *VM, tx UnsignedTx, creds []verify.Verifiable) error {
	for i, in := range t.Ins {
		cred := creds[i]
		if err := vm.verifyTransfer(tx, in, cred); err != nil {
			return err
		}
	}
	for _, out := range t.Outs {
		fxIndex, err := vm.getFx(out.Out)
		if err != nil {
			return err
		}
		if assetID := out.AssetID(); !vm.verifyFxUsage(fxIndex, assetID) {
			return errIncompatibleFx
		}
	}
	return nil
}

// ExecuteWithSideEffects writes the batch with any additional side effects
func (t *BaseTx) ExecuteWithSideEffects(_ *VM, batch database.Batch) error { return batch.Write() }
<|endoftext|>


<commit_before>// Copyright 2017 Istio Authors
//
// 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 main

import (
	"bufio"
	"bytes"
	"context"
	"errors"
	"flag"
	"fmt"
	"os"
	"strconv"
	"strings"
	"text/template"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/golang/glog"
	"github.com/golang/sync/errgroup"
	multierror "github.com/hashicorp/go-multierror"
	"k8s.io/client-go/kubernetes"

	proxyconfig "istio.io/api/proxy/v1/config"
	"istio.io/manager/model"
	"istio.io/manager/platform/kube"
	"istio.io/manager/test/util"
)

var (
	params infra

	// Enable/disable auth, or run both for the tests.
	authmode string
	verbose  bool
	count    int

	// The particular test to run, e.g. "HTTP reachability" or "routing rules"
	testType string

	kubeconfig  string
	client      kubernetes.Interface
	istioClient *kube.Client
)

const (
	// CA image tag is the short SHA *update manually*
	caTag = "0107c0c"

	// Mixer image tag is the short SHA *update manually*
	mixerTag = "6655a67"

	// retry budget
	budget = 90
)

func init() {
	flag.StringVar(&params.Hub, "hub", "gcr.io/istio-testing", "Docker hub")
	flag.StringVar(&params.Tag, "tag", "", "Docker tag")
	flag.StringVar(&params.CaImage, "ca", "gcr.io/istio-testing/istio-ca:"+caTag,
		"CA Docker image")
	flag.StringVar(&params.MixerImage, "mixer", "gcr.io/istio-testing/mixer:"+mixerTag,
		"Mixer Docker image")
	flag.StringVar(&params.Namespace, "n", "",
		"Namespace to use for testing (empty to create/delete temporary one)")
	flag.BoolVar(&verbose, "verbose", false, "Debug level noise from proxies")
	flag.BoolVar(&params.checkLogs, "logs", true, "Validate pod logs (expensive in long-running tests)")

	flag.StringVar(&kubeconfig, "kubeconfig", "platform/kube/config",
		"kube config file (missing or empty file makes the test use in-cluster kube config instead)")
	flag.IntVar(&count, "count", 1, "Number of times to run the tests after deploying")
	flag.StringVar(&authmode, "auth", "both", "Enable / disable auth, or test both.")

	// If specified, only run one test
	flag.StringVar(&testType, "testtype", "", "Select test to run (default is all tests)")
}

type test interface {
	String() string
	setup() error
	run() error
	teardown()
}

func main() {
	flag.Parse()
	if params.Tag == "" {
		glog.Fatal("No docker tag specified")
	}

	if verbose {
		params.Verbosity = 3
	} else {
		params.Verbosity = 2
	}

	if err := setupClient(); err != nil {
		glog.Fatal(err)
	}

	params.Name = "(default infra)"
	params.Auth = proxyconfig.ProxyMeshConfig_NONE
	params.Mixer = true
	params.Ingress = true
	params.Egress = true
	params.Zipkin = false // TODO: enable after Zipkin Envoy support is merged
	switch authmode {
	case "enable":
		runTests(setAuth(params))
	case "disable":
		runTests(params)
	case "both":
		runTests(params, setAuth(params))
	default:
		glog.Infof("Invald auth flag: %s. Please choose from: enable/disable/both.", authmode)
	}
}

func setAuth(params infra) infra {
	out := params
	out.Name = "(auth infra)"
	out.Auth = proxyconfig.ProxyMeshConfig_MUTUAL_TLS
	return out
}

func log(header, s string) {
	glog.Infof("\n\n"+
		"\033[1;34m=================== %s =====================\033[0m\n"+
		"\033[1;34m%s\033[0m\n\n", header, s)
}

func logError(header, s string) {
	glog.Errorf("\n\n"+
		"\033[1;31m=================== %s =====================\033[0m\n"+
		"\033[1;31m%s\033[0m\n\n", header, s)
}

func runTests(envs ...infra) {
	var result error
	for _, istio := range envs {
		var errs error
		log("Deploying infrastructure", spew.Sdump(istio))
		if err := istio.setup(); err != nil {
			result = multierror.Append(result, err)
			continue
		}
		if err := istio.deployApps(); err != nil {
			result = multierror.Append(result, err)
			continue
		}

		istio.apps, errs = util.GetAppPods(client, istio.Namespace)

		tests := []test{
			&http{infra: &istio},
			&apiServerTest{infra: &istio},
			&grpc{infra: &istio},
			&tcp{infra: &istio},
			&ingress{infra: &istio},
			&egress{infra: &istio},
			&routing{infra: &istio},
			&zipkin{infra: &istio},
		}

		for _, test := range tests {
			// If the user has specified a test, skip all other tests
			if len(testType) > 0 && testType != test.String() {
				continue
			}

			for i := 0; i < count; i++ {
				log("Test run", strconv.Itoa(i))
				if err := test.setup(); err != nil {
					errs = multierror.Append(errs, multierror.Prefix(err, test.String()))
				} else {
					log("Running test", test.String())
					if err := test.run(); err != nil {
						errs = multierror.Append(errs, multierror.Prefix(err, fmt.Sprintf("%v run %d", test, i)))
					} else {
						log("Success!", test.String())
					}
				}
				log("Tearing down test", test.String())
				test.teardown()
			}
		}

		// spill all logs on error
		if errs != nil {
			for _, pod := range util.GetPods(client, istio.Namespace) {
				if strings.HasPrefix(pod, "istio-manager") {
					log("Manager log", pod)
					glog.Info(util.FetchLogs(client, pod, istio.Namespace, "manager"))
				} else if strings.HasPrefix(pod, "istio-mixer") {
					log("Mixer log", pod)
					glog.Info(util.FetchLogs(client, pod, istio.Namespace, "mixer"))
				} else {
					log("Proxy log", pod)
					glog.Info(util.FetchLogs(client, pod, istio.Namespace, "proxy"))
				}
			}
		}

		// always remove infra even if the tests fail
		log("Tearing down infrastructure", spew.Sdump(istio))
		istio.teardown()

		if errs == nil {
			log("Passed all tests!", fmt.Sprintf("tests: %v, count: %d", tests, count))
		} else {
			logError("Failed tests!", errs.Error())
			result = multierror.Append(result, multierror.Prefix(errs, istio.Name))
		}
	}

	if result == nil {
		log("Passed infrastructure tests!", spew.Sdump(envs))
	} else {
		logError("Failed infrastructure tests!", result.Error())
		os.Exit(1)
	}
}

// fill a file based on a template
func fill(inFile string, values interface{}) (string, error) {
	var bytes bytes.Buffer
	w := bufio.NewWriter(&bytes)

	tmpl, err := template.ParseFiles("test/integration/testdata/" + inFile)
	if err != nil {
		return "", err
	}

	if err := tmpl.Execute(w, values); err != nil {
		return "", err
	}

	if err := w.Flush(); err != nil {
		return "", err
	}

	return bytes.String(), nil
}

type status error

var (
	errAgain status = errors.New("try again")
)

// run in parallel with retries. all funcs must succeed for the function to succeed
func parallel(fs map[string]func() status) error {
	g, ctx := errgroup.WithContext(context.Background())
	repeat := func(name string, f func() status) func() error {
		return func() error {
			for n := 0; n < budget; n++ {
				glog.Infof("%s (attempt %d)", name, n)
				err := f()
				switch err {
				case nil:
					// success
					return nil
				case errAgain:
					// do nothing
				default:
					return fmt.Errorf("failed %s at attempt %d: %v", name, n, err)
				}
				select {
				case <-time.After(time.Second):
					// try again
				case <-ctx.Done():
					return nil
				}
			}
			return fmt.Errorf("failed all %d attempts for %s", budget, name)
		}
	}
	for name, f := range fs {
		g.Go(repeat(name, f))
	}
	return g.Wait()
}

// connect to K8S cluster and register TPRs
func setupClient() error {
	var err error
	istioClient, err = kube.NewClient(kubeconfig, model.IstioConfig)
	if err != nil {
		return err
	}
	client = istioClient.GetKubernetesClient()
	return istioClient.RegisterResources()
}
<commit_msg>Enable Zipkin in integration tests (#758)<commit_after>// Copyright 2017 Istio Authors
//
// 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 main

import (
	"bufio"
	"bytes"
	"context"
	"errors"
	"flag"
	"fmt"
	"os"
	"strconv"
	"strings"
	"text/template"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/golang/glog"
	"github.com/golang/sync/errgroup"
	multierror "github.com/hashicorp/go-multierror"
	"k8s.io/client-go/kubernetes"

	proxyconfig "istio.io/api/proxy/v1/config"
	"istio.io/manager/model"
	"istio.io/manager/platform/kube"
	"istio.io/manager/test/util"
)

var (
	params infra

	// Enable/disable auth, or run both for the tests.
	authmode string
	verbose  bool
	count    int

	// The particular test to run, e.g. "HTTP reachability" or "routing rules"
	testType string

	kubeconfig  string
	client      kubernetes.Interface
	istioClient *kube.Client
)

const (
	// CA image tag is the short SHA *update manually*
	caTag = "0107c0c"

	// Mixer image tag is the short SHA *update manually*
	mixerTag = "6655a67"

	// retry budget
	budget = 90
)

func init() {
	flag.StringVar(&params.Hub, "hub", "gcr.io/istio-testing", "Docker hub")
	flag.StringVar(&params.Tag, "tag", "", "Docker tag")
	flag.StringVar(&params.CaImage, "ca", "gcr.io/istio-testing/istio-ca:"+caTag,
		"CA Docker image")
	flag.StringVar(&params.MixerImage, "mixer", "gcr.io/istio-testing/mixer:"+mixerTag,
		"Mixer Docker image")
	flag.StringVar(&params.Namespace, "n", "",
		"Namespace to use for testing (empty to create/delete temporary one)")
	flag.BoolVar(&verbose, "verbose", false, "Debug level noise from proxies")
	flag.BoolVar(&params.checkLogs, "logs", true, "Validate pod logs (expensive in long-running tests)")

	flag.StringVar(&kubeconfig, "kubeconfig", "platform/kube/config",
		"kube config file (missing or empty file makes the test use in-cluster kube config instead)")
	flag.IntVar(&count, "count", 1, "Number of times to run the tests after deploying")
	flag.StringVar(&authmode, "auth", "both", "Enable / disable auth, or test both.")

	// If specified, only run one test
	flag.StringVar(&testType, "testtype", "", "Select test to run (default is all tests)")
}

type test interface {
	String() string
	setup() error
	run() error
	teardown()
}

func main() {
	flag.Parse()
	if params.Tag == "" {
		glog.Fatal("No docker tag specified")
	}

	if verbose {
		params.Verbosity = 3
	} else {
		params.Verbosity = 2
	}

	if err := setupClient(); err != nil {
		glog.Fatal(err)
	}

	params.Name = "(default infra)"
	params.Auth = proxyconfig.ProxyMeshConfig_NONE
	params.Mixer = true
	params.Ingress = true
	params.Egress = true
	params.Zipkin = true
	switch authmode {
	case "enable":
		runTests(setAuth(params))
	case "disable":
		runTests(params)
	case "both":
		runTests(params, setAuth(params))
	default:
		glog.Infof("Invald auth flag: %s. Please choose from: enable/disable/both.", authmode)
	}
}

func setAuth(params infra) infra {
	out := params
	out.Name = "(auth infra)"
	out.Auth = proxyconfig.ProxyMeshConfig_MUTUAL_TLS
	return out
}

func log(header, s string) {
	glog.Infof("\n\n"+
		"\033[1;34m=================== %s =====================\033[0m\n"+
		"\033[1;34m%s\033[0m\n\n", header, s)
}

func logError(header, s string) {
	glog.Errorf("\n\n"+
		"\033[1;31m=================== %s =====================\033[0m\n"+
		"\033[1;31m%s\033[0m\n\n", header, s)
}

func runTests(envs ...infra) {
	var result error
	for _, istio := range envs {
		var errs error
		log("Deploying infrastructure", spew.Sdump(istio))
		if err := istio.setup(); err != nil {
			result = multierror.Append(result, err)
			continue
		}
		if err := istio.deployApps(); err != nil {
			result = multierror.Append(result, err)
			continue
		}

		istio.apps, errs = util.GetAppPods(client, istio.Namespace)

		tests := []test{
			&http{infra: &istio},
			&apiServerTest{infra: &istio},
			&grpc{infra: &istio},
			&tcp{infra: &istio},
			&ingress{infra: &istio},
			&egress{infra: &istio},
			&routing{infra: &istio},
			&zipkin{infra: &istio},
		}

		for _, test := range tests {
			// If the user has specified a test, skip all other tests
			if len(testType) > 0 && testType != test.String() {
				continue
			}

			for i := 0; i < count; i++ {
				log("Test run", strconv.Itoa(i))
				if err := test.setup(); err != nil {
					errs = multierror.Append(errs, multierror.Prefix(err, test.String()))
				} else {
					log("Running test", test.String())
					if err := test.run(); err != nil {
						errs = multierror.Append(errs, multierror.Prefix(err, fmt.Sprintf("%v run %d", test, i)))
					} else {
						log("Success!", test.String())
					}
				}
				log("Tearing down test", test.String())
				test.teardown()
			}
		}

		// spill all logs on error
		if errs != nil {
			for _, pod := range util.GetPods(client, istio.Namespace) {
				if strings.HasPrefix(pod, "istio-manager") {
					log("Manager log", pod)
					glog.Info(util.FetchLogs(client, pod, istio.Namespace, "manager"))
				} else if strings.HasPrefix(pod, "istio-mixer") {
					log("Mixer log", pod)
					glog.Info(util.FetchLogs(client, pod, istio.Namespace, "mixer"))
				} else {
					log("Proxy log", pod)
					glog.Info(util.FetchLogs(client, pod, istio.Namespace, "proxy"))
				}
			}
		}

		// always remove infra even if the tests fail
		log("Tearing down infrastructure", spew.Sdump(istio))
		istio.teardown()

		if errs == nil {
			log("Passed all tests!", fmt.Sprintf("tests: %v, count: %d", tests, count))
		} else {
			logError("Failed tests!", errs.Error())
			result = multierror.Append(result, multierror.Prefix(errs, istio.Name))
		}
	}

	if result == nil {
		log("Passed infrastructure tests!", spew.Sdump(envs))
	} else {
		logError("Failed infrastructure tests!", result.Error())
		os.Exit(1)
	}
}

// fill a file based on a template
func fill(inFile string, values interface{}) (string, error) {
	var bytes bytes.Buffer
	w := bufio.NewWriter(&bytes)

	tmpl, err := template.ParseFiles("test/integration/testdata/" + inFile)
	if err != nil {
		return "", err
	}

	if err := tmpl.Execute(w, values); err != nil {
		return "", err
	}

	if err := w.Flush(); err != nil {
		return "", err
	}

	return bytes.String(), nil
}

type status error

var (
	errAgain status = errors.New("try again")
)

// run in parallel with retries. all funcs must succeed for the function to succeed
func parallel(fs map[string]func() status) error {
	g, ctx := errgroup.WithContext(context.Background())
	repeat := func(name string, f func() status) func() error {
		return func() error {
			for n := 0; n < budget; n++ {
				glog.Infof("%s (attempt %d)", name, n)
				err := f()
				switch err {
				case nil:
					// success
					return nil
				case errAgain:
					// do nothing
				default:
					return fmt.Errorf("failed %s at attempt %d: %v", name, n, err)
				}
				select {
				case <-time.After(time.Second):
					// try again
				case <-ctx.Done():
					return nil
				}
			}
			return fmt.Errorf("failed all %d attempts for %s", budget, name)
		}
	}
	for name, f := range fs {
		g.Go(repeat(name, f))
	}
	return g.Wait()
}

// connect to K8S cluster and register TPRs
func setupClient() error {
	var err error
	istioClient, err = kube.NewClient(kubeconfig, model.IstioConfig)
	if err != nil {
		return err
	}
	client = istioClient.GetKubernetesClient()
	return istioClient.RegisterResources()
}
<|endoftext|>


<commit_before><commit_msg>startupSecret from k8s secret<commit_after><|endoftext|>


<commit_before>package logstash

import (
	"crypto/tls"
	"fmt"
	"math/rand"
	"net"
	"strings"
	"time"

	"github.com/elastic/libbeat/logp"
)

// TransportClient interfaces adds (re-)connect support to net.Conn.
type TransportClient interface {
	net.Conn
	Connect(timeout time.Duration) error
	IsConnected() bool
}

type tcpClient struct {
	hostport  string
	connected bool
	net.Conn
}

type tlsClient struct {
	tcpClient
	tls tls.Config
}

func newTCPClient(host string, defaultPort int) (*tcpClient, error) {
	return &tcpClient{hostport: fullAddress(host, defaultPort)}, nil
}

func (c *tcpClient) Connect(timeout time.Duration) error {
	if c.IsConnected() {
		_ = c.Close()
	}

	host, port, err := net.SplitHostPort(c.hostport)
	if err != nil {
		return err
	}

	// TODO: address lookup copied from logstash-forwarded. Really required?
	addresses, err := net.LookupHost(host)
	c.Conn = nil
	if err != nil {
		logp.Warn("DNS lookup failure \"%s\": %s", host, err)
		return err
	}

	// connect to random address
	// Use randomization on DNS reported addresses combined with timeout and ACKs
	// to spread potential load when starting up large number of beats using
	// lumberjack.
	//
	// RFCs discussing reasons for ignoring order of DNS records:
	// http://www.ietf.org/rfc/rfc3484.txt
	// > is specific to locality-based address selection for multiple dns
	// > records, but exists as prior art in "Choose some different ordering for
	// > the dns records" done by a client
	//
	// https://tools.ietf.org/html/rfc1794
	// > "Clients, of course, may reorder this information" - with respect to
	// > handling order of dns records in a response. address :=
	address := addresses[rand.Int()%len(addresses)]
	addressport := net.JoinHostPort(address, port)
	conn, err := net.DialTimeout("tcp", addressport, timeout)
	if err != nil {
		return err
	}

	c.Conn = conn
	c.connected = true
	return nil
}

func (c *tcpClient) IsConnected() bool {
	return c.connected
}

func (c *tcpClient) Close() error {
	err := c.Conn.Close()
	c.connected = false
	return err
}

func newTLSClient(host string, defaultPort int, tls *tls.Config) (*tlsClient, error) {
	c := tlsClient{}
	c.hostport = fullAddress(host, defaultPort)
	c.tls = *tls
	return &c, nil
}

func (c *tlsClient) Connect(timeout time.Duration) error {
	host, _, err := net.SplitHostPort(c.hostport)
	if err != nil {
		return err
	}

	var tlsconfig tls.Config
	tlsconfig.MinVersion = c.tls.MinVersion
	tlsconfig.RootCAs = c.tls.RootCAs
	tlsconfig.Certificates = c.tls.Certificates
	tlsconfig.ServerName = host

	if err := c.tcpClient.Connect(timeout); err != nil {
		return c.onFail(err)
	}

	socket := tls.Client(c.Conn, &tlsconfig)
	if err := socket.SetDeadline(time.Now().Add(timeout)); err != nil {
		_ = socket.Close()
		return c.onFail(err)
	}
	if err := socket.Handshake(); err != nil {
		_ = socket.Close()
		return c.onFail(err)
	}

	c.Conn = socket
	c.connected = true
	return nil
}

func (c *tlsClient) onFail(err error) error {
	logp.Err("SSL client failed to connect with: %v", err)
	c.Conn = nil
	c.connected = false
	return err
}

func fullAddress(host string, defaultPort int) string {
	if _, _, err := net.SplitHostPort(host); err == nil {
		return host
	}

	idx := strings.Index(host, ":")
	if idx >= 0 {
		// IPv6 address detected
		return fmt.Sprintf("[%v]:%v", host, defaultPort)
	}
	return fmt.Sprintf("%v:%v", host, defaultPort)
}
<commit_msg>Initialize Insecure with the configurable value<commit_after>package logstash

import (
	"crypto/tls"
	"fmt"
	"math/rand"
	"net"
	"strings"
	"time"

	"github.com/elastic/libbeat/logp"
)

// TransportClient interfaces adds (re-)connect support to net.Conn.
type TransportClient interface {
	net.Conn
	Connect(timeout time.Duration) error
	IsConnected() bool
}

type tcpClient struct {
	hostport  string
	connected bool
	net.Conn
}

type tlsClient struct {
	tcpClient
	tls tls.Config
}

func newTCPClient(host string, defaultPort int) (*tcpClient, error) {
	return &tcpClient{hostport: fullAddress(host, defaultPort)}, nil
}

func (c *tcpClient) Connect(timeout time.Duration) error {
	if c.IsConnected() {
		_ = c.Close()
	}

	host, port, err := net.SplitHostPort(c.hostport)
	if err != nil {
		return err
	}

	// TODO: address lookup copied from logstash-forwarded. Really required?
	addresses, err := net.LookupHost(host)
	c.Conn = nil
	if err != nil {
		logp.Warn("DNS lookup failure \"%s\": %s", host, err)
		return err
	}

	// connect to random address
	// Use randomization on DNS reported addresses combined with timeout and ACKs
	// to spread potential load when starting up large number of beats using
	// lumberjack.
	//
	// RFCs discussing reasons for ignoring order of DNS records:
	// http://www.ietf.org/rfc/rfc3484.txt
	// > is specific to locality-based address selection for multiple dns
	// > records, but exists as prior art in "Choose some different ordering for
	// > the dns records" done by a client
	//
	// https://tools.ietf.org/html/rfc1794
	// > "Clients, of course, may reorder this information" - with respect to
	// > handling order of dns records in a response. address :=
	address := addresses[rand.Int()%len(addresses)]
	addressport := net.JoinHostPort(address, port)
	conn, err := net.DialTimeout("tcp", addressport, timeout)
	if err != nil {
		return err
	}

	c.Conn = conn
	c.connected = true
	return nil
}

func (c *tcpClient) IsConnected() bool {
	return c.connected
}

func (c *tcpClient) Close() error {
	err := c.Conn.Close()
	c.connected = false
	return err
}

func newTLSClient(host string, defaultPort int, tls *tls.Config) (*tlsClient, error) {
	c := tlsClient{}
	c.hostport = fullAddress(host, defaultPort)
	c.tls = *tls
	return &c, nil
}

func (c *tlsClient) Connect(timeout time.Duration) error {
	host, _, err := net.SplitHostPort(c.hostport)
	if err != nil {
		return err
	}

	var tlsconfig tls.Config
	tlsconfig.MinVersion = c.tls.MinVersion
	tlsconfig.RootCAs = c.tls.RootCAs
	tlsconfig.Certificates = c.tls.Certificates
	tlsconfig.ServerName = host
	tlsconfig.InsecureSkipVerify = c.tls.InsecureSkipVerify

	if err := c.tcpClient.Connect(timeout); err != nil {
		return c.onFail(err)
	}

	socket := tls.Client(c.Conn, &tlsconfig)
	if err := socket.SetDeadline(time.Now().Add(timeout)); err != nil {
		_ = socket.Close()
		return c.onFail(err)
	}
	if err := socket.Handshake(); err != nil {
		_ = socket.Close()
		return c.onFail(err)
	}

	c.Conn = socket
	c.connected = true
	return nil
}

func (c *tlsClient) onFail(err error) error {
	logp.Err("SSL client failed to connect with: %v", err)
	c.Conn = nil
	c.connected = false
	return err
}

func fullAddress(host string, defaultPort int) string {
	if _, _, err := net.SplitHostPort(host); err == nil {
		return host
	}

	idx := strings.Index(host, ":")
	if idx >= 0 {
		// IPv6 address detected
		return fmt.Sprintf("[%v]:%v", host, defaultPort)
	}
	return fmt.Sprintf("%v:%v", host, defaultPort)
}
<|endoftext|>